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.
|
||||
|
||||
## 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'
|
||||
- Apps: 'dev/apps.md'
|
||||
- Objects: 'dev/objects.md'
|
||||
- 'Repapp configuration': 'dev/configuration.md'
|
||||
- 'Repair-Café & Makespace':
|
||||
- Repair-Café: https://www.repaircafe-hilpoltstein.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