Add home; overwrite save method on Period model

This commit is contained in:
Nathan Chapman 2021-01-30 17:40:40 -07:00
parent 787066d474
commit 5e56fc0139
23 changed files with 133 additions and 50 deletions

View File

@ -29,6 +29,7 @@ class Student(models.Model):
student_number = models.IntegerField() student_number = models.IntegerField()
department = models.ForeignKey(Department, on_delete=models.CASCADE) department = models.ForeignKey(Department, on_delete=models.CASCADE)
is_clocked_in = models.BooleanField(default=False) is_clocked_in = models.BooleanField(default=False)
current_period_id = models.IntegerField(blank=True, null=True)
created = models.DateTimeField(auto_now_add=True) created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True) modified = models.DateTimeField(auto_now=True)

View File

@ -7,3 +7,20 @@ class AttendanceUpdateForm(forms.Form):
max_length=100, max_length=100,
widget=forms.TextInput(attrs={'autofocus': True}) widget=forms.TextInput(attrs={'autofocus': True})
) )
class PeriodForm(forms.ModelForm):
class Meta:
model = Period
fields = ['student', 'station_number', 'clocked_in', 'clocked_out']
widgets = {
'clocked_in': forms.DateTimeInput(format = '%Y-%m-%d %H:%M', attrs = {
'placeholder': '2006-10-25 14:30'
}),
'clocked_out': forms.DateTimeInput(format = '%Y-%m-%d %H:%M', attrs = {
'placeholder': '2006-10-25 14:30'
}),
}
labels = {
'clocked_in': 'Clocked in (example 2006-10-25 14:30):',
'clocked_out': 'Clocked out (example 2006-10-25 14:30):',
}

View File

@ -1,3 +1,4 @@
from datetime import datetime
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
from accounts.models import Student from accounts.models import Student
@ -20,7 +21,7 @@ class Code(models.Model):
class Period(models.Model): class Period(models.Model):
student = models.ForeignKey(Student, on_delete=models.CASCADE) student = models.ForeignKey(Student, on_delete=models.CASCADE)
clocked_in = models.DateTimeField(auto_now_add=True) clocked_in = models.DateTimeField()
clocked_out = models.DateTimeField(blank=True, null=True) clocked_out = models.DateTimeField(blank=True, null=True)
station_number = models.IntegerField() station_number = models.IntegerField()
duration = models.DurationField(blank=True, null=True) duration = models.DurationField(blank=True, null=True)
@ -38,3 +39,8 @@ class Period(models.Model):
def __str__(self): def __str__(self):
return f'{self.clocked_in}: {self.student.user.first_name} {self.student.user.last_name}' return f'{self.clocked_in}: {self.student.user.first_name} {self.student.user.last_name}'
def save(self, *args, **kwargs):
if isinstance(self.clocked_out, datetime):
self.duration = self.clocked_out - self.clocked_in
super(Period, self).save(*args, **kwargs)

View File

@ -7,6 +7,9 @@
</header> </header>
<h3>Active sessions</h3> <h3>Active sessions</h3>
<p>
<a href="{% url 'period-create' %}">+ Add new session</a>
</p>
<table> <table>
<thead> <thead>
<tr> <tr>

View File

@ -1,13 +1,17 @@
<section> <section>
<header> <header class="header">
<h1>{{ user.first_name }} {{ user.last_name }}</h1> <h1>{{ user.first_name }} {{ user.last_name }}</h1>
<a href="{% url 'account-update' user.id %}">Update profile</a> <a href="{% url 'account-update' user.id %}">Update profile</a>
</header> </header>
<h5>{{ user.student.department.name }}</h5> <h5>{{ user.student.department.name }}</h5>
{% if user.student.is_clocked_in %} {% if user.student.is_clocked_in and user.student.code_set.last %}
<p>You are currently clocked in.<br> <p>You are currently clocked in.<br>
Current sesson: {{ period_list.first.clocked_in|timesince }}</p> Current sesson: {{ current_period.clocked_in|timesince }}</p>
<a href="{% url 'code-update' user.student.code_set.last.id %}">Clock out →</a> <a href="{% url 'code-update' user.student.code_set.last.id %}">Clock out →</a>
{% elif user.student.is_clocked_in and not user.student.code_set.last %}
<p><span class="message info">You are currently clocked in.</span><br>
Current sesson: <strong>{{ current_period.clocked_in|timesince }}</strong></p>
<a href="{% url 'code-create' %}">Clock out →</a>
{% elif user.student.code_set.last %} {% elif user.student.code_set.last %}
<p>You are not clocked in.</p> <p>You are not clocked in.</p>
<a href="{% url 'code-update' user.student.code_set.last.id %}">Clock in →</a> <a href="{% url 'code-update' user.student.code_set.last.id %}">Clock in →</a>

View File

@ -8,7 +8,8 @@
{% csrf_token %} {% csrf_token %}
{{ form.as_p }} {{ form.as_p }}
<input type="submit" value="Generate code" /> <input type="submit" value="Generate code"> or
<a href="{{request.META.HTTP_REFERER}}">Cancel</a>
</form> </form>
</section> </section>
{% endblock %} {% endblock %}

View File

@ -19,6 +19,8 @@
<dd>{{ period.clocked_in }}</dd> <dd>{{ period.clocked_in }}</dd>
<dt>Clocked out</dt> <dt>Clocked out</dt>
<dd>{{ period.clocked_out }}</dd> <dd>{{ period.clocked_out }}</dd>
<dt>Duration</dt>
<dd>{{ period.duration }}</dd>
</dl> </dl>
</section> </section>
{% endblock %} {% endblock %}

View File

@ -4,11 +4,12 @@
<h1>Generate Period</h1> <h1>Generate Period</h1>
<section> <section>
<form method="post" action="{% url 'period-update' period.id %}"> <form method="post" action="">
{% csrf_token %} {% csrf_token %}
{{ form.as_p }} {{ form.as_p }}
<input type="submit" value="Save changes" /> <input type="submit" value="Save changes" /> or
<a href="{{request.META.HTTP_REFERER}}">Cancel</a>
</form> </form>
</section> </section>
{% endblock %} {% endblock %}

View File

@ -10,6 +10,7 @@ from . import views
urlpatterns = [ urlpatterns = [
path('', views.AttendanceOverview.as_view(), name='attendance-overview'), path('', views.AttendanceOverview.as_view(), name='attendance-overview'),
path('update/', views.AttendanceUpdateView.as_view(), name='attendance-update'), path('update/', views.AttendanceUpdateView.as_view(), name='attendance-update'),
path('periods/new/', views.PeriodCreateView.as_view(), name='period-create'),
path('periods/<int:pk>/', include([ path('periods/<int:pk>/', include([
path('', views.PeriodDetailView.as_view(), name='period-detail'), path('', views.PeriodDetailView.as_view(), name='period-detail'),
path('update/', views.PeriodUpdateView.as_view(), name='period-update'), path('update/', views.PeriodUpdateView.as_view(), name='period-update'),

View File

@ -12,7 +12,7 @@ from django.contrib import messages
from django.contrib.auth.models import User from django.contrib.auth.models import User
from accounts.models import Instructor, Student from accounts.models import Instructor, Student
from .models import Code, Period from .models import Code, Period
from .forms import AttendanceUpdateForm from .forms import AttendanceUpdateForm, PeriodForm
# EXAMPLE PERMISSION MIXIN # EXAMPLE PERMISSION MIXIN
# class MyView(PermissionRequiredMixin, View): # class MyView(PermissionRequiredMixin, View):
@ -32,19 +32,18 @@ class AttendanceOverview(LoginRequiredMixin, TemplateView):
context['period_list'] = Period.objects.order_by('-clocked_in') context['period_list'] = Period.objects.order_by('-clocked_in')
elif hasattr(self.request.user, 'student'): elif hasattr(self.request.user, 'student'):
# sum all duration fields for student # sum all duration fields for student
total_duration = Period.objects.filter( periods_duration_sum = Period.objects.filter(
student=self.request.user.student student = self.request.user.student
).filter( ).filter(
clocked_in__year=timezone.now().year clocked_in__year=timezone.now().year
).filter( ).filter(
clocked_in__month=timezone.now().month clocked_in__month=timezone.now().month
).order_by('-clocked_in'
).aggregate(total_duration=Sum('duration')) ).aggregate(total_duration=Sum('duration'))
hours = "" hours = 0
# Convert to hours floating point # Convert to hours floating point
if hasattr(total_duration, 'total_duration'): if periods_duration_sum['total_duration'] != None:
hours = round((total_duration['total_duration'].total_seconds() / 3600), 2) hours = round((periods_duration_sum['total_duration'].total_seconds() / 3600), 2)
context['period_list'] = Period.objects.filter( context['period_list'] = Period.objects.filter(
student=self.request.user.student student=self.request.user.student
@ -54,6 +53,7 @@ class AttendanceOverview(LoginRequiredMixin, TemplateView):
clocked_in__month=timezone.now().month clocked_in__month=timezone.now().month
).order_by('-clocked_in') ).order_by('-clocked_in')
context['period_total'] = hours context['period_total'] = hours
context['current_period'] = Period.objects.get(pk=self.request.user.student.current_period_id)
return context return context
class AttendanceUpdateView(LoginRequiredMixin, FormView): class AttendanceUpdateView(LoginRequiredMixin, FormView):
@ -67,14 +67,14 @@ class AttendanceUpdateView(LoginRequiredMixin, FormView):
student = Student.objects.get(student_number=student_number) student = Student.objects.get(student_number=student_number)
if student.is_clocked_in: if student.is_clocked_in:
student.is_clocked_in=False student.is_clocked_in=False
period = student.period_set.last() period = student.period_set.get(pk=student.current_period_id)
period.clocked_out=timezone.now() period.clocked_out=timezone.now()
period.duration=period.clocked_out-period.clocked_in
student.save() student.save()
period.save() period.save()
messages.add_message(self.request, messages.INFO, f'{student.user.first_name} {student.user.last_name} clocked out.') messages.add_message(self.request, messages.INFO, f'{student.user.first_name} {student.user.last_name} clocked out.')
else: else:
student.period_set.create(student=student, station_number=station_number) c_p = student.period_set.create(student=student, clocked_in=timezone.now(), station_number=station_number)
student.current_period_id = c_p.id
student.is_clocked_in=True student.is_clocked_in=True
student.save() student.save()
messages.add_message(self.request, messages.INFO, f'{student.user.first_name} {student.user.last_name} clocked in.') messages.add_message(self.request, messages.INFO, f'{student.user.first_name} {student.user.last_name} clocked in.')
@ -89,7 +89,7 @@ class AttendanceUpdateView(LoginRequiredMixin, FormView):
# PERIODS # PERIODS
class PeriodCreateView(LoginRequiredMixin, CreateView): class PeriodCreateView(LoginRequiredMixin, CreateView):
model = Period model = Period
fields = ['student'] form_class = PeriodForm
template_name = 'attendance/period_form.html' template_name = 'attendance/period_form.html'
class PeriodDetailView(LoginRequiredMixin, DetailView): class PeriodDetailView(LoginRequiredMixin, DetailView):
@ -98,9 +98,17 @@ class PeriodDetailView(LoginRequiredMixin, DetailView):
class PeriodUpdateView(LoginRequiredMixin, UpdateView): class PeriodUpdateView(LoginRequiredMixin, UpdateView):
model = Period model = Period
fields = ['clocked_out'] form_class = PeriodForm
template_name = 'attendance/period_form.html' template_name = 'attendance/period_form.html'
# When editing a period, check if it matches the current period of the student and clock them out
def form_valid(self, form):
student = form.instance.student
if form.instance.id == student.current_period_id:
student.is_clocked_in = False
student.save()
return super().form_valid(form)
def get_success_url(self): def get_success_url(self):
pk = self.kwargs["pk"] pk = self.kwargs["pk"]
return reverse('period-detail', kwargs={'pk': pk}) return reverse('period-detail', kwargs={'pk': pk})

0
home/__init__.py Normal file
View File

3
home/admin.py Normal file
View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

5
home/apps.py Normal file
View File

@ -0,0 +1,5 @@
from django.apps import AppConfig
class HomeConfig(AppConfig):
name = 'home'

View File

3
home/models.py Normal file
View File

@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

View File

@ -0,0 +1,6 @@
{% extends 'application.html' %}
{% load static %}
{% block content %}
<h1>Welcome home</h1>
{% endblock %}

3
home/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

12
home/urls.py Normal file
View File

@ -0,0 +1,12 @@
from django.urls import include, path
from . import views
# list/ /users/
# create/ /users/new/
# detail/ /users/1/
# update/ /users/1/update/ (update shift preferences)
# delete/ /users/1/delete/
urlpatterns = [
path('', views.HomeView.as_view(), name='home'),
]

5
home/views.py Normal file
View File

@ -0,0 +1,5 @@
from django.shortcuts import render
from django.views.generic.base import TemplateView
class HomeView(TemplateView):
template_name = "home/home.html"

View File

@ -6,10 +6,8 @@ accent 2 (orange) #9b6f45
accent 3 (blue) #242e34 accent 3 (blue) #242e34
*/ */
@import url('https://fonts.googleapis.com/css2?family=Heebo:wght@400;700;900&display=swap');
html { html {
font-size: 1.125em; font-size: 100%;
} }
body { body {
@ -17,46 +15,36 @@ body {
margin: 0.25in auto; margin: 0.25in auto;
background-color: #f7f7f7; background-color: #f7f7f7;
color: #323834; color: #323834;
font-family: 'Heebo', sans-serif; font-family: 'Lato', sans-serif;
font-weight: 400; font-weight: 400;
line-height: 1.65; line-height: 1.75;
} }
header {
display: flex;
align-items: baseline;
justify-content: space-between;
}
/* Text Elements */
p { p {
margin-bottom: 1.15rem; margin-bottom: 1rem;
} }
h1, h2, h3, h4, h5 { h1, h2, h3, h4, h5 {
/*margin: 2.75rem 0 1.05rem;*/ margin: 3rem 0 1.38rem;
margin: 0 0 1.05rem; font-family: 'Lato', sans-serif;
font-weight: 800; line-height: 1.3;
line-height: 1.15;
} }
h1 { h1 {
margin-top: 0; margin-top: 0;
font-size: 2.488em; font-size: 2.488rem;
} }
h2 { h2 {
font-size: 2.074em; font-size: 2.074rem;
} }
h3 { h3 {
font-size: 1.728em; font-size: 1.728rem;
} }
h4 { h4 {
font-size: 1.44em; font-size: 1.44rem;
} }
h5 { h5 {
font-size: 1.2em; font-size: 1.2rem;
} }
small { small {
font-size: 0.833em; font-size: 0.833rem;
} }
a { a {
@ -65,14 +53,23 @@ a {
white-space: nowrap; white-space: nowrap;
} }
small {
font-size: 0.8em;
}
hr { hr {
margin: 0; margin: 0;
border: 0.8px solid #bdc3c7; border: 0.8px solid #bdc3c7;
} }
.navigation {
display: flex;
align-items: baseline;
justify-content: space-between;
}
.header {
display: flex;
align-items: top;
justify-content: space-between;
}
blockquote { blockquote {
text-align: left; text-align: left;
padding: 0 15px; padding: 0 15px;
@ -109,6 +106,7 @@ label {
input[type=text], input[type=text],
input[type=email], input[type=email],
input[type=number],
input[type=password], input[type=password],
select[multiple=multiple], select[multiple=multiple],
textarea, textarea,
@ -219,4 +217,4 @@ th {
/* Messages */ /* Messages */
.messages > .info {color: green;} .info {color: green;}

View File

@ -9,11 +9,13 @@
<link rel="shortcut icon" type="image/png" href="{% static 'favicon.ico' %}"/> <link rel="shortcut icon" type="image/png" href="{% static 'favicon.ico' %}"/>
{% block head %} {% block head %}
{% endblock %} {% endblock %}
<link rel="preconnect" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,400;0,700;1,400;1,700&display=swap" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="{% static 'css/base.css' %}"> <link rel="stylesheet" type="text/css" href="{% static 'css/base.css' %}">
</head> </head>
<body> <body>
<header> <header class="navigation">
<h4>BTech Time Tracker</h4> <p><strong>BTech Time Tracker</strong></p>
{% if user.is_authenticated %} {% if user.is_authenticated %}
<nav> <nav>
<a href="{% url 'account-update' user.id %}">{{ user.first_name }} {{ user.last_name }}</a> / <a href="{% url 'account-update' user.id %}">{{ user.first_name }} {{ user.last_name }}</a> /

View File

@ -39,6 +39,7 @@ INSTALLED_APPS = [
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'compressor', 'compressor',
'qr_code', 'qr_code',
'home.apps.HomeConfig',
'accounts.apps.AccountsConfig', 'accounts.apps.AccountsConfig',
'attendance.apps.AttendanceConfig', 'attendance.apps.AttendanceConfig',
] ]

View File

@ -17,6 +17,7 @@ from django.contrib import admin
from django.urls import path, include from django.urls import path, include
urlpatterns = [ urlpatterns = [
path('', include('home.urls')),
path('accounts/', include('django.contrib.auth.urls')), path('accounts/', include('django.contrib.auth.urls')),
path('accounts/', include('accounts.urls')), path('accounts/', include('accounts.urls')),
path('attendance/', include('attendance.urls')), path('attendance/', include('attendance.urls')),