diff --git a/.gitignore b/.gitignore
index 0c438cb..ff58e89 100644
--- a/.gitignore
+++ b/.gitignore
@@ -251,3 +251,6 @@ dist
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
+
+# CACHES
+CACHE/
diff --git a/Pipfile b/Pipfile
index b2b100d..2ffa8ef 100644
--- a/Pipfile
+++ b/Pipfile
@@ -9,6 +9,9 @@ django-compressor = "*"
qrcode = "*"
pillow = "*"
django-qr-code = "*"
+pytz = "*"
+django-easy-timezones = "*"
+django-libsass = "*"
[dev-packages]
diff --git a/Pipfile.lock b/Pipfile.lock
index 5f3847f..d7ef7f9 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
- "sha256": "d1dd018056bda89f00de6e3fbdd88cfa2fc56b756047e88153a4f8e99171e1bb"
+ "sha256": "fa23d606797893ecd178aaeda5c8301bcff6f72d7b79fcdb1bf4947207b7a9bc"
},
"pipfile-spec": 6,
"requires": {
@@ -26,11 +26,11 @@
},
"django": {
"hashes": [
- "sha256:2d78425ba74c7a1a74b196058b261b9733a8570782f4e2828974777ccca7edf7",
- "sha256:efa2ab96b33b20c2182db93147a0c3cd7769d418926f9e9f140a60dca7c64ca9"
+ "sha256:169e2e7b4839a7910b393eec127fd7cbae62e80fa55f89c6510426abf673fe5f",
+ "sha256:c6c0462b8b361f8691171af1fb87eceb4442da28477e12200c40420176206ba7"
],
"index": "pypi",
- "version": "==3.1.5"
+ "version": "==3.1.6"
},
"django-appconf": {
"hashes": [
@@ -47,6 +47,22 @@
"index": "pypi",
"version": "==2.4"
},
+ "django-easy-timezones": {
+ "hashes": [
+ "sha256:1c90f8a9731bf0762b6c7d239beb5b9434f45e8c9bbdc3586faa69851d00c434",
+ "sha256:d28a5e60263de39eeef72240dac01090a11f7212d942dfbd3b4ba4be7b2bf77e"
+ ],
+ "index": "pypi",
+ "version": "==0.8.0"
+ },
+ "django-libsass": {
+ "hashes": [
+ "sha256:38fab4ce1245542f3afd7248dc48f8a0b261f5f6c61e7cc43969a9c9079b5ffd",
+ "sha256:3e74fd8e75ac0e6ebc0443efc3e530167981bf279fecc2294094c820ae266fbb"
+ ],
+ "index": "pypi",
+ "version": "==0.8"
+ },
"django-qr-code": {
"hashes": [
"sha256:5fdd84f4c02127c262e6987bd1a9396a47f2b806be04aa0bebfcb786a9f6a0b7",
@@ -55,6 +71,31 @@
"index": "pypi",
"version": "==2.1.0"
},
+ "ipaddress": {
+ "hashes": [
+ "sha256:5a3182b322a706525c46282ca6f064d27a02cffbd449f9f47416f1dc96aa71b0",
+ "sha256:935712800ce4760701d89ad677666cd52691fd2f6f0b340c8b4239a3c17988a5"
+ ],
+ "version": "==1.0.16"
+ },
+ "libsass": {
+ "hashes": [
+ "sha256:1521d2a8d4b397c6ec90640a1f6b5529077035efc48ef1c2e53095544e713d1b",
+ "sha256:1b2d415bbf6fa7da33ef46e549db1418498267b459978eff8357e5e823962d35",
+ "sha256:25ebc2085f5eee574761ccc8d9cd29a9b436fc970546d5ef08c6fa41eb57dff1",
+ "sha256:2ae806427b28bc1bb7cb0258666d854fcf92ba52a04656b0b17ba5e190fb48a9",
+ "sha256:4a246e4b88fd279abef8b669206228c92534d96ddcd0770d7012088c408dff23",
+ "sha256:553e5096414a8d4fb48d0a48f5a038d3411abe254d79deac5e008516c019e63a",
+ "sha256:697f0f9fa8a1367ca9ec6869437cb235b1c537fc8519983d1d890178614a8903",
+ "sha256:a8fd4af9f853e8bf42b1425c5e48dd90b504fa2e70d7dac5ac80b8c0a5a5fe85",
+ "sha256:c9411fec76f480ffbacc97d8188322e02a5abca6fc78e70b86a2a2b421eae8a2",
+ "sha256:daa98a51086d92aa7e9c8871cf1a8258124b90e2abf4697852a3dca619838618",
+ "sha256:e0e60836eccbf2d9e24ec978a805cd6642fa92515fbd95e3493fee276af76f8a",
+ "sha256:e64ae2587f1a683e831409aad03ba547c245ef997e1329fffadf7a866d2510b8",
+ "sha256:f6852828e9e104d2ce0358b73c550d26dd86cc3a69439438c3b618811b9584f5"
+ ],
+ "version": "==0.20.1"
+ },
"pillow": {
"hashes": [
"sha256:165c88bc9d8dba670110c689e3cc5c71dbe4bfb984ffa7cbebf1fac9554071d6",
@@ -93,12 +134,31 @@
"index": "pypi",
"version": "==8.1.0"
},
+ "pygeoip": {
+ "hashes": [
+ "sha256:1938b9dac7b00d77f94d040b9465ea52c938f3fcdcd318b5537994f3c16aef96",
+ "sha256:f22c4e00ddf1213e0fae36dc60b46ee7c25a6339941ec1a975539014c1f9a96d"
+ ],
+ "version": "==0.3.2"
+ },
"pytz": {
"hashes": [
- "sha256:16962c5fb8db4a8f63a26646d8886e9d769b6c511543557bc84e9569fb9a9cb4",
- "sha256:180befebb1927b16f6b57101720075a984c019ac16b1b7575673bea42c6c3da5"
+ "sha256:0ca2c5966a10e8044db98bac372ed238f323d8d526bbd9a601c3520b98a4bbd3",
+ "sha256:1ce2d13b6503a7260c950a25fa6d26ba8d4da1a7077e062aa9e82c74949aaf4b",
+ "sha256:35b5a38d6ffda82220a9e6b97b7b7162887b2d7ff82a1b601979e60d13062718",
+ "sha256:4e9d04cb71d5db26123a1602eb402dab9a7b3686e2786d1e170858e31b59850f",
+ "sha256:51ee2cbb7309df4c2f7c1d2c418aeb869ad48928ed590da7689c034dda7c912f",
+ "sha256:58f594447ca62397c7be6c40fc9406c1d60836cb40974f279058062e58752cea",
+ "sha256:62fd4e22635f609cf1f5da3037612214758ff5f510f4e877ec31d3bbbb995802",
+ "sha256:7dd7accd1690ed32fbd90ad9d273c84e8e09178cdc190b195347d27daebd40df",
+ "sha256:8eba058ab00830a8e3c0a8b81fe5d7b367f0fd18277c94afc9d674a99eae5d42",
+ "sha256:937f30580a1ef85c28fea2672871c2e3df9a22fb9070bc77c20d26ab99ea6369",
+ "sha256:b1f51ed44fe5c25f088bc8c7c189060dc1258b157b5bb6d46d08cc033fca6d1a",
+ "sha256:de72bec3c2f2c86bea4c0c83a921031613976b05ff7f14e1bae1149f177b7746",
+ "sha256:f25c9941d5e80865bfab0f49627e7e199e81ac2f47497b19bebb498dc494b7c9"
],
- "version": "==2020.5"
+ "index": "pypi",
+ "version": "==2016.6"
},
"qrcode": {
"hashes": [
@@ -155,6 +215,13 @@
],
"markers": "python_version >= '3.5'",
"version": "==0.4.1"
+ },
+ "wheel": {
+ "hashes": [
+ "sha256:1ebb8ad7e26b448e9caa4773d2357849bf80ff9e313964bcaf79cbf0201a1648",
+ "sha256:ea8033fc9905804e652f75474d33410a07404c1a78dd3c949a66863bd1050ebd"
+ ],
+ "version": "==0.29.0"
}
},
"develop": {}
diff --git a/accounts/models.py b/accounts/models.py
index 6330c67..73df69f 100644
--- a/accounts/models.py
+++ b/accounts/models.py
@@ -14,6 +14,7 @@ class Department(models.Model):
class Instructor(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
department = models.ForeignKey(Department, on_delete=models.CASCADE)
+ timezone = models.CharField(max_length=20)
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
@@ -30,6 +31,7 @@ class Student(models.Model):
department = models.ForeignKey(Department, on_delete=models.CASCADE)
is_clocked_in = models.BooleanField(default=False)
current_period_id = models.IntegerField(blank=True, null=True)
+ timezone = models.CharField(max_length=20)
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
diff --git a/accounts/templates/accounts/account_form.html b/accounts/templates/accounts/account_form.html
index 083ffd0..6b3918a 100644
--- a/accounts/templates/accounts/account_form.html
+++ b/accounts/templates/accounts/account_form.html
@@ -1,19 +1,20 @@
{% extends 'application.html' %}
{% block content %}
-
Update User
+
+ Update User
-
{{ user.first_name }} {{ user.last_name }}
{% if user.student %}
Student ID: {{user.student.student_number}}
{% endif %}
+ Change password
- Change password
{% endblock %}
diff --git a/accounts/templates/accounts/timezone.html b/accounts/templates/accounts/timezone.html
new file mode 100644
index 0000000..d9012b5
--- /dev/null
+++ b/accounts/templates/accounts/timezone.html
@@ -0,0 +1,23 @@
+{% extends 'application.html' %}
+{% load tz %}
+{% get_current_timezone as TIME_ZONE %}
+
+{% block content %}
+
+{% endblock %}
diff --git a/accounts/urls.py b/accounts/urls.py
index e0b42e8..ce61e20 100644
--- a/accounts/urls.py
+++ b/accounts/urls.py
@@ -1,13 +1,14 @@
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/
+# list/ /accounts/
+# create/ /accounts/new/
+# detail/ /accounts/1/
+# update/ /accounts/1/update/
+# delete/ /accounts/1/delete/
urlpatterns = [
+ path('timezone/', views.set_timezone, name='set_timezone'),
path('', views.AccountListView.as_view(), name='account-list'),
path('new/', views.AccountCreateView.as_view(), name='account-create'),
path('/', include([
diff --git a/accounts/views.py b/accounts/views.py
index 7cfd233..9b4aa25 100644
--- a/accounts/views.py
+++ b/accounts/views.py
@@ -1,4 +1,5 @@
-from django.shortcuts import render, reverse
+import pytz
+from django.shortcuts import render, reverse, redirect
from django.urls import reverse_lazy
from django.views.generic.edit import FormView, CreateView, UpdateView, DeleteView
from django.views.generic.detail import DetailView
@@ -8,6 +9,13 @@ from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.models import User
from django.contrib.auth.forms import PasswordChangeForm
+def set_timezone(request):
+ if request.method == 'POST':
+ request.session['django_timezone'] = request.POST['timezone']
+ return redirect('attendance-overview')
+ else:
+ return render(request, 'accounts/timezone.html', {'timezones': pytz.common_timezones})
+
class AccountListView(LoginRequiredMixin, ListView):
model = User
template_name = 'accounts/account_list.html'
diff --git a/attendance/templates/attendance/_student_code.html b/attendance/templates/attendance/_student_code.html
new file mode 100644
index 0000000..5d0259e
--- /dev/null
+++ b/attendance/templates/attendance/_student_code.html
@@ -0,0 +1,21 @@
+{% if user.student.code_set.last %}
+
+
+ {% if clocked_in %}
+ Clock out →
+ {% else %}
+ Clock in →
+ {% endif %}
+
+
+{% else %}
+
+
+ {% if clocked_in %}
+ Clock out →
+ {% else %}
+ Clock in →
+ {% endif %}
+
+
+{% endif %}
diff --git a/attendance/templates/attendance/_student_periods.html b/attendance/templates/attendance/_student_periods.html
new file mode 100644
index 0000000..d46eff2
--- /dev/null
+++ b/attendance/templates/attendance/_student_periods.html
@@ -0,0 +1,29 @@
+
+ {% now "F Y" %}
+
+
+ | Station |
+ Clocked in |
+ Clocked out |
+ Duration |
+
+
+
+ {% for period in period_list %}
+ {% if period.clocked_out %}
+
+ | {{ period.station_number }} |
+ {{ period.clocked_in }} |
+ {% if period.clocked_out %}
+ {{ period.clocked_out }} |
+ {{ period.clocked_in|timesince:period.clocked_out }} |
+ {% else %}
+ Current sesson: {{ period.clocked_in|timesince }} |
+ {% endif %}
+
+ {% endif %}
+ {% empty %}
+ | No periods yet. |
+ {% endfor %}
+
+
diff --git a/attendance/templates/attendance/attendance_overview_instructor.html b/attendance/templates/attendance/attendance_overview_instructor.html
index 44511f3..c7b818f 100644
--- a/attendance/templates/attendance/attendance_overview_instructor.html
+++ b/attendance/templates/attendance/attendance_overview_instructor.html
@@ -1,4 +1,3 @@
-{% load l10n %}