diff --git a/backend/config/urls.py b/backend/config/urls.py index a6b96bc..12c21f2 100644 --- a/backend/config/urls.py +++ b/backend/config/urls.py @@ -1,12 +1,44 @@ from django.conf import settings from django.conf.urls.static import static from django.contrib import admin +from django.contrib.auth import views as auth_views from django.urls import include, path +from workflows.forms import AppAuthenticationForm, AppPasswordResetForm, AppSetPasswordForm + urlpatterns = [ path('admin/', admin.site.urls), path('i18n/', include('django.conf.urls.i18n')), - path('accounts/', include('django.contrib.auth.urls')), + path( + 'accounts/login/', + auth_views.LoginView.as_view(template_name='workflows/auth/login.html', authentication_form=AppAuthenticationForm), + name='login', + ), + path( + 'accounts/logout/', + auth_views.LogoutView.as_view(), + name='logout', + ), + path( + 'accounts/password_reset/', + auth_views.PasswordResetView.as_view(template_name='workflows/auth/password_reset_form.html', form_class=AppPasswordResetForm), + name='password_reset', + ), + path( + 'accounts/password_reset/done/', + auth_views.PasswordResetDoneView.as_view(template_name='workflows/auth/password_reset_done.html'), + name='password_reset_done', + ), + path( + 'accounts/reset///', + auth_views.PasswordResetConfirmView.as_view(template_name='workflows/auth/password_reset_confirm.html', form_class=AppSetPasswordForm), + name='password_reset_confirm', + ), + path( + 'accounts/reset/done/', + auth_views.PasswordResetCompleteView.as_view(template_name='workflows/auth/password_reset_complete.html'), + name='password_reset_complete', + ), path('', include('workflows.urls')), ] diff --git a/backend/locale/en/LC_MESSAGES/django.mo b/backend/locale/en/LC_MESSAGES/django.mo index f50c5ec..f69d1a6 100644 Binary files a/backend/locale/en/LC_MESSAGES/django.mo and b/backend/locale/en/LC_MESSAGES/django.mo differ diff --git a/backend/locale/en/LC_MESSAGES/django.po b/backend/locale/en/LC_MESSAGES/django.po index 04ef874..af32b22 100644 --- a/backend/locale/en/LC_MESSAGES/django.po +++ b/backend/locale/en/LC_MESSAGES/django.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: tubco-portal\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-03-26 09:06+0000\n" +"POT-Creation-Date: 2026-03-26 09:25+0000\n" "PO-Revision-Date: 2026-03-24 00:00+0000\n" "Language: en\n" "MIME-Version: 1.0\n" @@ -55,52 +55,54 @@ msgstr "" msgid "Remote Backup in Nextcloud konnte nicht gelöscht werden." msgstr "" -#: workflows/forms.py:102 -msgid "Vorname" -msgstr "" - -#: workflows/forms.py:103 -msgid "Nachname" -msgstr "" - -#: workflows/forms.py:104 workflows/templates/workflows/user_management.html:81 +#: workflows/forms.py:103 workflows/forms.py:128 +#: workflows/templates/workflows/user_management.html:72 msgid "Benutzername" msgstr "" -#: workflows/forms.py:105 +#: workflows/forms.py:104 +msgid "Passwort" +msgstr "Password" + +#: workflows/forms.py:108 workflows/forms.py:129 #, fuzzy #| msgid "E-Mail" msgid "E-Mail-Adresse" msgstr "Email" -#: workflows/forms.py:106 workflows/templates/workflows/user_management.html:83 -#: workflows/templates/workflows/user_management.html:102 +#: workflows/forms.py:113 workflows/templates/workflows/user_management.html:77 +#: workflows/templates/workflows/user_management.html:108 +msgid "Neues Passwort" +msgstr "New password" + +#: workflows/forms.py:119 +msgid "Neues Passwort bestätigen" +msgstr "Confirm new password" + +#: workflows/forms.py:126 +msgid "Vorname" +msgstr "" + +#: workflows/forms.py:127 +msgid "Nachname" +msgstr "" + +#: workflows/forms.py:130 workflows/templates/workflows/user_management.html:74 +#: workflows/templates/workflows/user_management.html:93 #, fuzzy #| msgid "Rolle:" msgid "Rolle" msgstr "Role:" -#: workflows/forms.py:107 -msgid "Passwort" -msgstr "Password" - -#: workflows/forms.py:108 -msgid "Passwort bestätigen" -msgstr "Confirm password" - -#: workflows/forms.py:121 +#: workflows/forms.py:143 msgid "Dieser Benutzername ist bereits vergeben." msgstr "This username is already taken." -#: workflows/forms.py:130 workflows/views.py:433 +#: workflows/forms.py:152 workflows/views.py:472 msgid "Ungültige Rolle." msgstr "Invalid role." -#: workflows/forms.py:138 -msgid "Die Passwörter stimmen nicht überein." -msgstr "The passwords do not match." - -#: workflows/forms.py:394 +#: workflows/forms.py:408 #, python-format msgid "" "Das Übergabedatum muss mindestens %(days)s Tage in der Zukunft liegen " @@ -465,28 +467,129 @@ msgstr "Handover / successor context reviewed: %(value)s" #: workflows/templates/registration/login.html:4 #: workflows/templates/registration/login.html:19 +#: workflows/templates/workflows/auth/login.html:4 +#: workflows/templates/workflows/auth/login.html:17 msgid "Anmeldung" msgstr "Sign in" #: workflows/templates/registration/login.html:20 +#: workflows/templates/workflows/auth/login.html:18 msgid "Bitte melden Sie sich mit Ihrem Benutzerkonto an." msgstr "Please sign in with your user account." #: workflows/templates/registration/login.html:30 +#: workflows/templates/workflows/auth/login.html:28 #, fuzzy #| msgid "Fehlgeschlagen" msgid "Anmeldung fehlgeschlagen" msgstr "Failed" #: workflows/templates/registration/login.html:31 +#: workflows/templates/workflows/auth/login.html:29 msgid "" "Benutzername oder Passwort sind nicht korrekt. Bitte versuchen Sie es erneut." msgstr "" #: workflows/templates/registration/login.html:37 +#: workflows/templates/workflows/auth/login.html:35 msgid "Anmelden" msgstr "Sign in" +#: workflows/templates/registration/password_reset_complete.html:4 +#: workflows/templates/registration/password_reset_complete.html:17 +#: workflows/templates/workflows/auth/password_reset_complete.html:4 +#: workflows/templates/workflows/auth/password_reset_complete.html:17 +msgid "Passwort gespeichert" +msgstr "Password saved" + +#: workflows/templates/registration/password_reset_complete.html:18 +#: workflows/templates/workflows/auth/password_reset_complete.html:18 +msgid "" +"Ihr Passwort wurde erfolgreich gesetzt. Sie können sich jetzt mit Ihrem " +"Benutzerkonto anmelden." +msgstr "Your password has been set successfully. You can now sign in with your account." + +#: workflows/templates/registration/password_reset_complete.html:19 +#: workflows/templates/registration/password_reset_confirm.html:41 +#: workflows/templates/registration/password_reset_done.html:19 +#: workflows/templates/workflows/auth/password_reset_complete.html:19 +#: workflows/templates/workflows/auth/password_reset_confirm.html:44 +#: workflows/templates/workflows/auth/password_reset_done.html:19 +msgid "Zur Anmeldung" +msgstr "Back to sign in" + +#: workflows/templates/registration/password_reset_confirm.html:4 +#: workflows/templates/registration/password_reset_confirm.html:18 +#: workflows/templates/workflows/auth/password_reset_confirm.html:4 +#: workflows/templates/workflows/auth/password_reset_confirm.html:18 +msgid "Passwort festlegen" +msgstr "Set password" + +#: workflows/templates/registration/password_reset_confirm.html:19 +#: workflows/templates/workflows/auth/password_reset_confirm.html:19 +msgid "Bitte vergeben Sie jetzt ein neues Passwort für Ihr Konto." +msgstr "Please set a new password for your account now." + +#: workflows/templates/registration/password_reset_confirm.html:25 +#: workflows/templates/workflows/auth/password_reset_confirm.html:25 +msgid "Passwort konnte nicht gespeichert werden" +msgstr "Password could not be saved" + +#: workflows/templates/registration/password_reset_confirm.html:26 +#: workflows/templates/workflows/auth/password_reset_confirm.html:26 +msgid "Bitte prüfen Sie die beiden Passwortfelder und versuchen Sie es erneut." +msgstr "Please check both password fields and try again." + +#: workflows/templates/registration/password_reset_confirm.html:36 +#: workflows/templates/workflows/auth/password_reset_confirm.html:39 +msgid "Passwort speichern" +msgstr "Save password" + +#: workflows/templates/registration/password_reset_confirm.html:39 +#: workflows/templates/workflows/auth/password_reset_confirm.html:42 +msgid "Link ungültig" +msgstr "Invalid link" + +#: workflows/templates/registration/password_reset_confirm.html:40 +#: workflows/templates/workflows/auth/password_reset_confirm.html:43 +msgid "" +"Dieser Link ist nicht mehr gültig. Bitte fordern Sie einen neuen Passwort-" +"Link an." +msgstr "This link is no longer valid. Please request a new password link." + +#: workflows/templates/registration/password_reset_done.html:4 +#: workflows/templates/registration/password_reset_done.html:17 +#: workflows/templates/workflows/auth/password_reset_done.html:4 +#: workflows/templates/workflows/auth/password_reset_done.html:17 +msgid "E-Mail versendet" +msgstr "Email sent" + +#: workflows/templates/registration/password_reset_done.html:18 +#: workflows/templates/workflows/auth/password_reset_done.html:18 +msgid "" +"Wenn ein passendes Konto existiert, wurde ein Passwort-Link an die " +"hinterlegte E-Mail-Adresse verschickt." +msgstr "If a matching account exists, a password link has been sent to the stored email address." + +#: workflows/templates/registration/password_reset_form.html:4 +#: workflows/templates/registration/password_reset_form.html:17 +#: workflows/templates/workflows/auth/password_reset_form.html:4 +#: workflows/templates/workflows/auth/password_reset_form.html:17 +msgid "Passwort zurücksetzen" +msgstr "Reset password" + +#: workflows/templates/registration/password_reset_form.html:18 +#: workflows/templates/workflows/auth/password_reset_form.html:18 +msgid "" +"Geben Sie Ihre E-Mail-Adresse ein. Wenn ein Konto vorhanden ist, erhalten " +"Sie einen Passwort-Link." +msgstr "Enter your email address. If an account exists, you will receive a password link." + +#: workflows/templates/registration/password_reset_form.html:24 +#: workflows/templates/workflows/auth/password_reset_form.html:25 +msgid "Link anfordern" +msgstr "Request link" + #: workflows/templates/workflows/audit_log.html:4 #: workflows/templates/workflows/audit_log.html:15 #: workflows/templates/workflows/home.html:132 @@ -596,7 +699,7 @@ msgid "" msgstr "Create database and media backups and verify existing bundles safely." #: workflows/templates/workflows/backup_recovery.html:20 -#: workflows/templates/workflows/user_management.html:87 +#: workflows/templates/workflows/user_management.html:78 msgid "Aktionen" msgstr "Actions" @@ -716,7 +819,7 @@ msgstr "Delete this backup bundle?" #: workflows/templates/workflows/intro_builder.html:66 #: workflows/templates/workflows/intro_builder.html:102 #: workflows/templates/workflows/requests_dashboard.html:286 -#: workflows/templates/workflows/user_management.html:136 +#: workflows/templates/workflows/user_management.html:127 #: workflows/templates/workflows/welcome_emails.html:70 msgid "Löschen" msgstr "Delete" @@ -817,7 +920,7 @@ msgstr "Label (EN)" #: workflows/templates/workflows/form_builder.html:91 #: workflows/templates/workflows/integrations_setup.html:263 #: workflows/templates/workflows/intro_builder.html:65 -#: workflows/templates/workflows/user_management.html:84 +#: workflows/templates/workflows/user_management.html:75 msgid "Aktiv" msgstr "Active" @@ -1033,13 +1136,13 @@ msgstr "Nextcloud:" #: workflows/templates/workflows/home.html:40 #: workflows/templates/workflows/integrations_setup.html:60 -#: workflows/templates/workflows/user_management.html:112 +#: workflows/templates/workflows/user_management.html:103 msgid "aktiv" msgstr "active" #: workflows/templates/workflows/home.html:40 #: workflows/templates/workflows/integrations_setup.html:60 -#: workflows/templates/workflows/user_management.html:112 +#: workflows/templates/workflows/user_management.html:103 msgid "inaktiv" msgstr "inactive" @@ -1774,7 +1877,7 @@ msgstr "Employee" #: workflows/templates/workflows/onboarding_intro_session.html:27 #: workflows/templates/workflows/request_timeline.html:66 -#: workflows/templates/workflows/user_management.html:80 +#: workflows/templates/workflows/user_management.html:71 msgid "Name" msgstr "Name" @@ -1787,7 +1890,7 @@ msgid "Dienstliche E-Mail" msgstr "Work email" #: workflows/templates/workflows/onboarding_intro_session.html:31 -#: workflows/views.py:688 +#: workflows/views.py:708 msgid "Vertragsbeginn" msgstr "Contract start" @@ -2047,7 +2150,7 @@ msgstr "" #: workflows/templates/workflows/request_timeline.html:74 #: workflows/templates/workflows/requests_dashboard.html:190 -#: workflows/templates/workflows/user_management.html:82 +#: workflows/templates/workflows/user_management.html:73 msgid "E-Mail" msgstr "Email" @@ -2234,60 +2337,63 @@ msgstr "Super admins manage user accounts, roles, and active access." msgid "Benutzer anlegen" msgstr "Create user" -#: workflows/templates/workflows/user_management.html:64 -msgid "Benutzer erstellen" +#: workflows/templates/workflows/user_management.html:23 +msgid "" +"Nach dem Anlegen wird automatisch eine Zugangseinladung mit Passwort-Link " +"per E-Mail versendet." +msgstr "" + +#: workflows/templates/workflows/user_management.html:55 +#, fuzzy +#| msgid "Benutzer anlegen" +msgid "Benutzer anlegen und einladen" msgstr "Create user" -#: workflows/templates/workflows/user_management.html:72 +#: workflows/templates/workflows/user_management.html:63 msgid "Benutzerübersicht" msgstr "User overview" -#: workflows/templates/workflows/user_management.html:73 +#: workflows/templates/workflows/user_management.html:64 msgid "Rollen ändern, Zugriffe sperren oder ein neues Passwort setzen." msgstr "Change roles, block access, or set a new password." -#: workflows/templates/workflows/user_management.html:85 +#: workflows/templates/workflows/user_management.html:76 msgid "Letzte Anmeldung" msgstr "Last login" -#: workflows/templates/workflows/user_management.html:86 -#: workflows/templates/workflows/user_management.html:117 -msgid "Neues Passwort" -msgstr "New password" - -#: workflows/templates/workflows/user_management.html:96 +#: workflows/templates/workflows/user_management.html:87 msgid "Sie selbst" msgstr "You" -#: workflows/templates/workflows/user_management.html:118 +#: workflows/templates/workflows/user_management.html:109 msgid "Optional" msgstr "Optional" -#: workflows/templates/workflows/user_management.html:124 +#: workflows/templates/workflows/user_management.html:115 msgid "Speichern" msgstr "Save" -#: workflows/templates/workflows/user_management.html:128 +#: workflows/templates/workflows/user_management.html:119 msgid "Reset-Link senden" msgstr "" -#: workflows/templates/workflows/user_management.html:131 +#: workflows/templates/workflows/user_management.html:122 #, fuzzy #| msgid "Welcome E-Mails" msgid "Keine E-Mail" msgstr "Welcome Emails" -#: workflows/templates/workflows/user_management.html:136 +#: workflows/templates/workflows/user_management.html:127 #, fuzzy #| msgid "Option wirklich löschen?" msgid "Benutzer wirklich löschen?" msgstr "Delete this option?" -#: workflows/templates/workflows/user_management.html:143 +#: workflows/templates/workflows/user_management.html:134 msgid "Es sind noch keine Benutzer vorhanden." msgstr "No users exist yet." -#: workflows/templates/workflows/user_management.html:149 +#: workflows/templates/workflows/user_management.html:140 msgid "" "Hinweis: Der aktuell angemeldete Super Admin kann sich hier nicht selbst " "deaktivieren oder auf eine niedrigere Rolle setzen." @@ -2397,7 +2503,7 @@ msgstr "Devices, software, and access" msgid "Notizen und Freigabe" msgstr "Notes and approval" -#: workflows/views.py:128 workflows/views.py:774 workflows/views.py:779 +#: workflows/views.py:128 workflows/views.py:794 workflows/views.py:799 msgid "Sie haben keine Berechtigung für diese Aktion." msgstr "You do not have permission for this action." @@ -2609,48 +2715,35 @@ msgstr "Request saved" msgid "Backup-Einstellungen gespeichert" msgstr "Save welcome settings" -#: workflows/views.py:407 -msgid "Benutzer konnte nicht erstellt werden. Bitte prüfen Sie die Eingaben." -msgstr "User could not be created. Please check the input." - -#: workflows/views.py:419 -#, python-format -msgid "Benutzer wurde erstellt: %(username)s" -msgstr "User created: %(username)s" - -#: workflows/views.py:437 -msgid "" -"Der aktuell angemeldete Super Admin kann sich hier nicht selbst sperren oder " -"herabstufen." -msgstr "" -"The currently signed-in super admin cannot lock or downgrade themselves here." - -#: workflows/views.py:440 -#, fuzzy -#| msgid "" -#| "Der aktuell angemeldete Super Admin kann sich hier nicht selbst sperren " -#| "oder herabstufen." -msgid "" -"Der letzte aktive Super Admin kann nicht deaktiviert oder herabgestuft " -"werden." -msgstr "" -"The currently signed-in super admin cannot lock or downgrade themselves here." - -#: workflows/views.py:457 -#, python-format -msgid "Benutzer wurde aktualisiert: %(username)s" -msgstr "User updated: %(username)s" - -#: workflows/views.py:468 +#: workflows/views.py:400 msgid "Für diesen Benutzer ist keine E-Mail-Adresse hinterlegt." msgstr "" -#: workflows/views.py:477 +#: workflows/views.py:408 +#, python-format +msgid "Zugangseinladung für %(username)s" +msgstr "" + +#: workflows/views.py:410 +#, python-format +msgid "" +"Hallo %(name)s,\n" +"\n" +"für Sie wurde ein Benutzerkonto im TUBCO Onboarding- und Offboarding-Portal " +"angelegt.\n" +"Bitte öffnen Sie den folgenden Link, um Ihr Passwort zu setzen:\n" +"%(url)s\n" +"\n" +"Wenn Sie diese Einladung nicht erwartet haben, melden Sie sich bitte bei " +"Ihrem Administrator." +msgstr "" + +#: workflows/views.py:420 #, python-format msgid "Passwort zurücksetzen für %(username)s" msgstr "" -#: workflows/views.py:479 +#: workflows/views.py:422 #, python-format msgid "" "Hallo %(name)s,\n" @@ -2663,13 +2756,46 @@ msgid "" "ignorieren." msgstr "" -#: workflows/views.py:498 +#: workflows/views.py:445 +msgid "Benutzer konnte nicht erstellt werden. Bitte prüfen Sie die Eingaben." +msgstr "User could not be created. Please check the input." + +#: workflows/views.py:458 +#, fuzzy, python-format +#| msgid "Benutzer wurde erstellt: %(username)s" +msgid "Benutzer wurde erstellt und eingeladen: %(username)s" +msgstr "User created: %(username)s" + +#: workflows/views.py:476 +msgid "" +"Der aktuell angemeldete Super Admin kann sich hier nicht selbst sperren oder " +"herabstufen." +msgstr "" +"The currently signed-in super admin cannot lock or downgrade themselves here." + +#: workflows/views.py:479 +#, fuzzy +#| msgid "" +#| "Der aktuell angemeldete Super Admin kann sich hier nicht selbst sperren " +#| "oder herabstufen." +msgid "" +"Der letzte aktive Super Admin kann nicht deaktiviert oder herabgestuft " +"werden." +msgstr "" +"The currently signed-in super admin cannot lock or downgrade themselves here." + +#: workflows/views.py:496 +#, python-format +msgid "Benutzer wurde aktualisiert: %(username)s" +msgstr "User updated: %(username)s" + +#: workflows/views.py:518 #, fuzzy, python-format #| msgid "Benutzer wurde erstellt: %(username)s" msgid "Passwort-Reset-Link wurde versendet: %(username)s" msgstr "User created: %(username)s" -#: workflows/views.py:509 +#: workflows/views.py:529 #, fuzzy #| msgid "" #| "Der aktuell angemeldete Super Admin kann sich hier nicht selbst sperren " @@ -2679,7 +2805,7 @@ msgid "" msgstr "" "The currently signed-in super admin cannot lock or downgrade themselves here." -#: workflows/views.py:512 +#: workflows/views.py:532 #, fuzzy #| msgid "" #| "Der aktuell angemeldete Super Admin kann sich hier nicht selbst sperren " @@ -2688,124 +2814,130 @@ msgid "Der letzte aktive Super Admin kann nicht gelöscht werden." msgstr "" "The currently signed-in super admin cannot lock or downgrade themselves here." -#: workflows/views.py:525 +#: workflows/views.py:545 #, fuzzy, python-format #| msgid "Benutzer wurde erstellt: %(username)s" msgid "Benutzer wurde gelöscht: %(username)s" msgstr "User created: %(username)s" -#: workflows/views.py:612 +#: workflows/views.py:632 #, python-format msgid "Backup wurde erstellt: %(name)s" msgstr "" -#: workflows/views.py:614 +#: workflows/views.py:634 #, python-format msgid "Backup konnte nicht erstellt werden: %(error)s" msgstr "" -#: workflows/views.py:630 +#: workflows/views.py:650 #, python-format msgid "Backup wurde verifiziert: %(name)s" msgstr "" -#: workflows/views.py:632 +#: workflows/views.py:652 #, python-format msgid "Backup-Verifikation fehlgeschlagen: %(error)s" msgstr "" -#: workflows/views.py:648 +#: workflows/views.py:668 #, python-format msgid "Backup wurde gelöscht: %(name)s" msgstr "" -#: workflows/views.py:650 +#: workflows/views.py:670 #, python-format msgid "Backup konnte nicht gelöscht werden: %(error)s" msgstr "" -#: workflows/views.py:676 +#: workflows/views.py:696 #, fuzzy #| msgid "Anfrage gespeichert" msgid "Anfrage erstellt" msgstr "Request saved" -#: workflows/views.py:678 +#: workflows/views.py:698 #, fuzzy, python-format #| msgid "Sitzungsstatus" msgid "Status: %(status)s" msgstr "Session status" -#: workflows/views.py:690 +#: workflows/views.py:710 #, fuzzy #| msgid "Geplant für" msgid "Geplanter Start" msgstr "Scheduled for" -#: workflows/views.py:700 +#: workflows/views.py:720 msgid "Geräteübergabe / Hardware-Abholung" msgstr "" -#: workflows/views.py:702 +#: workflows/views.py:722 msgid "Geplanter Hardware-Termin" msgstr "" -#: workflows/views.py:711 +#: workflows/views.py:731 #, fuzzy #| msgid "Noch nicht verfügbar" msgid "PDF verfügbar" msgstr "Not available yet" -#: workflows/views.py:737 +#: workflows/views.py:757 #, fuzzy #| msgid "Einweisung" msgid "Einweisungssitzung" msgstr "Introduction" -#: workflows/views.py:749 +#: workflows/views.py:769 #, fuzzy #| msgid "Welcome E-Mails" msgid "Welcome E-Mail" msgstr "Welcome Emails" -#: workflows/views.py:788 +#: workflows/views.py:808 msgid "Keine Einträge ausgewählt." msgstr "No entries selected." -#: workflows/views.py:831 +#: workflows/views.py:851 #, python-format msgid "%(count)s Eintrag/Einträge gelöscht." msgstr "%(count)s entry/entries deleted." -#: workflows/views.py:833 +#: workflows/views.py:853 #, python-format msgid "%(count)s Auswahl(en) konnten nicht verarbeitet werden." msgstr "%(count)s selection(s) could not be processed." -#: workflows/views.py:835 +#: workflows/views.py:855 msgid "Keine passenden Einträge gefunden." msgstr "No matching entries found." -#: workflows/views.py:1062 +#: workflows/views.py:1082 msgid "Einweisungs- und Übergabeprotokoll wurde erzeugt." msgstr "Introduction and handover protocol was generated." -#: workflows/views.py:1079 +#: workflows/views.py:1099 msgid "Einweisungsprotokoll aus Live-Status wurde erzeugt." msgstr "Introduction protocol from live status was generated." -#: workflows/views.py:1108 +#: workflows/views.py:1128 msgid "Einweisung wurde zurückgesetzt." msgstr "Introduction was reset." -#: workflows/views.py:1122 +#: workflows/views.py:1142 msgid "Einweisung wurde als abgeschlossen gespeichert." msgstr "Introduction was saved as completed." -#: workflows/views.py:1135 +#: workflows/views.py:1155 msgid "Einweisung wurde als Entwurf gespeichert." msgstr "Introduction was saved as draft." +#~ msgid "Die Passwörter stimmen nicht überein." +#~ msgstr "The passwords do not match." + +#~ msgid "Benutzer erstellen" +#~ msgstr "Create user" + #~ msgid "Backup läuft" #~ msgstr "Backup in progress" diff --git a/backend/workflows/forms.py b/backend/workflows/forms.py index 0c326ad..2853db5 100644 --- a/backend/workflows/forms.py +++ b/backend/workflows/forms.py @@ -1,9 +1,10 @@ from django import forms from pathlib import Path from datetime import timedelta -from django.contrib.auth import get_user_model +from django.contrib.auth import get_user_model, password_validation +from django.contrib.auth.forms import AuthenticationForm, PasswordResetForm, SetPasswordForm from django.utils import timezone -from django.utils.translation import get_language, gettext as _ +from django.utils.translation import get_language, gettext as _, gettext_lazy from .form_builder import apply_form_field_config from .models import EmployeeProfile, FormOption, OffboardingRequest, OnboardingRequest, WorkflowConfig @@ -98,14 +99,35 @@ HARDWARE_EXTRA_CHOICES = [('Smartphone', 'Smartphone'), ('Anderes', 'Anderes')] SOFTWARE_EXTRA_CHOICES = [('Adobe Acrobat Pro (Abonnement: Zusätzliche Kosten)', 'Adobe Acrobat Pro (Abonnement: Zusätzliche Kosten)'), ('Anderes', 'Anderes')] +class AppAuthenticationForm(AuthenticationForm): + username = forms.CharField(label=gettext_lazy('Benutzername')) + password = forms.CharField(label=gettext_lazy('Passwort'), strip=False, widget=forms.PasswordInput(attrs={'autocomplete': 'current-password'})) + + +class AppPasswordResetForm(PasswordResetForm): + email = forms.EmailField(label=gettext_lazy('E-Mail-Adresse')) + + +class AppSetPasswordForm(SetPasswordForm): + new_password1 = forms.CharField( + label=gettext_lazy('Neues Passwort'), + strip=False, + widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}), + help_text=password_validation.password_validators_help_text_html(), + ) + new_password2 = forms.CharField( + label=gettext_lazy('Neues Passwort bestätigen'), + strip=False, + widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}), + ) + + class UserManagementCreateForm(forms.Form): first_name = forms.CharField(label=_('Vorname'), max_length=150, required=False) last_name = forms.CharField(label=_('Nachname'), max_length=150, required=False) username = forms.CharField(label=_('Benutzername'), max_length=150) email = forms.EmailField(label=_('E-Mail-Adresse')) role_key = forms.ChoiceField(label=_('Rolle')) - password1 = forms.CharField(label=_('Passwort'), widget=forms.PasswordInput()) - password2 = forms.CharField(label=_('Passwort bestätigen'), widget=forms.PasswordInput()) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -130,20 +152,12 @@ class UserManagementCreateForm(forms.Form): raise forms.ValidationError(_('Ungültige Rolle.')) return role_key - def clean(self): - cleaned = super().clean() - password1 = cleaned.get('password1') - password2 = cleaned.get('password2') - if password1 and password2 and password1 != password2: - self.add_error('password2', _('Die Passwörter stimmen nicht überein.')) - return cleaned - def save(self): user_model = get_user_model() user = user_model.objects.create_user( username=self.cleaned_data['username'], email=self.cleaned_data['email'], - password=self.cleaned_data['password1'], + password=None, first_name=self.cleaned_data.get('first_name', ''), last_name=self.cleaned_data.get('last_name', ''), is_active=True, diff --git a/backend/workflows/templates/registration/password_reset_complete.html b/backend/workflows/templates/registration/password_reset_complete.html new file mode 100644 index 0000000..4dd2712 --- /dev/null +++ b/backend/workflows/templates/registration/password_reset_complete.html @@ -0,0 +1,22 @@ +{% extends 'workflows/base_shell.html' %} +{% load static i18n %} + +{% block title %}{% trans "Passwort gespeichert" %}{% endblock %} + +{% block shell_header %} +{% include 'workflows/includes/app_header.html' with header_inside_shell=1 %} +{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block shell_body %} + +{% endblock %} diff --git a/backend/workflows/templates/registration/password_reset_confirm.html b/backend/workflows/templates/registration/password_reset_confirm.html new file mode 100644 index 0000000..a8c854e --- /dev/null +++ b/backend/workflows/templates/registration/password_reset_confirm.html @@ -0,0 +1,45 @@ +{% extends 'workflows/base_shell.html' %} +{% load static i18n %} + +{% block title %}{% trans "Passwort festlegen" %}{% endblock %} + +{% block shell_header %} +{% include 'workflows/includes/app_header.html' with header_inside_shell=1 %} +{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block shell_body %} + +{% endblock %} diff --git a/backend/workflows/templates/registration/password_reset_done.html b/backend/workflows/templates/registration/password_reset_done.html new file mode 100644 index 0000000..d3db1cd --- /dev/null +++ b/backend/workflows/templates/registration/password_reset_done.html @@ -0,0 +1,22 @@ +{% extends 'workflows/base_shell.html' %} +{% load static i18n %} + +{% block title %}{% trans "E-Mail versendet" %}{% endblock %} + +{% block shell_header %} +{% include 'workflows/includes/app_header.html' with header_inside_shell=1 %} +{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block shell_body %} + +{% endblock %} diff --git a/backend/workflows/templates/registration/password_reset_form.html b/backend/workflows/templates/registration/password_reset_form.html new file mode 100644 index 0000000..4ef41cb --- /dev/null +++ b/backend/workflows/templates/registration/password_reset_form.html @@ -0,0 +1,28 @@ +{% extends 'workflows/base_shell.html' %} +{% load static i18n %} + +{% block title %}{% trans "Passwort zurücksetzen" %}{% endblock %} + +{% block shell_header %} +{% include 'workflows/includes/app_header.html' with header_inside_shell=1 %} +{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block shell_body %} + +{% endblock %} diff --git a/backend/workflows/templates/workflows/auth/login.html b/backend/workflows/templates/workflows/auth/login.html new file mode 100644 index 0000000..cbca437 --- /dev/null +++ b/backend/workflows/templates/workflows/auth/login.html @@ -0,0 +1,39 @@ +{% extends 'workflows/base_shell.html' %} +{% load static i18n %} + +{% block title %}{% trans "Anmeldung" %}{% endblock %} + +{% block shell_header %} +{% include 'workflows/includes/app_header.html' with header_show_lang=1 header_inside_shell=1 %} +{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block shell_body %} + +{% endblock %} diff --git a/backend/workflows/templates/workflows/auth/password_reset_complete.html b/backend/workflows/templates/workflows/auth/password_reset_complete.html new file mode 100644 index 0000000..af9d634 --- /dev/null +++ b/backend/workflows/templates/workflows/auth/password_reset_complete.html @@ -0,0 +1,22 @@ +{% extends 'workflows/base_shell.html' %} +{% load static i18n %} + +{% block title %}{% trans "Passwort gespeichert" %}{% endblock %} + +{% block shell_header %} +{% include 'workflows/includes/app_header.html' with header_show_lang=1 header_inside_shell=1 %} +{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block shell_body %} + +{% endblock %} diff --git a/backend/workflows/templates/workflows/auth/password_reset_confirm.html b/backend/workflows/templates/workflows/auth/password_reset_confirm.html new file mode 100644 index 0000000..671bf77 --- /dev/null +++ b/backend/workflows/templates/workflows/auth/password_reset_confirm.html @@ -0,0 +1,47 @@ +{% extends 'workflows/base_shell.html' %} +{% load static i18n %} + +{% block title %}{% trans "Passwort festlegen" %}{% endblock %} + +{% block shell_header %} +{% include 'workflows/includes/app_header.html' with header_show_lang=1 header_inside_shell=1 %} +{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block shell_body %} + +{% endblock %} diff --git a/backend/workflows/templates/workflows/auth/password_reset_done.html b/backend/workflows/templates/workflows/auth/password_reset_done.html new file mode 100644 index 0000000..8b0870f --- /dev/null +++ b/backend/workflows/templates/workflows/auth/password_reset_done.html @@ -0,0 +1,22 @@ +{% extends 'workflows/base_shell.html' %} +{% load static i18n %} + +{% block title %}{% trans "E-Mail versendet" %}{% endblock %} + +{% block shell_header %} +{% include 'workflows/includes/app_header.html' with header_show_lang=1 header_inside_shell=1 %} +{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block shell_body %} + +{% endblock %} diff --git a/backend/workflows/templates/workflows/auth/password_reset_form.html b/backend/workflows/templates/workflows/auth/password_reset_form.html new file mode 100644 index 0000000..9395dd2 --- /dev/null +++ b/backend/workflows/templates/workflows/auth/password_reset_form.html @@ -0,0 +1,29 @@ +{% extends 'workflows/base_shell.html' %} +{% load static i18n %} + +{% block title %}{% trans "Passwort zurücksetzen" %}{% endblock %} + +{% block shell_header %} +{% include 'workflows/includes/app_header.html' with header_show_lang=1 header_inside_shell=1 %} +{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block shell_body %} + +{% endblock %} diff --git a/backend/workflows/templates/workflows/developer_handbook.html b/backend/workflows/templates/workflows/developer_handbook.html index 0e94ede..94841e4 100644 --- a/backend/workflows/templates/workflows/developer_handbook.html +++ b/backend/workflows/templates/workflows/developer_handbook.html @@ -107,7 +107,7 @@ docker compose exec -T web python manage.py check
  • Capability checks are centralized in workflows.roles.CAPABILITIES.
  • Use _require_capability(...) in views instead of flat is_staff checks.
  • Templates receive permission flags from workflows.context_processors.role_context.
  • -
  • Super-admin-only user management lives at /admin-tools/users/ and is the preferred path for normal role assignment, account activation, password-reset mail dispatch, and controlled user deletion.
  • +
  • Super-admin-only user management lives at /admin-tools/users/ and is the preferred path for normal role assignment, account activation, invitation mail dispatch, password-reset mail dispatch, and controlled user deletion.
  • Backward-compatibility rule: authenticated legacy users with is_staff=True but no explicit role group currently fall back to the Admin capability set.
  • superuser accounts resolve to Super Admin.
  • When adding a new operational page or action, define the capability in roles.py, gate the view, and hide the UI affordance when the capability is absent.
  • diff --git a/backend/workflows/templates/workflows/project_wiki.html b/backend/workflows/templates/workflows/project_wiki.html index add71c6..31e8982 100644 --- a/backend/workflows/templates/workflows/project_wiki.html +++ b/backend/workflows/templates/workflows/project_wiki.html @@ -178,7 +178,7 @@
  • Form Builder: manage field visibility/order/options.
  • Einweisungs-Builder: manage custom checklist items for the intro PDF and live introduction checklist, including section, visibility, and conditional display logic.
  • Integrations: Nextcloud, SMTP, default routing addresses, notification rules, workflow rules, and remote backup target settings.
  • -
  • Benutzer & Rollen: super-admin-only page for creating users, assigning roles, activating/deactivating access, sending password-reset links, and deleting accounts when appropriate.
  • +
  • Benutzer & Rollen: super-admin-only page for creating users, assigning roles, activating/deactivating access, sending access or password-reset links by email, and deleting accounts when appropriate.
  • Welcome Emails: scheduled jobs, pause/resume/cancel/trigger now.
  • Audit Log: staff-only trace of important admin changes such as builder edits, settings updates, PDF generation, welcome-email operations, and request deletions. Supports filtering by action, user, and date range.
  • Requests Dashboard: search records, open PDFs, delete records (single/bulk for staff).
  • diff --git a/backend/workflows/templates/workflows/user_management.html b/backend/workflows/templates/workflows/user_management.html index 98b1771..9067263 100644 --- a/backend/workflows/templates/workflows/user_management.html +++ b/backend/workflows/templates/workflows/user_management.html @@ -20,6 +20,7 @@

    {% trans "Benutzer anlegen" %}

    +

    {% trans "Nach dem Anlegen wird automatisch eine Zugangseinladung mit Passwort-Link per E-Mail versendet." %}

    {% csrf_token %}
    @@ -48,20 +49,10 @@ {{ create_form.role_key }} {% for error in create_form.role_key.errors %}
    {{ error }}
    {% endfor %}
    -
    - - {{ create_form.password1 }} - {% for error in create_form.password1.errors %}
    {{ error }}
    {% endfor %} -
    -
    - - {{ create_form.password2 }} - {% for error in create_form.password2.errors %}
    {{ error }}
    {% endfor %} -
    {% for error in create_form.non_field_errors %}
    {{ error }}
    {% endfor %}
    - +
    diff --git a/backend/workflows/views.py b/backend/workflows/views.py index 51d475b..5bd956f 100644 --- a/backend/workflows/views.py +++ b/backend/workflows/views.py @@ -394,6 +394,44 @@ def _would_remove_last_super_admin(user, new_role_key: str | None = None, new_is return False +def _send_user_access_email(request, target_user, *, invitation: bool) -> None: + email = (target_user.email or '').strip() + if not email: + raise ValueError(_('Für diesen Benutzer ist keine E-Mail-Adresse hinterlegt.')) + + uid = urlsafe_base64_encode(force_bytes(target_user.pk)) + token = default_token_generator.make_token(target_user) + reset_path = reverse('password_reset_confirm', kwargs={'uidb64': uid, 'token': token}) + reset_url = request.build_absolute_uri(reset_path) + + if invitation: + subject = _('Zugangseinladung für %(username)s') % {'username': target_user.username} + body = _( + 'Hallo %(name)s,\n\n' + 'für Sie wurde ein Benutzerkonto im TUBCO Onboarding- und Offboarding-Portal angelegt.\n' + 'Bitte öffnen Sie den folgenden Link, um Ihr Passwort zu setzen:\n' + '%(url)s\n\n' + 'Wenn Sie diese Einladung nicht erwartet haben, melden Sie sich bitte bei Ihrem Administrator.' + ) % { + 'name': _display_user_name(target_user), + 'url': reset_url, + } + else: + subject = _('Passwort zurücksetzen für %(username)s') % {'username': target_user.username} + body = _( + 'Hallo %(name)s,\n\n' + 'für Ihr Konto wurde ein Link zum Zurücksetzen des Passworts erstellt.\n' + 'Bitte öffnen Sie den folgenden Link:\n' + '%(url)s\n\n' + 'Wenn Sie diese Anfrage nicht erwartet haben, können Sie diese E-Mail ignorieren.' + ) % { + 'name': _display_user_name(target_user), + 'url': reset_url, + } + + send_system_email(subject=subject, body=body, to=[email]) + + @_require_capability('manage_users') def user_management_page(request): return _render_user_management(request) @@ -408,15 +446,16 @@ def create_user_from_admin(request): return _render_user_management(request, create_form=form, status_code=400) user = form.save() + _send_user_access_email(request, user, invitation=True) _audit( request, 'user_created', target_type='user', target_id=user.id, target_label=_display_user_name(user), - details={'username': user.username, 'role': get_user_role_key(user)}, + details={'username': user.username, 'role': get_user_role_key(user), 'invitation_sent': True}, ) - messages.success(request, _('Benutzer wurde erstellt: %(username)s') % {'username': user.username}) + messages.success(request, _('Benutzer wurde erstellt und eingeladen: %(username)s') % {'username': user.username}) return redirect('user_management_page') @@ -463,37 +502,18 @@ def update_user_from_admin(request, user_id: int): def send_password_reset_from_admin(request, user_id: int): user_model = get_user_model() target_user = get_object_or_404(user_model, id=user_id) - email = (target_user.email or '').strip() - if not email: - messages.error(request, _('Für diesen Benutzer ist keine E-Mail-Adresse hinterlegt.')) + try: + _send_user_access_email(request, target_user, invitation=False) + except ValueError as exc: + messages.error(request, str(exc)) return redirect('user_management_page') - - uid = urlsafe_base64_encode(force_bytes(target_user.pk)) - token = default_token_generator.make_token(target_user) - reset_path = reverse('password_reset_confirm', kwargs={'uidb64': uid, 'token': token}) - reset_url = request.build_absolute_uri(reset_path) - - send_system_email( - subject=_('Passwort zurücksetzen für %(username)s') % {'username': target_user.username}, - body=_( - 'Hallo %(name)s,\n\n' - 'für Ihr Konto wurde ein Link zum Zurücksetzen des Passworts erstellt.\n' - 'Bitte öffnen Sie den folgenden Link:\n' - '%(url)s\n\n' - 'Wenn Sie diese Anfrage nicht erwartet haben, können Sie diese E-Mail ignorieren.' - ) % { - 'name': _display_user_name(target_user), - 'url': reset_url, - }, - to=[email], - ) _audit( request, 'user_password_reset_sent', target_type='user', target_id=target_user.id, target_label=_display_user_name(target_user), - details={'username': target_user.username, 'email': email}, + details={'username': target_user.username, 'email': target_user.email}, ) messages.success(request, _('Passwort-Reset-Link wurde versendet: %(username)s') % {'username': target_user.username}) return redirect('user_management_page')