commit
8e96c18b7a
41 changed files with 975 additions and 743 deletions
10
cspell.json
10
cspell.json
|
|
@ -9,7 +9,15 @@
|
||||||
"reparateur",
|
"reparateur",
|
||||||
"organisator",
|
"organisator",
|
||||||
"Reparateurs",
|
"Reparateurs",
|
||||||
"repapp"
|
"repapp",
|
||||||
|
"oidc",
|
||||||
|
"anmeldung",
|
||||||
|
"repaircafe",
|
||||||
|
"hilpoltstein",
|
||||||
|
"KeycloakOIDCAB",
|
||||||
|
"Keycloak",
|
||||||
|
"tabindex",
|
||||||
|
"onetimelogin"
|
||||||
],
|
],
|
||||||
"ignoreWords": [],
|
"ignoreWords": [],
|
||||||
"import": []
|
"import": []
|
||||||
|
|
|
||||||
|
|
@ -20,38 +20,44 @@ Ein Organisator ist Mitglied des Repair-Cafés das die Termine mit den Gästen o
|
||||||
|
|
||||||
### Gast
|
### Gast
|
||||||
|
|
||||||
- [x] Als Gast möchte ich einen Termin für eine Reparatur vereinbaren. (A1)
|
- [x] M1: Als Gast möchte ich einen Termin für eine Reparatur vereinbaren. (A1)
|
||||||
- [x] Als Gast möchte ich eine Bestätigung für eine Reparaturanfrage bekommen. (A2)
|
- [x] M1: Als Gast möchte ich eine Bestätigung für eine Reparaturanfrage bekommen. (A2)
|
||||||
- [ ] Als Gast möchte ich eine Terminbenachrichtigung oder eine Absage bekommen. (A3)
|
- [ ] M1: Als Gast möchte ich eine Terminbenachrichtigung oder eine Absage bekommen. (A3)
|
||||||
- [ ] Als Gast möchte ich mein angemeldetes Gerät und dessen Status einsehen können. (A4)
|
- [ ] Als Gast möchte ich mein angemeldetes Gerät und dessen Status einsehen können. (A4)
|
||||||
- [ ] Als Gast möchte ich Rückfragen zum Gerät empfangen und diese beantworten können. (A5)
|
- [ ] M1: Als Gast möchte ich Rückfragen zum Gerät empfangen und diese beantworten können. (A5)
|
||||||
|
- [ ] M1: Als Gast möchte ich Fragen ans Repair-Café stellen können. (A26)
|
||||||
- [ ] Als Gast möchte ich ein Gerät für eine Folgereparatur anmelden können. (A6)
|
- [ ] Als Gast möchte ich ein Gerät für eine Folgereparatur anmelden können. (A6)
|
||||||
- [ ] Als Gast möchte ich meine Daten ansehen können. (A21)
|
- [ ] Als Gast möchte ich meine Daten ansehen können. (A21)
|
||||||
- [ ] Als Gast möchte ich meine Daten löschen können. (A22)
|
- [ ] Als Gast möchte ich meine Daten löschen können. (A22)
|
||||||
- [ ] Als Gast möchte ich eine Geräteanmeldung löschen können. (A23)
|
- [ ] Als Gast möchte ich eine Geräteanmeldung löschen können. (A23)
|
||||||
|
- [ ] M1: Als Gast möchte ausschließlich per eMail mit dem System interagieren können. (A30)
|
||||||
|
|
||||||
### Reparateur
|
### Reparateur
|
||||||
|
|
||||||
- [ ] Als Reparateur möchte ich die angemeldeten Geräte ansehen können. (A7)
|
- [ ] M1: Als Reparateur möchte ich die angemeldeten Geräte ansehen können. (A7)
|
||||||
- [ ] Als Reparateur möchte ich mir ein angemeldetes Gerät zuordnen können. (A8)
|
- [ ] M1: Als Reparateur möchte ich mir ein angemeldetes Gerät zuordnen können. (A8)
|
||||||
- [ ] Als Reparateur möchte ich ein angemeldetes Gerät ablehnen können. (A9)
|
- [ ] Als Reparateur möchte ich ein angemeldetes Gerät ablehnen können. (A9)
|
||||||
- [ ] Als Reparateur möchte ich Rückfragen zum Gerät stellen können. (A10)
|
- [ ] M1: Als Reparateur möchte ich Rückfragen zum Gerät stellen können. (A10)
|
||||||
- [ ] Als Reparateur möchte ich eine Benachrichtigung bekommen wenn eine Rückfrage beantwortet wird. (A11)
|
- [ ] M1: Als Reparateur möchte ich eine Benachrichtigung bekommen wenn eine Rückfrage beantwortet wird. (A11)
|
||||||
- [ ] Als Reparateur möchte ich eine Benachrichtigung bekommen wenn mir ein Gerät zugeordnet wird. (A12)
|
- [ ] M1: Als Reparateur möchte ich eine Benachrichtigung bekommen wenn mir ein Gerät zugeordnet wird. (A12)
|
||||||
- [ ] Als Reparateur möchte ich mich für ein Repair-Café anmelden können. (A13)
|
- [ ] M1: Als Reparateur möchte ich mich für ein Repair-Café anmelden können. (A13)
|
||||||
- [ ] Als Reparateur möchte ich mich für ein Repair-Café abmelden können. (A14)
|
- [ ] M1: Als Reparateur möchte ich mich für ein Repair-Café abmelden können. (A14)
|
||||||
- [ ] Als Reparateur möchte ich benachrichtigt werden wenn ein Gast eine mir zugeordnete Geräteanmeldung löscht. (A25)
|
- [ ] Als Reparateur möchte ich benachrichtigt werden wenn ein Gast eine mir zugeordnete Geräteanmeldung löscht. (A25)
|
||||||
|
|
||||||
### Organisator
|
### Organisator
|
||||||
|
|
||||||
- [x] Als Organisator möchte ich benachrichtigt werden wenn ein Gast ein Gerät anmeldet. (A25)
|
- [x] M1: Als Organisator möchte ich benachrichtigt werden wenn ein Gast ein Gerät anmeldet. (A25)
|
||||||
- [ ] Als Organisator möchte ich ein Repair-Café anlegen können. (A15)
|
- [ ] M1: Als Organisator möchte ich ein Repair-Café anlegen können. (A15)
|
||||||
- [ ] Als Organisator möchte ich die Geräte einsehen können. (A16)
|
- [ ] M1: Als Organisator möchte ich die Geräte einsehen können. (A16)
|
||||||
- [ ] Als Organisator möchte ich eine Rückfrage zu einem Geräte stellen können. (A17)
|
- [ ] M1: Als Organisator möchte ich eine Rückfrage zu einem Geräte stellen können. (A17)
|
||||||
- [ ] Als Organisator möchte ich ein Geräte ablehnen können. (A18)
|
- [ ] M1: Als Organisator möchte ich ein Geräte ablehnen können. (A18)
|
||||||
- [ ] Als Organisator möchte ich ein Geräte zu einem Termin zuordnen können. (A19)
|
- [ ] M1: Als Organisator möchte ich ein Geräte zu einem Termin zuordnen können. (A19)
|
||||||
- [ ] Als Organisator möchte ich einen Reparateur zu einem Termin zuordnen können. (A20)
|
- [ ] M1: Als Organisator möchte ich einen Reparateur zu einem Termin zuordnen können. (A20)
|
||||||
|
- [ ] M1: Als Organisator möchte ich ein Gerät bearbeiten können. (A28)
|
||||||
|
- [ ] M1: Als Organisator möchte ich ein Gerät absagen können, wenn der Gast mich informiert hat dass er nicht kommt. (A29)
|
||||||
- [ ] Als Organisator möchte ich benachrichtigt werden wenn ein Gast eine Geräteanmeldung löscht. (A24)
|
- [ ] Als Organisator möchte ich benachrichtigt werden wenn ein Gast eine Geräteanmeldung löscht. (A24)
|
||||||
|
- [ ] Als Organisator möchte ich alle Daten exportieren können. (A31)
|
||||||
|
- [ ] Als Organisator möchte ich Geräte auf ein späteres Repair-Café verschieben können. (A32)
|
||||||
|
|
||||||
## Daten
|
## Daten
|
||||||
|
|
||||||
|
|
@ -284,3 +290,32 @@ Als bekannter Gast der ein defekten Geräte anmelden möchte,
|
||||||
- Die nächste Seite (S2) enthält ein Formular mit den Feldern `eMail-Adresse`, `Art des Geräts`, `Hersteller & Modell/Typ`, `Fehlerbeschreibung`, `Foto vom Gerät`, `Foto vom Typenschild`, ein Kontrollkästchen `Folgetermin`, ein Kontrollkästchen `Informationen zur Reparaturabwicklung` une ein Kontrollkästchen `Datenschutz`. Nachdem ich die Felder ausgefüllt habe klicke auf auf `Absenden`. Dieser Klick bringt mich auf die nächste Seite.
|
- Die nächste Seite (S2) enthält ein Formular mit den Feldern `eMail-Adresse`, `Art des Geräts`, `Hersteller & Modell/Typ`, `Fehlerbeschreibung`, `Foto vom Gerät`, `Foto vom Typenschild`, ein Kontrollkästchen `Folgetermin`, ein Kontrollkästchen `Informationen zur Reparaturabwicklung` une ein Kontrollkästchen `Datenschutz`. Nachdem ich die Felder ausgefüllt habe klicke auf auf `Absenden`. Dieser Klick bringt mich auf die nächste Seite.
|
||||||
- Die nächste Seite (S5) bestätigt mir die Anmeldung meines Gerätes.
|
- Die nächste Seite (S5) bestätigt mir die Anmeldung meines Gerätes.
|
||||||
- In meinem eMail Posteingang finde ich ebenfalls eine Bestätigung der Geräteanmeldung.
|
- In meinem eMail Posteingang finde ich ebenfalls eine Bestätigung der Geräteanmeldung.
|
||||||
|
|
||||||
|
## Konfigurationsparameter
|
||||||
|
|
||||||
|
- DJANGO_SECRET_KEY
|
||||||
|
- DJANGO_DEBUG
|
||||||
|
- DJANGO_EMAIL_HOST
|
||||||
|
- DJANGO_EMAIL_PORT
|
||||||
|
- DJANGO_EMAIL_HOST_USER
|
||||||
|
- DJANGO_EMAIL_HOST_PASSWORD
|
||||||
|
- DJANGO_EMAIL_USE_TLS
|
||||||
|
- OIDC_RP_CLIENT_SECRET
|
||||||
|
|
||||||
|
### Feste Konfiguration
|
||||||
|
|
||||||
|
- ALLOWED_HOSTS
|
||||||
|
- LANGUAGE_CODE
|
||||||
|
- TIME_ZONE
|
||||||
|
- CSRF_TRUSTED_ORIGINS
|
||||||
|
- AUTH_USER_MODEL
|
||||||
|
- AUTHENTICATION_BACKENDS
|
||||||
|
- LOGIN_REDIRECT_URL
|
||||||
|
- OIDC_RP_CLIENT_ID
|
||||||
|
- OIDC_RP_SIGN_ALGO
|
||||||
|
- OIDC_OP_JWKS_ENDPOINT
|
||||||
|
- OIDC_OP_AUTHORIZATION_ENDPOINT
|
||||||
|
- OIDC_OP_TOKEN_ENDPOINT
|
||||||
|
- OIDC_OP_USER_ENDPOINT
|
||||||
|
- LOGIN_REDIRECT_URL
|
||||||
|
- LOGOUT_REDIRECT_URL
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
"""
|
"""
|
||||||
Django settings for rc_hip project.
|
Django settings for rc_hip project.
|
||||||
|
|
||||||
Generated by 'django-admin startproject' using Django 4.2.
|
Generated by "django-admin startproject" using Django 4.2.
|
||||||
|
|
||||||
For more information on this file, see
|
For more information on this file, see
|
||||||
https://docs.djangoproject.com/en/4.2/topics/settings/
|
https://docs.djangoproject.com/en/4.2/topics/settings/
|
||||||
|
|
@ -15,7 +15,7 @@ from pathlib import Path
|
||||||
|
|
||||||
mimetypes.add_type("text/css", ".css", True)
|
mimetypes.add_type("text/css", ".css", True)
|
||||||
|
|
||||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
# Build paths inside the project like this: BASE_DIR / "subdir".
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
STATIC_ROOT = os.path.join(BASE_DIR, "static")
|
STATIC_ROOT = os.path.join(BASE_DIR, "static")
|
||||||
|
|
@ -25,74 +25,77 @@ MEDIA_ROOT = os.path.join(BASE_DIR, "media")
|
||||||
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
|
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
|
||||||
|
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
# SECRET_KEY = 'django-insecure-ura&_-(r(&&b$0s!a=07c@k6c!8=8m7$5(4#w8m@79-e%rn6s*'
|
# SECRET_KEY = "django-insecure-ura&_-(r(&&b$0s!a=07c@k6c!8=8m7$5(4#w8m@79-e%rn6s*"
|
||||||
SECRET_KEY = os.environ.get(
|
SECRET_KEY = os.getenv(
|
||||||
"DJANGO_SECRET_KEY", 'django-insecure-ura&_-(r(&&b$0s!a=07c@k6c!8=8m7$5(4#w8m@79-e%rn6s*')
|
"DJANGO_SECRET_KEY", "django-insecure-ura&_-(r(&&b$0s!a=07c@k6c!8=8m7$5(4#w8m@79-e%rn6s*")
|
||||||
|
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
# SECURITY WARNING: don"t run with debug turned on in production!
|
||||||
DEBUG = os.getenv("DJANGO_DEBUG", 'true').lower() in ('true', '1', 't')
|
DEBUG = os.getenv("DJANGO_DEBUG", "true").lower() in ("true", "1", "t")
|
||||||
|
|
||||||
ALLOWED_HOSTS = [
|
ALLOWED_HOSTS = [
|
||||||
'127.0.0.1',
|
"127.0.0.1",
|
||||||
'localhost',
|
"localhost",
|
||||||
'repapp.rc-hip.de'
|
"repapp.rc-hip.de",
|
||||||
|
"anmeldung.repaircafe-hilpoltstein.de",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
'repapp.apps.RepappConfig',
|
"repapp.apps.RepappConfig",
|
||||||
'django.contrib.admin',
|
"django.contrib.admin",
|
||||||
'django.contrib.auth',
|
"django.contrib.auth",
|
||||||
'django.contrib.contenttypes',
|
"mozilla_django_oidc",
|
||||||
'django.contrib.sessions',
|
"django.contrib.contenttypes",
|
||||||
'django.contrib.messages',
|
"django.contrib.sessions",
|
||||||
'django.contrib.staticfiles',
|
"django.contrib.messages",
|
||||||
'crispy_forms',
|
"django.contrib.staticfiles",
|
||||||
'crispy_bootstrap4',
|
"crispy_forms",
|
||||||
|
"crispy_bootstrap4",
|
||||||
|
"import_export",
|
||||||
]
|
]
|
||||||
|
|
||||||
CRISPY_ALLOWED_TEMPLATE_PACKS = 'bootstrap4'
|
CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap4"
|
||||||
CRISPY_TEMPLATE_PACK = 'bootstrap4'
|
CRISPY_TEMPLATE_PACK = "bootstrap4"
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
'django.middleware.security.SecurityMiddleware',
|
"django.middleware.security.SecurityMiddleware",
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
'django.middleware.common.CommonMiddleware',
|
"django.middleware.common.CommonMiddleware",
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
"django.middleware.csrf.CsrfViewMiddleware",
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
"django.contrib.messages.middleware.MessageMiddleware",
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||||
]
|
]
|
||||||
|
|
||||||
ROOT_URLCONF = 'rc_hip.urls'
|
ROOT_URLCONF = "rc_hip.urls"
|
||||||
|
|
||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
{
|
{
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||||
'DIRS': [],
|
"DIRS": [],
|
||||||
'APP_DIRS': True,
|
"APP_DIRS": True,
|
||||||
'OPTIONS': {
|
"OPTIONS": {
|
||||||
'context_processors': [
|
"context_processors": [
|
||||||
'django.template.context_processors.debug',
|
"django.template.context_processors.debug",
|
||||||
'django.template.context_processors.request',
|
"django.template.context_processors.request",
|
||||||
'django.contrib.auth.context_processors.auth',
|
"django.contrib.auth.context_processors.auth",
|
||||||
'django.contrib.messages.context_processors.messages',
|
"django.contrib.messages.context_processors.messages",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
WSGI_APPLICATION = 'rc_hip.wsgi.application'
|
WSGI_APPLICATION = "rc_hip.wsgi.application"
|
||||||
|
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
|
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
|
||||||
|
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
"default": {
|
||||||
'ENGINE': 'django.db.backends.sqlite3',
|
"ENGINE": "django.db.backends.sqlite3",
|
||||||
'NAME': BASE_DIR / 'data' / 'db.sqlite3',
|
"NAME": BASE_DIR / "data" / "db.sqlite3",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -102,16 +105,16 @@ DATABASES = {
|
||||||
|
|
||||||
AUTH_PASSWORD_VALIDATORS = [
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
{
|
{
|
||||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -119,9 +122,9 @@ AUTH_PASSWORD_VALIDATORS = [
|
||||||
# Internationalization
|
# Internationalization
|
||||||
# https://docs.djangoproject.com/en/4.2/topics/i18n/
|
# https://docs.djangoproject.com/en/4.2/topics/i18n/
|
||||||
|
|
||||||
LANGUAGE_CODE = 'de'
|
LANGUAGE_CODE = "de"
|
||||||
|
|
||||||
TIME_ZONE = 'Europe/Berlin'
|
TIME_ZONE = "Europe/Berlin"
|
||||||
|
|
||||||
USE_I18N = True
|
USE_I18N = True
|
||||||
|
|
||||||
|
|
@ -131,22 +134,55 @@ USE_TZ = True
|
||||||
# Static files (CSS, JavaScript, Images)
|
# Static files (CSS, JavaScript, Images)
|
||||||
# https://docs.djangoproject.com/en/4.2/howto/static-files/
|
# https://docs.djangoproject.com/en/4.2/howto/static-files/
|
||||||
|
|
||||||
STATIC_URL = 'static/'
|
STATIC_URL = "static/"
|
||||||
MEDIA_URL = 'media/'
|
MEDIA_URL = "media/"
|
||||||
|
|
||||||
# Default primary key field type
|
# Default primary key field type
|
||||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
|
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
|
||||||
|
|
||||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
||||||
|
|
||||||
|
|
||||||
CSRF_TRUSTED_ORIGINS = [
|
CSRF_TRUSTED_ORIGINS = [
|
||||||
'https://repapp.rc-hip.de',
|
"http://127.0.0.1:8020",
|
||||||
'http://127.0.0.1:8020'
|
"http://localhost:8020",
|
||||||
|
"http://127.0.0.1:8000",
|
||||||
|
"http://localhost:8000",
|
||||||
|
"https://repapp.rc-hip.de",
|
||||||
|
"https://anmeldung.repaircafe-hilpoltstein.de",
|
||||||
]
|
]
|
||||||
|
|
||||||
EMAIL_HOST = os.getenv("DJANGO_EMAIL_HOST", '')
|
EMAIL_HOST = os.getenv("DJANGO_EMAIL_HOST", "")
|
||||||
EMAIL_PORT = (int)(os.getenv("DJANGO_EMAIL_PORT", '25'))
|
EMAIL_PORT = (int)(os.getenv("DJANGO_EMAIL_PORT", "25"))
|
||||||
EMAIL_HOST_USER = os.getenv("DJANGO_EMAIL_HOST_USER", None)
|
EMAIL_HOST_USER = os.getenv("DJANGO_EMAIL_HOST_USER", None)
|
||||||
EMAIL_HOST_PASSWORD = os.getenv("DJANGO_EMAIL_HOST_PASSWORD", 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')
|
EMAIL_USE_TLS = os.getenv("DJANGO_EMAIL_USE_TLS", "true") in ("true", "1", "t")
|
||||||
|
|
||||||
|
AUTH_USER_MODEL = "repapp.CustomUser"
|
||||||
|
|
||||||
|
AUTHENTICATION_BACKENDS = [
|
||||||
|
"repapp.backends.EmailBackend",
|
||||||
|
"django.contrib.auth.backends.ModelBackend",
|
||||||
|
# "mozilla_django_oidc.auth.OIDCAuthenticationBackend",
|
||||||
|
"repapp.backends.KeycloakOIDCAB",
|
||||||
|
"repapp.backends.OneTimeLoginBackend",
|
||||||
|
]
|
||||||
|
|
||||||
|
LOGIN_REDIRECT_URL = "/guest/profile/"
|
||||||
|
|
||||||
|
OIDC_RP_CLIENT_ID = "repapp"
|
||||||
|
OIDC_RP_CLIENT_SECRET = os.getenv("DJANGO_OIDC_RP_CLIENT_SECRET", None)
|
||||||
|
|
||||||
|
OIDC_RP_SIGN_ALGO = "RS256"
|
||||||
|
OIDC_OP_JWKS_ENDPOINT = "https://sso.makes-hacks-hip.de/realms/Makes-Hacks-Hip/protocol/openid-connect/certs"
|
||||||
|
|
||||||
|
OIDC_OP_AUTHORIZATION_ENDPOINT = "https://sso.makes-hacks-hip.de/realms/Makes-Hacks-Hip/protocol/openid-connect/auth"
|
||||||
|
OIDC_OP_TOKEN_ENDPOINT = "https://sso.makes-hacks-hip.de/realms/Makes-Hacks-Hip/protocol/openid-connect/token"
|
||||||
|
OIDC_OP_USER_ENDPOINT = "https://sso.makes-hacks-hip.de/realms/Makes-Hacks-Hip/protocol/openid-connect/userinfo"
|
||||||
|
|
||||||
|
if DEBUG:
|
||||||
|
LOGIN_REDIRECT_URL = "http://127.0.0.1:8000/"
|
||||||
|
LOGOUT_REDIRECT_URL = "http://127.0.0.1:8000/"
|
||||||
|
else:
|
||||||
|
LOGIN_REDIRECT_URL = "https://repapp.rc-hip.de/"
|
||||||
|
LOGOUT_REDIRECT_URL = "https://repapp.rc-hip.de/"
|
||||||
|
|
|
||||||
|
|
@ -22,5 +22,7 @@ from django.conf.urls.static import static
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", include("repapp.urls")),
|
path("", include("repapp.urls")),
|
||||||
|
path('oidc/', include('mozilla_django_oidc.urls')),
|
||||||
|
path('accounts/', include('django.contrib.auth.urls')),
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,107 @@
|
||||||
Admin interface configuration for RepApp.
|
Admin interface configuration for RepApp.
|
||||||
"""
|
"""
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from import_export.admin import ImportExportModelAdmin, ExportActionMixin
|
||||||
|
from import_export import resources
|
||||||
|
from .models import (Organisator, Cafe, Question, Device, CustomUser,
|
||||||
|
Reparateur, Appointment, Guest, Candidate, OneTimeLogin)
|
||||||
|
|
||||||
|
|
||||||
|
class GuestResource(resources.ModelResource):
|
||||||
|
"""
|
||||||
|
Resource wrapper for Guest model.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
"""
|
||||||
|
Meta data for resource model.
|
||||||
|
"""
|
||||||
|
model = Guest
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_display_name():
|
||||||
|
return "Gäste"
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceResource(resources.ModelResource):
|
||||||
|
"""
|
||||||
|
Resource wrapper for Device model.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
"""
|
||||||
|
Meta data for resource model.
|
||||||
|
"""
|
||||||
|
model = Device
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_display_name():
|
||||||
|
return "Geräte"
|
||||||
|
|
||||||
|
|
||||||
|
class QuestionResource(resources.ModelResource):
|
||||||
|
"""
|
||||||
|
Resource wrapper for Question model.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
"""
|
||||||
|
Meta data for resource model.
|
||||||
|
"""
|
||||||
|
model = Question
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_display_name():
|
||||||
|
return "Fragen"
|
||||||
|
|
||||||
|
|
||||||
|
class AppointmentResource(resources.ModelResource):
|
||||||
|
"""
|
||||||
|
Resource wrapper for Appointment model.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
"""
|
||||||
|
Meta data for resource model.
|
||||||
|
"""
|
||||||
|
model = Appointment
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_display_name():
|
||||||
|
return "Termine"
|
||||||
|
|
||||||
|
|
||||||
|
class CandidateResource(resources.ModelResource):
|
||||||
|
"""
|
||||||
|
Resource wrapper for Candidate model.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
"""
|
||||||
|
Meta data for resource model.
|
||||||
|
"""
|
||||||
|
model = Candidate
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_display_name():
|
||||||
|
return "Kandidaten"
|
||||||
|
|
||||||
|
|
||||||
|
class ExportAdmin(ImportExportModelAdmin, ExportActionMixin):
|
||||||
|
"""
|
||||||
|
Admin model for data export, supporting buttons and action.
|
||||||
|
"""
|
||||||
|
resource_classes = [
|
||||||
|
DeviceResource,
|
||||||
|
GuestResource,
|
||||||
|
QuestionResource,
|
||||||
|
AppointmentResource,
|
||||||
|
CandidateResource,
|
||||||
|
]
|
||||||
|
|
||||||
from .models import (Organisator, Cafe, Question, Device,
|
|
||||||
Reparateur, Appointment, Guest, Candidate)
|
|
||||||
|
|
||||||
admin.site.register(Organisator)
|
admin.site.register(Organisator)
|
||||||
admin.site.register(Cafe)
|
admin.site.register(Cafe)
|
||||||
admin.site.register(Question)
|
admin.site.register(Question, ExportAdmin)
|
||||||
admin.site.register(Guest)
|
admin.site.register(Guest, ExportAdmin)
|
||||||
admin.site.register(Device)
|
admin.site.register(Device, ExportAdmin)
|
||||||
admin.site.register(Reparateur)
|
admin.site.register(Reparateur)
|
||||||
admin.site.register(Appointment)
|
admin.site.register(Appointment, ExportAdmin)
|
||||||
admin.site.register(Candidate)
|
admin.site.register(Candidate, ExportAdmin)
|
||||||
|
admin.site.register(CustomUser)
|
||||||
|
admin.site.register(OneTimeLogin)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,12 @@
|
||||||
|
"""
|
||||||
|
RepApp application.
|
||||||
|
"""
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
class RepappConfig(AppConfig):
|
class RepappConfig(AppConfig):
|
||||||
|
"""
|
||||||
|
Django config for RepApp application.
|
||||||
|
"""
|
||||||
default_auto_field = 'django.db.models.BigAutoField'
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
name = 'repapp'
|
name = 'repapp'
|
||||||
|
|
|
||||||
65
rc_hip/repapp/backends.py
Normal file
65
rc_hip/repapp/backends.py
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
"""
|
||||||
|
Authentication backends for RepApp.
|
||||||
|
"""
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.contrib.auth.backends import ModelBackend
|
||||||
|
from mozilla_django_oidc.auth import OIDCAuthenticationBackend
|
||||||
|
from .utils import generate_username, create_repapp_user
|
||||||
|
from .models import OneTimeLogin
|
||||||
|
|
||||||
|
|
||||||
|
class OneTimeLoginBackend(ModelBackend):
|
||||||
|
"""
|
||||||
|
OneTimeLoginBackend implements a login using a onetime secret.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def authenticate(self, request, username=None, password=None, **kwargs):
|
||||||
|
print(f'authenticate {username}')
|
||||||
|
login = OneTimeLogin.objects.filter(secret=username).first()
|
||||||
|
if login:
|
||||||
|
return login.user
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class EmailBackend(ModelBackend):
|
||||||
|
"""
|
||||||
|
EmailBackend allows a login using email address as username.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def authenticate(self, request, username=None, password=None, **kwargs):
|
||||||
|
userModel = get_user_model()
|
||||||
|
try:
|
||||||
|
user = userModel.objects.get(email=username)
|
||||||
|
except userModel.DoesNotExist:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
if user.check_password(password):
|
||||||
|
return user
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class KeycloakOIDCAB(OIDCAuthenticationBackend):
|
||||||
|
"""
|
||||||
|
KeycloakOIDCAB allows a login using Open ID Connect (with the Repair-Café Keycloak Single Sign On server)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def create_user(self, claims):
|
||||||
|
user = super(KeycloakOIDCAB, self).create_user(claims)
|
||||||
|
|
||||||
|
user.username = claims.get(
|
||||||
|
'preferred_username', generate_username(user.email))
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
create_repapp_user(user)
|
||||||
|
|
||||||
|
return user
|
||||||
|
|
||||||
|
def update_user(self, user, claims):
|
||||||
|
user.username = claims.get(
|
||||||
|
'preferred_username', generate_username(user.email))
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
create_repapp_user(user)
|
||||||
|
|
||||||
|
return user
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
"""
|
||||||
|
Django forms for RepApp.
|
||||||
|
"""
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,12 @@
|
||||||
# Generated by Django 4.2 on 2023-04-16 17:49
|
# Generated by Django 4.2 on 2023-04-24 15:16
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
import django.contrib.auth.models
|
||||||
|
import django.contrib.auth.validators
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
import repapp.models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
@ -9,54 +14,170 @@ class Migration(migrations.Migration):
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
('auth', '0012_alter_user_first_name_max_length'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Cafe',
|
name='CustomUser',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('ort', models.CharField(max_length=200)),
|
('password', models.CharField(max_length=128, verbose_name='password')),
|
||||||
('datum', models.DateField()),
|
('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')),
|
||||||
|
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
|
||||||
|
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
|
||||||
|
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
|
||||||
|
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
|
||||||
|
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
|
||||||
|
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
|
||||||
|
('email', models.EmailField(max_length=254, unique=True, verbose_name='eMail Adresse')),
|
||||||
|
('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={
|
||||||
|
'verbose_name': 'Benutzer',
|
||||||
|
'verbose_name_plural': 'Benutzer',
|
||||||
|
},
|
||||||
|
managers=[
|
||||||
|
('objects', django.contrib.auth.models.UserManager()),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Gerät',
|
name='Cafe',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('identifier', models.CharField(max_length=200)),
|
('location', models.CharField(max_length=200, verbose_name='Ort')),
|
||||||
('besitzer', models.CharField(max_length=200)),
|
('address', models.CharField(max_length=200, verbose_name='Adresse')),
|
||||||
('mail', models.CharField(max_length=200)),
|
('event_date', models.DateField(verbose_name='Datum')),
|
||||||
('gerät', models.CharField(max_length=200)),
|
|
||||||
('fehler', models.TextField()),
|
|
||||||
('folgetermin', models.BooleanField()),
|
|
||||||
],
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Repair-Café',
|
||||||
|
'verbose_name_plural': 'Repair-Cafés',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Device',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('identifier', models.CharField(max_length=200, verbose_name='ID')),
|
||||||
|
('device', models.CharField(max_length=200, verbose_name='Art des Geräts')),
|
||||||
|
('manufacturer', models.CharField(max_length=200, verbose_name='Hersteller & Modell/Typ')),
|
||||||
|
('error', models.TextField(verbose_name='Fehlerbeschreibung')),
|
||||||
|
('follow_up', models.BooleanField(verbose_name='Folgetermin')),
|
||||||
|
('device_picture', models.FileField(null=True, upload_to=repapp.models.device_directory_path, verbose_name='Foto vom Gerät')),
|
||||||
|
('type_plate_picture', models.FileField(null=True, upload_to=repapp.models.device_directory_path, verbose_name='Foto vom Typenschild')),
|
||||||
|
('confirmed', models.BooleanField(verbose_name='Bestätigung gesendet?')),
|
||||||
|
('date', models.DateField(default=django.utils.timezone.now, verbose_name='Erstellungsdatum')),
|
||||||
|
('cafe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='repapp.cafe', verbose_name='Repair-Café')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Gerät',
|
||||||
|
'verbose_name_plural': 'Geräte',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Guest',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('identifier', models.CharField(max_length=200, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=200, verbose_name='Name')),
|
||||||
|
('phone', models.CharField(max_length=200, verbose_name='Telefonnummer')),
|
||||||
|
('residence', models.CharField(max_length=200, verbose_name='Wohnort')),
|
||||||
|
('mail', models.CharField(max_length=200, verbose_name='eMail')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Gast',
|
||||||
|
'verbose_name_plural': 'Gäste',
|
||||||
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Organisator',
|
name='Organisator',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('name', models.CharField(max_length=200)),
|
('name', models.CharField(max_length=200, verbose_name='Name')),
|
||||||
('mail', models.CharField(max_length=200)),
|
('mail', models.CharField(max_length=200, verbose_name='eMail')),
|
||||||
],
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Organisator',
|
||||||
|
'verbose_name_plural': 'Organisatoren',
|
||||||
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Reparateur',
|
name='Reparateur',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('name', models.CharField(max_length=200)),
|
('name', models.CharField(max_length=200, verbose_name='Name')),
|
||||||
('mail', models.CharField(max_length=200)),
|
('mail', models.CharField(max_length=200, verbose_name='eMail')),
|
||||||
],
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Reparateur',
|
||||||
|
'verbose_name_plural': 'Reparateure',
|
||||||
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Termin',
|
name='Question',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('uhrzeit', models.TimeField()),
|
('question', models.TextField(verbose_name='Frage')),
|
||||||
('bestätigt', models.BooleanField()),
|
('answer', models.TextField(verbose_name='Antwort')),
|
||||||
('cafeid', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='repapp.cafe')),
|
('date', models.DateField(default=django.utils.timezone.now, verbose_name='Erstellungsdatum')),
|
||||||
('gerätid', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='repapp.gerät')),
|
('device', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='repapp.device', verbose_name='Gerät')),
|
||||||
('reparateurid', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='repapp.reparateur')),
|
('organisator', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='repapp.organisator', verbose_name='Organisator')),
|
||||||
|
('reparateur', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='repapp.reparateur', verbose_name='Reparateur')),
|
||||||
],
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Frage',
|
||||||
|
'verbose_name_plural': 'Fragen',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
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='Erstellungsdatum')),
|
||||||
|
('login_used', models.BooleanField(default=False, verbose_name='Login benutzt?')),
|
||||||
|
('login_date', models.DateField(null=True, verbose_name='Login Datum')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Benutzer')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Einmal-Login',
|
||||||
|
'verbose_name_plural': 'Einmal-Login',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='device',
|
||||||
|
name='guest',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='repapp.guest', verbose_name='Gast'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Candidate',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('confirmed', models.BooleanField(verbose_name='bestätigt')),
|
||||||
|
('cafe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='repapp.cafe', verbose_name='Repair-Café')),
|
||||||
|
('device', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='repapp.device', verbose_name='Gerät')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Kandidat',
|
||||||
|
'verbose_name_plural': 'Kandidaten',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Appointment',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('time', models.TimeField(verbose_name='Zeit')),
|
||||||
|
('confirmed', models.BooleanField(verbose_name='bestätigt')),
|
||||||
|
('cafe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='repapp.cafe', verbose_name='Repair-Café')),
|
||||||
|
('device', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='repapp.device', verbose_name='Gerät')),
|
||||||
|
('reparateur', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='repapp.reparateur', verbose_name='Reparateur')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Termin',
|
||||||
|
'verbose_name_plural': 'Termine',
|
||||||
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
# Generated by Django 4.2 on 2023-04-16 17:53
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('repapp', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='termin',
|
|
||||||
name='cafeid',
|
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='repapp.cafe'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='termin',
|
|
||||||
name='gerätid',
|
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='repapp.gerät'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='termin',
|
|
||||||
name='reparateurid',
|
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='repapp.reparateur'),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Frage',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('frage', models.TextField()),
|
|
||||||
('antwort', models.TextField()),
|
|
||||||
('datum', models.DateField()),
|
|
||||||
('gerätid', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='repapp.gerät')),
|
|
||||||
('organisatorid', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='repapp.organisator')),
|
|
||||||
('reparateurid', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='repapp.reparateur')),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
||||||
29
rc_hip/repapp/migrations/0002_message.py
Normal file
29
rc_hip/repapp/migrations/0002_message.py
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
# Generated by Django 4.2 on 2023-04-24 17:25
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('repapp', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Message',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('message', models.TextField(verbose_name='Nachricht')),
|
||||||
|
('answer', models.TextField(verbose_name='Antwort')),
|
||||||
|
('date', models.DateField(default=django.utils.timezone.now, verbose_name='Erstellungsdatum')),
|
||||||
|
('guest', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='repapp.guest', verbose_name='Gast')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Nachricht',
|
||||||
|
'verbose_name_plural': 'Nachrichten',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
# Generated by Django 4.2 on 2023-04-16 18:12
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('repapp', '0002_alter_termin_cafeid_alter_termin_gerätid_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='frage',
|
|
||||||
name='organisatorid',
|
|
||||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='repapp.organisator'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='frage',
|
|
||||||
name='reparateurid',
|
|
||||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='repapp.reparateur'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='termin',
|
|
||||||
name='gerätid',
|
|
||||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='repapp.gerät'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,153 +0,0 @@
|
||||||
# Generated by Django 4.2 on 2023-04-16 18:44
|
|
||||||
|
|
||||||
import datetime
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('repapp', '0003_alter_frage_organisatorid_alter_frage_reparateurid_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Appointment',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('time', models.TimeField(verbose_name='Zeit')),
|
|
||||||
('confirmed', models.BooleanField(verbose_name='bestätigt')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Termin',
|
|
||||||
'verbose_name_plural': 'Termine',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Device',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('identifier', models.CharField(max_length=200, verbose_name='ID')),
|
|
||||||
('owner', models.CharField(max_length=200, verbose_name='Besitzer')),
|
|
||||||
('mail', models.CharField(max_length=200, verbose_name='eMail')),
|
|
||||||
('device', models.CharField(max_length=200, verbose_name='Gerät')),
|
|
||||||
('error', models.TextField(verbose_name='Fehler')),
|
|
||||||
('follow_up', models.BooleanField(verbose_name='Folgetermin')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Gerät',
|
|
||||||
'verbose_name_plural': 'Geräte',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Question',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('question', models.TextField(verbose_name='Frage')),
|
|
||||||
('answer', models.TextField(verbose_name='Antwort')),
|
|
||||||
('date', models.DateField(verbose_name='Erstellungsdatum')),
|
|
||||||
('deviceid', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='repapp.device', verbose_name='Gerät')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Frage',
|
|
||||||
'verbose_name_plural': 'Fragen',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='termin',
|
|
||||||
name='cafeid',
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='termin',
|
|
||||||
name='gerätid',
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='termin',
|
|
||||||
name='reparateurid',
|
|
||||||
),
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='cafe',
|
|
||||||
options={'verbose_name': 'Repair-Café', 'verbose_name_plural': 'Repair-Cafés'},
|
|
||||||
),
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='organisator',
|
|
||||||
options={'verbose_name': 'Organisator', 'verbose_name_plural': 'Organisatoren'},
|
|
||||||
),
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='reparateur',
|
|
||||||
options={'verbose_name': 'Reparateur', 'verbose_name_plural': 'Reparateure'},
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='cafe',
|
|
||||||
name='datum',
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='cafe',
|
|
||||||
name='ort',
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='cafe',
|
|
||||||
name='event_date',
|
|
||||||
field=models.DateField(default=datetime.date(2023, 4, 16), verbose_name='Datum'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='cafe',
|
|
||||||
name='location',
|
|
||||||
field=models.CharField(default='', max_length=200, verbose_name='Ort'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='organisator',
|
|
||||||
name='mail',
|
|
||||||
field=models.CharField(max_length=200, verbose_name='eMail'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='organisator',
|
|
||||||
name='name',
|
|
||||||
field=models.CharField(max_length=200, verbose_name='Name'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='reparateur',
|
|
||||||
name='mail',
|
|
||||||
field=models.CharField(max_length=200, verbose_name='eMail'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='reparateur',
|
|
||||||
name='name',
|
|
||||||
field=models.CharField(max_length=200, verbose_name='Name'),
|
|
||||||
),
|
|
||||||
migrations.DeleteModel(
|
|
||||||
name='Frage',
|
|
||||||
),
|
|
||||||
migrations.DeleteModel(
|
|
||||||
name='Gerät',
|
|
||||||
),
|
|
||||||
migrations.DeleteModel(
|
|
||||||
name='Termin',
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='question',
|
|
||||||
name='organisatorid',
|
|
||||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='repapp.organisator', verbose_name='Organisator'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='question',
|
|
||||||
name='reparateurid',
|
|
||||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='repapp.reparateur', verbose_name='Reparateur'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='appointment',
|
|
||||||
name='cafeid',
|
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='repapp.cafe', verbose_name='Repair-Café'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='appointment',
|
|
||||||
name='deviceid',
|
|
||||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='repapp.device', verbose_name='Gerät'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='appointment',
|
|
||||||
name='reparateurid',
|
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='repapp.reparateur', verbose_name='Reparateur'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
# Generated by Django 4.2 on 2023-04-16 18:45
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('repapp', '0004_appointment_device_question_remove_termin_cafeid_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='cafe',
|
|
||||||
name='event_date',
|
|
||||||
field=models.DateField(verbose_name='Datum'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='cafe',
|
|
||||||
name='location',
|
|
||||||
field=models.CharField(max_length=200, verbose_name='Ort'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
# Generated by Django 4.2 on 2023-04-16 19:14
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('repapp', '0005_alter_cafe_event_date_alter_cafe_location'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='device',
|
|
||||||
name='phone',
|
|
||||||
field=models.CharField(default='', max_length=200, verbose_name='Telefonnummer'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='device',
|
|
||||||
name='residence',
|
|
||||||
field=models.CharField(default='', max_length=200, verbose_name='Wohnort'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
# Generated by Django 4.2 on 2023-04-16 19:15
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('repapp', '0006_device_phone_device_residence'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='device',
|
|
||||||
name='phone',
|
|
||||||
field=models.CharField(max_length=200, verbose_name='Telefonnummer'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='device',
|
|
||||||
name='residence',
|
|
||||||
field=models.CharField(max_length=200, verbose_name='Wohnort'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
# Generated by Django 4.2 on 2023-04-17 05:26
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('repapp', '0007_alter_device_phone_alter_device_residence'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Guest',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('identifier', models.CharField(max_length=200, verbose_name='ID')),
|
|
||||||
('name', models.CharField(max_length=200, verbose_name='Name')),
|
|
||||||
('phone', models.CharField(max_length=200, verbose_name='Telefonnummer')),
|
|
||||||
('residence', models.CharField(max_length=200, verbose_name='Wohnort')),
|
|
||||||
('mail', models.CharField(max_length=200, verbose_name='eMail')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Gast',
|
|
||||||
'verbose_name_plural': 'Gäste',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='device',
|
|
||||||
name='mail',
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='device',
|
|
||||||
name='owner',
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='device',
|
|
||||||
name='phone',
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='device',
|
|
||||||
name='residence',
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='device',
|
|
||||||
name='guestid',
|
|
||||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='repapp.guest', verbose_name='Gast'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
# Generated by Django 4.2 on 2023-04-17 07:40
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('repapp', '0008_guest_remove_device_mail_remove_device_owner_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RenameField(
|
|
||||||
model_name='appointment',
|
|
||||||
old_name='cafeid',
|
|
||||||
new_name='cafe_id',
|
|
||||||
),
|
|
||||||
migrations.RenameField(
|
|
||||||
model_name='appointment',
|
|
||||||
old_name='deviceid',
|
|
||||||
new_name='device_id',
|
|
||||||
),
|
|
||||||
migrations.RenameField(
|
|
||||||
model_name='appointment',
|
|
||||||
old_name='reparateurid',
|
|
||||||
new_name='reparateur_id',
|
|
||||||
),
|
|
||||||
migrations.RenameField(
|
|
||||||
model_name='device',
|
|
||||||
old_name='guestid',
|
|
||||||
new_name='guest_id',
|
|
||||||
),
|
|
||||||
migrations.RenameField(
|
|
||||||
model_name='question',
|
|
||||||
old_name='deviceid',
|
|
||||||
new_name='device_id',
|
|
||||||
),
|
|
||||||
migrations.RenameField(
|
|
||||||
model_name='question',
|
|
||||||
old_name='organisatorid',
|
|
||||||
new_name='organisator_id',
|
|
||||||
),
|
|
||||||
migrations.RenameField(
|
|
||||||
model_name='question',
|
|
||||||
old_name='reparateurid',
|
|
||||||
new_name='reparateur_id',
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
# Generated by Django 4.2 on 2023-04-17 07:57
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('repapp', '0009_rename_cafeid_appointment_cafe_id_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Candidate',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('confirmed', models.BooleanField(verbose_name='bestätigt')),
|
|
||||||
('cafe_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='repapp.cafe', verbose_name='Repair-Café')),
|
|
||||||
('device_id', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='repapp.device', verbose_name='Gerät')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Kandidat',
|
|
||||||
'verbose_name_plural': 'Kandidaten',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,80 +0,0 @@
|
||||||
# Generated by Django 4.2 on 2023-04-20 18:43
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('repapp', '0010_candidate'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RenameField(
|
|
||||||
model_name='appointment',
|
|
||||||
old_name='cafe_id',
|
|
||||||
new_name='cafe',
|
|
||||||
),
|
|
||||||
migrations.RenameField(
|
|
||||||
model_name='appointment',
|
|
||||||
old_name='device_id',
|
|
||||||
new_name='device',
|
|
||||||
),
|
|
||||||
migrations.RenameField(
|
|
||||||
model_name='appointment',
|
|
||||||
old_name='reparateur_id',
|
|
||||||
new_name='reparateur',
|
|
||||||
),
|
|
||||||
migrations.RenameField(
|
|
||||||
model_name='candidate',
|
|
||||||
old_name='cafe_id',
|
|
||||||
new_name='cafe',
|
|
||||||
),
|
|
||||||
migrations.RenameField(
|
|
||||||
model_name='candidate',
|
|
||||||
old_name='device_id',
|
|
||||||
new_name='device',
|
|
||||||
),
|
|
||||||
migrations.RenameField(
|
|
||||||
model_name='device',
|
|
||||||
old_name='guest_id',
|
|
||||||
new_name='guest',
|
|
||||||
),
|
|
||||||
migrations.RenameField(
|
|
||||||
model_name='question',
|
|
||||||
old_name='device_id',
|
|
||||||
new_name='device',
|
|
||||||
),
|
|
||||||
migrations.RenameField(
|
|
||||||
model_name='question',
|
|
||||||
old_name='organisator_id',
|
|
||||||
new_name='organisator',
|
|
||||||
),
|
|
||||||
migrations.RenameField(
|
|
||||||
model_name='question',
|
|
||||||
old_name='reparateur_id',
|
|
||||||
new_name='reparateur',
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='device',
|
|
||||||
name='cafe',
|
|
||||||
field=models.ForeignKey(default=2, on_delete=django.db.models.deletion.CASCADE, to='repapp.cafe', verbose_name='Repair-Café'),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='device',
|
|
||||||
name='confirmed',
|
|
||||||
field=models.BooleanField(default=False, verbose_name='Bestätigt'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='device',
|
|
||||||
name='secret',
|
|
||||||
field=models.CharField(default='TEST12345', max_length=200, verbose_name='Bestätigungscode'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='guest',
|
|
||||||
name='confirmed',
|
|
||||||
field=models.BooleanField(default=False, verbose_name='Bestätigt'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
# Generated by Django 4.2 on 2023-04-21 15:21
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
import repapp.models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('repapp', '0011_rename_cafe_id_appointment_cafe_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='device',
|
|
||||||
name='secret',
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='guest',
|
|
||||||
name='confirmed',
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='cafe',
|
|
||||||
name='address',
|
|
||||||
field=models.CharField(default='', max_length=200, verbose_name='Ort'),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='device',
|
|
||||||
name='device_picture',
|
|
||||||
field=models.FileField(null=True, upload_to=repapp.models.device_directory_path, verbose_name='Foto vom Gerät'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='device',
|
|
||||||
name='manufacturer',
|
|
||||||
field=models.CharField(default='', max_length=200, verbose_name='Hersteller'),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='device',
|
|
||||||
name='type_plate_picture',
|
|
||||||
field=models.FileField(null=True, upload_to=repapp.models.device_directory_path, verbose_name='Foto vom Typenschild'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='device',
|
|
||||||
name='confirmed',
|
|
||||||
field=models.BooleanField(verbose_name='Bestätigung gesendet?'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
# Generated by Django 4.2 on 2023-04-23 10:09
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('repapp', '0012_remove_device_secret_remove_guest_confirmed_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='cafe',
|
|
||||||
name='address',
|
|
||||||
field=models.CharField(max_length=200, verbose_name='Adresse'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='device',
|
|
||||||
name='device',
|
|
||||||
field=models.CharField(max_length=200, verbose_name='Art des Geräts'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='device',
|
|
||||||
name='error',
|
|
||||||
field=models.TextField(verbose_name='Fehlerbeschreibung'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='device',
|
|
||||||
name='manufacturer',
|
|
||||||
field=models.CharField(max_length=200, verbose_name='Hersteller & Modell/Typ'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
"""
|
"""
|
||||||
This module implements the models of RepApp.
|
This module implements the models of RepApp.
|
||||||
"""
|
"""
|
||||||
|
import django.utils.timezone
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.contrib.auth.models import AbstractUser
|
||||||
|
|
||||||
|
|
||||||
class Cafe(models.Model):
|
class Cafe(models.Model):
|
||||||
|
|
@ -72,6 +74,9 @@ class Guest(models.Model):
|
||||||
|
|
||||||
|
|
||||||
def device_directory_path(instance, filename):
|
def device_directory_path(instance, filename):
|
||||||
|
"""
|
||||||
|
device_directory_path generates a device-specific storage path for file uploads.
|
||||||
|
"""
|
||||||
return f'device_{instance.identifier}/{filename}'
|
return f'device_{instance.identifier}/{filename}'
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -94,6 +99,8 @@ class Device(models.Model):
|
||||||
cafe = models.ForeignKey(
|
cafe = models.ForeignKey(
|
||||||
Cafe, on_delete=models.CASCADE, null=False, verbose_name=_("Repair-Café"))
|
Cafe, on_delete=models.CASCADE, null=False, verbose_name=_("Repair-Café"))
|
||||||
confirmed = models.BooleanField(verbose_name=_("Bestätigung gesendet?"))
|
confirmed = models.BooleanField(verbose_name=_("Bestätigung gesendet?"))
|
||||||
|
date = models.DateField(verbose_name=_(
|
||||||
|
"Erstellungsdatum"), default=django.utils.timezone.now)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('Gerät')
|
verbose_name = _('Gerät')
|
||||||
|
|
@ -131,7 +138,8 @@ class Question(models.Model):
|
||||||
"""
|
"""
|
||||||
question = models.TextField(verbose_name=_("Frage"))
|
question = models.TextField(verbose_name=_("Frage"))
|
||||||
answer = models.TextField(verbose_name=_("Antwort"))
|
answer = models.TextField(verbose_name=_("Antwort"))
|
||||||
date = models.DateField(verbose_name=_("Erstellungsdatum"))
|
date = models.DateField(verbose_name=_(
|
||||||
|
"Erstellungsdatum"), default=django.utils.timezone.now)
|
||||||
organisator = models.ForeignKey(
|
organisator = models.ForeignKey(
|
||||||
Organisator, on_delete=models.CASCADE, null=True, verbose_name=_("Organisator"))
|
Organisator, on_delete=models.CASCADE, null=True, verbose_name=_("Organisator"))
|
||||||
reparateur = models.ForeignKey(
|
reparateur = models.ForeignKey(
|
||||||
|
|
@ -164,3 +172,57 @@ class Candidate(models.Model):
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'Kandidat {self.cafe.event_date} für Gerät {self.device.device}'
|
return f'Kandidat {self.cafe.event_date} für Gerät {self.device.device}'
|
||||||
|
|
||||||
|
|
||||||
|
class CustomUser(AbstractUser):
|
||||||
|
email = models.EmailField(unique=True, verbose_name=_("eMail Adresse"))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('Benutzer')
|
||||||
|
verbose_name_plural = _('Benutzer')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'{self.email}'
|
||||||
|
|
||||||
|
|
||||||
|
class OneTimeLogin(models.Model):
|
||||||
|
"""
|
||||||
|
A OneTimeLogin 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)
|
||||||
|
user = models.ForeignKey(
|
||||||
|
CustomUser, on_delete=models.CASCADE, null=False, verbose_name=_("Benutzer"))
|
||||||
|
url = models.CharField(max_length=200, verbose_name=_("URL"))
|
||||||
|
created = models.DateField(verbose_name=_(
|
||||||
|
"Erstellungsdatum"), default=django.utils.timezone.now)
|
||||||
|
login_used = models.BooleanField(
|
||||||
|
verbose_name=_("Login benutzt?"), default=False)
|
||||||
|
login_date = models.DateField(verbose_name=_(
|
||||||
|
"Login Datum"), null=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('Einmal-Login')
|
||||||
|
verbose_name_plural = _('Einmal-Login')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'Einmal-Login {self.user.email}'
|
||||||
|
|
||||||
|
|
||||||
|
class Message(models.Model):
|
||||||
|
"""
|
||||||
|
A Message is a request from a guest.
|
||||||
|
"""
|
||||||
|
message = models.TextField(verbose_name=_("Nachricht"))
|
||||||
|
answer = models.TextField(verbose_name=_("Antwort"))
|
||||||
|
date = models.DateField(verbose_name=_(
|
||||||
|
"Erstellungsdatum"), default=django.utils.timezone.now)
|
||||||
|
guest = models.ForeignKey(
|
||||||
|
Guest, on_delete=models.CASCADE, null=True, verbose_name=_("Gast"))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('Nachricht')
|
||||||
|
verbose_name_plural = _('Nachrichten')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'Nachricht vom {self.date} von {self.guest.mail}'
|
||||||
|
|
|
||||||
13
rc_hip/repapp/templates/registration/login.html
Normal file
13
rc_hip/repapp/templates/registration/login.html
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
{% extends "repapp/base.html" %}
|
||||||
|
{% load crispy_forms_tags %}
|
||||||
|
{% block title %}
|
||||||
|
- Gast Login
|
||||||
|
{% endblock title %}
|
||||||
|
{% block content %}
|
||||||
|
<h2>Gast Login</h2>
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form|crispy }}
|
||||||
|
<button type="submit" class="btn btn-primary">Anmelden</button>
|
||||||
|
</form>
|
||||||
|
{% endblock content %}
|
||||||
|
|
@ -34,5 +34,14 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<footer>
|
||||||
|
{% if user.is_authenticated %}
|
||||||
|
<a href="{% url 'logout' %}">Gast Logout</a>
|
||||||
|
<a href="{% url 'member_login' %}">Mitarbeiter Logout</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{% url 'login' %}">Gast Login</a>
|
||||||
|
<a href="{% url 'member_login' %}">Mitarbeiter Login</a>
|
||||||
|
{% endif %}
|
||||||
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
28
rc_hip/repapp/templates/repapp/guest_profile.html
Normal file
28
rc_hip/repapp/templates/repapp/guest_profile.html
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
{% extends "repapp/base.html" %}
|
||||||
|
{% block title %}
|
||||||
|
- Gast Daten
|
||||||
|
{% endblock title %}
|
||||||
|
{% block content %}
|
||||||
|
<h1 class="mt-2">Ihre Daten</h1>
|
||||||
|
<hr class="mt-0 mb-4">
|
||||||
|
<table class="table">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">eMail Adresse</th>
|
||||||
|
<td>{{ guest.mail }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Name</th>
|
||||||
|
<td>{{ guest.name }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Wohnort</th>
|
||||||
|
<td>{{ guest.residence }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Telefonnummer</th>
|
||||||
|
<td>{{ guest.phone }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endblock content %}
|
||||||
19
rc_hip/repapp/templates/repapp/mail/mail_new_guest_html.html
Normal file
19
rc_hip/repapp/templates/repapp/mail/mail_new_guest_html.html
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="description" content="Repair-Café Hilpoltstein - Benutzerkonto" />
|
||||||
|
<meta name="keywords" content="Benutzerkonto" />
|
||||||
|
<title>Repair-Café Hilpoltstein - Benutzerkonto</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>Guten Tag,</p>
|
||||||
|
<p>
|
||||||
|
wir haben ein Benutzerkonto für Sie erstellt. Klicken sie <a href="{{ link }}">hier</a> um ihre Daten einzusehen und zu ändern.
|
||||||
|
</p>
|
||||||
|
<p>Ihr Anmeldedaten sind:</p>
|
||||||
|
<p>Benutzername: {{ username }}</p>
|
||||||
|
<p>Passwort: {{ password }}</p>
|
||||||
|
<p>Herzliche Grüße sendet dir das Organisationsteam vom Repair-Café Hilpoltstein!</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
Repair-Café Hilpoltstein - Benutzerkonto
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
Guten Tag,
|
||||||
|
wir haben ein Benutzerkonto für Sie erstellt. Sie können sich im Web-Broswer unter {{ link }} um ihre Daten einzusehen und zu ändern.
|
||||||
|
Ihr Anmeldedaten sind:
|
||||||
|
Benutzername: {{ username }}
|
||||||
|
Passwort: {{ password }}
|
||||||
|
Herzliche Grüße sendet dir das Organisationsteam vom Repair-Café Hilpoltstein!
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="description" content="Einmal-Anmeldelink" />
|
||||||
|
<meta name="keywords" content="Login" />
|
||||||
|
<title>Einmal-Anmeldelink</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>Guten Tag,</p>
|
||||||
|
<p>
|
||||||
|
wir haben für Sie einen Einmal-Ammeldelink erstellt. Wenn sie <a href="{{ link }}">hier</a> klicken können sie auf ihre Informationen zugreifen.
|
||||||
|
</p>
|
||||||
|
<p>Herzliche Grüße sendet dir das Organisationsteam vom Repair-Café Hilpoltstein!</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
Repair-Café Hilpoltstein - Einmal-Ammeldelink
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
Guten Tag,
|
||||||
|
wir haben für Sie einen Einmal-Ammeldelink erstellt. Wenn sie {{ link }} im Web-Broswer öffnen können sie auf ihre Informationen zugreifen.
|
||||||
|
Herzliche Grüße sendet dir das Organisationsteam vom Repair-Café Hilpoltstein!
|
||||||
|
|
@ -1,5 +1,11 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="de">
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="description" content="Repair-Café Hilpoltstein Geräteanmeldung" />
|
||||||
|
<meta name="keywords" content="Geräteanmeldung" />
|
||||||
|
<title>Repair-Café Hilpoltstein Geräteanmeldung</title>
|
||||||
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<p>Guten Tag,</p>
|
<p>Guten Tag,</p>
|
||||||
<p>
|
<p>
|
||||||
|
|
@ -8,6 +14,9 @@
|
||||||
<p>
|
<p>
|
||||||
Vielen Dank für die Anmeldung des Geräts {{ device.device }} - {{ device.manufacturer }} zur Reparatur im Repair-Café am {{ cafe.event_date|date }}.
|
Vielen Dank für die Anmeldung des Geräts {{ device.device }} - {{ device.manufacturer }} zur Reparatur im Repair-Café am {{ cafe.event_date|date }}.
|
||||||
</p>
|
</p>
|
||||||
|
<p>
|
||||||
|
Sie können die Daten zu ihrer Geräteanmeldung <a href="{{ login_url }}">hier</a> einsehen.
|
||||||
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Die Organisation unserer Repair-Cafés beginnt i.a. einige Tage vor dem jeweiligen Termin. Dann erstellen wir anhand der angemeldeten Geräte und der Spezialgebiete der anwesenden Reparateure einen Zeitplan.
|
Die Organisation unserer Repair-Cafés beginnt i.a. einige Tage vor dem jeweiligen Termin. Dann erstellen wir anhand der angemeldeten Geräte und der Spezialgebiete der anwesenden Reparateure einen Zeitplan.
|
||||||
</p>
|
</p>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
Guten Tag,
|
Guten Tag,
|
||||||
dies ist eine automatische Nachricht, die du erhältst, nachdem du unser Anmeldeformular abgesendet hast und deine Angaben in unserer Datenbank gespeichert wurden.
|
dies ist eine automatische Nachricht, die du erhältst, nachdem du unser Anmeldeformular abgesendet hast und deine Angaben in unserer Datenbank gespeichert wurden.
|
||||||
Vielen Dank für die Anmeldung des Geräts {{ device.device }} - {{ device.manufacturer }} zur Reparatur im Repair-Café am {{ cafe.event_date|date }}.
|
Vielen Dank für die Anmeldung des Geräts {{ device.device }} - {{ device.manufacturer }} zur Reparatur im Repair-Café am {{ cafe.event_date|date }}.
|
||||||
|
Sie können die Daten zu ihrer Geräteanmeldung <a href="{{ login_url }}">hier</a> einsehen.
|
||||||
Die Organisation unserer Repair-Cafés beginnt i.a. einige Tage vor dem jeweiligen Termin. Dann erstellen wir anhand der angemeldeten Geräte und der Spezialgebiete der anwesenden Reparateure einen Zeitplan.
|
Die Organisation unserer Repair-Cafés beginnt i.a. einige Tage vor dem jeweiligen Termin. Dann erstellen wir anhand der angemeldeten Geräte und der Spezialgebiete der anwesenden Reparateure einen Zeitplan.
|
||||||
Sobald dieser Zeitplan fertig gestellt ist, erhältst du von einem Mitglied unseres Organisationsteams eine Mail. Diese beinhaltet im Normalfall den Reparaturtermin inkl. der Uhrzeit, für die die Reparatur vorgesehen ist. In Ausnahmefällen (z.B. wenn kein Spezialist für die Gerätegruppe anwesend sein wird oder überbucht ist) ist es aber auch möglich, dass wir deinen gewünschten Termin absagen und dir einen Alternativtermin vorschlagen müssen.
|
Sobald dieser Zeitplan fertig gestellt ist, erhältst du von einem Mitglied unseres Organisationsteams eine Mail. Diese beinhaltet im Normalfall den Reparaturtermin inkl. der Uhrzeit, für die die Reparatur vorgesehen ist. In Ausnahmefällen (z.B. wenn kein Spezialist für die Gerätegruppe anwesend sein wird oder überbucht ist) ist es aber auch möglich, dass wir deinen gewünschten Termin absagen und dir einen Alternativtermin vorschlagen müssen.
|
||||||
Falls du in der Zwischenzeit Rückfragen hast oder deinen Wunschtermin doch nicht wahrnehmen kannst, antworte bitte einfach auf diese Nachricht.
|
Falls du in der Zwischenzeit Rückfragen hast oder deinen Wunschtermin doch nicht wahrnehmen kannst, antworte bitte einfach auf diese Nachricht.
|
||||||
|
|
|
||||||
26
rc_hip/repapp/templates/repapp/member_login.html
Normal file
26
rc_hip/repapp/templates/repapp/member_login.html
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
{% extends "repapp/base.html" %}
|
||||||
|
{% block title %}
|
||||||
|
- Mitarbeiter Login
|
||||||
|
{% endblock title %}
|
||||||
|
{% block content %}
|
||||||
|
<h1 class="mt-2">
|
||||||
|
Mitarbeiter
|
||||||
|
{% if user.is_authenticated %}
|
||||||
|
Logout
|
||||||
|
{% else %}
|
||||||
|
Login
|
||||||
|
{% endif %}
|
||||||
|
</h1>
|
||||||
|
<hr class="mt-0 mb-4">
|
||||||
|
<div>
|
||||||
|
{% if user.is_authenticated %}
|
||||||
|
<p>Angemeldeter Benutzer: {{ user.email }}</p>
|
||||||
|
<form action="{% url 'oidc_logout' %}" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<button type="submit" class="btn btn-primary">Abmelden</button>
|
||||||
|
</form>
|
||||||
|
{% else %}
|
||||||
|
<a href="{% url 'oidc_authentication_init' %}">Anmelden mit Makes-Hacks-Hip</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endblock content %}
|
||||||
|
|
@ -1,3 +1 @@
|
||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
# Create your tests here.
|
# Create your tests here.
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
Urls of RepApp.
|
Urls of RepApp.
|
||||||
"""
|
"""
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
|
@ -21,4 +20,12 @@ urlpatterns = [
|
||||||
views.register_device_final, name="register_device_final"),
|
views.register_device_final, name="register_device_final"),
|
||||||
path("device/<str:device_identifier>/",
|
path("device/<str:device_identifier>/",
|
||||||
views.device_view, name="view_device"),
|
views.device_view, name="view_device"),
|
||||||
|
path("guest/profile/",
|
||||||
|
views.profile, name="guest_profile"),
|
||||||
|
path("member/login/",
|
||||||
|
views.member_login, name="member_login"),
|
||||||
|
path("cron", views.cron, name="cron"),
|
||||||
|
path("process_mails", views.process_mails, name="process_mails"),
|
||||||
|
path("onetimelogin/<str:secret>/",
|
||||||
|
views.one_time_login, name="one_time_login"),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
171
rc_hip/repapp/utils.py
Normal file
171
rc_hip/repapp/utils.py
Normal file
|
|
@ -0,0 +1,171 @@
|
||||||
|
import os
|
||||||
|
import unicodedata
|
||||||
|
import datetime
|
||||||
|
import random
|
||||||
|
from hashlib import sha256
|
||||||
|
from django.template.loader import render_to_string
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.core.mail import send_mail
|
||||||
|
from django.contrib import messages
|
||||||
|
from repapp.models import Organisator, Reparateur, OneTimeLogin
|
||||||
|
|
||||||
|
|
||||||
|
def create_one_time_login(user, url):
|
||||||
|
"""
|
||||||
|
create_one_time_login create a one time login object for user logins.
|
||||||
|
"""
|
||||||
|
secret = sha256(
|
||||||
|
f'{user.email}{url}{datetime.datetime.now()}{random.randint(0,9999999)}'.encode(
|
||||||
|
'utf-8')
|
||||||
|
).hexdigest()
|
||||||
|
otl = OneTimeLogin(
|
||||||
|
secret=secret,
|
||||||
|
user=user,
|
||||||
|
url=url,
|
||||||
|
)
|
||||||
|
otl.save()
|
||||||
|
return otl
|
||||||
|
|
||||||
|
|
||||||
|
def send_one_time_login_mail(one_time_login, request):
|
||||||
|
url = request.build_absolute_uri(
|
||||||
|
f'/onetimelogin/{one_time_login.secret}/')
|
||||||
|
subject = render_to_string(
|
||||||
|
'repapp/mail/mail_one_time_login_subject.html').replace('\n', '')
|
||||||
|
text = render_to_string('repapp/mail/mail_one_time_login_text.html', {
|
||||||
|
'link': url,
|
||||||
|
})
|
||||||
|
html = render_to_string('repapp/mail/mail_one_time_login_html.html', {
|
||||||
|
'link': url,
|
||||||
|
})
|
||||||
|
|
||||||
|
ok = send_mail(
|
||||||
|
subject=subject,
|
||||||
|
message=text,
|
||||||
|
from_email=os.getenv("DJANGO_SENDER_ADDRESS", ""),
|
||||||
|
recipient_list=[f"{one_time_login.user.email}"],
|
||||||
|
fail_silently=True,
|
||||||
|
html_message=html
|
||||||
|
)
|
||||||
|
|
||||||
|
if ok > 0:
|
||||||
|
messages.add_message(request, messages.INFO,
|
||||||
|
'Sie haben einen neuen Login per eMail erhalten.')
|
||||||
|
|
||||||
|
|
||||||
|
def create_repapp_user(user):
|
||||||
|
organisator = Organisator.objects.filter(mail=user.email).first()
|
||||||
|
if not organisator:
|
||||||
|
reparateur = Reparateur.objects.filter(mail=user.email).first()
|
||||||
|
if not reparateur:
|
||||||
|
reparateur = Reparateur(
|
||||||
|
name=user.username,
|
||||||
|
mail=user.email,
|
||||||
|
)
|
||||||
|
reparateur.save()
|
||||||
|
else:
|
||||||
|
reparateur.name = user.username
|
||||||
|
reparateur.save()
|
||||||
|
else:
|
||||||
|
organisator.name = user.username
|
||||||
|
organisator.save()
|
||||||
|
|
||||||
|
|
||||||
|
def generate_username(email):
|
||||||
|
# Using Python 3 and Django 1.11+, usernames can contain alphanumeric
|
||||||
|
# (ascii and unicode), _, @, +, . and - characters. So we normalize
|
||||||
|
# it and slice at 150 characters.
|
||||||
|
return unicodedata.normalize('NFKC', email)[:150]
|
||||||
|
|
||||||
|
|
||||||
|
def send_confirmation_mails(device, guest, cafe, request):
|
||||||
|
organizers = []
|
||||||
|
for organizer in Organisator.objects.all():
|
||||||
|
organizers.append(organizer.mail)
|
||||||
|
|
||||||
|
text = render_to_string('repapp/mail/notice_new_device.html', {
|
||||||
|
'guest': device.guest,
|
||||||
|
'device': device,
|
||||||
|
'cafe': device.cafe,
|
||||||
|
})
|
||||||
|
|
||||||
|
send_mail(
|
||||||
|
subject=f"Neues Gerät { device.device }",
|
||||||
|
message=text,
|
||||||
|
from_email=os.getenv("DJANGO_SENDER_ADDRESS", ""),
|
||||||
|
recipient_list=organizers,
|
||||||
|
fail_silently=True
|
||||||
|
)
|
||||||
|
|
||||||
|
path = reverse('view_device', kwargs={
|
||||||
|
'device_identifier': device.identifier})
|
||||||
|
url = request.build_absolute_uri(path)
|
||||||
|
otl = create_one_time_login(guest.user, url)
|
||||||
|
login_url = request.build_absolute_uri(f'/onetimelogin/{otl.secret}/')
|
||||||
|
|
||||||
|
subject = render_to_string('repapp/mail/mail_register_device_subject.html', {
|
||||||
|
'cafe': cafe,
|
||||||
|
}).replace('\n', '')
|
||||||
|
text = render_to_string('repapp/mail/mail_register_device_text.html', {
|
||||||
|
'guest': guest,
|
||||||
|
'device': device,
|
||||||
|
'cafe': cafe,
|
||||||
|
'login_url': login_url,
|
||||||
|
})
|
||||||
|
html = render_to_string('repapp/mail/mail_register_device_html.html', {
|
||||||
|
'guest': guest,
|
||||||
|
'device': device,
|
||||||
|
'cafe': cafe,
|
||||||
|
'login_url': login_url,
|
||||||
|
})
|
||||||
|
|
||||||
|
mail_count = send_mail(
|
||||||
|
subject=subject,
|
||||||
|
message=text,
|
||||||
|
from_email=os.getenv("DJANGO_SENDER_ADDRESS", ""),
|
||||||
|
recipient_list=[f"{guest.mail}"],
|
||||||
|
fail_silently=True,
|
||||||
|
html_message=html
|
||||||
|
)
|
||||||
|
|
||||||
|
if mail_count > 0:
|
||||||
|
device.confirmed = True
|
||||||
|
device.save()
|
||||||
|
else:
|
||||||
|
messages.add_message(request, messages.ERROR,
|
||||||
|
'Fehler beim senden der Bestätigungs-eMail!')
|
||||||
|
|
||||||
|
|
||||||
|
def send_guest_account_mail(guest, password, request):
|
||||||
|
url = request.build_absolute_uri('/guest/profile/')
|
||||||
|
subject = render_to_string(
|
||||||
|
'repapp/mail/mail_new_guest_subject.html').replace('\n', '')
|
||||||
|
text = render_to_string('repapp/mail/mail_new_guest_text.html', {
|
||||||
|
'link': url,
|
||||||
|
'username': guest.user.email,
|
||||||
|
'password': password,
|
||||||
|
})
|
||||||
|
html = render_to_string('repapp/mail/mail_new_guest_html.html', {
|
||||||
|
'link': url,
|
||||||
|
'username': guest.user.email,
|
||||||
|
'password': password,
|
||||||
|
})
|
||||||
|
|
||||||
|
ok = send_mail(
|
||||||
|
subject=subject,
|
||||||
|
message=text,
|
||||||
|
from_email=os.getenv("DJANGO_SENDER_ADDRESS", ""),
|
||||||
|
recipient_list=[f"{guest.user.email}"],
|
||||||
|
fail_silently=True,
|
||||||
|
html_message=html
|
||||||
|
)
|
||||||
|
|
||||||
|
if ok > 0:
|
||||||
|
messages.add_message(request, messages.INFO,
|
||||||
|
'Sie haben ihre Benutzerdaten per eMail erhalten.')
|
||||||
|
|
||||||
|
|
||||||
|
def is_member(user):
|
||||||
|
organisator = Organisator.objects.filter(mail=user.email).first()
|
||||||
|
reparateur = Reparateur.objects.filter(mail=user.email).first()
|
||||||
|
return organisator or reparateur
|
||||||
|
|
@ -2,17 +2,24 @@
|
||||||
Views of RepApp.
|
Views of RepApp.
|
||||||
"""
|
"""
|
||||||
import datetime
|
import datetime
|
||||||
import os
|
import random
|
||||||
|
import string
|
||||||
|
import time
|
||||||
from hashlib import sha256
|
from hashlib import sha256
|
||||||
from django.views import generic
|
from django.views import generic
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.shortcuts import get_object_or_404, render
|
from django.shortcuts import get_object_or_404, render
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.core.mail import send_mail
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.template.loader import render_to_string
|
from django.contrib.auth import authenticate, login
|
||||||
from .models import Cafe, Device, Guest, Organisator
|
from django.utils.timezone import now
|
||||||
|
from repapp.models import CustomUser
|
||||||
|
from .models import Cafe, Device, Guest, OneTimeLogin
|
||||||
from .forms import RegisterDevice, RegisterGuest
|
from .forms import RegisterDevice, RegisterGuest
|
||||||
|
from .utils import (send_confirmation_mails, create_one_time_login,
|
||||||
|
send_one_time_login_mail, send_guest_account_mail, is_member)
|
||||||
|
|
||||||
|
|
||||||
class IndexView(generic.ListView):
|
class IndexView(generic.ListView):
|
||||||
|
|
@ -27,59 +34,10 @@ class IndexView(generic.ListView):
|
||||||
return Cafe.objects.filter(event_date__gte=datetime.date.today())
|
return Cafe.objects.filter(event_date__gte=datetime.date.today())
|
||||||
|
|
||||||
|
|
||||||
def send_confirmation_mails(device, guest, cafe, request):
|
|
||||||
organizers = []
|
|
||||||
for organizer in Organisator.objects.all():
|
|
||||||
organizers.append(organizer.mail)
|
|
||||||
|
|
||||||
text = render_to_string('repapp/mail/notice_new_device.html', {
|
|
||||||
'guest': device.guest,
|
|
||||||
'device': device,
|
|
||||||
'cafe': device.cafe,
|
|
||||||
})
|
|
||||||
|
|
||||||
send_mail(
|
|
||||||
subject=f"Neues Gerät { device.device }",
|
|
||||||
message=text,
|
|
||||||
from_email=os.getenv("DJANGO_SENDER_ADDRESS", ""),
|
|
||||||
recipient_list=organizers,
|
|
||||||
fail_silently=True
|
|
||||||
)
|
|
||||||
|
|
||||||
subject = render_to_string('repapp/mail/mail_register_device_subject.html', {
|
|
||||||
'guest': guest,
|
|
||||||
'device': device,
|
|
||||||
'cafe': cafe,
|
|
||||||
}).replace('\n', '')
|
|
||||||
text = render_to_string('repapp/mail/mail_register_device_text.html', {
|
|
||||||
'guest': guest,
|
|
||||||
'device': device,
|
|
||||||
'cafe': cafe,
|
|
||||||
})
|
|
||||||
html = render_to_string('repapp/mail/mail_register_device_html.html', {
|
|
||||||
'guest': guest,
|
|
||||||
'device': device,
|
|
||||||
'cafe': cafe,
|
|
||||||
})
|
|
||||||
|
|
||||||
ok = send_mail(
|
|
||||||
subject=subject,
|
|
||||||
message=text,
|
|
||||||
from_email=os.getenv("DJANGO_SENDER_ADDRESS", ""),
|
|
||||||
recipient_list=[f"{guest.mail}"],
|
|
||||||
fail_silently=True,
|
|
||||||
html_message=html
|
|
||||||
)
|
|
||||||
|
|
||||||
if ok > 0:
|
|
||||||
device.confirmed = True
|
|
||||||
device.save()
|
|
||||||
else:
|
|
||||||
messages.add_message(request, messages.ERROR,
|
|
||||||
'Fehler beim senden der Bestätigungs-eMail!')
|
|
||||||
|
|
||||||
|
|
||||||
class RegisterDeviceFormView(generic.edit.FormView):
|
class RegisterDeviceFormView(generic.edit.FormView):
|
||||||
|
"""
|
||||||
|
RegisterDeviceFormView shows the form for registering new devices.
|
||||||
|
"""
|
||||||
template_name = "repapp/register_device.html"
|
template_name = "repapp/register_device.html"
|
||||||
form_class = RegisterDevice
|
form_class = RegisterDevice
|
||||||
|
|
||||||
|
|
@ -156,6 +114,7 @@ class RegisterGuestFormView(generic.edit.FormView):
|
||||||
device = get_object_or_404(Device, identifier=device_identifier)
|
device = get_object_or_404(Device, identifier=device_identifier)
|
||||||
|
|
||||||
name = form.cleaned_data['name']
|
name = form.cleaned_data['name']
|
||||||
|
mail = self.kwargs['mail']
|
||||||
residence = form.cleaned_data['residence']
|
residence = form.cleaned_data['residence']
|
||||||
identifier = sha256(
|
identifier = sha256(
|
||||||
f'{name}{residence}{datetime.datetime.now()}'.encode('utf-8')
|
f'{name}{residence}{datetime.datetime.now()}'.encode('utf-8')
|
||||||
|
|
@ -165,13 +124,24 @@ class RegisterGuestFormView(generic.edit.FormView):
|
||||||
name=name,
|
name=name,
|
||||||
phone=form.cleaned_data['phone'],
|
phone=form.cleaned_data['phone'],
|
||||||
residence=residence,
|
residence=residence,
|
||||||
mail=self.kwargs['mail'],
|
mail=mail,
|
||||||
)
|
)
|
||||||
guest.save()
|
guest.save()
|
||||||
|
|
||||||
device.guest = guest
|
device.guest = guest
|
||||||
device.save()
|
device.save()
|
||||||
|
|
||||||
|
if not CustomUser.objects.filter(email=mail).exists():
|
||||||
|
password = ''.join(random.choice(string.ascii_letters)
|
||||||
|
for i in range(10))
|
||||||
|
user = CustomUser.objects.create_user(
|
||||||
|
username=name,
|
||||||
|
email=mail,
|
||||||
|
password=password)
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
send_guest_account_mail(guest, password, self.request)
|
||||||
|
|
||||||
send_confirmation_mails(device, guest, cafe, self.request)
|
send_confirmation_mails(device, guest, cafe, self.request)
|
||||||
|
|
||||||
return HttpResponseRedirect(
|
return HttpResponseRedirect(
|
||||||
|
|
@ -208,11 +178,80 @@ def register_device_final(request, cafe, device_identifier):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
def device_view(request, device_identifier):
|
def device_view(request, device_identifier):
|
||||||
|
user = request.user
|
||||||
|
if not user:
|
||||||
|
raise PermissionDenied()
|
||||||
|
|
||||||
device = get_object_or_404(Device, identifier=device_identifier)
|
device = get_object_or_404(Device, identifier=device_identifier)
|
||||||
|
if not device.guest:
|
||||||
|
raise PermissionDenied()
|
||||||
|
|
||||||
|
if not (is_member(user) or device.guest.mail == user.email):
|
||||||
|
raise PermissionDenied()
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
"repapp/device_view.html",
|
"repapp/device_view.html",
|
||||||
{"device": device},
|
{"device": device},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def profile(request):
|
||||||
|
user = request.user
|
||||||
|
if not user:
|
||||||
|
raise PermissionDenied()
|
||||||
|
|
||||||
|
guest = get_object_or_404(Guest, mail=user.email)
|
||||||
|
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
"repapp/guest_profile.html",
|
||||||
|
{
|
||||||
|
'guest': guest
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def member_login(request):
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
"repapp/member_login.html"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def cron(request):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def process_mails(request):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def one_time_login(request, secret):
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
otl = get_object_or_404(OneTimeLogin, secret=secret)
|
||||||
|
|
||||||
|
if otl.login_used:
|
||||||
|
messages.add_message(request, messages.ERROR,
|
||||||
|
'Der Einmal-Login wurde schon verwendet und ist nichtmehr gültig.')
|
||||||
|
new_otl = create_one_time_login(otl.user, otl.url)
|
||||||
|
send_one_time_login_mail(new_otl, request)
|
||||||
|
return HttpResponseRedirect(reverse_lazy('index'))
|
||||||
|
else:
|
||||||
|
otl.login_used = True
|
||||||
|
otl.login_date = now()
|
||||||
|
otl.save()
|
||||||
|
|
||||||
|
user = authenticate(request, username=secret, password=None)
|
||||||
|
|
||||||
|
if user is not None:
|
||||||
|
login(request, user)
|
||||||
|
messages.add_message(request, messages.INFO, 'Login erfolgreich!')
|
||||||
|
return HttpResponseRedirect(otl.url)
|
||||||
|
else:
|
||||||
|
messages.add_message(request, messages.ERROR, 'Login fehlgeschlagen.')
|
||||||
|
return HttpResponseRedirect(reverse_lazy('index'))
|
||||||
|
|
|
||||||
|
|
@ -6,3 +6,6 @@ pylint-django==2.5.3
|
||||||
pylint-plugin-utils==0.7
|
pylint-plugin-utils==0.7
|
||||||
django-crispy-forms==2.0
|
django-crispy-forms==2.0
|
||||||
crispy-bootstrap4==2022.1
|
crispy-bootstrap4==2022.1
|
||||||
|
mozilla-django-oidc==3.0.0
|
||||||
|
djlint==1.23.3
|
||||||
|
django-import-export==3.2.0
|
||||||
|
|
|
||||||
|
|
@ -2,3 +2,5 @@ Django==4.2
|
||||||
gunicorn==20.1.0
|
gunicorn==20.1.0
|
||||||
django-crispy-forms==2.0
|
django-crispy-forms==2.0
|
||||||
crispy-bootstrap4==2022.1
|
crispy-bootstrap4==2022.1
|
||||||
|
mozilla-django-oidc==3.0.0
|
||||||
|
django-import-export==3.2.0
|
||||||
|
|
|
||||||
Reference in a new issue