diff --git a/cspell.json b/cspell.json index f3042a7..2e63262 100644 --- a/cspell.json +++ b/cspell.json @@ -9,7 +9,15 @@ "reparateur", "organisator", "Reparateurs", - "repapp" + "repapp", + "oidc", + "anmeldung", + "repaircafe", + "hilpoltstein", + "KeycloakOIDCAB", + "Keycloak", + "tabindex", + "onetimelogin" ], "ignoreWords": [], "import": [] diff --git a/docs/index.md b/docs/index.md index 06eda45..3f03358 100644 --- a/docs/index.md +++ b/docs/index.md @@ -20,38 +20,44 @@ Ein Organisator ist Mitglied des Repair-Cafés das die Termine mit den Gästen o ### Gast -- [x] 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) -- [ ] Als Gast möchte ich eine Terminbenachrichtigung oder eine Absage bekommen. (A3) +- [x] M1: Als Gast möchte ich einen Termin für eine Reparatur vereinbaren. (A1) +- [x] M1: Als Gast möchte ich eine Bestätigung für eine Reparaturanfrage bekommen. (A2) +- [ ] 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 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 meine Daten ansehen können. (A21) - [ ] 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) +- [ ] M1: Als Gast möchte ausschließlich per eMail mit dem System interagieren können. (A30) ### Reparateur -- [ ] 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 die angemeldeten Geräte ansehen können. (A7) +- [ ] 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 Rückfragen zum Gerät stellen können. (A10) -- [ ] 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) -- [ ] 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 Rückfragen zum Gerät stellen können. (A10) +- [ ] M1: 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 mir ein Gerät zugeordnet wird. (A12) +- [ ] M1: 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é abmelden können. (A14) - [ ] Als Reparateur möchte ich benachrichtigt werden wenn ein Gast eine mir zugeordnete Geräteanmeldung löscht. (A25) ### Organisator -- [x] 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) -- [ ] 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) -- [ ] 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) -- [ ] Als Organisator möchte ich einen Reparateur zu einem Termin zuordnen können. (A20) +- [x] M1: Als Organisator möchte ich benachrichtigt werden wenn ein Gast ein Gerät anmeldet. (A25) +- [ ] M1: Als Organisator möchte ich ein Repair-Café anlegen können. (A15) +- [ ] M1: Als Organisator möchte ich die Geräte einsehen können. (A16) +- [ ] M1: Als Organisator möchte ich eine Rückfrage zu einem Geräte stellen können. (A17) +- [ ] M1: Als Organisator möchte ich ein Geräte ablehnen können. (A18) +- [ ] M1: Als Organisator möchte ich ein Geräte zu einem Termin zuordnen können. (A19) +- [ ] 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 alle Daten exportieren können. (A31) +- [ ] Als Organisator möchte ich Geräte auf ein späteres Repair-Café verschieben können. (A32) ## 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 (S5) bestätigt mir die Anmeldung meines Gerätes. - 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 diff --git a/rc_hip/rc_hip/settings.py b/rc_hip/rc_hip/settings.py index b5c9c16..61bbc36 100644 --- a/rc_hip/rc_hip/settings.py +++ b/rc_hip/rc_hip/settings.py @@ -1,7 +1,7 @@ """ 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 https://docs.djangoproject.com/en/4.2/topics/settings/ @@ -15,7 +15,7 @@ from pathlib import Path 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 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/ # 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 = os.environ.get( - "DJANGO_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.getenv( + "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! -DEBUG = os.getenv("DJANGO_DEBUG", 'true').lower() in ('true', '1', 't') +# SECURITY WARNING: don"t run with debug turned on in production! +DEBUG = os.getenv("DJANGO_DEBUG", "true").lower() in ("true", "1", "t") ALLOWED_HOSTS = [ - '127.0.0.1', - 'localhost', - 'repapp.rc-hip.de' + "127.0.0.1", + "localhost", + "repapp.rc-hip.de", + "anmeldung.repaircafe-hilpoltstein.de", ] # Application definition INSTALLED_APPS = [ - 'repapp.apps.RepappConfig', - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'crispy_forms', - 'crispy_bootstrap4', + "repapp.apps.RepappConfig", + "django.contrib.admin", + "django.contrib.auth", + "mozilla_django_oidc", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "crispy_forms", + "crispy_bootstrap4", + "import_export", ] -CRISPY_ALLOWED_TEMPLATE_PACKS = 'bootstrap4' -CRISPY_TEMPLATE_PACK = 'bootstrap4' +CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap4" +CRISPY_TEMPLATE_PACK = "bootstrap4" MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", ] -ROOT_URLCONF = 'rc_hip.urls' +ROOT_URLCONF = "rc_hip.urls" TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", ], }, }, ] -WSGI_APPLICATION = 'rc_hip.wsgi.application' +WSGI_APPLICATION = "rc_hip.wsgi.application" # Database # https://docs.djangoproject.com/en/4.2/ref/settings/#databases DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': BASE_DIR / 'data' / 'db.sqlite3', + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": BASE_DIR / "data" / "db.sqlite3", } } @@ -102,16 +105,16 @@ DATABASES = { 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 # 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 @@ -131,22 +134,55 @@ USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/4.2/howto/static-files/ -STATIC_URL = 'static/' -MEDIA_URL = 'media/' +STATIC_URL = "static/" +MEDIA_URL = "media/" # Default primary key field type # 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 = [ - '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_PORT = (int)(os.getenv("DJANGO_EMAIL_PORT", '25')) +EMAIL_HOST = os.getenv("DJANGO_EMAIL_HOST", "") +EMAIL_PORT = (int)(os.getenv("DJANGO_EMAIL_PORT", "25")) EMAIL_HOST_USER = os.getenv("DJANGO_EMAIL_HOST_USER", None) EMAIL_HOST_PASSWORD = os.getenv("DJANGO_EMAIL_HOST_PASSWORD", None) -EMAIL_USE_TLS = os.getenv("DJANGO_EMAIL_USE_TLS", 'true') in ('true', '1', 't') +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/" diff --git a/rc_hip/rc_hip/urls.py b/rc_hip/rc_hip/urls.py index 2469ed7..9cd3412 100644 --- a/rc_hip/rc_hip/urls.py +++ b/rc_hip/rc_hip/urls.py @@ -22,5 +22,7 @@ from django.conf.urls.static import static urlpatterns = [ path("", include("repapp.urls")), + path('oidc/', include('mozilla_django_oidc.urls')), + path('accounts/', include('django.contrib.auth.urls')), path('admin/', admin.site.urls), ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/rc_hip/repapp/admin.py b/rc_hip/repapp/admin.py index 0bccd55..6f24f09 100644 --- a/rc_hip/repapp/admin.py +++ b/rc_hip/repapp/admin.py @@ -2,15 +2,107 @@ Admin interface configuration for RepApp. """ 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(Cafe) -admin.site.register(Question) -admin.site.register(Guest) -admin.site.register(Device) +admin.site.register(Question, ExportAdmin) +admin.site.register(Guest, ExportAdmin) +admin.site.register(Device, ExportAdmin) admin.site.register(Reparateur) -admin.site.register(Appointment) -admin.site.register(Candidate) +admin.site.register(Appointment, ExportAdmin) +admin.site.register(Candidate, ExportAdmin) +admin.site.register(CustomUser) +admin.site.register(OneTimeLogin) diff --git a/rc_hip/repapp/apps.py b/rc_hip/repapp/apps.py index 67bb157..3d8d8f3 100644 --- a/rc_hip/repapp/apps.py +++ b/rc_hip/repapp/apps.py @@ -1,6 +1,12 @@ +""" +RepApp application. +""" from django.apps import AppConfig class RepappConfig(AppConfig): + """ + Django config for RepApp application. + """ default_auto_field = 'django.db.models.BigAutoField' name = 'repapp' diff --git a/rc_hip/repapp/backends.py b/rc_hip/repapp/backends.py new file mode 100644 index 0000000..126dcf8 --- /dev/null +++ b/rc_hip/repapp/backends.py @@ -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 diff --git a/rc_hip/repapp/forms.py b/rc_hip/repapp/forms.py index 376b9e3..9193c73 100644 --- a/rc_hip/repapp/forms.py +++ b/rc_hip/repapp/forms.py @@ -1,3 +1,6 @@ +""" +Django forms for RepApp. +""" from django import forms from django.core.exceptions import ValidationError diff --git a/rc_hip/repapp/migrations/0001_initial.py b/rc_hip/repapp/migrations/0001_initial.py index c5d90c0..6d14d46 100644 --- a/rc_hip/repapp/migrations/0001_initial.py +++ b/rc_hip/repapp/migrations/0001_initial.py @@ -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 import django.db.models.deletion +import django.utils.timezone +import repapp.models class Migration(migrations.Migration): @@ -9,54 +14,170 @@ class Migration(migrations.Migration): initial = True dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), ] operations = [ migrations.CreateModel( - name='Cafe', + name='CustomUser', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('ort', models.CharField(max_length=200)), - ('datum', models.DateField()), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('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( - name='Gerät', + name='Cafe', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('identifier', models.CharField(max_length=200)), - ('besitzer', models.CharField(max_length=200)), - ('mail', models.CharField(max_length=200)), - ('gerät', models.CharField(max_length=200)), - ('fehler', models.TextField()), - ('folgetermin', models.BooleanField()), + ('location', models.CharField(max_length=200, verbose_name='Ort')), + ('address', models.CharField(max_length=200, verbose_name='Adresse')), + ('event_date', models.DateField(verbose_name='Datum')), ], + 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( name='Organisator', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=200)), - ('mail', models.CharField(max_length=200)), + ('name', models.CharField(max_length=200, verbose_name='Name')), + ('mail', models.CharField(max_length=200, verbose_name='eMail')), ], + options={ + 'verbose_name': 'Organisator', + 'verbose_name_plural': 'Organisatoren', + }, ), migrations.CreateModel( name='Reparateur', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=200)), - ('mail', models.CharField(max_length=200)), + ('name', models.CharField(max_length=200, verbose_name='Name')), + ('mail', models.CharField(max_length=200, verbose_name='eMail')), ], + options={ + 'verbose_name': 'Reparateur', + 'verbose_name_plural': 'Reparateure', + }, ), migrations.CreateModel( - name='Termin', + name='Question', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('uhrzeit', models.TimeField()), - ('bestätigt', models.BooleanField()), - ('cafeid', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='repapp.cafe')), - ('gerätid', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='repapp.gerät')), - ('reparateurid', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='repapp.reparateur')), + ('question', models.TextField(verbose_name='Frage')), + ('answer', models.TextField(verbose_name='Antwort')), + ('date', models.DateField(default=django.utils.timezone.now, verbose_name='Erstellungsdatum')), + ('device', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='repapp.device', verbose_name='Gerät')), + ('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', + }, ), ] diff --git a/rc_hip/repapp/migrations/0002_alter_termin_cafeid_alter_termin_gerätid_and_more.py b/rc_hip/repapp/migrations/0002_alter_termin_cafeid_alter_termin_gerätid_and_more.py deleted file mode 100644 index ef73fc2..0000000 --- a/rc_hip/repapp/migrations/0002_alter_termin_cafeid_alter_termin_gerätid_and_more.py +++ /dev/null @@ -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')), - ], - ), - ] diff --git a/rc_hip/repapp/migrations/0002_message.py b/rc_hip/repapp/migrations/0002_message.py new file mode 100644 index 0000000..040eb04 --- /dev/null +++ b/rc_hip/repapp/migrations/0002_message.py @@ -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', + }, + ), + ] diff --git a/rc_hip/repapp/migrations/0003_alter_frage_organisatorid_alter_frage_reparateurid_and_more.py b/rc_hip/repapp/migrations/0003_alter_frage_organisatorid_alter_frage_reparateurid_and_more.py deleted file mode 100644 index 97fdd74..0000000 --- a/rc_hip/repapp/migrations/0003_alter_frage_organisatorid_alter_frage_reparateurid_and_more.py +++ /dev/null @@ -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'), - ), - ] diff --git a/rc_hip/repapp/migrations/0004_appointment_device_question_remove_termin_cafeid_and_more.py b/rc_hip/repapp/migrations/0004_appointment_device_question_remove_termin_cafeid_and_more.py deleted file mode 100644 index c3a132a..0000000 --- a/rc_hip/repapp/migrations/0004_appointment_device_question_remove_termin_cafeid_and_more.py +++ /dev/null @@ -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'), - ), - ] diff --git a/rc_hip/repapp/migrations/0005_alter_cafe_event_date_alter_cafe_location.py b/rc_hip/repapp/migrations/0005_alter_cafe_event_date_alter_cafe_location.py deleted file mode 100644 index 1deaaa8..0000000 --- a/rc_hip/repapp/migrations/0005_alter_cafe_event_date_alter_cafe_location.py +++ /dev/null @@ -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'), - ), - ] diff --git a/rc_hip/repapp/migrations/0006_device_phone_device_residence.py b/rc_hip/repapp/migrations/0006_device_phone_device_residence.py deleted file mode 100644 index 39d70b1..0000000 --- a/rc_hip/repapp/migrations/0006_device_phone_device_residence.py +++ /dev/null @@ -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'), - ), - ] diff --git a/rc_hip/repapp/migrations/0007_alter_device_phone_alter_device_residence.py b/rc_hip/repapp/migrations/0007_alter_device_phone_alter_device_residence.py deleted file mode 100644 index 21d8d27..0000000 --- a/rc_hip/repapp/migrations/0007_alter_device_phone_alter_device_residence.py +++ /dev/null @@ -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'), - ), - ] diff --git a/rc_hip/repapp/migrations/0008_guest_remove_device_mail_remove_device_owner_and_more.py b/rc_hip/repapp/migrations/0008_guest_remove_device_mail_remove_device_owner_and_more.py deleted file mode 100644 index 77829c8..0000000 --- a/rc_hip/repapp/migrations/0008_guest_remove_device_mail_remove_device_owner_and_more.py +++ /dev/null @@ -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'), - ), - ] diff --git a/rc_hip/repapp/migrations/0009_rename_cafeid_appointment_cafe_id_and_more.py b/rc_hip/repapp/migrations/0009_rename_cafeid_appointment_cafe_id_and_more.py deleted file mode 100644 index bb3ed52..0000000 --- a/rc_hip/repapp/migrations/0009_rename_cafeid_appointment_cafe_id_and_more.py +++ /dev/null @@ -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', - ), - ] diff --git a/rc_hip/repapp/migrations/0010_candidate.py b/rc_hip/repapp/migrations/0010_candidate.py deleted file mode 100644 index 78ef11d..0000000 --- a/rc_hip/repapp/migrations/0010_candidate.py +++ /dev/null @@ -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', - }, - ), - ] diff --git a/rc_hip/repapp/migrations/0011_rename_cafe_id_appointment_cafe_and_more.py b/rc_hip/repapp/migrations/0011_rename_cafe_id_appointment_cafe_and_more.py deleted file mode 100644 index 2ecef41..0000000 --- a/rc_hip/repapp/migrations/0011_rename_cafe_id_appointment_cafe_and_more.py +++ /dev/null @@ -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'), - ), - ] diff --git a/rc_hip/repapp/migrations/0012_remove_device_secret_remove_guest_confirmed_and_more.py b/rc_hip/repapp/migrations/0012_remove_device_secret_remove_guest_confirmed_and_more.py deleted file mode 100644 index cd4999e..0000000 --- a/rc_hip/repapp/migrations/0012_remove_device_secret_remove_guest_confirmed_and_more.py +++ /dev/null @@ -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?'), - ), - ] diff --git a/rc_hip/repapp/migrations/0013_alter_cafe_address_alter_device_device_and_more.py b/rc_hip/repapp/migrations/0013_alter_cafe_address_alter_device_device_and_more.py deleted file mode 100644 index 0155f4c..0000000 --- a/rc_hip/repapp/migrations/0013_alter_cafe_address_alter_device_device_and_more.py +++ /dev/null @@ -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'), - ), - ] diff --git a/rc_hip/repapp/models.py b/rc_hip/repapp/models.py index 0836015..833c08b 100644 --- a/rc_hip/repapp/models.py +++ b/rc_hip/repapp/models.py @@ -1,8 +1,10 @@ """ This module implements the models of RepApp. """ +import django.utils.timezone from django.db import models from django.utils.translation import gettext_lazy as _ +from django.contrib.auth.models import AbstractUser class Cafe(models.Model): @@ -72,6 +74,9 @@ class Guest(models.Model): def device_directory_path(instance, filename): + """ + device_directory_path generates a device-specific storage path for file uploads. + """ return f'device_{instance.identifier}/{filename}' @@ -94,6 +99,8 @@ class Device(models.Model): cafe = models.ForeignKey( Cafe, on_delete=models.CASCADE, null=False, verbose_name=_("Repair-Café")) confirmed = models.BooleanField(verbose_name=_("Bestätigung gesendet?")) + date = models.DateField(verbose_name=_( + "Erstellungsdatum"), default=django.utils.timezone.now) class Meta: verbose_name = _('Gerät') @@ -131,7 +138,8 @@ class Question(models.Model): """ question = models.TextField(verbose_name=_("Frage")) 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, on_delete=models.CASCADE, null=True, verbose_name=_("Organisator")) reparateur = models.ForeignKey( @@ -164,3 +172,57 @@ class Candidate(models.Model): def __str__(self): 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}' diff --git a/rc_hip/repapp/templates/registration/login.html b/rc_hip/repapp/templates/registration/login.html new file mode 100644 index 0000000..c292974 --- /dev/null +++ b/rc_hip/repapp/templates/registration/login.html @@ -0,0 +1,13 @@ +{% extends "repapp/base.html" %} +{% load crispy_forms_tags %} +{% block title %} + - Gast Login +{% endblock title %} +{% block content %} +