Merge branch 'develop'

This commit is contained in:
Nathan Chapman 2021-02-04 22:01:06 -07:00
commit ec573cafdc
37 changed files with 927 additions and 112 deletions

3
.gitignore vendored
View File

@ -251,3 +251,6 @@ dist
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
# CACHES
CACHE/

View File

@ -9,6 +9,9 @@ django-compressor = "*"
qrcode = "*"
pillow = "*"
django-qr-code = "*"
pytz = "*"
django-easy-timezones = "*"
django-libsass = "*"
[dev-packages]

81
Pipfile.lock generated
View File

@ -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": {}

View File

@ -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)

View File

@ -1,19 +1,20 @@
{% extends 'application.html' %}
{% block content %}
<h1>Update User</h1>
<section class="panel test">
<h1>Update User</h1>
<section>
<h3>{{ user.first_name }} {{ user.last_name }}</h3>
{% if user.student %}
<p>Student ID: {{user.student.student_number}}</p>
{% endif %}
<p><a href="{% url 'password_change' %}">Change password</a></p>
<form method="post" action="{% url 'account-update' user.id %}">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Save changes" />
<input type="submit" value="Save changes"> or
<a href="{% url 'account-detail' user.id %}">Cancel</a>
</form>
<p><a href="{% url 'password_change' %}">Change password</a></p>
</section>
{% endblock %}

View File

@ -0,0 +1,23 @@
{% extends 'application.html' %}
{% load tz %}
{% get_current_timezone as TIME_ZONE %}
{% block content %}
<section class="panel">
<h1>Set timezone</h1>
<form action="{% url 'set_timezone' %}" method="POST">
{% csrf_token %}
<p>
<label for="timezone">Please select your timezone:</label><br>
<select name="timezone">
{% for tz in timezones %}
<option value="{{ tz }}"{% if tz == TIME_ZONE %} selected{% elif tz == 'US/Mountain' %} selected{% endif %}>{{ tz }}</option>
{% endfor %}
</select>
</p>
<p>
<input type="submit" value="Set timezone">
</p>
</form>
</section>
{% endblock %}

View File

@ -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('<int:pk>/', include([

View File

@ -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'

View File

@ -0,0 +1,21 @@
{% if user.student.code_set.last %}
<p class="student__code code">
<a class="code__button action-button" href="{% url 'code-update' user.student.code_set.last.id %}">
{% if clocked_in %}
Clock out →
{% else %}
Clock in →
{% endif %}
</a>
</p>
{% else %}
<p class="student__code code">
<a class="action-button" href="{% url 'code-create' %}">
{% if clocked_in %}
Clock out →
{% else %}
Clock in →
{% endif %}
</a>
</p>
{% endif %}

View File

@ -0,0 +1,29 @@
<table class="attendance__log">
<caption class="attendance__month">{% now "F Y" %}</caption>
<thead>
<tr>
<th>Station</th>
<th>Clocked in</th>
<th>Clocked out</th>
<th>Duration</th>
</tr>
</thead>
<tbody>
{% for period in period_list %}
{% if period.clocked_out %}
<tr>
<td>{{ period.station_number }}</td>
<td>{{ period.clocked_in }}</td>
{% if period.clocked_out %}
<td>{{ period.clocked_out }}</td>
<td>{{ period.clocked_in|timesince:period.clocked_out }}</td>
{% else %}
<td colspan="2">Current sesson: {{ period.clocked_in|timesince }}</td>
{% endif %}
</tr>
{% endif %}
{% empty %}
<tr><td colspan="2">No periods yet.</td></tr>
{% endfor %}
</tbody>
</table>

View File

@ -1,4 +1,3 @@
{% load l10n %}
<section>
<header>
<h1>{{ user.instructor.department.name }}</h1>
@ -9,7 +8,7 @@
<h3>Active sessions</h3>
<p>
<a href="{% url 'period-create' %}">+ Add new session</a>
<a class="action-button" href="{% url 'period-create' %}">+ Add new session</a>
</p>
<table>
<thead>
@ -26,9 +25,9 @@
<tr>
<td>{{ period.student }}</td>
<td>{{ period.station_number }}</td>
<td>{{ period.clocked_in|localize }}</td>
<td>{{ period.clocked_in }}</td>
{% if period.clocked_out %}
<td>{{ period.clocked_out|localize }}</td>
<td>{{ period.clocked_out }}</td>
<td>{{ period.clocked_in|timesince:period.clocked_out }}</td>
{% else %}
<td>Current sesson: {{ period.clocked_in|timesince }}</td>
@ -61,9 +60,9 @@
<tr>
<td>{{ period.student }}</td>
<td>{{ period.station_number }}</td>
<td>{{ period.clocked_in|localize }}</td>
<td>{{ period.clocked_in }}</td>
{% if period.clocked_out %}
<td>{{ period.clocked_out|localize }}</td>
<td>{{ period.clocked_out }}</td>
<td>{{ period.clocked_in|timesince:period.clocked_out }}</td>
{% else %}
<td colspan="2">Current sesson: {{ period.clocked_in|timesince }}</td>

View File

@ -1,55 +1,22 @@
<section>
<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 and user.student.code_set.last %}
<p>You are currently clocked in.<br>
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>
<section class="student panel">
<h1 class="student__department">{{ user.student.department.name }}</h1>
{% if user.student.is_clocked_in %}
<div class="student__statusbar status">
<p><span class="status__clocked--clocked-in">You are currently clocked in.</span></p>
<p>
Clocked in at: <strong>{{ current_period.clocked_in|date:"P" }}</strong><br>
Current sesson: <strong>{{ current_period.clocked_in|timesince }}</strong>
</p>
</div>
{% include 'attendance/_student_code.html' with clocked_in=True %}
{% else %}
<p>You are not clocked in.</p>
<a href="{% url 'code-create' %}">Clock in →</a>
<div class="status__clocked"></div>You are not clocked in.</p>
{% include 'attendance/_student_code.html' with clocked_in=False %}
{% endif %}
</section>
<article>
<h2>Attendance log</h2>
<p>Total hours for the month: {{period_total}}<br>
<section class="attendance">
<h2 class="attendance__title">Attendance log</h2>
<p class="attendance__total">Total hours for the month: <strong>{{period_total}}</strong><br>
<small>(Does not include current session.)</small></p>
<table>
<thead>
<tr>
<th>Station</th>
<th>Clocked in</th>
<th>Clocked out</th>
<th>Duration</th>
</tr>
</thead>
<tbody>
{% for period in period_list %}
{% if period.clocked_out %}
<tr>
<td>{{ period.station_number }}</td>
<td>{{ period.clocked_in|localize }}</td>
{% if period.clocked_out %}
<td>{{ period.clocked_out|localize }}</td>
<td>{{ period.clocked_in|timesince:period.clocked_out }}</td>
{% else %}
<td colspan="2">Current sesson: {{ period.clocked_in|timesince }}</td>
{% endif %}
</tr>
{% endif %}
{% empty %}
<tr><td colspan="2">No periods yet.</td></tr>
{% endfor %}
</tbody>
</table>
</article>
{% include 'attendance/_student_periods.html' %}
</section>

View File

@ -4,11 +4,9 @@
{% block content %}
<h1>Clock in</h1>
<section>
<p>
{% qr_from_text code.qr_code_str size=24 version=2 %}
</p>
<section class="code_detail">
<p><em>Scan code at clock-in station</em></p>
<a href="{% url 'attendance-overview' %}">Done</a>
<span>{% qr_from_text code.qr_code_str size=24 version=2 %}</span>
<span><a class="action-button" href="{% url 'attendance-overview' %}">Done</a></span>
</section>
{% endblock %}

View File

@ -44,8 +44,6 @@ class AttendanceOverview(LoginRequiredMixin, TemplateView):
clocked_in__month=timezone.now().month
).aggregate(total_duration=Sum('duration'))
print(periods_duration_sum)
print(timezone.now().month)
hours = 0
# Convert to hours floating point
if periods_duration_sum['total_duration'] != None:

View File

@ -62,6 +62,7 @@ hr {
display: flex;
align-items: baseline;
justify-content: space-between;
margin: 0 1rem;
}
.header {
@ -104,13 +105,23 @@ label {
font-weight: 700;
}
select {
text-align: left;
border: 4px solid #bdc3c7;
padding: 0.5em;
outline: 0;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
input[type=text],
input[type=email],
input[type=number],
input[type=password],
select[multiple=multiple],
textarea,
.action-button,
.datepickr {
text-align: left;
color: #2c3e50;
@ -178,6 +189,10 @@ textarea {
font-style: oblique;
}
article {
margin: 0 1rem;
}
section {
padding: 1em;
@ -212,9 +227,31 @@ th {
border-color: #bdc3c7;
}
.action-button {
font-weight: 900;
padding: 0.5em 1em;
border: none;
max-width: 12rem;
background-color: #2980B9;
color: #ffffff;
cursor: pointer;
text-decoration: none;
box-shadow: 0 4px 0 #2980B9;
}
/* Messages */
.info {color: green;}
.code_detail {
display: flex;
flex-direction: column;
justify-content: center;
text-align: center;
}

349
static/css/normalize.css vendored Normal file
View File

@ -0,0 +1,349 @@
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
/* Document
========================================================================== */
/**
* 1. Correct the line height in all browsers.
* 2. Prevent adjustments of font size after orientation changes in iOS.
*/
html {
line-height: 1.15; /* 1 */
-webkit-text-size-adjust: 100%; /* 2 */
}
/* Sections
========================================================================== */
/**
* Remove the margin in all browsers.
*/
body {
margin: 0;
}
/**
* Render the `main` element consistently in IE.
*/
main {
display: block;
}
/**
* Correct the font size and margin on `h1` elements within `section` and
* `article` contexts in Chrome, Firefox, and Safari.
*/
h1 {
font-size: 2em;
margin: 0.67em 0;
}
/* Grouping content
========================================================================== */
/**
* 1. Add the correct box sizing in Firefox.
* 2. Show the overflow in Edge and IE.
*/
hr {
box-sizing: content-box; /* 1 */
height: 0; /* 1 */
overflow: visible; /* 2 */
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
pre {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/* Text-level semantics
========================================================================== */
/**
* Remove the gray background on active links in IE 10.
*/
a {
background-color: transparent;
}
/**
* 1. Remove the bottom border in Chrome 57-
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
*/
abbr[title] {
border-bottom: none; /* 1 */
text-decoration: underline; /* 2 */
text-decoration: underline dotted; /* 2 */
}
/**
* Add the correct font weight in Chrome, Edge, and Safari.
*/
b,
strong {
font-weight: bolder;
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
samp {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/**
* Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` elements from affecting the line height in
* all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/* Embedded content
========================================================================== */
/**
* Remove the border on images inside links in IE 10.
*/
img {
border-style: none;
}
/* Forms
========================================================================== */
/**
* 1. Change the font styles in all browsers.
* 2. Remove the margin in Firefox and Safari.
*/
button,
input,
optgroup,
select,
textarea {
font-family: inherit; /* 1 */
font-size: 100%; /* 1 */
line-height: 1.15; /* 1 */
margin: 0; /* 2 */
}
/**
* Show the overflow in IE.
* 1. Show the overflow in Edge.
*/
button,
input { /* 1 */
overflow: visible;
}
/**
* Remove the inheritance of text transform in Edge, Firefox, and IE.
* 1. Remove the inheritance of text transform in Firefox.
*/
button,
select { /* 1 */
text-transform: none;
}
/**
* Correct the inability to style clickable types in iOS and Safari.
*/
button,
[type="button"],
[type="reset"],
[type="submit"] {
-webkit-appearance: button;
}
/**
* Remove the inner border and padding in Firefox.
*/
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
border-style: none;
padding: 0;
}
/**
* Restore the focus styles unset by the previous rule.
*/
button:-moz-focusring,
[type="button"]:-moz-focusring,
[type="reset"]:-moz-focusring,
[type="submit"]:-moz-focusring {
outline: 1px dotted ButtonText;
}
/**
* Correct the padding in Firefox.
*/
fieldset {
padding: 0.35em 0.75em 0.625em;
}
/**
* 1. Correct the text wrapping in Edge and IE.
* 2. Correct the color inheritance from `fieldset` elements in IE.
* 3. Remove the padding so developers are not caught out when they zero out
* `fieldset` elements in all browsers.
*/
legend {
box-sizing: border-box; /* 1 */
color: inherit; /* 2 */
display: table; /* 1 */
max-width: 100%; /* 1 */
padding: 0; /* 3 */
white-space: normal; /* 1 */
}
/**
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
*/
progress {
vertical-align: baseline;
}
/**
* Remove the default vertical scrollbar in IE 10+.
*/
textarea {
overflow: auto;
}
/**
* 1. Add the correct box sizing in IE 10.
* 2. Remove the padding in IE 10.
*/
[type="checkbox"],
[type="radio"] {
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
}
/**
* Correct the cursor style of increment and decrement buttons in Chrome.
*/
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
/**
* 1. Correct the odd appearance in Chrome and Safari.
* 2. Correct the outline style in Safari.
*/
[type="search"] {
-webkit-appearance: textfield; /* 1 */
outline-offset: -2px; /* 2 */
}
/**
* Remove the inner padding in Chrome and Safari on macOS.
*/
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* 1. Correct the inability to style clickable types in iOS and Safari.
* 2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button; /* 1 */
font: inherit; /* 2 */
}
/* Interactive
========================================================================== */
/*
* Add the correct display in Edge, IE 10+, and Firefox.
*/
details {
display: block;
}
/*
* Add the correct display in all browsers.
*/
summary {
display: list-item;
}
/* Misc
========================================================================== */
/**
* Add the correct display in IE 10+.
*/
template {
display: none;
}
/**
* Add the correct display in IE 10.
*/
[hidden] {
display: none;
}

0
static/scss/_codes.scss Normal file
View File

94
static/scss/_forms.scss Normal file
View File

@ -0,0 +1,94 @@
button,
input,
optgroup,
select,
textarea {
color: inherit;
font: inherit;
margin: 0;
max-width: 100%;
}
input {
text-align: left;
outline: 0;
}
label {
text-align: left;
font-weight: 700;
}
select {
text-align: left;
border: 4px solid $grey;
padding: 0.5em;
outline: 0;
}
input[type=text],
input[type=email],
input[type=number],
input[type=password],
select[multiple=multiple],
textarea {
display: block;
text-align: left;
color: $primary-color;
border: 4px solid $grey;
padding: 0.5rem;
outline: 0;
}
input[type=submit],
.action-button {
font-weight: 900;
padding: 0.5em 1em;
border: none;
background-color: $blue;
color: white;
cursor: pointer;
text-decoration: none;
}
input:focus,
textarea:focus {
border-color: $blue;
}
select[multiple=multiple] {
height: 125px;
}
input[type=number] {
max-width: 6.25rem;
padding: 0 1rem;
// height: 3.32em;
}
input[type=checkbox] {
width: 1em;
vertical-align: text-top;
}
textarea {
width: 100%;
height: 6.25rem;
line-height: 1.45;
}
::-webkit-input-placeholder {
font-style: oblique;
}
::-moz-input-placeholder {
font-style: oblique;
}
::-ms-input-placeholder {
font-style: oblique;
}
.action-button {
border-radius: 3.75rem;
}

81
static/scss/_globals.scss Normal file
View File

@ -0,0 +1,81 @@
html {
font-size: 100%;
}
body {
background-color: $bg-color;
color: $primary-color;
font-family: 'Lato', sans-serif;
font-weight: 400;
line-height: 1.75;
text-rendering: optimizeLegibility;
}
p {
margin-bottom: 1rem;
}
h1, h2, h3, h4, h5 {
margin: 3rem 0 1.38rem;
font-family: 'Lato', sans-serif;
line-height: 1.3;
text-rendering: optimizeLegibility;
}
h1 {
margin-top: 0;
font-size: 2.488rem;
}
h2 {
font-size: 2.074rem;
}
h3 {
font-size: 1.728rem;
}
h4 {
font-size: 1.44rem;
}
h5 {
font-size: 1.2rem;
}
small {
font-size: 0.833rem;
}
a {
color: $blue;
cursor: pointer;
white-space: nowrap;
}
hr {
margin: 0;
border: 0.8px solid $grey;
}
// BLOCK ELEMENTS
main {
max-width: 58rem;
margin: 0 auto;
}
// TABLES
table {
border-collapse: collapse;
border-spacing: 0;
width: 100%;
margin-bottom: 1.3em;
border: 1px solid $grey;
}
table a {
white-space: normal;
}
td,
th {
text-align: left;
font-size: 0.85em;
padding: 2px 10px;
border-bottom: 1px solid;
border-color: $grey;
}

14
static/scss/_helpers.scss Normal file
View File

@ -0,0 +1,14 @@
.panel {
max-width: 58rem;
padding: 1rem;
background-color: white;
border-top: 1px solid $grey;
border-bottom: 1px solid $grey;
margin-bottom: 1.5rem;
}
@media screen and (min-width: 58rem) {
.panel {
border: 1px solid $grey;
}
}

View File

27
static/scss/_nav.scss Normal file
View File

@ -0,0 +1,27 @@
.navbar {
display: flex;
justify-content: space-between;
margin: 1rem;
&__logo {
font-weight: bold;
font-size: 1rem;
}
&__nav {
}
}
.nav {
&nav__user {
}
&nav__logout {
}
&nav__login {
}
}

View File

View File

View File

@ -0,0 +1,33 @@
.student {
&__header {
display: flex;
justify-content: space-between;
align-items: baseline;
}
&__department {
margin-top: 0;
}
}
.status {
&__clocked {
}
&__clocked--clocked-in {
background-color: $green;
color: white;
padding: 0.5rem;
font-weight: bold;
}
}
.attendance {
margin: 0 1rem;
&__month {
font-weight: bold;
}
}

View File

@ -0,0 +1,16 @@
/*bg (gray) white
primary (black) #323834
faded (light-black) #747a7c
accent 1 (red) #2980B9
accent 2 (orange) #9b6f45
accent 3 (blue) #242e34
*/
$bg-color: #f7f7f7;
$primary-color: #323834;
$faded-color: #747a7c;
$blue: #2980B9;
$orange: #9b6f45;
$darkblue: #242e34;
$grey: #bdc3c7;
$green: green;

11
static/scss/main.scss Normal file
View File

@ -0,0 +1,11 @@
@import "variables";
@import "globals";
@import "helpers";
@import "forms";
@import "nav";
@import "registration";
@import "students";
@import "instructors";
@import "periods";
@import "codes";

View File

@ -1,4 +1,5 @@
{% load static %}
{% load compress %}
<!DOCTYPE html>
<html lang="en">
<head>
@ -11,24 +12,32 @@
{% 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' %}">
{% compress css %}
<link href="{% static 'css/normalize.css' %}" rel="stylesheet" media="screen">
<link type="text/x-scss" href="{% static 'scss/main.scss' %}" rel="stylesheet" media="screen">
{% endcompress %}
</head>
<body>
<header class="navigation">
<p><strong>BTech Time Tracker</strong></p>
<header class="navbar">
<span class="navbar__logo">BTech Time Tracker</span>
<nav class="navbar__nav nav">
{% if user.is_authenticated %}
<nav>
<a href="{% url 'account-update' user.id %}">{{ user.first_name }} {{ user.last_name }}</a> /
<a class="logout" href="{% url 'logout' %}">Logout</a>
</nav>
<a class="nav__user" href="{% url 'account-update' user.id %}">{{ user.first_name }} {{ user.last_name }}</a> /
<a class="nav__logout" class="logout" href="{% url 'logout' %}">Logout</a>
{% else %}
<nav>
<a class="login" href="{% url 'login' %}">Login</a>
</nav>
<a class="nav__login" class="login" href="{% url 'login' %}">Login</a>
{% endif %}
</nav>
</header>
<aside>
{% if messages %}
<div class="messages">
{% for message in messages %}
<span {% if message.tags %} class="{{ message.tags }} messages__message"{% endif %}>{{ message }}</span>
{% endfor %}
</div>
{% endif %}
{% block aside %}
{% endblock %}
</aside>
@ -36,13 +45,6 @@
<main>
{% block content %}
{% endblock %}
{% if messages %}
<div class="messages">
{% for message in messages %}
<p {% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</p>
{% endfor %}
</div>
{% endif %}
</main>
<footer>

View File

@ -1,7 +1,7 @@
{% extends 'application.html' %}
{% block content %}
<section>
<section class="panel">
<p>You have been logged out. <a href="{% url 'login' %}">Log in</a></p>
</section>
{% endblock %}

View File

@ -1,7 +1,7 @@
{% extends 'application.html' %}
{% block content %}
<section>
<section class="panel">
<h1>Log in</h1>
{% if form.errors %}
<p class="error">Your username and password didn't match. Please try again.</p>
@ -19,6 +19,7 @@
{{ form.password.label_tag }}
<br>
{{ form.password }}
<br>
<small>
Forgot your password? <a href="{% url 'password_reset' %}">Reset your password here</a>.
</small>

View File

@ -1,12 +1,14 @@
{% extends 'application.html' %}
{% block content %}
<section>
<section class="panel">
<h1>Change password</h1>
<form method="post" action="{% url 'password_change' %}">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Change my password" />
<input type="submit" value="Change my password"> or
<a href="{{request.META.HTTP_REFERER}}">Cancel</a>
</form>
</section>
{% endblock %}

View File

@ -1,7 +1,7 @@
{% extends 'application.html' %}
{% block content %}
<sectin>
<section class="panel">
<p>Password was reset successfully.</p>
</section>
{% endblock %}

View File

@ -3,7 +3,7 @@
{% block content %}
{% if validlink %}
<section>
<section class="panel">
<h1>Reset password</h1>
<p>Enter a <em>new</em> password below.</p>
<form method="post" action=".">
@ -15,7 +15,7 @@
</section>
{% else %}
<section>
<section class="panel">
<p>Password reset failed</p>
</section>

View File

@ -1,7 +1,7 @@
{% extends 'application.html' %}
{% block content %}
<section>
<section class="panel">
<p>An email with password reset instructions has been sent.</p>
</section>
{% endblock %}

View File

@ -1,7 +1,7 @@
{% extends 'application.html' %}
{% block content %}
<section>
<section class="panel">
<h1>Reset your password</h1>
<p>Enter your email address below and we'll send you instructions.</p>
<form method="post" action="{% url 'password_reset' %}">

18
tracker/middleware.py Normal file
View File

@ -0,0 +1,18 @@
import pytz
from django.utils import timezone
class TimezoneMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
tzname = request.session.get('django_timezone')
print(tzname)
if tzname:
timezone.activate(pytz.timezone(tzname))
print("tz activated")
else:
timezone.deactivate()
print("tz deactivated")
return self.get_response(request)

View File

@ -52,6 +52,7 @@ MIDDLEWARE = [
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'tracker.middleware.TimezoneMiddleware',
]
ROOT_URLCONF = 'tracker.urls'
@ -110,7 +111,7 @@ AUTH_PASSWORD_VALIDATORS = [
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'US/Mountain'
TIME_ZONE = 'UTC'
USE_I18N = True
@ -130,13 +131,22 @@ STATICFILES_FINDERS = (
)
STATIC_URL = '/static/'
STATICFILES_DIRS = ['static']
STATIC_ROOT = '/var/www/static/'
COMPRESS_ROOT = BASE_DIR / 'static'
STATICFILES_DIRS = [BASE_DIR / 'static']
STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
'compressor.finders.CompressorFinder',
)
COMPRESS_PRECOMPILERS = (
('text/x-scss', 'django_libsass.SassCompiler'),
)
# Media file storage
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'
LOGIN_REDIRECT_URL = '/attendance'
LOGIN_REDIRECT_URL = '/accounts/timezone/'