one time login started
This commit is contained in:
parent
f83b264e69
commit
1437a796c8
37 changed files with 548 additions and 0 deletions
|
|
@ -2,3 +2,16 @@
|
||||||
|
|
||||||
The repapp is split into different modules, realized as Django apps, to improve the maintainability.
|
The repapp is split into different modules, realized as Django apps, to improve the maintainability.
|
||||||
|
|
||||||
|
## one time login
|
||||||
|
|
||||||
|
The one time login module provides a login for guests using a one time secret as part of an URL.
|
||||||
|
|
||||||
|
### Settings
|
||||||
|
|
||||||
|
- The templates use the `settings.ORGANIZATION` variable to fill the organization name.
|
||||||
|
- The Django mail module is used to send emails. See https://docs.djangoproject.com/en/4.2/topics/email/ for details how to configure the mail host.
|
||||||
|
|
||||||
|
## mail interface
|
||||||
|
|
||||||
|
|
||||||
|
## repapp members
|
||||||
|
|
|
||||||
23
docs/dev/configuration.md
Normal file
23
docs/dev/configuration.md
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Repapp configuration
|
||||||
|
|
||||||
|
## Debug mode
|
||||||
|
|
||||||
|
The Django debug mode is activated if there is a environment variable `DJANGO_DEBUG` with the value "true", "1" or "t". By default, the debug mode is disabled.
|
||||||
|
|
||||||
|
## Organization name
|
||||||
|
|
||||||
|
Some templates use and organization name. This value is taken from `settings.ORGANIZATION` and defaults to "Repair-Café Hilpoltstein".
|
||||||
|
|
||||||
|
## Email settings
|
||||||
|
|
||||||
|
The following environment variables are used to configure the Django email feature:
|
||||||
|
|
||||||
|
``` Python
|
||||||
|
EMAIL_HOST = os.getenv("DJANGO_EMAIL_HOST", "")
|
||||||
|
EMAIL_PORT = (int)(os.getenv("DJANGO_EMAIL_PORT", "25"))
|
||||||
|
EMAIL_HOST_USER = os.getenv("DJANGO_EMAIL_HOST_USER", None)
|
||||||
|
EMAIL_HOST_PASSWORD = os.getenv("DJANGO_EMAIL_HOST_PASSWORD", None)
|
||||||
|
EMAIL_USE_TLS = os.getenv("DJANGO_EMAIL_USE_TLS", "true") in ("true", "1", "t")
|
||||||
|
```
|
||||||
|
See https://docs.djangoproject.com/en/4.2/topics/email/ for more details.
|
||||||
|
|
||||||
|
|
@ -14,6 +14,7 @@ nav:
|
||||||
- 'Use cases': 'dev/use_cases.md'
|
- 'Use cases': 'dev/use_cases.md'
|
||||||
- Apps: 'dev/apps.md'
|
- Apps: 'dev/apps.md'
|
||||||
- Objects: 'dev/objects.md'
|
- Objects: 'dev/objects.md'
|
||||||
|
- 'Repapp configuration': 'dev/configuration.md'
|
||||||
- 'Repair-Café & Makespace':
|
- 'Repair-Café & Makespace':
|
||||||
- Repair-Café: https://www.repaircafe-hilpoltstein.de
|
- Repair-Café: https://www.repaircafe-hilpoltstein.de
|
||||||
- Makespace: https://makes-hacks-hip.de
|
- Makespace: https://makes-hacks-hip.de
|
||||||
|
|
|
||||||
0
repapp/data/.gitkeep
Normal file
0
repapp/data/.gitkeep
Normal file
0
repapp/mail_inteface/__init__.py
Normal file
0
repapp/mail_inteface/__init__.py
Normal file
3
repapp/mail_inteface/admin.py
Normal file
3
repapp/mail_inteface/admin.py
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
6
repapp/mail_inteface/apps.py
Normal file
6
repapp/mail_inteface/apps.py
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class MailIntefaceConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'mail_inteface'
|
||||||
0
repapp/mail_inteface/migrations/__init__.py
Normal file
0
repapp/mail_inteface/migrations/__init__.py
Normal file
3
repapp/mail_inteface/models.py
Normal file
3
repapp/mail_inteface/models.py
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
# Create your models here.
|
||||||
3
repapp/mail_inteface/tests.py
Normal file
3
repapp/mail_inteface/tests.py
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
3
repapp/mail_inteface/views.py
Normal file
3
repapp/mail_inteface/views.py
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
# Create your views here.
|
||||||
22
repapp/manage.py
Executable file
22
repapp/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', 'repapp.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
repapp/one_time_login/__init__.py
Normal file
0
repapp/one_time_login/__init__.py
Normal file
12
repapp/one_time_login/admin.py
Normal file
12
repapp/one_time_login/admin.py
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
from .models import OneTimeLogin
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(OneTimeLogin)
|
||||||
|
class OneTimeLoginAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ['user', 'url', 'login_used', 'login_date', 'created']
|
||||||
|
list_filter = ['user', 'url', 'login_used', 'login_date', 'created']
|
||||||
|
search_fields = ['user', 'url']
|
||||||
|
raw_id_fields = ['user']
|
||||||
|
date_hierarchy = 'created'
|
||||||
|
ordering = ['created', 'user', 'url']
|
||||||
64
repapp/one_time_login/api.py
Normal file
64
repapp/one_time_login/api.py
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
import os
|
||||||
|
import datetime
|
||||||
|
import random
|
||||||
|
import logging
|
||||||
|
from hashlib import sha256
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.core.mail import send_mail
|
||||||
|
from django.template.loader import render_to_string
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from .models import OneTimeLogin
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def create_one_time_login(user, url) -> str:
|
||||||
|
"""
|
||||||
|
create_one_time_login creates a one time login object for guest user logins.
|
||||||
|
"""
|
||||||
|
secret = sha256(
|
||||||
|
f'{user.email}{url}{datetime.datetime.now()}{random.randint(0,9999999)}'.encode(
|
||||||
|
'utf-8')
|
||||||
|
).hexdigest()
|
||||||
|
secret_hash = sha256(secret.encode('utf-8')).hexdigest()
|
||||||
|
otl = OneTimeLogin(
|
||||||
|
secret=secret_hash,
|
||||||
|
user=user,
|
||||||
|
url=url,
|
||||||
|
)
|
||||||
|
otl.save()
|
||||||
|
return secret
|
||||||
|
|
||||||
|
|
||||||
|
def send_one_time_login_mail(secret, mail, request):
|
||||||
|
"""
|
||||||
|
Send a mail with a one time login link.
|
||||||
|
"""
|
||||||
|
url = request.build_absolute_uri(
|
||||||
|
f'/onetimelogin/{secret}/')
|
||||||
|
subject = render_to_string('one_time_login/mail_subject.txt', {
|
||||||
|
'organization': settings.ORGANIZATION,
|
||||||
|
}).replace('\n', '')
|
||||||
|
html = render_to_string('one_time_login/mail_content.html', {
|
||||||
|
'link': url,
|
||||||
|
'organization': settings.ORGANIZATION,
|
||||||
|
})
|
||||||
|
text = render_to_string('one_time_login/mail_content.html', {
|
||||||
|
'link': url,
|
||||||
|
'organization': settings.ORGANIZATION,
|
||||||
|
})
|
||||||
|
|
||||||
|
send_ok = send_mail(
|
||||||
|
subject=subject,
|
||||||
|
message=text,
|
||||||
|
from_email=os.getenv("DJANGO_SENDER_ADDRESS", ""),
|
||||||
|
recipient_list=[mail],
|
||||||
|
fail_silently=True,
|
||||||
|
html_message=html
|
||||||
|
)
|
||||||
|
|
||||||
|
if send_ok > 0:
|
||||||
|
messages.add_message(request, messages.INFO, _(
|
||||||
|
'A new login link was sent by mail.'))
|
||||||
6
repapp/one_time_login/apps.py
Normal file
6
repapp/one_time_login/apps.py
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class OneTimeLoginConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'one_time_login'
|
||||||
34
repapp/one_time_login/migrations/0001_initial.py
Normal file
34
repapp/one_time_login/migrations/0001_initial.py
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
# Generated by Django 4.2 on 2023-08-27 19:52
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='OneTimeLogin',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('secret', models.CharField(max_length=200, unique=True, verbose_name='Secret')),
|
||||||
|
('url', models.CharField(max_length=200, verbose_name='URL')),
|
||||||
|
('created', models.DateField(default=django.utils.timezone.now, verbose_name='Creation date')),
|
||||||
|
('login_used', models.BooleanField(default=False, verbose_name='Was the login used?')),
|
||||||
|
('login_date', models.DateField(null=True, verbose_name='Login date')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'One time login',
|
||||||
|
'verbose_name_plural': 'One time logins',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
0
repapp/one_time_login/migrations/__init__.py
Normal file
0
repapp/one_time_login/migrations/__init__.py
Normal file
35
repapp/one_time_login/models.py
Normal file
35
repapp/one_time_login/models.py
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
"""
|
||||||
|
Data models for the one time login feature.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import django.utils.timezone
|
||||||
|
from django.db import models
|
||||||
|
from django.conf import settings
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class OneTimeLogin(models.Model):
|
||||||
|
"""
|
||||||
|
A one time login (otl) is a secret which can be used once to login a user.
|
||||||
|
"""
|
||||||
|
secret = models.CharField(max_length=200, verbose_name=_(
|
||||||
|
"Secret"), unique=True, null=False)
|
||||||
|
url = models.CharField(max_length=200, verbose_name=_("URL"))
|
||||||
|
created = models.DateField(verbose_name=_(
|
||||||
|
"Creation date"), default=django.utils.timezone.now)
|
||||||
|
login_used = models.BooleanField(
|
||||||
|
verbose_name=_("Was the login used?"), default=False)
|
||||||
|
login_date = models.DateField(verbose_name=_("Login date"), null=True)
|
||||||
|
user = models.ForeignKey(
|
||||||
|
settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=False, verbose_name=_("User"))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('One time login')
|
||||||
|
verbose_name_plural = _('One time logins')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'One time login for {self.user}'
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse('one_time_login:login', args=[self.secret])
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="description" content="one time login link" />
|
||||||
|
<meta name="keywords" content="login" />
|
||||||
|
<title>One time login</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>Hello,</p>
|
||||||
|
<p>
|
||||||
|
we prepared a one time login for you. Click <a href="{{ link }}">here</a> to use the link.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Kind regards,
|
||||||
|
{{ organization }}
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
Hello,
|
||||||
|
|
||||||
|
we prepared a one time login for you. Open {{ link }} in your browser to use the link.
|
||||||
|
|
||||||
|
Kind regards,
|
||||||
|
{{ organization }}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{{ organization }} - Einmal-Ammeldelink
|
||||||
3
repapp/one_time_login/tests.py
Normal file
3
repapp/one_time_login/tests.py
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
13
repapp/one_time_login/urls.py
Normal file
13
repapp/one_time_login/urls.py
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
"""
|
||||||
|
Urls of RepApp.
|
||||||
|
"""
|
||||||
|
from django.urls import path
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
|
||||||
|
app_name = 'one_time_login'
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("<str:secret>/", views.one_time_login, name="login"),
|
||||||
|
]
|
||||||
43
repapp/one_time_login/views.py
Normal file
43
repapp/one_time_login/views.py
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
import time
|
||||||
|
from hashlib import sha256
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.contrib.auth import authenticate, login
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
from django.http import HttpResponseRedirect
|
||||||
|
from django.urls import reverse_lazy
|
||||||
|
from django.utils.timezone import now
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from .models import OneTimeLogin
|
||||||
|
from .api import create_one_time_login, send_one_time_login_mail
|
||||||
|
|
||||||
|
|
||||||
|
def one_time_login(request, secret: str):
|
||||||
|
"""
|
||||||
|
View for one time login.
|
||||||
|
"""
|
||||||
|
# waste a little time as brute force protection
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
secret_hash = sha256(secret.encode('utf-8')).hexdigest()
|
||||||
|
otl = get_object_or_404(OneTimeLogin, secret=secret_hash)
|
||||||
|
|
||||||
|
if otl.login_used:
|
||||||
|
messages.add_message(request, messages.ERROR,
|
||||||
|
_('Login was used already.'))
|
||||||
|
new_secret = create_one_time_login(otl.user, otl.url)
|
||||||
|
send_one_time_login_mail(new_secret, otl.user.email, request)
|
||||||
|
return HttpResponseRedirect(reverse_lazy('index'))
|
||||||
|
else:
|
||||||
|
otl.login_used = True
|
||||||
|
otl.login_date = now()
|
||||||
|
otl.save()
|
||||||
|
|
||||||
|
user = authenticate(request, username=secret_hash, password=None)
|
||||||
|
|
||||||
|
if user is not None:
|
||||||
|
login(request, user)
|
||||||
|
messages.add_message(request, messages.INFO, _('Login successful!'))
|
||||||
|
return HttpResponseRedirect(otl.url)
|
||||||
|
else:
|
||||||
|
messages.add_message(request, messages.ERROR, _('Login failed!'))
|
||||||
|
return HttpResponseRedirect(reverse_lazy('index'))
|
||||||
0
repapp/repapp/__init__.py
Normal file
0
repapp/repapp/__init__.py
Normal file
16
repapp/repapp/asgi.py
Normal file
16
repapp/repapp/asgi.py
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
"""
|
||||||
|
ASGI config for repapp 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/4.2/howto/deployment/asgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'repapp.settings')
|
||||||
|
|
||||||
|
application = get_asgi_application()
|
||||||
162
repapp/repapp/settings.py
Normal file
162
repapp/repapp/settings.py
Normal file
|
|
@ -0,0 +1,162 @@
|
||||||
|
"""
|
||||||
|
Django settings for repapp project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 4.2.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/4.2/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/4.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/4.2/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = 'django-insecure-py(c)@wu#^$!rd_eyxg7-1j&)ep$8+1w^#m4mw909@yd^+_cx9'
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
# DEBUG = True
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = []
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
"one_time_login.apps.OneTimeLoginConfig",
|
||||||
|
'django.contrib.admin',
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
]
|
||||||
|
|
||||||
|
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 = 'repapp.urls'
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
'DIRS': [],
|
||||||
|
'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 = 'repapp.wsgi.application'
|
||||||
|
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
|
"NAME": BASE_DIR / "data" / "db.sqlite3",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/4.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/4.2/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = 'en-us'
|
||||||
|
|
||||||
|
TIME_ZONE = 'UTC'
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/4.2/howto/static-files/
|
||||||
|
|
||||||
|
STATIC_URL = 'static/'
|
||||||
|
|
||||||
|
# Default primary key field type
|
||||||
|
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
|
||||||
|
|
||||||
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
|
|
||||||
|
######################
|
||||||
|
# Repapp configuration
|
||||||
|
######################
|
||||||
|
|
||||||
|
# Set debug mode according to env variable
|
||||||
|
DEBUG = os.getenv("DJANGO_DEBUG", "false").lower() in ("true", "1", "t")
|
||||||
|
|
||||||
|
# basic Python logging configuration
|
||||||
|
LOG_LEVEL = "INFO"
|
||||||
|
if DEBUG:
|
||||||
|
LOG_LEVEL = "DEBUG"
|
||||||
|
|
||||||
|
LOGGING = {
|
||||||
|
"version": 1,
|
||||||
|
"disable_existing_loggers": False,
|
||||||
|
"handlers": {
|
||||||
|
"console": {
|
||||||
|
"class": "logging.StreamHandler",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"handlers": ["console"],
|
||||||
|
"level": LOG_LEVEL,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# Organization name
|
||||||
|
# used by one time login
|
||||||
|
ORGANIZATION = "Repair-Café Hilpoltstein"
|
||||||
|
|
||||||
|
# Email settings
|
||||||
|
# used by one time login
|
||||||
|
EMAIL_HOST = os.getenv("DJANGO_EMAIL_HOST", "")
|
||||||
|
EMAIL_PORT = (int)(os.getenv("DJANGO_EMAIL_PORT", "25"))
|
||||||
|
EMAIL_HOST_USER = os.getenv("DJANGO_EMAIL_HOST_USER", None)
|
||||||
|
EMAIL_HOST_PASSWORD = os.getenv("DJANGO_EMAIL_HOST_PASSWORD", None)
|
||||||
|
EMAIL_USE_TLS = os.getenv("DJANGO_EMAIL_USE_TLS", "true") in ("true", "1", "t")
|
||||||
23
repapp/repapp/urls.py
Normal file
23
repapp/repapp/urls.py
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
"""
|
||||||
|
URL configuration for repapp project.
|
||||||
|
|
||||||
|
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||||
|
https://docs.djangoproject.com/en/4.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
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('admin/', admin.site.urls),
|
||||||
|
path('one_time_login/', include('one_time_login.urls', namespace='one_time_login')),
|
||||||
|
]
|
||||||
16
repapp/repapp/wsgi.py
Normal file
16
repapp/repapp/wsgi.py
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
"""
|
||||||
|
WSGI config for repapp 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/4.2/howto/deployment/wsgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'repapp.settings')
|
||||||
|
|
||||||
|
application = get_wsgi_application()
|
||||||
0
repapp/repapp_members/__init__.py
Normal file
0
repapp/repapp_members/__init__.py
Normal file
3
repapp/repapp_members/admin.py
Normal file
3
repapp/repapp_members/admin.py
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
6
repapp/repapp_members/apps.py
Normal file
6
repapp/repapp_members/apps.py
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class RepappMembersConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'repapp_members'
|
||||||
0
repapp/repapp_members/migrations/__init__.py
Normal file
0
repapp/repapp_members/migrations/__init__.py
Normal file
3
repapp/repapp_members/models.py
Normal file
3
repapp/repapp_members/models.py
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
# Create your models here.
|
||||||
3
repapp/repapp_members/tests.py
Normal file
3
repapp/repapp_members/tests.py
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
3
repapp/repapp_members/views.py
Normal file
3
repapp/repapp_members/views.py
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
# Create your views here.
|
||||||
Reference in a new issue