Init commit
This commit is contained in:
commit
fbe577ddda
37 changed files with 1515 additions and 0 deletions
237
.gitignore
vendored
Normal file
237
.gitignore
vendored
Normal file
|
@ -0,0 +1,237 @@
|
|||
.DS_Store
|
||||
.idea
|
||||
*.log
|
||||
tmp/
|
||||
|
||||
|
||||
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/django,python
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=django,python
|
||||
|
||||
### Django ###
|
||||
*.log
|
||||
*.pot
|
||||
*.pyc
|
||||
__pycache__/
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
media
|
||||
|
||||
# If your build process includes running collectstatic, then you probably don't need or want to include staticfiles/
|
||||
# in your Git repository. Update and uncomment the following line accordingly.
|
||||
# <django-project-name>/staticfiles/
|
||||
|
||||
### Django.Python Stack ###
|
||||
# Byte-compiled / optimized / DLL files
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
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/
|
||||
pytestdebug.log
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
|
||||
# Django stuff:
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
doc/_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
|
||||
|
||||
# poetry
|
||||
#poetry.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
|
||||
.env/
|
||||
.venv/
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
pythonenv*
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# operating system-related files
|
||||
# file properties cache/storage on macOS
|
||||
*.DS_Store
|
||||
# thumbnail cache on Windows
|
||||
Thumbs.db
|
||||
|
||||
# profiling data
|
||||
.prof
|
||||
|
||||
|
||||
### Python ###
|
||||
# Byte-compiled / optimized / DLL files
|
||||
|
||||
# C extensions
|
||||
|
||||
# Distribution / packaging
|
||||
|
||||
# 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.
|
||||
|
||||
# Installer logs
|
||||
|
||||
# Unit test / coverage reports
|
||||
|
||||
# Translations
|
||||
|
||||
# Django stuff:
|
||||
|
||||
# Flask stuff:
|
||||
|
||||
# Scrapy stuff:
|
||||
|
||||
# Sphinx documentation
|
||||
|
||||
# PyBuilder
|
||||
|
||||
# Jupyter Notebook
|
||||
|
||||
# IPython
|
||||
|
||||
# pyenv
|
||||
|
||||
# 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.
|
||||
|
||||
# poetry
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||
|
||||
# Celery stuff
|
||||
|
||||
# SageMath parsed files
|
||||
|
||||
# Environments
|
||||
# .env
|
||||
|
||||
# Spyder project settings
|
||||
|
||||
# Rope project settings
|
||||
|
||||
# mkdocs documentation
|
||||
|
||||
# mypy
|
||||
|
||||
# Pyre type checker
|
||||
|
||||
# pytype static type analyzer
|
||||
|
||||
# operating system-related files
|
||||
# file properties cache/storage on macOS
|
||||
# thumbnail cache on Windows
|
||||
|
||||
# profiling data
|
||||
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/django,python
|
6
Makefile
Normal file
6
Makefile
Normal file
|
@ -0,0 +1,6 @@
|
|||
run:
|
||||
python manage.py runserver
|
||||
makemigrations:
|
||||
python manage.py makemigrations mordor
|
||||
migrate:
|
||||
python manage.py migrate
|
8
README.md
Normal file
8
README.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
# Mordor
|
||||
|
||||
Mate order tool
|
||||
|
||||
> Its Black Gates are protected by more than just Orcs. There is evil there that does not sleep, and the Eye of Sauron is ever watchful
|
||||
|
||||
> One does not simply walk into Mordor!
|
||||
|
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', 'mordor.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()
|
0
mordor/__init__.py
Normal file
0
mordor/__init__.py
Normal file
16
mordor/asgi.py
Normal file
16
mordor/asgi.py
Normal file
|
@ -0,0 +1,16 @@
|
|||
"""
|
||||
ASGI config for mordor 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.2/howto/deployment/asgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.asgi import get_asgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mordor.settings')
|
||||
|
||||
application = get_asgi_application()
|
12
mordor/forms.py
Normal file
12
mordor/forms.py
Normal file
|
@ -0,0 +1,12 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
|
||||
from django.forms import ModelForm
|
||||
|
||||
from . import models
|
||||
|
||||
|
||||
class OrderForm(ModelForm):
|
||||
class Meta:
|
||||
model = models.Order
|
||||
fields = ["amount_33", "amount_50"]
|
31
mordor/migrations/0001_initial.py
Normal file
31
mordor/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,31 @@
|
|||
# Generated by Django 3.2 on 2021-05-17 18:41
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Order',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('amount_33', models.IntegerField(blank=True, default=0)),
|
||||
('amount_50', models.IntegerField(blank=True, default=0)),
|
||||
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
]
|
0
mordor/migrations/__init__.py
Normal file
0
mordor/migrations/__init__.py
Normal file
34
mordor/models.py
Normal file
34
mordor/models.py
Normal file
|
@ -0,0 +1,34 @@
|
|||
from users.models import CustomUser
|
||||
from django.db import models
|
||||
|
||||
|
||||
class TimeStampMixin(models.Model):
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class Order(TimeStampMixin):
|
||||
amount_33 = models.IntegerField(default=0, blank=True)
|
||||
amount_50 = models.IntegerField(default=0, blank=True)
|
||||
user = models.ForeignKey(CustomUser, null=True, on_delete=models.SET_NULL)
|
||||
|
||||
def price_33(self):
|
||||
return self.amount_33 * 25
|
||||
|
||||
def price_50(self):
|
||||
return self.amount_50 * 30
|
||||
|
||||
def total_price(self):
|
||||
return self.price_33() + self.price_50()
|
||||
|
||||
def clean(self):
|
||||
if self.amount_33 is None:
|
||||
self.amount_33 = 0
|
||||
if self.amount_50 is None:
|
||||
self.amount_50 = 0
|
||||
|
||||
def __str__(self):
|
||||
return f"Order {self.amount_33}x33cl, {self.amount_50}x50cl"
|
147
mordor/settings.py
Normal file
147
mordor/settings.py
Normal file
|
@ -0,0 +1,147 @@
|
|||
"""
|
||||
Django settings for mordor project.
|
||||
|
||||
Generated by 'django-admin startproject' using Django 3.2.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/3.2/topics/settings/
|
||||
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/3.2/ref/settings/
|
||||
"""
|
||||
|
||||
import os
|
||||
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.2/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = "django-insecure-43206vz5-rhg6a2s$w&a2#*+--0)-#glqcgur=%glo9-x6(*2h"
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = os.getenv("DEBUG", "1") == "1"
|
||||
|
||||
_allowed_hosts = os.getenv("ALLOWED_HOSTS")
|
||||
ALLOWED_HOSTS = _allowed_hosts.split(",") if _allowed_hosts else []
|
||||
|
||||
OWN_APPS = ["mordor", "users", "oauth"]
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
"django.contrib.admin",
|
||||
"django.contrib.auth",
|
||||
"django.contrib.contenttypes",
|
||||
"django.contrib.sessions",
|
||||
"django.contrib.messages",
|
||||
"django.contrib.staticfiles",
|
||||
] + OWN_APPS
|
||||
|
||||
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 = "mordor.urls"
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||
"DIRS": [os.path.join(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 = "mordor.wsgi.application"
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
|
||||
|
||||
DATABASES = {
|
||||
"default": {
|
||||
"ENGINE": "django.db.backends.sqlite3",
|
||||
"NAME": BASE_DIR / "db.sqlite3",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/3.2/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.2/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = "nl-be"
|
||||
|
||||
TIME_ZONE = "Europe/Brussels"
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_L10N = True
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/3.2/howto/static-files/
|
||||
|
||||
STATIC_URL = "/static/"
|
||||
STATICFILES_DIRS = [os.path.join(BASE_DIR, "static")]
|
||||
STATIC_ROOT = os.getenv("STATIC_ROOT")
|
||||
|
||||
# Default primary key field type
|
||||
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
|
||||
|
||||
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
||||
|
||||
# OAuth
|
||||
|
||||
BASE_URL = os.getenv("BASE_URL", "http://localhost:8000")
|
||||
|
||||
AUTH_USER_MODEL = "users.CustomUser"
|
||||
|
||||
_BASE_OAUTH_URL = os.getenv("OAUTH_BASE_URL", "https://adams.ugent.be/oauth")
|
||||
|
||||
OAUTH = {
|
||||
"USER_API_URI": f"{_BASE_OAUTH_URL}/api/current_user/",
|
||||
"ACCESS_TOKEN_URI": f"{_BASE_OAUTH_URL}/oauth2/token/",
|
||||
"AUTHORIZE_URI": f"{_BASE_OAUTH_URL}/oauth2/authorize/",
|
||||
"REDIRECT_URI": f"{BASE_URL}/login/zeus/authorized",
|
||||
"CLIENT_ID": os.getenv("OAUTH_CLIENT_ID", "tomtest"),
|
||||
"CLIENT_SECRET": os.getenv("OAUTH_CLIENT_SECRET", "blargh"),
|
||||
}
|
12
mordor/templates/mordor/index.html
Normal file
12
mordor/templates/mordor/index.html
Normal file
|
@ -0,0 +1,12 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Home{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
|
||||
<a href="{% url 'oauth:login' %}" >
|
||||
<button style="margin-top: 4em;" class="big_but">Ik ben zeus</button>
|
||||
</a>
|
||||
|
||||
{% endblock %}
|
29
mordor/templates/mordor/orders.html
Normal file
29
mordor/templates/mordor/orders.html
Normal file
|
@ -0,0 +1,29 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Orders{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<main class="shop">
|
||||
{% if orders %}
|
||||
{% for order in orders %}
|
||||
<div class="leftcell">
|
||||
{{ order.created_at.date }}
|
||||
</div>
|
||||
<div>
|
||||
<div>{{ order.amount_33 }}x 33cl = €{{ order.price_33 }}</div>
|
||||
<div>{{ order.amount_50 }}x 50cl = €{{ order.price_50 }}</div>
|
||||
</div>
|
||||
<div class="midcell">
|
||||
€ {{ order.total_price }}</span></div>
|
||||
<div class="rightcell">
|
||||
<form action="{% url 'remove_order' order.id %}" method="post">
|
||||
{% csrf_token %}
|
||||
<button type="submit">annuleer</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<p>Nog geen orders geplaatst</p>
|
||||
{% endif %}
|
||||
</main>
|
||||
{% endblock %}
|
70
mordor/templates/mordor/winkel.html
Normal file
70
mordor/templates/mordor/winkel.html
Normal file
|
@ -0,0 +1,70 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% load static %}
|
||||
|
||||
{% block title %}Winkel{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<script>
|
||||
let sum_33 = 0
|
||||
let sum_55 = 0
|
||||
let total_sum = 0
|
||||
|
||||
function input_changed() {
|
||||
sum_33 = Number(document.getElementById(`amount_33`).value) * 25;
|
||||
sum_50 = Number(document.getElementById(`amount_50`).value) * 30;
|
||||
|
||||
let total_field_33 = document.getElementById(`sum_33`);
|
||||
total_field_33.innerText = sum_33;
|
||||
let total_field_50 = document.getElementById(`sum_50`);
|
||||
total_field_50.innerText = sum_50;
|
||||
|
||||
|
||||
let total_sum = document.getElementById(`total_sum`);
|
||||
total_sum.innerText = sum_33 + sum_50;
|
||||
}
|
||||
</script>
|
||||
|
||||
<form class="cart" action="{% url 'winkel' %}" method='post'>
|
||||
{% csrf_token %}
|
||||
<div class="leftcell buy_button_cell">
|
||||
<button class="buy_button" style="font-size:95%">Bestel</button>
|
||||
</div>
|
||||
<div class="total_quantity">
|
||||
</div>
|
||||
<div class="rightcell total_price">
|
||||
<span class="bold equals">=</span><span> €</span>
|
||||
<span id="total_sum">0</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="leftcell"><img class="photo" src="{% static 'mate33.jpg' %}"></div>
|
||||
<div class="midcell">
|
||||
<span class="quantity">
|
||||
<input id="amount_33" name="amount_33" type="number" value="0" placeholder="0" onfocus="this.placeholder=''"
|
||||
onblur="this.placeholder='0'" oninput="input_changed()">
|
||||
</span>
|
||||
<span>x</span>
|
||||
<span class="unit_price">€25</span>
|
||||
</div>
|
||||
<div class="rightcell price">
|
||||
<span class="bold equals">=</span>€
|
||||
<span id="sum_33">0</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="leftcell"><img class="photo" src="{% static 'mate50.jpeg' %}"></div>
|
||||
<div class="midcell">
|
||||
<span class="quantity">
|
||||
<input id="amount_50" name="amount_50" type="number" value="0" placeholder="0" onfocus="this.placeholder=''"
|
||||
onblur="this.placeholder='0'" oninput="input_changed()">
|
||||
</span>
|
||||
<span>x</span>
|
||||
<span class="unit_price">€30</span>
|
||||
</div>
|
||||
<div class="rightcell price">
|
||||
<span class="bold equals">=</span>€
|
||||
<span id="sum_50">0</span>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
30
mordor/urls.py
Normal file
30
mordor/urls.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
"""mordor URL Configuration
|
||||
|
||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||
https://docs.djangoproject.com/en/3.2/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
|
||||
|
||||
from . import views
|
||||
|
||||
app_name = "mordor"
|
||||
urlpatterns = [
|
||||
path("", views.index, name="index"),
|
||||
path("winkel", views.winkel, name="winkel"),
|
||||
path("orders", views.orders, name="orders"),
|
||||
path("remove_order/<int:order_id>", views.remove_order, name="remove_order"),
|
||||
path("admin/", admin.site.urls),
|
||||
path("login/zeus/", include("oauth.urls")),
|
||||
path("user/", include("users.urls")),
|
||||
]
|
46
mordor/views.py
Normal file
46
mordor/views.py
Normal file
|
@ -0,0 +1,46 @@
|
|||
from django.http import HttpResponse, HttpResponseRedirect
|
||||
from django.shortcuts import render, get_object_or_404
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
|
||||
from mordor.models import Order
|
||||
from mordor.forms import OrderForm
|
||||
|
||||
|
||||
def index(req):
|
||||
if not req.user.is_authenticated:
|
||||
return render(req, "mordor/index.html")
|
||||
else:
|
||||
return HttpResponseRedirect(reverse("winkel"))
|
||||
|
||||
|
||||
def winkel(req):
|
||||
if not req.user.is_authenticated:
|
||||
return HttpResponseRedirect(reverse("index"))
|
||||
|
||||
if req.method == "POST":
|
||||
order = Order(user=req.user)
|
||||
form = OrderForm(req.POST, instance=order)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
return HttpResponseRedirect(reverse("orders"))
|
||||
else:
|
||||
return render(req, "mordor/winkel.html")
|
||||
|
||||
|
||||
def remove_order(request, order_id):
|
||||
if request.method != "POST":
|
||||
return HttpResponse(status_code=405)
|
||||
|
||||
order = get_object_or_404(Order, id=order_id)
|
||||
order.delete()
|
||||
return HttpResponseRedirect(reverse("orders"))
|
||||
|
||||
|
||||
def orders(req):
|
||||
if not req.user.is_authenticated:
|
||||
return HttpResponseRedirect(reverse("index"))
|
||||
|
||||
user_orders = Order.objects.filter(user=req.user)
|
||||
|
||||
return render(req, "mordor/orders.html", {"orders": user_orders})
|
16
mordor/wsgi.py
Normal file
16
mordor/wsgi.py
Normal file
|
@ -0,0 +1,16 @@
|
|||
"""
|
||||
WSGI config for mordor 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.2/howto/deployment/wsgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mordor.settings')
|
||||
|
||||
application = get_wsgi_application()
|
0
oauth/__init__.py
Normal file
0
oauth/__init__.py
Normal file
11
oauth/templates/oauth/failed.html
Normal file
11
oauth/templates/oauth/failed.html
Normal file
|
@ -0,0 +1,11 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Login Failed{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Login Failed</h2>
|
||||
|
||||
{% if error %}
|
||||
<h3>{{ error }}</h3>
|
||||
{% endif %}
|
||||
{% endblock %}
|
10
oauth/urls.py
Normal file
10
oauth/urls.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
from django.contrib.auth import logout
|
||||
from django.urls import path
|
||||
|
||||
from . import views
|
||||
|
||||
app_name = "oauth"
|
||||
urlpatterns = [
|
||||
path('register', views.register, name='login'),
|
||||
path('authorized', views.register_callback),
|
||||
]
|
86
oauth/views.py
Normal file
86
oauth/views.py
Normal file
|
@ -0,0 +1,86 @@
|
|||
import logging
|
||||
|
||||
import requests
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import login
|
||||
from django.http.request import HttpRequest
|
||||
from django.shortcuts import redirect, render
|
||||
|
||||
import users
|
||||
from users.models import CustomUser
|
||||
|
||||
logger = logging.getLogger(__file__)
|
||||
|
||||
|
||||
class OAuthException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def register(_):
|
||||
RESPONSE_TYPE = 'code'
|
||||
return redirect(f'{settings.OAUTH["AUTHORIZE_URI"]}?'
|
||||
f'response_type={RESPONSE_TYPE}&'
|
||||
f'client_id={settings.OAUTH["CLIENT_ID"]}&'
|
||||
f'redirect_uri={settings.OAUTH["REDIRECT_URI"]}')
|
||||
|
||||
|
||||
def register_callback(req: HttpRequest):
|
||||
if 'code' not in req.GET or 'error' in req.GET:
|
||||
error = req.GET['error'] if 'error' in req.GET else None
|
||||
return login_fail(req, error)
|
||||
|
||||
try:
|
||||
access_token = get_access_token(req.GET['code'])
|
||||
user_info = get_user_info(access_token)
|
||||
|
||||
logger.debug(f'Succesfully authenticated user: {user_info["username"]} with id: {user_info["id"]}')
|
||||
|
||||
validated_user = validate_user(user_info['id'], user_info['username'])
|
||||
login(req, validated_user)
|
||||
return redirect('/')
|
||||
except OAuthException as e:
|
||||
logger.error(e)
|
||||
return login_fail(req, str(e))
|
||||
|
||||
|
||||
def login_fail(request, error: str = None):
|
||||
return render(request, "oauth/failed.html", {'error': error})
|
||||
|
||||
|
||||
def validate_user(zeus_id, username) -> CustomUser:
|
||||
try:
|
||||
user = CustomUser.objects.get(zeus_id=zeus_id)
|
||||
user.username = username
|
||||
user.save()
|
||||
return user
|
||||
except users.models.CustomUser.DoesNotExist:
|
||||
return CustomUser.objects.create_user(zeus_id, username)
|
||||
|
||||
|
||||
def get_access_token(code):
|
||||
response = requests.post(
|
||||
settings.OAUTH["ACCESS_TOKEN_URI"],
|
||||
data={'code': code,
|
||||
'grant_type': 'authorization_code',
|
||||
'client_id': settings.OAUTH["CLIENT_ID"],
|
||||
'client_secret': settings.OAUTH["CLIENT_SECRET"],
|
||||
'redirect_uri': settings.OAUTH["REDIRECT_URI"]})
|
||||
if response.status_code != 200:
|
||||
raise OAuthException(
|
||||
f'Status code {response.status_code} when requesting access token.\nresponse: {response.text}')
|
||||
if 'access_token' not in response.json():
|
||||
raise OAuthException('Got status code 200 but no access_token')
|
||||
return response.json()['access_token']
|
||||
|
||||
|
||||
def get_user_info(access_token):
|
||||
response = requests.get(
|
||||
settings.OAUTH["USER_API_URI"],
|
||||
headers={'Authorization': f'Bearer {access_token}'},
|
||||
)
|
||||
if response.status_code != 200:
|
||||
raise OAuthException(
|
||||
f'Status code {response.status_code} when requesting user info.\nresponse: {response.text}')
|
||||
if 'username' not in response.json() or 'id' not in response.json():
|
||||
raise OAuthException(f'username and id are expected values: {response.json()}')
|
||||
return response.json()
|
239
static/main.css
Normal file
239
static/main.css
Normal file
|
@ -0,0 +1,239 @@
|
|||
body {
|
||||
font-family: "Helvetica", "Arial", "Roboto", sans-serif;
|
||||
text-align: center;
|
||||
background-color: #cf863e;
|
||||
}
|
||||
|
||||
/*input {
|
||||
width: 4em;
|
||||
border: 0;
|
||||
background-color: #CF863E;
|
||||
}
|
||||
*/
|
||||
|
||||
input {
|
||||
margin-bottom: 5vh;
|
||||
border-right: none;
|
||||
border-top: none;
|
||||
border-left: none;
|
||||
background-color: transparent;
|
||||
outline: none;
|
||||
color: white;
|
||||
caret-color: white;
|
||||
width: 4em;
|
||||
}
|
||||
|
||||
input[type="number"] {
|
||||
text-align: center;
|
||||
color: white;
|
||||
font-family: "Roboto", sans-serif;
|
||||
-webkit-appearance: textfield;
|
||||
-moz-appearance: textfield;
|
||||
appearance: textfield;
|
||||
}
|
||||
|
||||
input[type="number"]::-webkit-inner-spin-button,
|
||||
input[type="number"]::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-bottom: 0.7em;
|
||||
}
|
||||
|
||||
@media (min-height: 500px) {
|
||||
hgroup {
|
||||
margin-top: 5em;
|
||||
}
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 125%;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.emptiness {
|
||||
margin-top: 2em;
|
||||
}
|
||||
|
||||
@media (min-height: 500px) {
|
||||
.emptiness {
|
||||
margin-top: 9em;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
font: inherit;
|
||||
background: #ff7f00;
|
||||
border: 0px solid #000;
|
||||
border-radius: 0.3em;
|
||||
padding: 0.3em 0.8em 0.3em 0.7em;
|
||||
}
|
||||
|
||||
.big_but {
|
||||
padding: 1em 2em;
|
||||
min-width: 10em;
|
||||
min-height: 4.7em;
|
||||
border-radius: 0.5em;
|
||||
}
|
||||
|
||||
.cart {
|
||||
margin: 3em auto 0;
|
||||
font-weight: 400;
|
||||
display: grid;
|
||||
max-width: 20em;
|
||||
grid-template-columns: auto 40% auto;
|
||||
row-gap: 2em;
|
||||
font-size: 125%;
|
||||
}
|
||||
|
||||
.shop {
|
||||
margin: 3em auto 0;
|
||||
font-weight: 400;
|
||||
display: grid;
|
||||
max-width: 30em;
|
||||
grid-template-columns: auto 30% 20% auto;
|
||||
row-gap: 2em;
|
||||
font-size: 125%;
|
||||
}
|
||||
|
||||
.shop {
|
||||
row-gap: 1em;
|
||||
}
|
||||
|
||||
.cart > div,
|
||||
.shop > div {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.leftcell {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.midcell {
|
||||
height: 100px;
|
||||
line-height: 100px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.rightcell {
|
||||
text-align: left;
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
.shop .midcell {
|
||||
text-align: left;
|
||||
padding-left: 2em;
|
||||
}
|
||||
|
||||
.buy_button_cell,
|
||||
.total_quantity,
|
||||
.total_price {
|
||||
padding-bottom: 2em;
|
||||
margin-bottom: -1.5em;
|
||||
}
|
||||
|
||||
.total_price {
|
||||
border-bottom: 1.5px solid #000;
|
||||
}
|
||||
|
||||
.cart .quantity {
|
||||
margin-right: 2em;
|
||||
}
|
||||
|
||||
.photo {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
border: 1.5px solid #000;
|
||||
border-radius: 100%;
|
||||
width: 5em;
|
||||
height: 5em;
|
||||
line-height: 5em;
|
||||
box-sizing: border-box;
|
||||
margin: 0 auto;
|
||||
font-size: 95%;
|
||||
}
|
||||
|
||||
.shop button {
|
||||
padding: 0 1em 0.1em;
|
||||
line-height: 0.9;
|
||||
}
|
||||
|
||||
.narrow {
|
||||
display: inline-block;
|
||||
transform: scaleX(0.9);
|
||||
}
|
||||
|
||||
.toonarrow {
|
||||
display: inline-block;
|
||||
transform: scaleX(0.75);
|
||||
}
|
||||
|
||||
.bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.equals {
|
||||
display: inline-block;
|
||||
transform: scaleY(0.55);
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
nav a {
|
||||
margin-left: 1em;
|
||||
margin-right: 1em;
|
||||
color: rgb(8, 8, 63);
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
text-decoration: none;
|
||||
}
|
||||
nav a:before {
|
||||
background-color: rgb(8, 8, 63);
|
||||
content: "";
|
||||
height: 2px;
|
||||
position: absolute;
|
||||
bottom: -1px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
transition: width 0.2s ease-in-out;
|
||||
width: 100%;
|
||||
}
|
||||
nav a:hover:before {
|
||||
width: 0;
|
||||
}
|
||||
|
||||
nav {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 2em;
|
||||
}
|
||||
|
||||
nav ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
nav li {
|
||||
list-style-type: none;
|
||||
display: inline-block;
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
nav li:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
nav li:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
nav span,
|
||||
nav a {
|
||||
display: inline-block;
|
||||
padding: 10px 1vw;
|
||||
}
|
||||
|
||||
.deemphasized {
|
||||
color: #66351d;
|
||||
}
|
BIN
static/mate33.jpg
Normal file
BIN
static/mate33.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.6 KiB |
BIN
static/mate50.jpeg
Normal file
BIN
static/mate50.jpeg
Normal file
Binary file not shown.
After Width: | Height: | Size: 19 KiB |
61
templates/base.html
Normal file
61
templates/base.html
Normal file
|
@ -0,0 +1,61 @@
|
|||
{% load static %}
|
||||
<!doctype html>
|
||||
<html class="no-js" lang="">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||
<title>{% block title %}{%endblock%} - Mordor</title>
|
||||
<meta name="description" content="">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'main.css' %}"/>
|
||||
{% block styles %}{% endblock %}
|
||||
<!-- Place favicon.ico in the root directory -->
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<hgroup>
|
||||
<h1>Mordor</h1>
|
||||
<div class="subtitle">Mate order tool</div>
|
||||
<nav>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="{% url 'winkel' %}">Winkel</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url 'orders' %}">Mijn orders</a>
|
||||
</li>
|
||||
{% if user.is_authenticated %}
|
||||
<li class="deemphasized">
|
||||
<span>
|
||||
Hallo, {{ user.username }}
|
||||
</span>
|
||||
</li>
|
||||
{% if user.is_staff or user.is_superuser %}
|
||||
<li>
|
||||
<a href="{% url 'admin:index' %}">
|
||||
Admininterface
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li>
|
||||
<a href="{% url 'users:logout' %}">
|
||||
Afmelden
|
||||
</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li>
|
||||
<a href="{% url 'oauth:login' %}">
|
||||
Inloggen
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</hgroup>
|
||||
<main>
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
0
users/__init__.py
Normal file
0
users/__init__.py
Normal file
39
users/admin.py
Normal file
39
users/admin.py
Normal file
|
@ -0,0 +1,39 @@
|
|||
from django.contrib import admin
|
||||
from django.contrib.auth.admin import UserAdmin
|
||||
|
||||
from .forms import CustomUserCreationForm, CustomUserChangeForm
|
||||
from .models import CustomUser
|
||||
|
||||
|
||||
class CustomUserAdmin(UserAdmin):
|
||||
add_form = CustomUserCreationForm
|
||||
form = CustomUserChangeForm
|
||||
model = CustomUser
|
||||
list_display = ("username", "is_staff", "is_superuser")
|
||||
list_filter = ("username", "is_staff")
|
||||
fieldsets = (
|
||||
(
|
||||
None,
|
||||
{
|
||||
"fields": (
|
||||
"username",
|
||||
"password",
|
||||
)
|
||||
},
|
||||
),
|
||||
("Permissions", {"fields": ("is_staff", "is_superuser")}),
|
||||
)
|
||||
add_fieldsets = (
|
||||
(
|
||||
None,
|
||||
{
|
||||
"classes": ("wide",),
|
||||
"fields": ("username", "password1", "password2", "is_staff"),
|
||||
},
|
||||
),
|
||||
)
|
||||
search_fields = ("username",)
|
||||
ordering = ("username",)
|
||||
|
||||
|
||||
admin.site.register(CustomUser, CustomUserAdmin)
|
84
users/forms.py
Normal file
84
users/forms.py
Normal file
|
@ -0,0 +1,84 @@
|
|||
from django.contrib import admin
|
||||
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
|
||||
from django.forms import ModelForm, TextInput
|
||||
|
||||
from .models import CustomUser
|
||||
|
||||
|
||||
class CustomUserCreationForm(UserCreationForm):
|
||||
class Meta(UserCreationForm):
|
||||
model = CustomUser
|
||||
fields = ("username",)
|
||||
|
||||
|
||||
class CustomUserChangeForm(UserChangeForm):
|
||||
class Meta:
|
||||
model = CustomUser
|
||||
fields = ("username",)
|
||||
|
||||
|
||||
class UserMetaForm(ModelForm):
|
||||
class Meta:
|
||||
model = CustomUser
|
||||
fields = []
|
||||
widgets = {}
|
||||
|
||||
|
||||
# Add user to groups in django admin
|
||||
|
||||
from django import forms
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.admin.widgets import FilteredSelectMultiple
|
||||
from django.contrib.auth.models import Group
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
# Create ModelForm based on the Group model.
|
||||
class GroupAdminForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Group
|
||||
exclude = []
|
||||
|
||||
# Add the users field.
|
||||
users = forms.ModelMultipleChoiceField(
|
||||
queryset=User.objects.all(),
|
||||
required=False,
|
||||
# Use the pretty 'filter_horizontal widget'.
|
||||
widget=FilteredSelectMultiple("users", False),
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
# Do the normal form initialisation.
|
||||
super(GroupAdminForm, self).__init__(*args, **kwargs)
|
||||
# If it is an existing group (saved objects have a pk).
|
||||
if self.instance.pk:
|
||||
# Populate the users field with the current Group users.
|
||||
self.fields["users"].initial = self.instance.user_set.all()
|
||||
|
||||
def save_m2m(self):
|
||||
# Add the users to the Group.
|
||||
self.instance.user_set.set(self.cleaned_data["users"])
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
# Default save
|
||||
instance = super(GroupAdminForm, self).save()
|
||||
# Save many-to-many data
|
||||
self.save_m2m()
|
||||
return instance
|
||||
|
||||
|
||||
# Unregister the original Group admin.
|
||||
admin.site.unregister(Group)
|
||||
|
||||
|
||||
# Create a new Group admin.
|
||||
class GroupAdmin(admin.ModelAdmin):
|
||||
# Use our custom form.
|
||||
form = GroupAdminForm
|
||||
# Filter permissions horizontal as well.
|
||||
filter_horizontal = ["permissions"]
|
||||
|
||||
|
||||
# Register the new Group ModelAdmin.
|
||||
admin.site.register(Group, GroupAdmin)
|
33
users/managers.py
Normal file
33
users/managers.py
Normal file
|
@ -0,0 +1,33 @@
|
|||
from django.contrib.auth.base_user import BaseUserManager
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
class CustomUserManager(BaseUserManager):
|
||||
"""
|
||||
Custom user model manager where email is the unique identifiers
|
||||
for authentication instead of usernames.
|
||||
"""
|
||||
|
||||
def create_user(self, zeus_id, username, password=None, **extra_fields):
|
||||
"""
|
||||
Create and save a User with the given email and password.
|
||||
"""
|
||||
if zeus_id is None or username is None:
|
||||
raise ValueError(_('The zeus_id and username must be set'))
|
||||
user = self.model(zeus_id=zeus_id, username=username, password=password, **extra_fields)
|
||||
user.set_password(password)
|
||||
user.save()
|
||||
return user
|
||||
|
||||
def create_superuser(self, zeus_id, username, password, **extra_fields):
|
||||
"""
|
||||
Create and save a SuperUser with the given email and password.
|
||||
"""
|
||||
extra_fields.setdefault('is_staff', True)
|
||||
extra_fields.setdefault('is_superuser', True)
|
||||
|
||||
if extra_fields.get('is_staff') is not True:
|
||||
raise ValueError(_('Superuser must have is_staff=True.'))
|
||||
if extra_fields.get('is_superuser') is not True:
|
||||
raise ValueError(_('Superuser must have is_superuser=True.'))
|
||||
return self.create_user(zeus_id, username, password, **extra_fields)
|
77
users/migrations/0001_initial.py
Normal file
77
users/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,77 @@
|
|||
# Generated by Django 3.0.8 on 2020-07-22 16:03
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.utils.timezone
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
("auth", "0011_update_proxy_permissions"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="CustomUser",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("password", models.CharField(max_length=128, verbose_name="password")),
|
||||
(
|
||||
"last_login",
|
||||
models.DateTimeField(
|
||||
blank=True, null=True, verbose_name="last login"
|
||||
),
|
||||
),
|
||||
(
|
||||
"is_superuser",
|
||||
models.BooleanField(
|
||||
default=False,
|
||||
help_text="Designates that this user has all permissions without explicitly assigning them.",
|
||||
verbose_name="superuser status",
|
||||
),
|
||||
),
|
||||
("zeus_id", models.IntegerField(null=True, unique=True)),
|
||||
("is_staff", models.BooleanField(default=False)),
|
||||
("username", models.CharField(max_length=50, unique=True)),
|
||||
(
|
||||
"date_joined",
|
||||
models.DateTimeField(default=django.utils.timezone.now),
|
||||
),
|
||||
(
|
||||
"groups",
|
||||
models.ManyToManyField(
|
||||
blank=True,
|
||||
help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
|
||||
related_name="user_set",
|
||||
related_query_name="user",
|
||||
to="auth.Group",
|
||||
verbose_name="groups",
|
||||
),
|
||||
),
|
||||
(
|
||||
"user_permissions",
|
||||
models.ManyToManyField(
|
||||
blank=True,
|
||||
help_text="Specific permissions for this user.",
|
||||
related_name="user_set",
|
||||
related_query_name="user",
|
||||
to="auth.Permission",
|
||||
verbose_name="user permissions",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"abstract": False,
|
||||
},
|
||||
),
|
||||
]
|
75
users/migrations/0002_seed_admin_and_zeus_board.py
Normal file
75
users/migrations/0002_seed_admin_and_zeus_board.py
Normal file
|
@ -0,0 +1,75 @@
|
|||
# Created manually
|
||||
|
||||
import logging
|
||||
import os
|
||||
from typing import Dict, List
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core.management.sql import emit_post_migrate_signal
|
||||
from django.db import migrations
|
||||
from django.utils import timezone
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
ENV_USERNAME = "MORDOR_ADMIN_USERNAME"
|
||||
ENV_PASSWORD = "MORDOR_ADMIN_PASSWORD"
|
||||
|
||||
|
||||
def create_superuser(apps, schema_editor):
|
||||
superuser = get_user_model()(
|
||||
is_superuser=True,
|
||||
is_staff=True,
|
||||
username=os.environ.get(ENV_USERNAME, "admin"),
|
||||
last_login=timezone.now(),
|
||||
)
|
||||
|
||||
dev_password = "admin"
|
||||
password = os.environ.get(ENV_PASSWORD, dev_password)
|
||||
if password == dev_password:
|
||||
log = logger.warning if settings.DEBUG else logger.error
|
||||
log(
|
||||
f"Admin password is '{password}'. This is not for use in production. Set environment variable {ENV_PASSWORD} to choose a different password."
|
||||
)
|
||||
if not settings.DEBUG:
|
||||
raise Exception("Development admin password used in production")
|
||||
|
||||
superuser.set_password(password)
|
||||
superuser.save()
|
||||
|
||||
|
||||
kers_group_permissions: Dict[str, List] = {
|
||||
"Bestuur": [
|
||||
# "add_event",
|
||||
# "delete_event",
|
||||
# "change_event",
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def add_group_permissions(apps, schema_editor):
|
||||
db_alias = schema_editor.connection.alias
|
||||
emit_post_migrate_signal(2, False, db_alias)
|
||||
|
||||
Group = apps.get_model("auth", "Group")
|
||||
Permission = apps.get_model("auth", "Permission")
|
||||
|
||||
for group in kers_group_permissions:
|
||||
role, created = Group.objects.get_or_create(name=group)
|
||||
logger.info(f'{group} Group {"created" if created else "exists"}')
|
||||
for perm in kers_group_permissions[group]:
|
||||
role.permissions.add(Permission.objects.get(codename=perm))
|
||||
logger.info(f"Permitting {group} to {perm}")
|
||||
role.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("users", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(create_superuser),
|
||||
migrations.RunPython(add_group_permissions),
|
||||
]
|
18
users/migrations/0003_alter_customuser_id.py
Normal file
18
users/migrations/0003_alter_customuser_id.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 3.2 on 2021-05-17 18:00
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0002_seed_admin_and_zeus_board'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='customuser',
|
||||
name='id',
|
||||
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||
),
|
||||
]
|
0
users/migrations/__init__.py
Normal file
0
users/migrations/__init__.py
Normal file
25
users/models.py
Normal file
25
users/models.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
from django.contrib.auth.base_user import AbstractBaseUser
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User, PermissionsMixin
|
||||
from django.utils import timezone
|
||||
|
||||
from users.managers import CustomUserManager
|
||||
|
||||
|
||||
class CustomUser(AbstractBaseUser, PermissionsMixin):
|
||||
zeus_id = models.IntegerField(unique=True, null=True)
|
||||
is_staff = models.BooleanField(default=False)
|
||||
username = models.CharField(max_length=50, unique=True) # zeus username
|
||||
|
||||
date_joined = models.DateTimeField(default=timezone.now)
|
||||
|
||||
USERNAME_FIELD = "username"
|
||||
REQUIRED_FIELDS = ["zeus_id"]
|
||||
|
||||
objects = CustomUserManager()
|
||||
|
||||
def __str__(self):
|
||||
return self.username
|
||||
|
||||
def __repr__(self):
|
||||
return "<User: {}>".format(self.username)
|
19
users/templates/users/profile.html
Normal file
19
users/templates/users/profile.html
Normal file
|
@ -0,0 +1,19 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Profiel{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Profiel</h2>
|
||||
{% if user.is_authenticated %}
|
||||
<p>Gebruikersnaam: {{ user.username }}</p>
|
||||
|
||||
<form action="/user/profile" method="post">
|
||||
{% csrf_token %}
|
||||
{{ form }}
|
||||
<button type="submit">Bijwerken</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<p>Niet ingelogd</p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
7
users/urls.py
Normal file
7
users/urls.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
from django.urls import path
|
||||
|
||||
from . import views
|
||||
|
||||
app_name = "users"
|
||||
|
||||
urlpatterns = [path("logout", views.logout_view, name="logout")]
|
15
users/views.py
Normal file
15
users/views.py
Normal file
|
@ -0,0 +1,15 @@
|
|||
from pprint import pprint
|
||||
|
||||
from django.contrib.auth import logout
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import render, redirect
|
||||
from django.urls import reverse
|
||||
|
||||
from users.forms import UserMetaForm
|
||||
from users.models import CustomUser
|
||||
|
||||
|
||||
def logout_view(request):
|
||||
logout(request)
|
||||
|
||||
return redirect(reverse("index"))
|
Loading…
Reference in a new issue