Merge branch 'master' into develop
This commit is contained in:
commit
863f3c061e
@ -19,7 +19,7 @@ class Instructor(models.Model):
|
||||
modified = models.DateTimeField(auto_now=True)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('user-detail', kwargs={'pk': self.pk})
|
||||
return reverse('account-detail', kwargs={'pk': self.pk})
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.user.first_name} {self.user.last_name}'
|
||||
@ -29,12 +29,13 @@ class Student(models.Model):
|
||||
student_number = models.IntegerField()
|
||||
department = models.ForeignKey(Department, on_delete=models.CASCADE)
|
||||
is_clocked_in = models.BooleanField(default=False)
|
||||
current_period_id = models.IntegerField(blank=True, null=True)
|
||||
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
modified = models.DateTimeField(auto_now=True)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('user-detail', kwargs={'pk': self.pk})
|
||||
return reverse('account-detail', kwargs={'pk': self.pk})
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.student_number}: {self.user.first_name} {self.user.last_name}'
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
<tr>
|
||||
<td>{{ user.username }}</td>
|
||||
<td>{{user.first_name}} {{user.last_name}}</td>
|
||||
<td><a href="{% url 'user-detail' user.id %}">Update</a></td>
|
||||
<td><a href="{% url 'account-detail' user.id %}">Update</a></td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td>No users yet.</td></tr>
|
||||
|
||||
@ -1,3 +1,108 @@
|
||||
from django.test import TestCase
|
||||
import json
|
||||
from django.test import TestCase, Client
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from django.contrib.auth.models import User
|
||||
from .models import Department, Instructor, Student
|
||||
|
||||
# Create your tests here.
|
||||
class TestModels(TestCase):
|
||||
def setUp(self):
|
||||
self.peter = User.objects.create_user('Peter Templer', 'peter@testing.com', 'peterspassword321')
|
||||
self.nick = User.objects.create_user('Nick Jenkins', 'nick@testing.com', 'nickspassword321')
|
||||
self.department = Department.objects.create(name = "Blandings Castle")
|
||||
self.student = Student.objects.create(
|
||||
user = self.peter,
|
||||
student_number = 357950,
|
||||
department = self.department,
|
||||
)
|
||||
self.instructor = Instructor.objects.create(
|
||||
user = self.nick,
|
||||
department = self.department,
|
||||
)
|
||||
|
||||
def test_student_user_relationship(self):
|
||||
self.assertEquals(self.peter, self.student.user)
|
||||
|
||||
def test_student_get_absolute_url(self):
|
||||
url = self.student.get_absolute_url()
|
||||
self.assertEquals(url, '/accounts/1/')
|
||||
|
||||
def test_instructor_user_relationship(self):
|
||||
self.assertEquals(self.nick, self.instructor.user)
|
||||
|
||||
def test_instructor_get_absolute_url(self):
|
||||
url = self.instructor.get_absolute_url()
|
||||
self.assertEquals(url, '/accounts/1/')
|
||||
|
||||
class TestViews(TestCase):
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
self.reverse_urls = {
|
||||
'list': reverse('account-list'),
|
||||
'create': reverse('account-create'),
|
||||
'detail': reverse('account-detail', args=['1']),
|
||||
'update': reverse('account-update', args=['1']),
|
||||
'delete': reverse('account-delete', args=['1'])
|
||||
}
|
||||
|
||||
self.peter = User.objects.create_user('Peter Templer', 'peter@testing.com', 'peterspassword321')
|
||||
self.nick = User.objects.create_user('Nick Jenkins', 'nick@testing.com', 'nickspassword321')
|
||||
self.department = Department.objects.create(name = "Blandings Castle")
|
||||
self.student = Student.objects.create(
|
||||
user = self.peter,
|
||||
student_number = 357950,
|
||||
department = self.department,
|
||||
)
|
||||
self.instructor = Instructor.objects.create(
|
||||
user = self.nick,
|
||||
department = self.department,
|
||||
)
|
||||
self.client.force_login(self.peter)
|
||||
|
||||
def test_students_list(self):
|
||||
response = self.client.get(self.reverse_urls['list'])
|
||||
self.assertEquals(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'accounts/account_list.html')
|
||||
|
||||
def test_students_create_GET(self):
|
||||
response = self.client.get(self.reverse_urls['create'])
|
||||
self.assertEquals(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'accounts/account_form.html')
|
||||
|
||||
def test_students_create_POST(self):
|
||||
response = self.client.post(self.reverse_urls['create'], {
|
||||
'user': self.peter.id,
|
||||
'student_number': 357950,
|
||||
'department': self.department,
|
||||
}, follow=True)
|
||||
|
||||
self.assertRedirects(
|
||||
response,
|
||||
reverse('account-detail', args=[self.peter.id]),
|
||||
status_code=302,
|
||||
target_status_code=200,
|
||||
fetch_redirect_response=True
|
||||
)
|
||||
|
||||
def test_students_detail(self):
|
||||
response = self.client.get(self.reverse_urls['detail'])
|
||||
self.assertEquals(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'accounts/account_detail.html')
|
||||
|
||||
def test_students_update_GET(self):
|
||||
response = self.client.get(self.reverse_urls['update'])
|
||||
self.assertEquals(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'accounts/account_form.html')
|
||||
|
||||
def test_students_update_POST(self):
|
||||
pass
|
||||
|
||||
def test_student_delete(self):
|
||||
response = self.client.get(self.reverse_urls['delete'])
|
||||
self.assertEquals(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'accounts/account_confirm_delete.html')
|
||||
|
||||
def test_student_delete(self):
|
||||
response = self.client.delete(self.reverse_urls['delete'], json.dumps({ 'id': 1 }))
|
||||
|
||||
self.assertEquals(response.status_code, 302)
|
||||
@ -7,3 +7,20 @@ class AttendanceUpdateForm(forms.Form):
|
||||
max_length=100,
|
||||
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):',
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
from datetime import datetime
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
from accounts.models import Student
|
||||
@ -20,7 +21,7 @@ class Code(models.Model):
|
||||
|
||||
class Period(models.Model):
|
||||
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)
|
||||
station_number = models.IntegerField()
|
||||
duration = models.DurationField(blank=True, null=True)
|
||||
@ -38,3 +39,8 @@ class Period(models.Model):
|
||||
|
||||
def __str__(self):
|
||||
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)
|
||||
|
||||
@ -7,6 +7,9 @@
|
||||
</header>
|
||||
|
||||
<h3>Active sessions</h3>
|
||||
<p>
|
||||
<a href="{% url 'period-create' %}">+ Add new session</a>
|
||||
</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
@ -1,13 +1,17 @@
|
||||
<section>
|
||||
<header>
|
||||
<header class="header">
|
||||
<h1>{{ user.first_name }} {{ user.last_name }}</h1>
|
||||
<a href="{% url 'account-update' user.id %}">Update profile</a>
|
||||
</header>
|
||||
<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>
|
||||
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>
|
||||
{% 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 %}
|
||||
<p>You are not clocked in.</p>
|
||||
<a href="{% url 'code-update' user.student.code_set.last.id %}">Clock in →</a>
|
||||
|
||||
@ -8,7 +8,8 @@
|
||||
{% csrf_token %}
|
||||
{{ 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>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
@ -19,6 +19,8 @@
|
||||
<dd>{{ period.clocked_in }}</dd>
|
||||
<dt>Clocked out</dt>
|
||||
<dd>{{ period.clocked_out }}</dd>
|
||||
<dt>Duration</dt>
|
||||
<dd>{{ period.duration }}</dd>
|
||||
</dl>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
@ -4,11 +4,12 @@
|
||||
<h1>Generate Period</h1>
|
||||
|
||||
<section>
|
||||
<form method="post" action="{% url 'period-update' period.id %}">
|
||||
<form method="post" action="">
|
||||
{% csrf_token %}
|
||||
{{ 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>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
@ -1,3 +1,104 @@
|
||||
from django.test import TestCase
|
||||
from django.test import TestCase, Client
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from django.contrib.auth.models import User
|
||||
from accounts.models import Department, Instructor, Student
|
||||
from .models import Code, Period
|
||||
from .forms import AttendanceUpdateForm
|
||||
|
||||
# Code
|
||||
# student
|
||||
# station_number
|
||||
# Period
|
||||
# student
|
||||
# station_number
|
||||
|
||||
class CodeModelTests(TestCase):
|
||||
def setUp(self):
|
||||
self.peter = User.objects.create_user('Peter Templer', 'peter@testing.com', 'peterspassword321')
|
||||
self.nick = User.objects.create_user('Nick Jenkins', 'nick@testing.com', 'nickspassword321')
|
||||
self.department = Department.objects.create(name = "Blandings Castle")
|
||||
self.student = Student.objects.create(
|
||||
user = self.peter,
|
||||
student_number = 357950,
|
||||
department = self.department,
|
||||
)
|
||||
self.instructor = Instructor.objects.create(
|
||||
user = self.nick,
|
||||
department = self.department,
|
||||
)
|
||||
|
||||
self.code = Code.objects.create(student=self.student, station_number=22)
|
||||
|
||||
def test_code_qr_code_str(self):
|
||||
"""test_qr_code_str() returns a string joining on ':' and contains student_number"""
|
||||
self.assertEqual("357950:22", self.code.qr_code_str())
|
||||
|
||||
class TestForms(TestCase):
|
||||
def setUp(self):
|
||||
pass
|
||||
|
||||
def test_attendance_update_form(self):
|
||||
form = AttendanceUpdateForm(data={
|
||||
'qr_string': '357950:22'
|
||||
})
|
||||
|
||||
self.assertTrue(form.is_valid())
|
||||
|
||||
class TestPeriodViews(TestCase):
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
self.reverse_urls = {
|
||||
# 'list': reverse('period-list'),
|
||||
# 'create': reverse('period-create'),
|
||||
'detail': reverse('period-detail', args=['1']),
|
||||
'update': reverse('period-update', args=['1']),
|
||||
'delete': reverse('period-delete', args=['1'])
|
||||
}
|
||||
self.peter = User.objects.create_user('Peter Templer', 'peter@testing.com', 'peterspassword321')
|
||||
self.nick = User.objects.create_user('Nick Jenkins', 'nick@testing.com', 'nickspassword321')
|
||||
self.department = Department.objects.create(name = "Blandings Castle")
|
||||
self.student = Student.objects.create(
|
||||
user = self.peter,
|
||||
student_number = 357950,
|
||||
department = self.department,
|
||||
)
|
||||
self.instructor = Instructor.objects.create(
|
||||
user = self.nick,
|
||||
department = self.department,
|
||||
)
|
||||
self.period = Period.objects.create(
|
||||
student = self.student,
|
||||
station_number = 22,
|
||||
)
|
||||
self.client.force_login(self.peter)
|
||||
|
||||
# def test_period_create_GET(self):
|
||||
# response = self.client.get(self.reverse_urls['create'])
|
||||
# self.assertEquals(response.status_code, 200)
|
||||
# self.assertTemplateUsed(response, 'attendance/period_form.html')
|
||||
|
||||
# def test_period_create_POST(self):
|
||||
# response = self.client.post(self.reverse_urls['create'], {
|
||||
# 'student': self.student.id,
|
||||
# 'station_number': 22,
|
||||
# }, follow=True)
|
||||
|
||||
# self.assertRedirects(
|
||||
# response,
|
||||
# reverse('period-detail', args=[self.a_shift.id]),
|
||||
# status_code=302,
|
||||
# target_status_code=200,
|
||||
# fetch_redirect_response=True
|
||||
# )
|
||||
|
||||
def test_period_detail(self):
|
||||
response = self.client.get(self.reverse_urls['detail'])
|
||||
self.assertEquals(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'attendance/period_detail.html')
|
||||
|
||||
def test_period_update_GET(self):
|
||||
response = self.client.get(self.reverse_urls['update'])
|
||||
self.assertEquals(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'attendance/period_form.html')
|
||||
|
||||
# Create your tests here.
|
||||
|
||||
@ -10,6 +10,7 @@ from . import views
|
||||
urlpatterns = [
|
||||
path('', views.AttendanceOverview.as_view(), name='attendance-overview'),
|
||||
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('', views.PeriodDetailView.as_view(), name='period-detail'),
|
||||
path('update/', views.PeriodUpdateView.as_view(), name='period-update'),
|
||||
|
||||
@ -12,7 +12,7 @@ from django.contrib import messages
|
||||
from django.contrib.auth.models import User
|
||||
from accounts.models import Instructor, Student
|
||||
from .models import Code, Period
|
||||
from .forms import AttendanceUpdateForm
|
||||
from .forms import AttendanceUpdateForm, PeriodForm
|
||||
|
||||
# EXAMPLE PERMISSION MIXIN
|
||||
# class MyView(PermissionRequiredMixin, View):
|
||||
@ -32,17 +32,18 @@ class AttendanceOverview(LoginRequiredMixin, TemplateView):
|
||||
context['period_list'] = Period.objects.order_by('-clocked_in')
|
||||
elif hasattr(self.request.user, 'student'):
|
||||
# sum all duration fields for student
|
||||
total_duration = Period.objects.filter(
|
||||
periods_duration_sum = Period.objects.filter(
|
||||
student = self.request.user.student
|
||||
).filter(
|
||||
clocked_in__year=timezone.now().year
|
||||
).filter(
|
||||
clocked_in__month=timezone.now().month
|
||||
).order_by('-clocked_in'
|
||||
).aggregate(total_duration=Sum('duration'))
|
||||
|
||||
hours = 0
|
||||
# Convert to hours floating point
|
||||
hours = round((total_duration['total_duration'].total_seconds() / 3600), 2)
|
||||
if periods_duration_sum['total_duration'] != None:
|
||||
hours = round((periods_duration_sum['total_duration'].total_seconds() / 3600), 2)
|
||||
|
||||
context['period_list'] = Period.objects.filter(
|
||||
student=self.request.user.student
|
||||
@ -52,6 +53,7 @@ class AttendanceOverview(LoginRequiredMixin, TemplateView):
|
||||
clocked_in__month=timezone.now().month
|
||||
).order_by('-clocked_in')
|
||||
context['period_total'] = hours
|
||||
context['current_period'] = Period.objects.get(pk=self.request.user.student.current_period_id)
|
||||
return context
|
||||
|
||||
class AttendanceUpdateView(LoginRequiredMixin, FormView):
|
||||
@ -65,14 +67,14 @@ class AttendanceUpdateView(LoginRequiredMixin, FormView):
|
||||
student = Student.objects.get(student_number=student_number)
|
||||
if student.is_clocked_in:
|
||||
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.duration=period.clocked_out-period.clocked_in
|
||||
student.save()
|
||||
period.save()
|
||||
messages.add_message(self.request, messages.INFO, f'{student.user.first_name} {student.user.last_name} clocked out.')
|
||||
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.save()
|
||||
messages.add_message(self.request, messages.INFO, f'{student.user.first_name} {student.user.last_name} clocked in.')
|
||||
@ -87,7 +89,7 @@ class AttendanceUpdateView(LoginRequiredMixin, FormView):
|
||||
# PERIODS
|
||||
class PeriodCreateView(LoginRequiredMixin, CreateView):
|
||||
model = Period
|
||||
fields = ['student']
|
||||
form_class = PeriodForm
|
||||
template_name = 'attendance/period_form.html'
|
||||
|
||||
class PeriodDetailView(LoginRequiredMixin, DetailView):
|
||||
@ -96,9 +98,17 @@ class PeriodDetailView(LoginRequiredMixin, DetailView):
|
||||
|
||||
class PeriodUpdateView(LoginRequiredMixin, UpdateView):
|
||||
model = Period
|
||||
fields = ['clocked_out']
|
||||
form_class = PeriodForm
|
||||
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):
|
||||
pk = self.kwargs["pk"]
|
||||
return reverse('period-detail', kwargs={'pk': pk})
|
||||
|
||||
0
home/__init__.py
Normal file
0
home/__init__.py
Normal file
3
home/admin.py
Normal file
3
home/admin.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
5
home/apps.py
Normal file
5
home/apps.py
Normal file
@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class HomeConfig(AppConfig):
|
||||
name = 'home'
|
||||
0
home/migrations/__init__.py
Normal file
0
home/migrations/__init__.py
Normal file
3
home/models.py
Normal file
3
home/models.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
6
home/templates/home/home.html
Normal file
6
home/templates/home/home.html
Normal file
@ -0,0 +1,6 @@
|
||||
{% extends 'application.html' %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Welcome home</h1>
|
||||
{% endblock %}
|
||||
3
home/tests.py
Normal file
3
home/tests.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
12
home/urls.py
Normal file
12
home/urls.py
Normal 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
5
home/views.py
Normal 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"
|
||||
@ -6,10 +6,8 @@ accent 2 (orange) #9b6f45
|
||||
accent 3 (blue) #242e34
|
||||
*/
|
||||
|
||||
@import url('https://fonts.googleapis.com/css2?family=Heebo:wght@400;700;900&display=swap');
|
||||
|
||||
html {
|
||||
font-size: 1.125em;
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
@ -17,46 +15,36 @@ body {
|
||||
margin: 0.25in auto;
|
||||
background-color: #f7f7f7;
|
||||
color: #323834;
|
||||
font-family: 'Heebo', sans-serif;
|
||||
font-family: 'Lato', sans-serif;
|
||||
font-weight: 400;
|
||||
line-height: 1.65;
|
||||
line-height: 1.75;
|
||||
}
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
/* Text Elements */
|
||||
|
||||
p {
|
||||
margin-bottom: 1.15rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
h1, h2, h3, h4, h5 {
|
||||
/*margin: 2.75rem 0 1.05rem;*/
|
||||
margin: 0 0 1.05rem;
|
||||
font-weight: 800;
|
||||
line-height: 1.15;
|
||||
margin: 3rem 0 1.38rem;
|
||||
font-family: 'Lato', sans-serif;
|
||||
line-height: 1.3;
|
||||
}
|
||||
h1 {
|
||||
margin-top: 0;
|
||||
font-size: 2.488em;
|
||||
font-size: 2.488rem;
|
||||
}
|
||||
h2 {
|
||||
font-size: 2.074em;
|
||||
font-size: 2.074rem;
|
||||
}
|
||||
h3 {
|
||||
font-size: 1.728em;
|
||||
font-size: 1.728rem;
|
||||
}
|
||||
h4 {
|
||||
font-size: 1.44em;
|
||||
font-size: 1.44rem;
|
||||
}
|
||||
h5 {
|
||||
font-size: 1.2em;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
small {
|
||||
font-size: 0.833em;
|
||||
font-size: 0.833rem;
|
||||
}
|
||||
|
||||
a {
|
||||
@ -65,14 +53,23 @@ a {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
hr {
|
||||
margin: 0;
|
||||
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 {
|
||||
text-align: left;
|
||||
padding: 0 15px;
|
||||
@ -109,6 +106,7 @@ label {
|
||||
|
||||
input[type=text],
|
||||
input[type=email],
|
||||
input[type=number],
|
||||
input[type=password],
|
||||
select[multiple=multiple],
|
||||
textarea,
|
||||
@ -219,4 +217,4 @@ th {
|
||||
|
||||
|
||||
/* Messages */
|
||||
.messages > .info {color: green;}
|
||||
.info {color: green;}
|
||||
|
||||
@ -9,11 +9,13 @@
|
||||
<link rel="shortcut icon" type="image/png" href="{% static 'favicon.ico' %}"/>
|
||||
{% block head %}
|
||||
{% 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' %}">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h4>BTech Time Tracker</h4>
|
||||
<header class="navigation">
|
||||
<p><strong>BTech Time Tracker</strong></p>
|
||||
{% if user.is_authenticated %}
|
||||
<nav>
|
||||
<a href="{% url 'account-update' user.id %}">{{ user.first_name }} {{ user.last_name }}</a> /
|
||||
|
||||
@ -39,6 +39,7 @@ INSTALLED_APPS = [
|
||||
'django.contrib.staticfiles',
|
||||
'compressor',
|
||||
'qr_code',
|
||||
'home.apps.HomeConfig',
|
||||
'accounts.apps.AccountsConfig',
|
||||
'attendance.apps.AttendanceConfig',
|
||||
]
|
||||
|
||||
@ -17,6 +17,7 @@ from django.contrib import admin
|
||||
from django.urls import path, include
|
||||
|
||||
urlpatterns = [
|
||||
path('', include('home.urls')),
|
||||
path('accounts/', include('django.contrib.auth.urls')),
|
||||
path('accounts/', include('accounts.urls')),
|
||||
path('attendance/', include('attendance.urls')),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user