snapshot: preserve auth invite flow and password reset UX cleanup

This commit is contained in:
Md Bayazid Bostame
2026-03-26 10:34:31 +01:00
parent b585287004
commit af10a5fdee
17 changed files with 635 additions and 170 deletions

View File

@@ -1,12 +1,44 @@
from django.conf import settings from django.conf import settings
from django.conf.urls.static import static from django.conf.urls.static import static
from django.contrib import admin from django.contrib import admin
from django.contrib.auth import views as auth_views
from django.urls import include, path from django.urls import include, path
from workflows.forms import AppAuthenticationForm, AppPasswordResetForm, AppSetPasswordForm
urlpatterns = [ urlpatterns = [
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
path('i18n/', include('django.conf.urls.i18n')), 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/<uidb64>/<token>/',
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')), path('', include('workflows.urls')),
] ]

View File

@@ -2,7 +2,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: tubco-portal\n" "Project-Id-Version: tubco-portal\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: 2026-03-24 00:00+0000\n"
"Language: en\n" "Language: en\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
@@ -55,52 +55,54 @@ msgstr ""
msgid "Remote Backup in Nextcloud konnte nicht gelöscht werden." msgid "Remote Backup in Nextcloud konnte nicht gelöscht werden."
msgstr "" msgstr ""
#: workflows/forms.py:102 #: workflows/forms.py:103 workflows/forms.py:128
msgid "Vorname" #: workflows/templates/workflows/user_management.html:72
msgstr ""
#: workflows/forms.py:103
msgid "Nachname"
msgstr ""
#: workflows/forms.py:104 workflows/templates/workflows/user_management.html:81
msgid "Benutzername" msgid "Benutzername"
msgstr "" msgstr ""
#: workflows/forms.py:105 #: workflows/forms.py:104
msgid "Passwort"
msgstr "Password"
#: workflows/forms.py:108 workflows/forms.py:129
#, fuzzy #, fuzzy
#| msgid "E-Mail" #| msgid "E-Mail"
msgid "E-Mail-Adresse" msgid "E-Mail-Adresse"
msgstr "Email" msgstr "Email"
#: workflows/forms.py:106 workflows/templates/workflows/user_management.html:83 #: workflows/forms.py:113 workflows/templates/workflows/user_management.html:77
#: workflows/templates/workflows/user_management.html:102 #: 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 #, fuzzy
#| msgid "Rolle:" #| msgid "Rolle:"
msgid "Rolle" msgid "Rolle"
msgstr "Role:" msgstr "Role:"
#: workflows/forms.py:107 #: workflows/forms.py:143
msgid "Passwort"
msgstr "Password"
#: workflows/forms.py:108
msgid "Passwort bestätigen"
msgstr "Confirm password"
#: workflows/forms.py:121
msgid "Dieser Benutzername ist bereits vergeben." msgid "Dieser Benutzername ist bereits vergeben."
msgstr "This username is already taken." 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." msgid "Ungültige Rolle."
msgstr "Invalid role." msgstr "Invalid role."
#: workflows/forms.py:138 #: workflows/forms.py:408
msgid "Die Passwörter stimmen nicht überein."
msgstr "The passwords do not match."
#: workflows/forms.py:394
#, python-format #, python-format
msgid "" msgid ""
"Das Übergabedatum muss mindestens %(days)s Tage in der Zukunft liegen " "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:4
#: workflows/templates/registration/login.html:19 #: workflows/templates/registration/login.html:19
#: workflows/templates/workflows/auth/login.html:4
#: workflows/templates/workflows/auth/login.html:17
msgid "Anmeldung" msgid "Anmeldung"
msgstr "Sign in" msgstr "Sign in"
#: workflows/templates/registration/login.html:20 #: workflows/templates/registration/login.html:20
#: workflows/templates/workflows/auth/login.html:18
msgid "Bitte melden Sie sich mit Ihrem Benutzerkonto an." msgid "Bitte melden Sie sich mit Ihrem Benutzerkonto an."
msgstr "Please sign in with your user account." msgstr "Please sign in with your user account."
#: workflows/templates/registration/login.html:30 #: workflows/templates/registration/login.html:30
#: workflows/templates/workflows/auth/login.html:28
#, fuzzy #, fuzzy
#| msgid "Fehlgeschlagen" #| msgid "Fehlgeschlagen"
msgid "Anmeldung fehlgeschlagen" msgid "Anmeldung fehlgeschlagen"
msgstr "Failed" msgstr "Failed"
#: workflows/templates/registration/login.html:31 #: workflows/templates/registration/login.html:31
#: workflows/templates/workflows/auth/login.html:29
msgid "" msgid ""
"Benutzername oder Passwort sind nicht korrekt. Bitte versuchen Sie es erneut." "Benutzername oder Passwort sind nicht korrekt. Bitte versuchen Sie es erneut."
msgstr "" msgstr ""
#: workflows/templates/registration/login.html:37 #: workflows/templates/registration/login.html:37
#: workflows/templates/workflows/auth/login.html:35
msgid "Anmelden" msgid "Anmelden"
msgstr "Sign in" 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:4
#: workflows/templates/workflows/audit_log.html:15 #: workflows/templates/workflows/audit_log.html:15
#: workflows/templates/workflows/home.html:132 #: workflows/templates/workflows/home.html:132
@@ -596,7 +699,7 @@ msgid ""
msgstr "Create database and media backups and verify existing bundles safely." msgstr "Create database and media backups and verify existing bundles safely."
#: workflows/templates/workflows/backup_recovery.html:20 #: workflows/templates/workflows/backup_recovery.html:20
#: workflows/templates/workflows/user_management.html:87 #: workflows/templates/workflows/user_management.html:78
msgid "Aktionen" msgid "Aktionen"
msgstr "Actions" msgstr "Actions"
@@ -716,7 +819,7 @@ msgstr "Delete this backup bundle?"
#: workflows/templates/workflows/intro_builder.html:66 #: workflows/templates/workflows/intro_builder.html:66
#: workflows/templates/workflows/intro_builder.html:102 #: workflows/templates/workflows/intro_builder.html:102
#: workflows/templates/workflows/requests_dashboard.html:286 #: 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 #: workflows/templates/workflows/welcome_emails.html:70
msgid "Löschen" msgid "Löschen"
msgstr "Delete" msgstr "Delete"
@@ -817,7 +920,7 @@ msgstr "Label (EN)"
#: workflows/templates/workflows/form_builder.html:91 #: workflows/templates/workflows/form_builder.html:91
#: workflows/templates/workflows/integrations_setup.html:263 #: workflows/templates/workflows/integrations_setup.html:263
#: workflows/templates/workflows/intro_builder.html:65 #: workflows/templates/workflows/intro_builder.html:65
#: workflows/templates/workflows/user_management.html:84 #: workflows/templates/workflows/user_management.html:75
msgid "Aktiv" msgid "Aktiv"
msgstr "Active" msgstr "Active"
@@ -1033,13 +1136,13 @@ msgstr "Nextcloud:"
#: workflows/templates/workflows/home.html:40 #: workflows/templates/workflows/home.html:40
#: workflows/templates/workflows/integrations_setup.html:60 #: workflows/templates/workflows/integrations_setup.html:60
#: workflows/templates/workflows/user_management.html:112 #: workflows/templates/workflows/user_management.html:103
msgid "aktiv" msgid "aktiv"
msgstr "active" msgstr "active"
#: workflows/templates/workflows/home.html:40 #: workflows/templates/workflows/home.html:40
#: workflows/templates/workflows/integrations_setup.html:60 #: workflows/templates/workflows/integrations_setup.html:60
#: workflows/templates/workflows/user_management.html:112 #: workflows/templates/workflows/user_management.html:103
msgid "inaktiv" msgid "inaktiv"
msgstr "inactive" msgstr "inactive"
@@ -1774,7 +1877,7 @@ msgstr "Employee"
#: workflows/templates/workflows/onboarding_intro_session.html:27 #: workflows/templates/workflows/onboarding_intro_session.html:27
#: workflows/templates/workflows/request_timeline.html:66 #: workflows/templates/workflows/request_timeline.html:66
#: workflows/templates/workflows/user_management.html:80 #: workflows/templates/workflows/user_management.html:71
msgid "Name" msgid "Name"
msgstr "Name" msgstr "Name"
@@ -1787,7 +1890,7 @@ msgid "Dienstliche E-Mail"
msgstr "Work email" msgstr "Work email"
#: workflows/templates/workflows/onboarding_intro_session.html:31 #: workflows/templates/workflows/onboarding_intro_session.html:31
#: workflows/views.py:688 #: workflows/views.py:708
msgid "Vertragsbeginn" msgid "Vertragsbeginn"
msgstr "Contract start" msgstr "Contract start"
@@ -2047,7 +2150,7 @@ msgstr ""
#: workflows/templates/workflows/request_timeline.html:74 #: workflows/templates/workflows/request_timeline.html:74
#: workflows/templates/workflows/requests_dashboard.html:190 #: workflows/templates/workflows/requests_dashboard.html:190
#: workflows/templates/workflows/user_management.html:82 #: workflows/templates/workflows/user_management.html:73
msgid "E-Mail" msgid "E-Mail"
msgstr "Email" msgstr "Email"
@@ -2234,60 +2337,63 @@ msgstr "Super admins manage user accounts, roles, and active access."
msgid "Benutzer anlegen" msgid "Benutzer anlegen"
msgstr "Create user" msgstr "Create user"
#: workflows/templates/workflows/user_management.html:64 #: workflows/templates/workflows/user_management.html:23
msgid "Benutzer erstellen" 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" msgstr "Create user"
#: workflows/templates/workflows/user_management.html:72 #: workflows/templates/workflows/user_management.html:63
msgid "Benutzerübersicht" msgid "Benutzerübersicht"
msgstr "User overview" 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." msgid "Rollen ändern, Zugriffe sperren oder ein neues Passwort setzen."
msgstr "Change roles, block access, or set a new password." 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" msgid "Letzte Anmeldung"
msgstr "Last login" msgstr "Last login"
#: workflows/templates/workflows/user_management.html:86 #: workflows/templates/workflows/user_management.html:87
#: workflows/templates/workflows/user_management.html:117
msgid "Neues Passwort"
msgstr "New password"
#: workflows/templates/workflows/user_management.html:96
msgid "Sie selbst" msgid "Sie selbst"
msgstr "You" msgstr "You"
#: workflows/templates/workflows/user_management.html:118 #: workflows/templates/workflows/user_management.html:109
msgid "Optional" msgid "Optional"
msgstr "Optional" msgstr "Optional"
#: workflows/templates/workflows/user_management.html:124 #: workflows/templates/workflows/user_management.html:115
msgid "Speichern" msgid "Speichern"
msgstr "Save" msgstr "Save"
#: workflows/templates/workflows/user_management.html:128 #: workflows/templates/workflows/user_management.html:119
msgid "Reset-Link senden" msgid "Reset-Link senden"
msgstr "" msgstr ""
#: workflows/templates/workflows/user_management.html:131 #: workflows/templates/workflows/user_management.html:122
#, fuzzy #, fuzzy
#| msgid "Welcome E-Mails" #| msgid "Welcome E-Mails"
msgid "Keine E-Mail" msgid "Keine E-Mail"
msgstr "Welcome Emails" msgstr "Welcome Emails"
#: workflows/templates/workflows/user_management.html:136 #: workflows/templates/workflows/user_management.html:127
#, fuzzy #, fuzzy
#| msgid "Option wirklich löschen?" #| msgid "Option wirklich löschen?"
msgid "Benutzer wirklich löschen?" msgid "Benutzer wirklich löschen?"
msgstr "Delete this option?" 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." msgid "Es sind noch keine Benutzer vorhanden."
msgstr "No users exist yet." msgstr "No users exist yet."
#: workflows/templates/workflows/user_management.html:149 #: workflows/templates/workflows/user_management.html:140
msgid "" msgid ""
"Hinweis: Der aktuell angemeldete Super Admin kann sich hier nicht selbst " "Hinweis: Der aktuell angemeldete Super Admin kann sich hier nicht selbst "
"deaktivieren oder auf eine niedrigere Rolle setzen." "deaktivieren oder auf eine niedrigere Rolle setzen."
@@ -2397,7 +2503,7 @@ msgstr "Devices, software, and access"
msgid "Notizen und Freigabe" msgid "Notizen und Freigabe"
msgstr "Notes and approval" 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." msgid "Sie haben keine Berechtigung für diese Aktion."
msgstr "You do not have permission for this action." msgstr "You do not have permission for this action."
@@ -2609,48 +2715,35 @@ msgstr "Request saved"
msgid "Backup-Einstellungen gespeichert" msgid "Backup-Einstellungen gespeichert"
msgstr "Save welcome settings" msgstr "Save welcome settings"
#: workflows/views.py:407 #: workflows/views.py:400
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
msgid "Für diesen Benutzer ist keine E-Mail-Adresse hinterlegt." msgid "Für diesen Benutzer ist keine E-Mail-Adresse hinterlegt."
msgstr "" 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 #, python-format
msgid "Passwort zurücksetzen für %(username)s" msgid "Passwort zurücksetzen für %(username)s"
msgstr "" msgstr ""
#: workflows/views.py:479 #: workflows/views.py:422
#, python-format #, python-format
msgid "" msgid ""
"Hallo %(name)s,\n" "Hallo %(name)s,\n"
@@ -2663,13 +2756,46 @@ msgid ""
"ignorieren." "ignorieren."
msgstr "" 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 #, fuzzy, python-format
#| msgid "Benutzer wurde erstellt: %(username)s" #| msgid "Benutzer wurde erstellt: %(username)s"
msgid "Passwort-Reset-Link wurde versendet: %(username)s" msgid "Passwort-Reset-Link wurde versendet: %(username)s"
msgstr "User created: %(username)s" msgstr "User created: %(username)s"
#: workflows/views.py:509 #: workflows/views.py:529
#, fuzzy #, fuzzy
#| msgid "" #| msgid ""
#| "Der aktuell angemeldete Super Admin kann sich hier nicht selbst sperren " #| "Der aktuell angemeldete Super Admin kann sich hier nicht selbst sperren "
@@ -2679,7 +2805,7 @@ msgid ""
msgstr "" msgstr ""
"The currently signed-in super admin cannot lock or downgrade themselves here." "The currently signed-in super admin cannot lock or downgrade themselves here."
#: workflows/views.py:512 #: workflows/views.py:532
#, fuzzy #, fuzzy
#| msgid "" #| msgid ""
#| "Der aktuell angemeldete Super Admin kann sich hier nicht selbst sperren " #| "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 "" msgstr ""
"The currently signed-in super admin cannot lock or downgrade themselves here." "The currently signed-in super admin cannot lock or downgrade themselves here."
#: workflows/views.py:525 #: workflows/views.py:545
#, fuzzy, python-format #, fuzzy, python-format
#| msgid "Benutzer wurde erstellt: %(username)s" #| msgid "Benutzer wurde erstellt: %(username)s"
msgid "Benutzer wurde gelöscht: %(username)s" msgid "Benutzer wurde gelöscht: %(username)s"
msgstr "User created: %(username)s" msgstr "User created: %(username)s"
#: workflows/views.py:612 #: workflows/views.py:632
#, python-format #, python-format
msgid "Backup wurde erstellt: %(name)s" msgid "Backup wurde erstellt: %(name)s"
msgstr "" msgstr ""
#: workflows/views.py:614 #: workflows/views.py:634
#, python-format #, python-format
msgid "Backup konnte nicht erstellt werden: %(error)s" msgid "Backup konnte nicht erstellt werden: %(error)s"
msgstr "" msgstr ""
#: workflows/views.py:630 #: workflows/views.py:650
#, python-format #, python-format
msgid "Backup wurde verifiziert: %(name)s" msgid "Backup wurde verifiziert: %(name)s"
msgstr "" msgstr ""
#: workflows/views.py:632 #: workflows/views.py:652
#, python-format #, python-format
msgid "Backup-Verifikation fehlgeschlagen: %(error)s" msgid "Backup-Verifikation fehlgeschlagen: %(error)s"
msgstr "" msgstr ""
#: workflows/views.py:648 #: workflows/views.py:668
#, python-format #, python-format
msgid "Backup wurde gelöscht: %(name)s" msgid "Backup wurde gelöscht: %(name)s"
msgstr "" msgstr ""
#: workflows/views.py:650 #: workflows/views.py:670
#, python-format #, python-format
msgid "Backup konnte nicht gelöscht werden: %(error)s" msgid "Backup konnte nicht gelöscht werden: %(error)s"
msgstr "" msgstr ""
#: workflows/views.py:676 #: workflows/views.py:696
#, fuzzy #, fuzzy
#| msgid "Anfrage gespeichert" #| msgid "Anfrage gespeichert"
msgid "Anfrage erstellt" msgid "Anfrage erstellt"
msgstr "Request saved" msgstr "Request saved"
#: workflows/views.py:678 #: workflows/views.py:698
#, fuzzy, python-format #, fuzzy, python-format
#| msgid "Sitzungsstatus" #| msgid "Sitzungsstatus"
msgid "Status: %(status)s" msgid "Status: %(status)s"
msgstr "Session status" msgstr "Session status"
#: workflows/views.py:690 #: workflows/views.py:710
#, fuzzy #, fuzzy
#| msgid "Geplant für" #| msgid "Geplant für"
msgid "Geplanter Start" msgid "Geplanter Start"
msgstr "Scheduled for" msgstr "Scheduled for"
#: workflows/views.py:700 #: workflows/views.py:720
msgid "Geräteübergabe / Hardware-Abholung" msgid "Geräteübergabe / Hardware-Abholung"
msgstr "" msgstr ""
#: workflows/views.py:702 #: workflows/views.py:722
msgid "Geplanter Hardware-Termin" msgid "Geplanter Hardware-Termin"
msgstr "" msgstr ""
#: workflows/views.py:711 #: workflows/views.py:731
#, fuzzy #, fuzzy
#| msgid "Noch nicht verfügbar" #| msgid "Noch nicht verfügbar"
msgid "PDF verfügbar" msgid "PDF verfügbar"
msgstr "Not available yet" msgstr "Not available yet"
#: workflows/views.py:737 #: workflows/views.py:757
#, fuzzy #, fuzzy
#| msgid "Einweisung" #| msgid "Einweisung"
msgid "Einweisungssitzung" msgid "Einweisungssitzung"
msgstr "Introduction" msgstr "Introduction"
#: workflows/views.py:749 #: workflows/views.py:769
#, fuzzy #, fuzzy
#| msgid "Welcome E-Mails" #| msgid "Welcome E-Mails"
msgid "Welcome E-Mail" msgid "Welcome E-Mail"
msgstr "Welcome Emails" msgstr "Welcome Emails"
#: workflows/views.py:788 #: workflows/views.py:808
msgid "Keine Einträge ausgewählt." msgid "Keine Einträge ausgewählt."
msgstr "No entries selected." msgstr "No entries selected."
#: workflows/views.py:831 #: workflows/views.py:851
#, python-format #, python-format
msgid "%(count)s Eintrag/Einträge gelöscht." msgid "%(count)s Eintrag/Einträge gelöscht."
msgstr "%(count)s entry/entries deleted." msgstr "%(count)s entry/entries deleted."
#: workflows/views.py:833 #: workflows/views.py:853
#, python-format #, python-format
msgid "%(count)s Auswahl(en) konnten nicht verarbeitet werden." msgid "%(count)s Auswahl(en) konnten nicht verarbeitet werden."
msgstr "%(count)s selection(s) could not be processed." msgstr "%(count)s selection(s) could not be processed."
#: workflows/views.py:835 #: workflows/views.py:855
msgid "Keine passenden Einträge gefunden." msgid "Keine passenden Einträge gefunden."
msgstr "No matching entries found." msgstr "No matching entries found."
#: workflows/views.py:1062 #: workflows/views.py:1082
msgid "Einweisungs- und Übergabeprotokoll wurde erzeugt." msgid "Einweisungs- und Übergabeprotokoll wurde erzeugt."
msgstr "Introduction and handover protocol was generated." msgstr "Introduction and handover protocol was generated."
#: workflows/views.py:1079 #: workflows/views.py:1099
msgid "Einweisungsprotokoll aus Live-Status wurde erzeugt." msgid "Einweisungsprotokoll aus Live-Status wurde erzeugt."
msgstr "Introduction protocol from live status was generated." msgstr "Introduction protocol from live status was generated."
#: workflows/views.py:1108 #: workflows/views.py:1128
msgid "Einweisung wurde zurückgesetzt." msgid "Einweisung wurde zurückgesetzt."
msgstr "Introduction was reset." msgstr "Introduction was reset."
#: workflows/views.py:1122 #: workflows/views.py:1142
msgid "Einweisung wurde als abgeschlossen gespeichert." msgid "Einweisung wurde als abgeschlossen gespeichert."
msgstr "Introduction was saved as completed." msgstr "Introduction was saved as completed."
#: workflows/views.py:1135 #: workflows/views.py:1155
msgid "Einweisung wurde als Entwurf gespeichert." msgid "Einweisung wurde als Entwurf gespeichert."
msgstr "Introduction was saved as draft." 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" #~ msgid "Backup läuft"
#~ msgstr "Backup in progress" #~ msgstr "Backup in progress"

View File

@@ -1,9 +1,10 @@
from django import forms from django import forms
from pathlib import Path from pathlib import Path
from datetime import timedelta 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 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 .form_builder import apply_form_field_config
from .models import EmployeeProfile, FormOption, OffboardingRequest, OnboardingRequest, WorkflowConfig 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')] 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): class UserManagementCreateForm(forms.Form):
first_name = forms.CharField(label=_('Vorname'), max_length=150, required=False) first_name = forms.CharField(label=_('Vorname'), max_length=150, required=False)
last_name = forms.CharField(label=_('Nachname'), max_length=150, required=False) last_name = forms.CharField(label=_('Nachname'), max_length=150, required=False)
username = forms.CharField(label=_('Benutzername'), max_length=150) username = forms.CharField(label=_('Benutzername'), max_length=150)
email = forms.EmailField(label=_('E-Mail-Adresse')) email = forms.EmailField(label=_('E-Mail-Adresse'))
role_key = forms.ChoiceField(label=_('Rolle')) 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): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@@ -130,20 +152,12 @@ class UserManagementCreateForm(forms.Form):
raise forms.ValidationError(_('Ungültige Rolle.')) raise forms.ValidationError(_('Ungültige Rolle.'))
return role_key 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): def save(self):
user_model = get_user_model() user_model = get_user_model()
user = user_model.objects.create_user( user = user_model.objects.create_user(
username=self.cleaned_data['username'], username=self.cleaned_data['username'],
email=self.cleaned_data['email'], email=self.cleaned_data['email'],
password=self.cleaned_data['password1'], password=None,
first_name=self.cleaned_data.get('first_name', ''), first_name=self.cleaned_data.get('first_name', ''),
last_name=self.cleaned_data.get('last_name', ''), last_name=self.cleaned_data.get('last_name', ''),
is_active=True, is_active=True,

View File

@@ -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 %}
<link rel="stylesheet" href="{% static 'workflows/css/login.css' %}" />
{% endblock %}
{% block shell_body %}
<section class="login-shell-body">
<div class="login-card">
<h1>{% trans "Passwort gespeichert" %}</h1>
<p>{% trans "Ihr Passwort wurde erfolgreich gesetzt. Sie können sich jetzt mit Ihrem Benutzerkonto anmelden." %}</p>
<a class="btn btn-primary" href="/accounts/login/">{% trans "Zur Anmeldung" %}</a>
</div>
</section>
{% endblock %}

View File

@@ -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 %}
<link rel="stylesheet" href="{% static 'workflows/css/login.css' %}" />
{% endblock %}
{% block shell_body %}
<section class="login-shell-body">
<div class="login-card">
{% if validlink %}
<h1>{% trans "Passwort festlegen" %}</h1>
<p>{% trans "Bitte vergeben Sie jetzt ein neues Passwort für Ihr Konto." %}</p>
<form method="post">
{% csrf_token %}
{% if form.errors %}
<div class="app-alert app-alert-error" role="alert" aria-live="assertive">
<div class="app-alert-body">
<strong>{% trans "Passwort konnte nicht gespeichert werden" %}</strong><br />
<span>{% trans "Bitte prüfen Sie die beiden Passwortfelder und versuchen Sie es erneut." %}</span>
</div>
</div>
{% endif %}
<div class="field{% if form.new_password1.errors %} has-error{% endif %}">
{{ form.new_password1.label_tag }}{{ form.new_password1 }}
</div>
<div class="field{% if form.new_password2.errors %} has-error{% endif %}">
{{ form.new_password2.label_tag }}{{ form.new_password2 }}
</div>
<button class="btn btn-primary" type="submit">{% trans "Passwort speichern" %}</button>
</form>
{% else %}
<h1>{% trans "Link ungültig" %}</h1>
<p>{% trans "Dieser Link ist nicht mehr gültig. Bitte fordern Sie einen neuen Passwort-Link an." %}</p>
<a class="btn btn-primary" href="/accounts/login/">{% trans "Zur Anmeldung" %}</a>
{% endif %}
</div>
</section>
{% endblock %}

View File

@@ -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 %}
<link rel="stylesheet" href="{% static 'workflows/css/login.css' %}" />
{% endblock %}
{% block shell_body %}
<section class="login-shell-body">
<div class="login-card">
<h1>{% trans "E-Mail versendet" %}</h1>
<p>{% trans "Wenn ein passendes Konto existiert, wurde ein Passwort-Link an die hinterlegte E-Mail-Adresse verschickt." %}</p>
<a class="btn btn-primary" href="/accounts/login/">{% trans "Zur Anmeldung" %}</a>
</div>
</section>
{% endblock %}

View File

@@ -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 %}
<link rel="stylesheet" href="{% static 'workflows/css/login.css' %}" />
{% endblock %}
{% block shell_body %}
<section class="login-shell-body">
<div class="login-card">
<h1>{% trans "Passwort zurücksetzen" %}</h1>
<p>{% trans "Geben Sie Ihre E-Mail-Adresse ein. Wenn ein Konto vorhanden ist, erhalten Sie einen Passwort-Link." %}</p>
<form method="post">
{% csrf_token %}
<div class="field{% if form.email.errors %} has-error{% endif %}">
{{ form.email.label_tag }}{{ form.email }}
</div>
<button class="btn btn-primary" type="submit">{% trans "Link anfordern" %}</button>
</form>
</div>
</section>
{% endblock %}

View File

@@ -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 %}
<link rel="stylesheet" href="{% static 'workflows/css/login.css' %}" />
{% endblock %}
{% block shell_body %}
<section class="login-shell-body">
<div class="login-card">
<h1>{% trans "Anmeldung" %}</h1>
<p>{% trans "Bitte melden Sie sich mit Ihrem Benutzerkonto an." %}</p>
<form method="post" action="/accounts/login/">
{% csrf_token %}
{% if next %}
<input type="hidden" name="next" value="{{ next }}" />
{% endif %}
{% if form.errors %}
<div class="app-alert app-alert-error" role="alert" aria-live="assertive">
<div class="app-alert-body">
<strong>{% trans "Anmeldung fehlgeschlagen" %}</strong><br />
<span>{% trans "Benutzername oder Passwort sind nicht korrekt. Bitte versuchen Sie es erneut." %}</span>
</div>
</div>
{% endif %}
<div class="field{% if form.errors %} has-error{% endif %}">{{ form.username.label_tag }}{{ form.username }}</div>
<div class="field{% if form.errors %} has-error{% endif %}">{{ form.password.label_tag }}{{ form.password }}</div>
<button class="btn btn-primary" type="submit">{% trans "Anmelden" %}</button>
</form>
</div>
</section>
{% endblock %}

View File

@@ -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 %}
<link rel="stylesheet" href="{% static 'workflows/css/login.css' %}" />
{% endblock %}
{% block shell_body %}
<section class="login-shell-body">
<div class="login-card">
<h1>{% trans "Passwort gespeichert" %}</h1>
<p>{% trans "Ihr Passwort wurde erfolgreich gesetzt. Sie können sich jetzt mit Ihrem Benutzerkonto anmelden." %}</p>
<a class="btn btn-primary" href="/accounts/login/">{% trans "Zur Anmeldung" %}</a>
</div>
</section>
{% endblock %}

View File

@@ -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 %}
<link rel="stylesheet" href="{% static 'workflows/css/login.css' %}" />
{% endblock %}
{% block shell_body %}
<section class="login-shell-body">
<div class="login-card">
{% if validlink %}
<h1>{% trans "Passwort festlegen" %}</h1>
<p>{% trans "Bitte vergeben Sie jetzt ein neues Passwort für Ihr Konto." %}</p>
<form method="post">
{% csrf_token %}
{% if form.errors %}
<div class="app-alert app-alert-error" role="alert" aria-live="assertive">
<div class="app-alert-body">
<strong>{% trans "Passwort konnte nicht gespeichert werden" %}</strong><br />
<span>{% trans "Bitte prüfen Sie die beiden Passwortfelder und versuchen Sie es erneut." %}</span>
</div>
</div>
{% endif %}
<div class="field{% if form.new_password1.errors %} has-error{% endif %}">
{{ form.new_password1.label_tag }}{{ form.new_password1 }}
{% for error in form.new_password1.errors %}<div class="hint">{{ error }}</div>{% endfor %}
</div>
<div class="field{% if form.new_password2.errors %} has-error{% endif %}">
{{ form.new_password2.label_tag }}{{ form.new_password2 }}
{% for error in form.new_password2.errors %}<div class="hint">{{ error }}</div>{% endfor %}
</div>
<button class="btn btn-primary" type="submit">{% trans "Passwort speichern" %}</button>
</form>
{% else %}
<h1>{% trans "Link ungültig" %}</h1>
<p>{% trans "Dieser Link ist nicht mehr gültig. Bitte fordern Sie einen neuen Passwort-Link an." %}</p>
<a class="btn btn-primary" href="/accounts/login/">{% trans "Zur Anmeldung" %}</a>
{% endif %}
</div>
</section>
{% endblock %}

View File

@@ -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 %}
<link rel="stylesheet" href="{% static 'workflows/css/login.css' %}" />
{% endblock %}
{% block shell_body %}
<section class="login-shell-body">
<div class="login-card">
<h1>{% trans "E-Mail versendet" %}</h1>
<p>{% trans "Wenn ein passendes Konto existiert, wurde ein Passwort-Link an die hinterlegte E-Mail-Adresse verschickt." %}</p>
<a class="btn btn-primary" href="/accounts/login/">{% trans "Zur Anmeldung" %}</a>
</div>
</section>
{% endblock %}

View File

@@ -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 %}
<link rel="stylesheet" href="{% static 'workflows/css/login.css' %}" />
{% endblock %}
{% block shell_body %}
<section class="login-shell-body">
<div class="login-card">
<h1>{% trans "Passwort zurücksetzen" %}</h1>
<p>{% trans "Geben Sie Ihre E-Mail-Adresse ein. Wenn ein Konto vorhanden ist, erhalten Sie einen Passwort-Link." %}</p>
<form method="post">
{% csrf_token %}
<div class="field{% if form.email.errors %} has-error{% endif %}">
{{ form.email.label_tag }}{{ form.email }}
{% for error in form.email.errors %}<div class="hint">{{ error }}</div>{% endfor %}
</div>
<button class="btn btn-primary" type="submit">{% trans "Link anfordern" %}</button>
</form>
</div>
</section>
{% endblock %}

View File

@@ -107,7 +107,7 @@ docker compose exec -T web python manage.py check</code></pre>
<li>Capability checks are centralized in <code>workflows.roles.CAPABILITIES</code>.</li> <li>Capability checks are centralized in <code>workflows.roles.CAPABILITIES</code>.</li>
<li>Use <code>_require_capability(...)</code> in views instead of flat <code>is_staff</code> checks.</li> <li>Use <code>_require_capability(...)</code> in views instead of flat <code>is_staff</code> checks.</li>
<li>Templates receive permission flags from <code>workflows.context_processors.role_context</code>.</li> <li>Templates receive permission flags from <code>workflows.context_processors.role_context</code>.</li>
<li>Super-admin-only user management lives at <code>/admin-tools/users/</code> and is the preferred path for normal role assignment, account activation, password-reset mail dispatch, and controlled user deletion.</li> <li>Super-admin-only user management lives at <code>/admin-tools/users/</code> and is the preferred path for normal role assignment, account activation, invitation mail dispatch, password-reset mail dispatch, and controlled user deletion.</li>
<li>Backward-compatibility rule: authenticated legacy users with <code>is_staff=True</code> but no explicit role group currently fall back to the <code>Admin</code> capability set.</li> <li>Backward-compatibility rule: authenticated legacy users with <code>is_staff=True</code> but no explicit role group currently fall back to the <code>Admin</code> capability set.</li>
<li><code>superuser</code> accounts resolve to <code>Super Admin</code>.</li> <li><code>superuser</code> accounts resolve to <code>Super Admin</code>.</li>
<li>When adding a new operational page or action, define the capability in <code>roles.py</code>, gate the view, and hide the UI affordance when the capability is absent.</li> <li>When adding a new operational page or action, define the capability in <code>roles.py</code>, gate the view, and hide the UI affordance when the capability is absent.</li>

View File

@@ -178,7 +178,7 @@
<li><strong>Form Builder:</strong> manage field visibility/order/options.</li> <li><strong>Form Builder:</strong> manage field visibility/order/options.</li>
<li><strong>Einweisungs-Builder:</strong> manage custom checklist items for the intro PDF and live introduction checklist, including section, visibility, and conditional display logic.</li> <li><strong>Einweisungs-Builder:</strong> manage custom checklist items for the intro PDF and live introduction checklist, including section, visibility, and conditional display logic.</li>
<li><strong>Integrations:</strong> Nextcloud, SMTP, default routing addresses, notification rules, workflow rules, and remote backup target settings.</li> <li><strong>Integrations:</strong> Nextcloud, SMTP, default routing addresses, notification rules, workflow rules, and remote backup target settings.</li>
<li><strong>Benutzer &amp; Rollen:</strong> super-admin-only page for creating users, assigning roles, activating/deactivating access, sending password-reset links, and deleting accounts when appropriate.</li> <li><strong>Benutzer &amp; Rollen:</strong> 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.</li>
<li><strong>Welcome Emails:</strong> scheduled jobs, pause/resume/cancel/trigger now.</li> <li><strong>Welcome Emails:</strong> scheduled jobs, pause/resume/cancel/trigger now.</li>
<li><strong>Audit Log:</strong> 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.</li> <li><strong>Audit Log:</strong> 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.</li>
<li><strong>Requests Dashboard:</strong> search records, open PDFs, delete records (single/bulk for staff).</li> <li><strong>Requests Dashboard:</strong> search records, open PDFs, delete records (single/bulk for staff).</li>

View File

@@ -20,6 +20,7 @@
<section class="card"> <section class="card">
<h2>{% trans "Benutzer anlegen" %}</h2> <h2>{% trans "Benutzer anlegen" %}</h2>
<p class="sub">{% trans "Nach dem Anlegen wird automatisch eine Zugangseinladung mit Passwort-Link per E-Mail versendet." %}</p>
<form method="post" action="{% url 'create_user_from_admin' %}"> <form method="post" action="{% url 'create_user_from_admin' %}">
{% csrf_token %} {% csrf_token %}
<div class="grid"> <div class="grid">
@@ -48,20 +49,10 @@
{{ create_form.role_key }} {{ create_form.role_key }}
{% for error in create_form.role_key.errors %}<div class="hint">{{ error }}</div>{% endfor %} {% for error in create_form.role_key.errors %}<div class="hint">{{ error }}</div>{% endfor %}
</div> </div>
<div>
<label for="{{ create_form.password1.id_for_label }}">{{ create_form.password1.label }}</label>
{{ create_form.password1 }}
{% for error in create_form.password1.errors %}<div class="hint">{{ error }}</div>{% endfor %}
</div>
<div>
<label for="{{ create_form.password2.id_for_label }}">{{ create_form.password2.label }}</label>
{{ create_form.password2 }}
{% for error in create_form.password2.errors %}<div class="hint">{{ error }}</div>{% endfor %}
</div>
</div> </div>
{% for error in create_form.non_field_errors %}<div class="hint">{{ error }}</div>{% endfor %} {% for error in create_form.non_field_errors %}<div class="hint">{{ error }}</div>{% endfor %}
<div class="actions"> <div class="actions">
<button class="btn btn-primary" type="submit">{% trans "Benutzer erstellen" %}</button> <button class="btn btn-primary" type="submit">{% trans "Benutzer anlegen und einladen" %}</button>
</div> </div>
</form> </form>
</section> </section>

View File

@@ -394,6 +394,44 @@ def _would_remove_last_super_admin(user, new_role_key: str | None = None, new_is
return False 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') @_require_capability('manage_users')
def user_management_page(request): def user_management_page(request):
return _render_user_management(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) return _render_user_management(request, create_form=form, status_code=400)
user = form.save() user = form.save()
_send_user_access_email(request, user, invitation=True)
_audit( _audit(
request, request,
'user_created', 'user_created',
target_type='user', target_type='user',
target_id=user.id, target_id=user.id,
target_label=_display_user_name(user), 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') 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): def send_password_reset_from_admin(request, user_id: int):
user_model = get_user_model() user_model = get_user_model()
target_user = get_object_or_404(user_model, id=user_id) target_user = get_object_or_404(user_model, id=user_id)
email = (target_user.email or '').strip() try:
if not email: _send_user_access_email(request, target_user, invitation=False)
messages.error(request, _('Für diesen Benutzer ist keine E-Mail-Adresse hinterlegt.')) except ValueError as exc:
messages.error(request, str(exc))
return redirect('user_management_page') 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( _audit(
request, request,
'user_password_reset_sent', 'user_password_reset_sent',
target_type='user', target_type='user',
target_id=target_user.id, target_id=target_user.id,
target_label=_display_user_name(target_user), 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}) messages.success(request, _('Passwort-Reset-Link wurde versendet: %(username)s') % {'username': target_user.username})
return redirect('user_management_page') return redirect('user_management_page')