Initial commit
This commit is contained in:
commit
c434b973cc
253
.gitignore
vendored
Normal file
253
.gitignore
vendored
Normal file
@ -0,0 +1,253 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
pip-wheel-metadata/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
**/migrations/**
|
||||
!**/migrations
|
||||
!**/migrations/__init__.py
|
||||
|
||||
*.sublime-workspace
|
||||
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
15
Pipfile
Normal file
15
Pipfile
Normal file
@ -0,0 +1,15 @@
|
||||
[[source]]
|
||||
url = "https://pypi.org/simple"
|
||||
verify_ssl = true
|
||||
name = "pypi"
|
||||
|
||||
[packages]
|
||||
django = "*"
|
||||
django-compressor = "*"
|
||||
qrcode = "*"
|
||||
pillow = "*"
|
||||
|
||||
[dev-packages]
|
||||
|
||||
[requires]
|
||||
python_version = "3.9"
|
||||
145
Pipfile.lock
generated
Normal file
145
Pipfile.lock
generated
Normal file
@ -0,0 +1,145 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "35627f97351787e78b306a9071b0db88411b941484359b8622cd032f3f6a64f2"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
"python_version": "3.9"
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
"name": "pypi",
|
||||
"url": "https://pypi.org/simple",
|
||||
"verify_ssl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"asgiref": {
|
||||
"hashes": [
|
||||
"sha256:5ee950735509d04eb673bd7f7120f8fa1c9e2df495394992c73234d526907e17",
|
||||
"sha256:7162a3cb30ab0609f1a4c95938fd73e8604f63bdba516a7f7d64b83ff09478f0"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==3.3.1"
|
||||
},
|
||||
"django": {
|
||||
"hashes": [
|
||||
"sha256:2d78425ba74c7a1a74b196058b261b9733a8570782f4e2828974777ccca7edf7",
|
||||
"sha256:efa2ab96b33b20c2182db93147a0c3cd7769d418926f9e9f140a60dca7c64ca9"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.1.5"
|
||||
},
|
||||
"django-appconf": {
|
||||
"hashes": [
|
||||
"sha256:1b1d0e1069c843ebe8ae5aa48ec52403b1440402b320c3e3a206a0907e97bb06",
|
||||
"sha256:be58deb54a43d77d2e1621fe59f787681376d3cd0b8bd8e4758ef6c3a6453380"
|
||||
],
|
||||
"version": "==1.0.4"
|
||||
},
|
||||
"django-compressor": {
|
||||
"hashes": [
|
||||
"sha256:57ac0a696d061e5fc6fbc55381d2050f353b973fb97eee5593f39247bc0f30af",
|
||||
"sha256:d2ed1c6137ddaac5536233ec0a819e14009553fee0a869bea65d03e5285ba74f"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.4"
|
||||
},
|
||||
"pillow": {
|
||||
"hashes": [
|
||||
"sha256:165c88bc9d8dba670110c689e3cc5c71dbe4bfb984ffa7cbebf1fac9554071d6",
|
||||
"sha256:1d208e670abfeb41b6143537a681299ef86e92d2a3dac299d3cd6830d5c7bded",
|
||||
"sha256:22d070ca2e60c99929ef274cfced04294d2368193e935c5d6febfd8b601bf865",
|
||||
"sha256:2353834b2c49b95e1313fb34edf18fca4d57446675d05298bb694bca4b194174",
|
||||
"sha256:39725acf2d2e9c17356e6835dccebe7a697db55f25a09207e38b835d5e1bc032",
|
||||
"sha256:3de6b2ee4f78c6b3d89d184ade5d8fa68af0848f9b6b6da2b9ab7943ec46971a",
|
||||
"sha256:47c0d93ee9c8b181f353dbead6530b26980fe4f5485aa18be8f1fd3c3cbc685e",
|
||||
"sha256:5e2fe3bb2363b862671eba632537cd3a823847db4d98be95690b7e382f3d6378",
|
||||
"sha256:604815c55fd92e735f9738f65dabf4edc3e79f88541c221d292faec1904a4b17",
|
||||
"sha256:6c5275bd82711cd3dcd0af8ce0bb99113ae8911fc2952805f1d012de7d600a4c",
|
||||
"sha256:731ca5aabe9085160cf68b2dbef95fc1991015bc0a3a6ea46a371ab88f3d0913",
|
||||
"sha256:7612520e5e1a371d77e1d1ca3a3ee6227eef00d0a9cddb4ef7ecb0b7396eddf7",
|
||||
"sha256:7916cbc94f1c6b1301ac04510d0881b9e9feb20ae34094d3615a8a7c3db0dcc0",
|
||||
"sha256:81c3fa9a75d9f1afafdb916d5995633f319db09bd773cb56b8e39f1e98d90820",
|
||||
"sha256:887668e792b7edbfb1d3c9d8b5d8c859269a0f0eba4dda562adb95500f60dbba",
|
||||
"sha256:93a473b53cc6e0b3ce6bf51b1b95b7b1e7e6084be3a07e40f79b42e83503fbf2",
|
||||
"sha256:96d4dc103d1a0fa6d47c6c55a47de5f5dafd5ef0114fa10c85a1fd8e0216284b",
|
||||
"sha256:a3d3e086474ef12ef13d42e5f9b7bbf09d39cf6bd4940f982263d6954b13f6a9",
|
||||
"sha256:b02a0b9f332086657852b1f7cb380f6a42403a6d9c42a4c34a561aa4530d5234",
|
||||
"sha256:b09e10ec453de97f9a23a5aa5e30b334195e8d2ddd1ce76cc32e52ba63c8b31d",
|
||||
"sha256:b6f00ad5ebe846cc91763b1d0c6d30a8042e02b2316e27b05de04fa6ec831ec5",
|
||||
"sha256:bba80df38cfc17f490ec651c73bb37cd896bc2400cfba27d078c2135223c1206",
|
||||
"sha256:c3d911614b008e8a576b8e5303e3db29224b455d3d66d1b2848ba6ca83f9ece9",
|
||||
"sha256:ca20739e303254287138234485579b28cb0d524401f83d5129b5ff9d606cb0a8",
|
||||
"sha256:cb192176b477d49b0a327b2a5a4979552b7a58cd42037034316b8018ac3ebb59",
|
||||
"sha256:cdbbe7dff4a677fb555a54f9bc0450f2a21a93c5ba2b44e09e54fcb72d2bd13d",
|
||||
"sha256:cf6e33d92b1526190a1de904df21663c46a456758c0424e4f947ae9aa6088bf7",
|
||||
"sha256:d355502dce85ade85a2511b40b4c61a128902f246504f7de29bbeec1ae27933a",
|
||||
"sha256:d673c4990acd016229a5c1c4ee8a9e6d8f481b27ade5fc3d95938697fa443ce0",
|
||||
"sha256:dc577f4cfdda354db3ae37a572428a90ffdbe4e51eda7849bf442fb803f09c9b",
|
||||
"sha256:dd9eef866c70d2cbbea1ae58134eaffda0d4bfea403025f4db6859724b18ab3d",
|
||||
"sha256:f50e7a98b0453f39000619d845be8b06e611e56ee6e8186f7f60c3b1e2f0feae"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==8.1.0"
|
||||
},
|
||||
"pytz": {
|
||||
"hashes": [
|
||||
"sha256:16962c5fb8db4a8f63a26646d8886e9d769b6c511543557bc84e9569fb9a9cb4",
|
||||
"sha256:180befebb1927b16f6b57101720075a984c019ac16b1b7575673bea42c6c3da5"
|
||||
],
|
||||
"version": "==2020.5"
|
||||
},
|
||||
"qrcode": {
|
||||
"hashes": [
|
||||
"sha256:3996ee560fc39532910603704c82980ff6d4d5d629f9c3f25f34174ce8606cf5",
|
||||
"sha256:505253854f607f2abf4d16092c61d4e9d511a3b4392e60bff957a68592b04369"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==6.1"
|
||||
},
|
||||
"rcssmin": {
|
||||
"hashes": [
|
||||
"sha256:ca87b695d3d7864157773a61263e5abb96006e9ff0e021eff90cbe0e1ba18270"
|
||||
],
|
||||
"version": "==1.0.6"
|
||||
},
|
||||
"rjsmin": {
|
||||
"hashes": [
|
||||
"sha256:0ab825839125eaca57cc59581d72e596e58a7a56fbc0839996b7528f0343a0a8",
|
||||
"sha256:211c2fe8298951663bbc02acdffbf714f6793df54bfc50e1c6c9e71b3f2559a3",
|
||||
"sha256:466fe70cc5647c7c51b3260c7e2e323a98b2b173564247f9c89e977720a0645f",
|
||||
"sha256:585e75a84d9199b68056fd4a083d9a61e2a92dfd10ff6d4ce5bdb04bc3bdbfaf",
|
||||
"sha256:6044ca86e917cd5bb2f95e6679a4192cef812122f28ee08c677513de019629b3",
|
||||
"sha256:714329db774a90947e0e2086cdddb80d5e8c4ac1c70c9f92436378dedb8ae345",
|
||||
"sha256:799890bd07a048892d8d3deb9042dbc20b7f5d0eb7da91e9483c561033b23ce2",
|
||||
"sha256:975b69754d6a76be47c0bead12367a1ca9220d08e5393f80bab0230d4625d1f4",
|
||||
"sha256:b15dc75c71f65d9493a8c7fa233fdcec823e3f1b88ad84a843ffef49b338ac32",
|
||||
"sha256:dd0f4819df4243ffe4c964995794c79ca43943b5b756de84be92b445a652fb86",
|
||||
"sha256:e3908b21ebb584ce74a6ac233bdb5f29485752c9d3be5e50c5484ed74169232c",
|
||||
"sha256:e487a7783ac4339e79ec610b98228eb9ac72178973e3dee16eba0e3feef25924",
|
||||
"sha256:ecd29f1b3e66a4c0753105baec262b331bcbceefc22fbe6f7e8bcd2067bcb4d7"
|
||||
],
|
||||
"version": "==1.1.0"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
|
||||
"sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.15.0"
|
||||
},
|
||||
"sqlparse": {
|
||||
"hashes": [
|
||||
"sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0",
|
||||
"sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==0.4.1"
|
||||
}
|
||||
},
|
||||
"develop": {}
|
||||
}
|
||||
0
accounts/__init__.py
Normal file
0
accounts/__init__.py
Normal file
6
accounts/admin.py
Normal file
6
accounts/admin.py
Normal file
@ -0,0 +1,6 @@
|
||||
from django.contrib import admin
|
||||
from .models import Department, Instructor, Student
|
||||
|
||||
admin.site.register(Department)
|
||||
admin.site.register(Instructor)
|
||||
admin.site.register(Student)
|
||||
5
accounts/apps.py
Normal file
5
accounts/apps.py
Normal file
@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class AccountsConfig(AppConfig):
|
||||
name = 'accounts'
|
||||
3
accounts/forms.py
Normal file
3
accounts/forms.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django import forms
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
0
accounts/migrations/__init__.py
Normal file
0
accounts/migrations/__init__.py
Normal file
37
accounts/models.py
Normal file
37
accounts/models.py
Normal file
@ -0,0 +1,37 @@
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
class Department(models.Model):
|
||||
name = models.CharField(max_length=200)
|
||||
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
modified = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Instructor(models.Model):
|
||||
user = models.OneToOneField(User, on_delete=models.CASCADE)
|
||||
department = models.ForeignKey(Department, on_delete=models.CASCADE)
|
||||
|
||||
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})
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.user.first_name} {self.user.last_name}'
|
||||
|
||||
class Student(models.Model):
|
||||
user = models.OneToOneField(User, on_delete=models.CASCADE)
|
||||
student_number = models.IntegerField()
|
||||
department = models.ForeignKey(Department, on_delete=models.CASCADE)
|
||||
is_clocked_in = models.BooleanField(default=False)
|
||||
|
||||
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})
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.student_number}: {self.user.first_name} {self.user.last_name}'
|
||||
12
accounts/templates/accounts/account_detail.html
Normal file
12
accounts/templates/accounts/account_detail.html
Normal file
@ -0,0 +1,12 @@
|
||||
{% extends 'application.html' %}
|
||||
|
||||
{% block content %}
|
||||
<h1>User</h1>
|
||||
<section>
|
||||
<h3>{{ user.first_name }} {{ user.last_name }}</h3>
|
||||
<p>{{ user.email }}</p>
|
||||
<p>
|
||||
<a href="{% url 'account-update' user.id %}">Update</a>
|
||||
</p>
|
||||
</section>
|
||||
{% endblock %}
|
||||
19
accounts/templates/accounts/account_form.html
Normal file
19
accounts/templates/accounts/account_form.html
Normal file
@ -0,0 +1,19 @@
|
||||
{% extends 'application.html' %}
|
||||
|
||||
{% block content %}
|
||||
<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 %}
|
||||
<form method="post" action="{% url 'account-update' user.id %}">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
|
||||
<input type="submit" value="Save changes" />
|
||||
</form>
|
||||
<p><a href="{% url 'password_change' %}">Update password</a></p>
|
||||
</section>
|
||||
{% endblock %}
|
||||
20
accounts/templates/accounts/account_list.html
Normal file
20
accounts/templates/accounts/account_list.html
Normal file
@ -0,0 +1,20 @@
|
||||
{% extends 'application.html' %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Users</h1>
|
||||
<section>
|
||||
<table>
|
||||
<tbody>
|
||||
{% for user in user_list %}
|
||||
<tr>
|
||||
<td>{{ user.username }}</td>
|
||||
<td>{{user.first_name}} {{user.last_name}}</td>
|
||||
<td><a href="{% url 'user-detail' user.id %}">Update</a></td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td>No users yet.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
{% endblock %}
|
||||
3
accounts/tests.py
Normal file
3
accounts/tests.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
18
accounts/urls.py
Normal file
18
accounts/urls.py
Normal file
@ -0,0 +1,18 @@
|
||||
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.AccountListView.as_view(), name='account-list'),
|
||||
path('new/', views.AccountCreateView.as_view(), name='account-create'),
|
||||
path('<int:pk>/', include([
|
||||
path('', views.AccountDetailView.as_view(), name='account-detail'),
|
||||
path('update/', views.AccountUpdateView.as_view(), name='account-update'),
|
||||
path('delete/', views.AccountDeleteView.as_view(), name='account-delete'),
|
||||
])),
|
||||
]
|
||||
36
accounts/views.py
Normal file
36
accounts/views.py
Normal file
@ -0,0 +1,36 @@
|
||||
from django.shortcuts import render, reverse
|
||||
from django.urls import reverse_lazy
|
||||
from django.views.generic.edit import FormView, CreateView, UpdateView, DeleteView
|
||||
from django.views.generic.detail import DetailView
|
||||
from django.views.generic.list import ListView
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.forms import PasswordChangeForm
|
||||
|
||||
class AccountListView(LoginRequiredMixin, ListView):
|
||||
model = User
|
||||
template_name = 'accounts/account_list.html'
|
||||
|
||||
class AccountCreateView(LoginRequiredMixin, CreateView):
|
||||
model = User
|
||||
fields = ['email', 'password']
|
||||
template_name = 'accounts/account_form.html'
|
||||
|
||||
class AccountDetailView(LoginRequiredMixin, DetailView):
|
||||
model = User
|
||||
template_name = 'accounts/account_detail.html'
|
||||
|
||||
class AccountUpdateView(LoginRequiredMixin, UpdateView):
|
||||
model = User
|
||||
# form_class = PasswordChangeForm
|
||||
fields = ['username', 'email']
|
||||
template_name = 'accounts/account_form.html'
|
||||
|
||||
def get_success_url(self):
|
||||
pk = self.kwargs["pk"]
|
||||
return reverse('account-detail', kwargs={'pk': pk})
|
||||
|
||||
class AccountDeleteView(LoginRequiredMixin, DeleteView):
|
||||
model = User
|
||||
success_url = reverse_lazy('account-list')
|
||||
0
attendance/__init__.py
Normal file
0
attendance/__init__.py
Normal file
5
attendance/admin.py
Normal file
5
attendance/admin.py
Normal file
@ -0,0 +1,5 @@
|
||||
from django.contrib import admin
|
||||
from .models import Code, Period
|
||||
|
||||
admin.site.register(Code)
|
||||
admin.site.register(Period)
|
||||
5
attendance/apps.py
Normal file
5
attendance/apps.py
Normal file
@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class AttendanceConfig(AppConfig):
|
||||
name = 'attendance'
|
||||
2
attendance/forms.py
Normal file
2
attendance/forms.py
Normal file
@ -0,0 +1,2 @@
|
||||
from django import forms
|
||||
|
||||
0
attendance/migrations/__init__.py
Normal file
0
attendance/migrations/__init__.py
Normal file
23
attendance/models.py
Normal file
23
attendance/models.py
Normal file
@ -0,0 +1,23 @@
|
||||
from django.db import models
|
||||
from accounts.models import Student
|
||||
|
||||
class Code(models.Model):
|
||||
student = models.ForeignKey(Student, on_delete=models.CASCADE)
|
||||
qr_code = models.ImageField(upload_to='qr_codes', blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.name)
|
||||
|
||||
class Period(models.Model):
|
||||
student = models.ForeignKey(Student, on_delete=models.CASCADE)
|
||||
clocked_in = models.DateTimeField(auto_now_add=True)
|
||||
clocked_out = models.DateTimeField(blank=True, null=True)
|
||||
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
modified = models.DateTimeField(auto_now=True)
|
||||
|
||||
def duration(self):
|
||||
return round(self.clocked_out - self.clocked_in)
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.clocked_in}: {self.student.user.first_name} {self.student.user.last_name}'
|
||||
1
attendance/templates/attendance/attendance_detail.html
Normal file
1
attendance/templates/attendance/attendance_detail.html
Normal file
@ -0,0 +1 @@
|
||||
attendance_detail
|
||||
11
attendance/templates/attendance/attendance_overview.html
Normal file
11
attendance/templates/attendance/attendance_overview.html
Normal file
@ -0,0 +1,11 @@
|
||||
{% extends 'application.html' %}
|
||||
|
||||
{% block content %}
|
||||
{% if user.student %}
|
||||
{% include "attendance/attendance_overview_student.html" %}
|
||||
{% elif user.instructor %}
|
||||
{% include "attendance/attendance_overview_instructor.html" %}
|
||||
{% else %}
|
||||
<p>You are neither student nor instructor.</p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@ -0,0 +1,29 @@
|
||||
<section>
|
||||
<header>
|
||||
<p>{{ user.instructor.department.name }}</p>
|
||||
<a href="">Update profile</a>
|
||||
</header>
|
||||
<h1>{{ user.first_name }} {{ user.last_name }}</h1>
|
||||
</section>
|
||||
<article>
|
||||
<h2>Attendance log</h2>
|
||||
<table>
|
||||
<tbody>
|
||||
{% for student in students %}
|
||||
<tr>
|
||||
<td>{{ student.user.first_name }}</td>
|
||||
{% for period in student.period_set.all %}
|
||||
<td>{{ period.clocked_in }}</td>
|
||||
{% if period.clocked_out %}
|
||||
<td>{{ period.clocked_out }}</td>
|
||||
{% else %}
|
||||
<td>You have not clocked out.</td>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="2">No periods yet.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</article>
|
||||
@ -0,0 +1,33 @@
|
||||
<section>
|
||||
<header>
|
||||
<p>{{ user.student.department.name }}</p>
|
||||
<a href="{% url 'account-update' user.id %}">Update profile</a>
|
||||
</header>
|
||||
<h1>{{ user.first_name }} {{ user.last_name }}</h1>
|
||||
{% if user.student.is_clocked_in %}
|
||||
<p>You are currently clocked in.</p>
|
||||
<a href="">Clock out ></a>
|
||||
{% else %}
|
||||
<p>You are not clocked in.</p>
|
||||
<a href="">Clock in ></a>
|
||||
{% endif %}
|
||||
</section>
|
||||
<article>
|
||||
<h2>Attendance log</h2>
|
||||
<table>
|
||||
<tbody>
|
||||
{% for period in user.student.period_set.all %}
|
||||
<tr>
|
||||
<td>{{ period.clocked_in }}</td>
|
||||
{% if period.clocked_out %}
|
||||
<td>{{ period.clocked_out }}</td>
|
||||
{% else %}
|
||||
<td>You have not clocked out.</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="2">No periods yet.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</article>
|
||||
3
attendance/tests.py
Normal file
3
attendance/tests.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
19
attendance/urls.py
Normal file
19
attendance/urls.py
Normal file
@ -0,0 +1,19 @@
|
||||
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.AttendanceOverview.as_view(), name='attendance-overview'),
|
||||
path('new/', views.AttendanceCreateView.as_view(), name='attendance-create'),
|
||||
path('<int:pk>/', include([
|
||||
path('', views.AttendanceDetailView.as_view(), name='attendance-detail'),
|
||||
path('update/', views.AttendanceUpdateView.as_view(), name='attendance-update'),
|
||||
path('delete/', views.AttendanceDeleteView.as_view(), name='attendance-delete'),
|
||||
])),
|
||||
]
|
||||
|
||||
50
attendance/views.py
Normal file
50
attendance/views.py
Normal file
@ -0,0 +1,50 @@
|
||||
from django.shortcuts import render, reverse
|
||||
from django.urls import reverse_lazy
|
||||
from django.views.generic.base import TemplateView
|
||||
from django.views.generic.edit import FormView, CreateView, UpdateView, DeleteView
|
||||
from django.views.generic.detail import DetailView
|
||||
from django.views.generic.list import ListView
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
|
||||
from django.contrib.auth.models import User
|
||||
from accounts.models import Instructor, Student
|
||||
from .models import Period
|
||||
|
||||
# EXAMPLE PERMISSION MIXIN
|
||||
# class MyView(PermissionRequiredMixin, View):
|
||||
# permission_required = 'polls.add_choice'
|
||||
# # Or multiple of permissions:
|
||||
# permission_required = ('polls.view_choice', 'polls.change_choice')
|
||||
|
||||
class AttendanceOverview(LoginRequiredMixin, TemplateView):
|
||||
template_name = 'attendance/attendance_overview.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['user'] = User.objects.get(pk=self.request.user.id)
|
||||
if hasattr(self.request.user, 'instructor'):
|
||||
context['students'] = Student.objects.filter(department=self.request.user.instructor.department)
|
||||
context['period_list'] = Period.objects.all()
|
||||
return context
|
||||
|
||||
class AttendanceCreateView(LoginRequiredMixin, CreateView):
|
||||
model = Period
|
||||
fields = ['email', 'password']
|
||||
template_name = 'attendance/attendance_form.html'
|
||||
|
||||
class AttendanceDetailView(LoginRequiredMixin, DetailView):
|
||||
model = Period
|
||||
template_name = 'attendance/attendance_detail.html'
|
||||
|
||||
class AttendanceUpdateView(LoginRequiredMixin, UpdateView):
|
||||
model = Period
|
||||
fields = ['username', 'email']
|
||||
template_name = 'attendance/attendance_form.html'
|
||||
|
||||
def get_success_url(self):
|
||||
pk = self.kwargs["pk"]
|
||||
return reverse('attendance-detail', kwargs={'pk': pk})
|
||||
|
||||
class AttendanceDeleteView(LoginRequiredMixin, DeleteView):
|
||||
model = Period
|
||||
success_url = reverse_lazy('account-overview')
|
||||
22
manage.py
Executable file
22
manage.py
Executable file
@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env python
|
||||
"""Django's command-line utility for administrative tasks."""
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
"""Run administrative tasks."""
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tracker.settings')
|
||||
try:
|
||||
from django.core.management import execute_from_command_line
|
||||
except ImportError as exc:
|
||||
raise ImportError(
|
||||
"Couldn't import Django. Are you sure it's installed and "
|
||||
"available on your PYTHONPATH environment variable? Did you "
|
||||
"forget to activate a virtual environment?"
|
||||
) from exc
|
||||
execute_from_command_line(sys.argv)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
215
static/css/base.css
Normal file
215
static/css/base.css
Normal file
@ -0,0 +1,215 @@
|
||||
/*bg (gray) white
|
||||
primary (black) #323834
|
||||
faded (light-black) #747a7c
|
||||
accent 1 (red) #2980B9
|
||||
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;
|
||||
}
|
||||
|
||||
body {
|
||||
max-width: 8.5in;
|
||||
margin: 0.25in auto;
|
||||
background-color: white;
|
||||
color: #323834;
|
||||
font-family: 'Heebo', sans-serif;
|
||||
font-weight: 400;
|
||||
line-height: 1.65;
|
||||
}
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
/* Text Elements */
|
||||
|
||||
p {
|
||||
margin-bottom: 1.15rem;
|
||||
}
|
||||
h1, h2, h3, h4, h5 {
|
||||
/*margin: 2.75rem 0 1.05rem;*/
|
||||
margin: 0 0 1.05rem;
|
||||
font-weight: 800;
|
||||
line-height: 1.15;
|
||||
}
|
||||
h1 {
|
||||
margin-top: 0;
|
||||
font-size: 2.488em;
|
||||
}
|
||||
h2 {
|
||||
font-size: 2.074em;
|
||||
}
|
||||
h3 {
|
||||
font-size: 1.728em;
|
||||
}
|
||||
h4 {
|
||||
font-size: 1.44em;
|
||||
}
|
||||
h5 {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
small {
|
||||
font-size: 0.833em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #2980B9;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
hr {
|
||||
margin: 0;
|
||||
border: 0.8px solid #bdc3c7;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
text-align: left;
|
||||
padding: 0 15px;
|
||||
color: #777;
|
||||
border-left: 4px solid #ddd;
|
||||
margin: 1.5em 0;
|
||||
padding: 0 15px;
|
||||
font-family: serif;
|
||||
}
|
||||
|
||||
|
||||
/* Forms */
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
margin: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
input {
|
||||
text-align: left;
|
||||
font-weight: 400;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
label {
|
||||
text-align: left;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
input[type=text],
|
||||
input[type=email],
|
||||
input[type=password],
|
||||
select[multiple=multiple],
|
||||
textarea,
|
||||
.action-button,
|
||||
.datepickr {
|
||||
text-align: left;
|
||||
color: #2c3e50;
|
||||
height: 2.75em;
|
||||
border: 4px solid #bdc3c7;
|
||||
padding: 1em;
|
||||
width: 100%;
|
||||
outline: 0;
|
||||
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
input[type=submit],
|
||||
.action-button {
|
||||
font-weight: 900;
|
||||
padding: 0.5em 1em;
|
||||
border: none;
|
||||
background-color: #2980B9;
|
||||
color: #ffffff;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
|
||||
box-shadow: 0 4px 0 #2980B9;
|
||||
}
|
||||
|
||||
input[type=submit]:active,
|
||||
.action-button:active {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
input:focus,
|
||||
textarea:focus {
|
||||
border-color: #2980B9;
|
||||
}
|
||||
|
||||
select[multiple=multiple] {
|
||||
height: 125px;
|
||||
}
|
||||
|
||||
input[type=number] {
|
||||
max-width: 150px;
|
||||
height: 3.32em;
|
||||
}
|
||||
|
||||
input[type=checkbox] {
|
||||
width: 1em;
|
||||
vertical-align: text-top;
|
||||
}
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
::-webkit-input-placeholder {
|
||||
font-style: oblique;
|
||||
}
|
||||
::-moz-input-placeholder {
|
||||
font-style: oblique;
|
||||
}
|
||||
::-ms-input-placeholder {
|
||||
font-style: oblique;
|
||||
}
|
||||
|
||||
|
||||
section {
|
||||
padding: 1em;
|
||||
border: 1px solid #bdc3c7;
|
||||
box-shadow: 0 0 4px #bdc3c7;
|
||||
margin-bottom: 1.3em;
|
||||
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
width: 100%;
|
||||
margin-bottom: 1.3em;
|
||||
border: 1px solid #bdc3c7;
|
||||
}
|
||||
|
||||
table a {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
text-align: left;
|
||||
font-size: 0.85em;
|
||||
padding: 2px 10px;
|
||||
border-bottom: 1px solid;
|
||||
border-color: #bdc3c7;
|
||||
}
|
||||
41
templates/application.html
Normal file
41
templates/application.html
Normal file
@ -0,0 +1,41 @@
|
||||
{% load static %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Tracker</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="chrome=1">
|
||||
<link rel="shortcut icon" type="image/png" href="{% static 'favicon.ico' %}"/>
|
||||
{% block head %}
|
||||
{% endblock %}
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'css/base.css' %}">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h4>Tracker</h4>
|
||||
{% if user.is_authenticated %}
|
||||
<nav>
|
||||
<a class="logout" href="{% url 'logout' %}">Logout</a>
|
||||
</nav>
|
||||
{% else %}
|
||||
<nav>
|
||||
<a class="login" href="{% url 'login' %}">Login</a>
|
||||
</nav>
|
||||
{% endif %}
|
||||
</header>
|
||||
|
||||
<aside>
|
||||
{% block aside %}
|
||||
{% endblock %}
|
||||
</aside>
|
||||
|
||||
<main>
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
7
templates/registration/logged_out.html
Normal file
7
templates/registration/logged_out.html
Normal file
@ -0,0 +1,7 @@
|
||||
{% extends 'application.html' %}
|
||||
|
||||
{% block content %}
|
||||
<section>
|
||||
<p>You have been logged out. <a href="{% url 'login' %}">Log in</a></p>
|
||||
</section>
|
||||
{% endblock %}
|
||||
33
templates/registration/login.html
Normal file
33
templates/registration/login.html
Normal file
@ -0,0 +1,33 @@
|
||||
{% extends 'application.html' %}
|
||||
|
||||
{% block content %}
|
||||
<section>
|
||||
<h1>Log in</h1>
|
||||
{% if form.errors %}
|
||||
<p class="error">Your username and password didn't match. Please try again.</p>
|
||||
{% endif %}
|
||||
|
||||
<form method="post" action="{% url 'login' %}">
|
||||
{% csrf_token %}
|
||||
<p>
|
||||
{{ form.username.label_tag }}
|
||||
<br>
|
||||
{{ form.username }}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{{ form.password.label_tag }}
|
||||
<br>
|
||||
{{ form.password }}
|
||||
<small>
|
||||
Forgot your password? <a href="{% url 'password_reset' %}">Reset your password here</a>.
|
||||
</small>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<input type="submit" value="Login">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
</p>
|
||||
</form>
|
||||
</section>
|
||||
{% endblock %}
|
||||
7
templates/registration/password_change_done.html
Normal file
7
templates/registration/password_change_done.html
Normal file
@ -0,0 +1,7 @@
|
||||
{% extends 'application.html' %}
|
||||
|
||||
{% block content %}
|
||||
<section>
|
||||
<p>Password has been changed. <a href="{% url 'login' %}">Log in</a></p>
|
||||
</section>
|
||||
{% endblock %}
|
||||
12
templates/registration/password_change_form.html
Normal file
12
templates/registration/password_change_form.html
Normal file
@ -0,0 +1,12 @@
|
||||
{% extends 'application.html' %}
|
||||
|
||||
{% block content %}
|
||||
<section>
|
||||
<form method="post" action="{% url 'password_change' %}">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
|
||||
<input type="submit" value="Change my password" />
|
||||
</form>
|
||||
</section>
|
||||
{% endblock %}
|
||||
7
templates/registration/password_reset_complete.html
Normal file
7
templates/registration/password_reset_complete.html
Normal file
@ -0,0 +1,7 @@
|
||||
{% extends 'application.html' %}
|
||||
|
||||
{% block content %}
|
||||
<sectin>
|
||||
<p>Password was reset successfully.</p>
|
||||
</section>
|
||||
{% endblock %}
|
||||
24
templates/registration/password_reset_confirm.html
Normal file
24
templates/registration/password_reset_confirm.html
Normal file
@ -0,0 +1,24 @@
|
||||
{% extends 'application.html' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% if validlink %}
|
||||
<section>
|
||||
<h1>Reset password</h1>
|
||||
<p>Enter a <em>new</em> password below.</p>
|
||||
<form method="post" action=".">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
|
||||
<input type="submit" value="Reset your password" />
|
||||
</form>
|
||||
</section>
|
||||
{% else %}
|
||||
|
||||
<section>
|
||||
<p>Password reset failed</p>
|
||||
</section>
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
7
templates/registration/password_reset_done.html
Normal file
7
templates/registration/password_reset_done.html
Normal file
@ -0,0 +1,7 @@
|
||||
{% extends 'application.html' %}
|
||||
|
||||
{% block content %}
|
||||
<section>
|
||||
<p>An email with password reset instructions has been sent.</p>
|
||||
</section>
|
||||
{% endblock %}
|
||||
5
templates/registration/password_reset_email.html
Normal file
5
templates/registration/password_reset_email.html
Normal file
@ -0,0 +1,5 @@
|
||||
<h1>Password Reset</h1>
|
||||
<p>
|
||||
<strong>Reset your password at {{ site_name }}</strong>:<br>
|
||||
{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}
|
||||
</p>
|
||||
17
templates/registration/password_reset_form.html
Normal file
17
templates/registration/password_reset_form.html
Normal file
@ -0,0 +1,17 @@
|
||||
{% extends 'application.html' %}
|
||||
|
||||
{% block content %}
|
||||
<section>
|
||||
<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' %}">
|
||||
{% csrf_token %}
|
||||
|
||||
{{ form.as_p }}
|
||||
|
||||
<p>
|
||||
<input type="submit" value="Send me instructions">
|
||||
</p>
|
||||
</form>
|
||||
</section>
|
||||
{% endblock %}
|
||||
0
tracker/__init__.py
Normal file
0
tracker/__init__.py
Normal file
16
tracker/asgi.py
Normal file
16
tracker/asgi.py
Normal file
@ -0,0 +1,16 @@
|
||||
"""
|
||||
ASGI config for tracker project.
|
||||
|
||||
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/3.1/howto/deployment/asgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.asgi import get_asgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tracker.settings')
|
||||
|
||||
application = get_asgi_application()
|
||||
140
tracker/settings.py
Normal file
140
tracker/settings.py
Normal file
@ -0,0 +1,140 @@
|
||||
"""
|
||||
Django settings for tracker project.
|
||||
|
||||
Generated by 'django-admin startproject' using Django 3.1.5.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/3.1/topics/settings/
|
||||
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/3.1/ref/settings/
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = '$i_349k*ollxqq9nwase3!da)eui(3&^!-_^05_w$%6uc!eclp'
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS = []
|
||||
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'compressor',
|
||||
'accounts.apps.AccountsConfig',
|
||||
'attendance.apps.AttendanceConfig',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'tracker.urls'
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [BASE_DIR / 'templates'],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.debug',
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = 'tracker.wsgi.application'
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/3.1/ref/settings/#databases
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': BASE_DIR / 'db.sqlite3',
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/3.1/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = 'en-us'
|
||||
|
||||
TIME_ZONE = 'US/Mountain'
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_L10N = True
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/3.1/howto/static-files/
|
||||
|
||||
STATICFILES_FINDERS = (
|
||||
'django.contrib.staticfiles.finders.FileSystemFinder',
|
||||
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
|
||||
# other finders..
|
||||
'compressor.finders.CompressorFinder',
|
||||
)
|
||||
|
||||
STATIC_URL = '/static/'
|
||||
STATICFILES_DIRS = ['static']
|
||||
STATIC_ROOT = '/var/www/static/'
|
||||
|
||||
# Media file storage
|
||||
|
||||
MEDIA_URL = '/media/'
|
||||
MEDIA_ROOT = [BASE_DIR / 'media']
|
||||
|
||||
|
||||
LOGIN_REDIRECT_URL = '/attendance'
|
||||
24
tracker/urls.py
Normal file
24
tracker/urls.py
Normal file
@ -0,0 +1,24 @@
|
||||
"""tracker URL Configuration
|
||||
|
||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||
https://docs.djangoproject.com/en/3.1/topics/http/urls/
|
||||
Examples:
|
||||
Function views
|
||||
1. Add an import: from my_app import views
|
||||
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||
Class-based views
|
||||
1. Add an import: from other_app.views import Home
|
||||
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||
Including another URLconf
|
||||
1. Import the include() function: from django.urls import include, path
|
||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||
"""
|
||||
from django.contrib import admin
|
||||
from django.urls import path, include
|
||||
|
||||
urlpatterns = [
|
||||
path('accounts/', include('django.contrib.auth.urls')),
|
||||
path('accounts/', include('accounts.urls')),
|
||||
path('attendance/', include('attendance.urls')),
|
||||
path('admin/', admin.site.urls),
|
||||
]
|
||||
16
tracker/wsgi.py
Normal file
16
tracker/wsgi.py
Normal file
@ -0,0 +1,16 @@
|
||||
"""
|
||||
WSGI config for tracker project.
|
||||
|
||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/3.1/howto/deployment/wsgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tracker.settings')
|
||||
|
||||
application = get_wsgi_application()
|
||||
Loading…
x
Reference in New Issue
Block a user