snapshot: preserve extended branding layer and branding UI polish
This commit is contained in:
@@ -2,14 +2,14 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: tubco-portal\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-03-26 10:55+0000\n"
|
||||
"POT-Creation-Date: 2026-03-26 11:02+0000\n"
|
||||
"PO-Revision-Date: 2026-03-24 00:00+0000\n"
|
||||
"Language: en\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: workflows/app_registry.py:32 workflows/models.py:261 workflows/models.py:342
|
||||
#: workflows/app_registry.py:32 workflows/models.py:273 workflows/models.py:354
|
||||
#: workflows/templates/workflows/onboarding_form.html:25
|
||||
#: workflows/templates/workflows/requests_dashboard.html:68
|
||||
#: workflows/templates/workflows/requests_dashboard.html:131
|
||||
@@ -36,7 +36,7 @@ msgstr "Multi-step form"
|
||||
msgid "E-Mail Routing"
|
||||
msgstr "Email routing"
|
||||
|
||||
#: workflows/app_registry.py:43 workflows/models.py:262 workflows/models.py:343
|
||||
#: workflows/app_registry.py:43 workflows/models.py:274 workflows/models.py:355
|
||||
#: workflows/templates/workflows/requests_dashboard.html:78
|
||||
#: workflows/templates/workflows/requests_dashboard.html:132
|
||||
msgid "Offboarding"
|
||||
@@ -213,7 +213,7 @@ msgstr "Django Admin"
|
||||
msgid "Vollständige Datenverwaltung."
|
||||
msgstr "Full data management."
|
||||
|
||||
#: workflows/app_registry.py:165 workflows/models.py:68
|
||||
#: workflows/app_registry.py:165 workflows/models.py:80
|
||||
msgid "Apps"
|
||||
msgstr "Apps"
|
||||
|
||||
@@ -221,7 +221,7 @@ msgstr "Apps"
|
||||
msgid "Wählen Sie den gewünschten Prozess."
|
||||
msgstr "Choose the desired process."
|
||||
|
||||
#: workflows/app_registry.py:171 workflows/models.py:69
|
||||
#: workflows/app_registry.py:171 workflows/models.py:81
|
||||
msgid "Platform Apps"
|
||||
msgstr ""
|
||||
|
||||
@@ -231,7 +231,7 @@ msgstr ""
|
||||
msgid "Produktweite Konfiguration und Produktsteuerung."
|
||||
msgstr "Configuration, tests, and controls."
|
||||
|
||||
#: workflows/app_registry.py:177 workflows/models.py:70
|
||||
#: workflows/app_registry.py:177 workflows/models.py:82
|
||||
msgid "Admin Apps"
|
||||
msgstr "Admin Apps"
|
||||
|
||||
@@ -338,307 +338,339 @@ msgstr "Invalid role."
|
||||
msgid "Nur Platform Owner dürfen diese Rolle vergeben."
|
||||
msgstr ""
|
||||
|
||||
#: workflows/forms.py:188
|
||||
#: workflows/forms.py:195
|
||||
msgid "Portal-Titel"
|
||||
msgstr "Portal title"
|
||||
|
||||
#: workflows/forms.py:189
|
||||
#: workflows/forms.py:196
|
||||
msgid "Firmenname"
|
||||
msgstr "Company name"
|
||||
|
||||
#: workflows/forms.py:190
|
||||
#: workflows/forms.py:197
|
||||
#, fuzzy
|
||||
#| msgid "Firmenname"
|
||||
msgid "Firmen-Domain"
|
||||
msgstr "Company name"
|
||||
|
||||
#: workflows/forms.py:191
|
||||
#: workflows/forms.py:198
|
||||
msgid "Support-E-Mail"
|
||||
msgstr "Support email"
|
||||
|
||||
#: workflows/forms.py:192
|
||||
#: workflows/forms.py:199
|
||||
msgid "Absender-Anzeigename"
|
||||
msgstr ""
|
||||
|
||||
#: workflows/forms.py:200
|
||||
msgid "Login-Untertitel"
|
||||
msgstr ""
|
||||
|
||||
#: workflows/forms.py:201
|
||||
msgid "Footer-Text DE"
|
||||
msgstr ""
|
||||
|
||||
#: workflows/forms.py:202
|
||||
msgid "Footer-Text EN"
|
||||
msgstr ""
|
||||
|
||||
#: workflows/forms.py:203
|
||||
msgid "Rechtlicher Hinweis DE"
|
||||
msgstr ""
|
||||
|
||||
#: workflows/forms.py:204
|
||||
msgid "Rechtlicher Hinweis EN"
|
||||
msgstr ""
|
||||
|
||||
#: workflows/forms.py:205
|
||||
msgid "Standardsprache"
|
||||
msgstr "Default language"
|
||||
|
||||
#: workflows/forms.py:193
|
||||
#: workflows/forms.py:206
|
||||
msgid "Logo"
|
||||
msgstr "Logo"
|
||||
|
||||
#: workflows/forms.py:194
|
||||
#: workflows/forms.py:207
|
||||
msgid "PDF-Briefkopf"
|
||||
msgstr "PDF letterhead"
|
||||
|
||||
#: workflows/forms.py:195
|
||||
#: workflows/forms.py:208
|
||||
msgid "Favicon"
|
||||
msgstr ""
|
||||
|
||||
#: workflows/forms.py:209
|
||||
msgid "Primärfarbe"
|
||||
msgstr "Primary color"
|
||||
|
||||
#: workflows/forms.py:196
|
||||
#: workflows/forms.py:210
|
||||
msgid "Sekundärfarbe"
|
||||
msgstr "Secondary color"
|
||||
|
||||
#: workflows/forms.py:210
|
||||
#: workflows/forms.py:227
|
||||
msgid "Das Logo darf maximal 5 MB groß sein."
|
||||
msgstr ""
|
||||
|
||||
#: workflows/forms.py:218
|
||||
#: workflows/forms.py:235
|
||||
msgid "Der PDF-Briefkopf darf maximal 10 MB groß sein."
|
||||
msgstr ""
|
||||
|
||||
#: workflows/forms.py:357 workflows/forms.py:542
|
||||
#: workflows/forms.py:243
|
||||
msgid "Das Favicon darf maximal 2 MB groß sein."
|
||||
msgstr ""
|
||||
|
||||
#: workflows/forms.py:382 workflows/forms.py:567
|
||||
#, python-format
|
||||
msgid "Bitte nutzen Sie das Format name@%(domain)s."
|
||||
msgstr ""
|
||||
|
||||
#: workflows/forms.py:379 workflows/forms.py:556
|
||||
#: workflows/forms.py:404 workflows/forms.py:581
|
||||
#, python-format
|
||||
msgid "Bitte verwenden Sie eine @%(domain)s E-Mail-Adresse."
|
||||
msgstr ""
|
||||
|
||||
#: workflows/forms.py:464
|
||||
#: workflows/forms.py:489
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Das Übergabedatum muss mindestens %(days)s Tage in der Zukunft liegen "
|
||||
"(frühestens %(date)s)."
|
||||
msgstr ""
|
||||
|
||||
#: workflows/models.py:139 workflows/views.py:200
|
||||
#: workflows/models.py:151 workflows/views.py:200
|
||||
msgid "Eingereicht"
|
||||
msgstr "Submitted"
|
||||
|
||||
#: workflows/models.py:140 workflows/views.py:201
|
||||
#: workflows/models.py:152 workflows/views.py:201
|
||||
msgid "In Bearbeitung"
|
||||
msgstr "Processing"
|
||||
|
||||
#: workflows/models.py:141 workflows/models.py:456 workflows/views.py:202
|
||||
#: workflows/models.py:153 workflows/models.py:468 workflows/views.py:202
|
||||
msgid "Abgeschlossen"
|
||||
msgstr "Completed"
|
||||
|
||||
#: workflows/models.py:142 workflows/models.py:396
|
||||
#: workflows/models.py:154 workflows/models.py:408
|
||||
#: workflows/templates/workflows/backup_recovery.html:70
|
||||
#: workflows/templates/workflows/requests_dashboard.html:222
|
||||
#: workflows/templates/workflows/welcome_emails.html:108 workflows/views.py:203
|
||||
msgid "Fehlgeschlagen"
|
||||
msgstr "Failed"
|
||||
|
||||
#: workflows/models.py:149
|
||||
#: workflows/models.py:161
|
||||
msgid "Herr"
|
||||
msgstr ""
|
||||
|
||||
#: workflows/models.py:149
|
||||
#: workflows/models.py:161
|
||||
msgid "Frau"
|
||||
msgstr ""
|
||||
|
||||
#: workflows/models.py:149
|
||||
#: workflows/models.py:161
|
||||
msgid "Divers"
|
||||
msgstr ""
|
||||
|
||||
#: workflows/models.py:159
|
||||
#: workflows/models.py:171
|
||||
msgid "befristet"
|
||||
msgstr ""
|
||||
|
||||
#: workflows/models.py:159
|
||||
#: workflows/models.py:171
|
||||
msgid "unbefristet"
|
||||
msgstr ""
|
||||
|
||||
#: workflows/models.py:222
|
||||
#: workflows/models.py:234
|
||||
#: workflows/templates/workflows/onboarding_intro_session.html:28
|
||||
#: workflows/templates/workflows/requests_dashboard.html:145
|
||||
msgid "Abteilung"
|
||||
msgstr "Department"
|
||||
|
||||
#: workflows/models.py:223
|
||||
#: workflows/models.py:235
|
||||
msgid "Geräte"
|
||||
msgstr ""
|
||||
|
||||
#: workflows/models.py:224
|
||||
#: workflows/models.py:236
|
||||
msgid "Software"
|
||||
msgstr ""
|
||||
|
||||
#: workflows/models.py:225
|
||||
#: workflows/models.py:237
|
||||
#, fuzzy
|
||||
#| msgid "Vorgänge"
|
||||
msgid "Zugänge"
|
||||
msgstr "Requests"
|
||||
|
||||
#: workflows/models.py:226
|
||||
#: workflows/models.py:238
|
||||
msgid "Workspace-Gruppen"
|
||||
msgstr ""
|
||||
|
||||
#: workflows/models.py:227
|
||||
#: workflows/models.py:239
|
||||
msgid "Ressourcen"
|
||||
msgstr ""
|
||||
|
||||
#: workflows/models.py:228
|
||||
#: workflows/models.py:240
|
||||
msgid "Telefonnummern"
|
||||
msgstr ""
|
||||
|
||||
#: workflows/models.py:254
|
||||
#: workflows/models.py:266
|
||||
msgid "Automatisch"
|
||||
msgstr ""
|
||||
|
||||
#: workflows/models.py:255 workflows/views.py:95
|
||||
#: workflows/models.py:267 workflows/views.py:95
|
||||
msgid "Stammdaten"
|
||||
msgstr "Master data"
|
||||
|
||||
#: workflows/models.py:256 workflows/views.py:96
|
||||
#: workflows/models.py:268 workflows/views.py:96
|
||||
msgid "Vertrag"
|
||||
msgstr "Contract"
|
||||
|
||||
#: workflows/models.py:257 workflows/views.py:97
|
||||
#: workflows/models.py:269 workflows/views.py:97
|
||||
msgid "IT-Setup"
|
||||
msgstr "IT setup"
|
||||
|
||||
#: workflows/models.py:258 workflows/views.py:98
|
||||
#: workflows/models.py:270 workflows/views.py:98
|
||||
msgid "Abschluss"
|
||||
msgstr "Finish"
|
||||
|
||||
#: workflows/models.py:300
|
||||
#: workflows/models.py:312
|
||||
#, fuzzy
|
||||
#| msgid "Onboarding"
|
||||
msgid "Onboarding: IT"
|
||||
msgstr "Onboarding"
|
||||
|
||||
#: workflows/models.py:301
|
||||
#: workflows/models.py:313
|
||||
#, fuzzy
|
||||
#| msgid "Offboarding-Anfrage speichern"
|
||||
msgid "Onboarding: Allgemeine Info"
|
||||
msgstr "Save offboarding request"
|
||||
|
||||
#: workflows/models.py:302
|
||||
#: workflows/models.py:314
|
||||
#, fuzzy
|
||||
#| msgid "Onboarding starten"
|
||||
msgid "Onboarding: Visitenkarte"
|
||||
msgstr "Start onboarding"
|
||||
|
||||
#: workflows/models.py:303
|
||||
#: workflows/models.py:315
|
||||
#, fuzzy
|
||||
#| msgid "Onboarding"
|
||||
msgid "Onboarding: HR Works"
|
||||
msgstr "Onboarding"
|
||||
|
||||
#: workflows/models.py:304
|
||||
#: workflows/models.py:316
|
||||
#, fuzzy
|
||||
#| msgid "Onboarding starten"
|
||||
msgid "Onboarding: Schlüssel"
|
||||
msgstr "Start onboarding"
|
||||
|
||||
#: workflows/models.py:305
|
||||
#: workflows/models.py:317
|
||||
msgid "Onboarding: Referenz Anfordernde Person"
|
||||
msgstr ""
|
||||
|
||||
#: workflows/models.py:306
|
||||
#: workflows/models.py:318
|
||||
#, fuzzy
|
||||
#| msgid "Welcome E-Mails"
|
||||
msgid "Onboarding: Welcome E-Mail"
|
||||
msgstr "Welcome Emails"
|
||||
|
||||
#: workflows/models.py:307
|
||||
#: workflows/models.py:319
|
||||
#, fuzzy
|
||||
#| msgid "Offboarding"
|
||||
msgid "Offboarding: IT"
|
||||
msgstr "Offboarding"
|
||||
|
||||
#: workflows/models.py:308
|
||||
#: workflows/models.py:320
|
||||
#, fuzzy
|
||||
#| msgid "Offboarding-Anfrage speichern"
|
||||
msgid "Offboarding: Allgemeine Info"
|
||||
msgstr "Save offboarding request"
|
||||
|
||||
#: workflows/models.py:309
|
||||
#: workflows/models.py:321
|
||||
#, fuzzy
|
||||
#| msgid "Offboarding starten"
|
||||
msgid "Offboarding: HR Works Deaktivierung"
|
||||
msgstr "Start offboarding"
|
||||
|
||||
#: workflows/models.py:310
|
||||
#: workflows/models.py:322
|
||||
msgid "Offboarding: Referenz Anfordernde Person"
|
||||
msgstr ""
|
||||
|
||||
#: workflows/models.py:346
|
||||
#: workflows/models.py:358
|
||||
msgid "Immer"
|
||||
msgstr ""
|
||||
|
||||
#: workflows/models.py:347 workflows/models.py:425
|
||||
#: workflows/models.py:359 workflows/models.py:437
|
||||
msgid "Enthält"
|
||||
msgstr ""
|
||||
|
||||
#: workflows/models.py:348 workflows/models.py:426
|
||||
#: workflows/models.py:360 workflows/models.py:438
|
||||
msgid "Ist gleich"
|
||||
msgstr ""
|
||||
|
||||
#: workflows/models.py:349
|
||||
#: workflows/models.py:361
|
||||
msgid "Ist aktiv/Ja"
|
||||
msgstr ""
|
||||
|
||||
#: workflows/models.py:350
|
||||
#: workflows/models.py:362
|
||||
#, fuzzy
|
||||
#| msgid "inaktiv"
|
||||
msgid "Ist inaktiv/Nein"
|
||||
msgstr "inactive"
|
||||
|
||||
#: workflows/models.py:392
|
||||
#: workflows/models.py:404
|
||||
#: workflows/templates/workflows/welcome_emails.html:100
|
||||
msgid "Geplant"
|
||||
msgstr "Scheduled"
|
||||
|
||||
#: workflows/models.py:393
|
||||
#: workflows/models.py:405
|
||||
#: workflows/templates/workflows/welcome_emails.html:102
|
||||
msgid "Pausiert"
|
||||
msgstr "Paused"
|
||||
|
||||
#: workflows/models.py:394
|
||||
#: workflows/models.py:406
|
||||
#: workflows/templates/workflows/welcome_emails.html:104
|
||||
msgid "Abgebrochen"
|
||||
msgstr "Cancelled"
|
||||
|
||||
#: workflows/models.py:395
|
||||
#: workflows/models.py:407
|
||||
#: workflows/templates/workflows/welcome_emails.html:106
|
||||
msgid "Gesendet"
|
||||
msgstr "Sent"
|
||||
|
||||
#: workflows/models.py:418 workflows/tasks.py:576
|
||||
#: workflows/models.py:430 workflows/tasks.py:576
|
||||
msgid "Geräte und Arbeitsplatz"
|
||||
msgstr "Devices and workplace"
|
||||
|
||||
#: workflows/models.py:419 workflows/tasks.py:577
|
||||
#: workflows/models.py:431 workflows/tasks.py:577
|
||||
msgid "Konten und Berechtigungen"
|
||||
msgstr "Accounts and permissions"
|
||||
|
||||
#: workflows/models.py:420 workflows/tasks.py:578
|
||||
#: workflows/models.py:432 workflows/tasks.py:578
|
||||
msgid "Software und Tools"
|
||||
msgstr "Software and tools"
|
||||
|
||||
#: workflows/models.py:421 workflows/tasks.py:579
|
||||
#: workflows/models.py:433 workflows/tasks.py:579
|
||||
msgid "Prozesse und Hinweise"
|
||||
msgstr "Processes and notes"
|
||||
|
||||
#: workflows/models.py:424
|
||||
#: workflows/models.py:436
|
||||
msgid "Immer anzeigen"
|
||||
msgstr "Always show"
|
||||
|
||||
#: workflows/models.py:427
|
||||
#: workflows/models.py:439
|
||||
msgid "Ist Ja / aktiv"
|
||||
msgstr "Is yes / active"
|
||||
|
||||
#: workflows/models.py:428
|
||||
#: workflows/models.py:440
|
||||
msgid "Ist Nein / inaktiv"
|
||||
msgstr "Is no / inactive"
|
||||
|
||||
#: workflows/models.py:455
|
||||
#: workflows/models.py:467
|
||||
msgid "Entwurf"
|
||||
msgstr "Draft"
|
||||
|
||||
#: workflows/models.py:475
|
||||
#: workflows/models.py:487
|
||||
#, fuzzy
|
||||
#| msgid "Nextcloud:"
|
||||
msgid "Nextcloud"
|
||||
msgstr "Nextcloud:"
|
||||
|
||||
#: workflows/models.py:476
|
||||
#: workflows/models.py:488
|
||||
msgid "S3"
|
||||
msgstr ""
|
||||
|
||||
#: workflows/models.py:477
|
||||
#: workflows/models.py:489
|
||||
msgid "NFS"
|
||||
msgstr ""
|
||||
|
||||
@@ -754,7 +786,6 @@ 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."
|
||||
|
||||
@@ -1173,28 +1204,28 @@ msgstr "Delete"
|
||||
msgid "Noch keine Backup-Bundles vorhanden."
|
||||
msgstr "No backup bundles available yet."
|
||||
|
||||
#: workflows/templates/workflows/base_shell.html:24
|
||||
#: workflows/templates/workflows/base_shell.html:31
|
||||
msgid "Bitte bestätigen"
|
||||
msgstr ""
|
||||
|
||||
#: workflows/templates/workflows/base_shell.html:28
|
||||
#: workflows/templates/workflows/base_shell.html:35
|
||||
#: workflows/templates/workflows/welcome_emails.html:134
|
||||
msgid "Abbrechen"
|
||||
msgstr "Cancel"
|
||||
|
||||
#: workflows/templates/workflows/base_shell.html:29
|
||||
#: workflows/templates/workflows/base_shell.html:36
|
||||
msgid "Bestätigen"
|
||||
msgstr ""
|
||||
|
||||
#: workflows/templates/workflows/base_shell.html:36
|
||||
#: workflows/templates/workflows/base_shell.html:43
|
||||
msgid "Bitte warten"
|
||||
msgstr "Please wait"
|
||||
|
||||
#: workflows/templates/workflows/base_shell.html:37
|
||||
#: workflows/templates/workflows/base_shell.html:44
|
||||
msgid "Aktion läuft"
|
||||
msgstr "Action in progress"
|
||||
|
||||
#: workflows/templates/workflows/base_shell.html:38
|
||||
#: workflows/templates/workflows/base_shell.html:45
|
||||
msgid "Die Aktion wird im aktuellen Tab ausgeführt."
|
||||
msgstr "The action is running in the current tab."
|
||||
|
||||
@@ -1209,28 +1240,43 @@ msgid ""
|
||||
"B. tub.co."
|
||||
msgstr ""
|
||||
|
||||
#: workflows/templates/workflows/branding_settings.html:53
|
||||
#: workflows/templates/workflows/branding_settings.html:41
|
||||
msgid "Wird für ausgehende System-E-Mails als Anzeigename verwendet."
|
||||
msgstr ""
|
||||
|
||||
#: workflows/templates/workflows/branding_settings.html:78
|
||||
msgid "Erlaubte Formate: SVG, PNG, JPG, JPEG, WEBP. Maximal 5 MB."
|
||||
msgstr ""
|
||||
|
||||
#: workflows/templates/workflows/branding_settings.html:56
|
||||
#: workflows/templates/workflows/branding_settings.html:81
|
||||
msgid "Aktuelles Logo:"
|
||||
msgstr "Current logo:"
|
||||
|
||||
#: workflows/templates/workflows/branding_settings.html:56
|
||||
#: workflows/templates/workflows/branding_settings.html:65
|
||||
#: workflows/templates/workflows/branding_settings.html:81
|
||||
#: workflows/templates/workflows/branding_settings.html:90
|
||||
#: workflows/templates/workflows/branding_settings.html:99
|
||||
msgid "öffnen"
|
||||
msgstr "open"
|
||||
|
||||
#: workflows/templates/workflows/branding_settings.html:62
|
||||
#: workflows/templates/workflows/branding_settings.html:87
|
||||
msgid "Erlaubtes Format: PDF. Maximal 10 MB."
|
||||
msgstr ""
|
||||
|
||||
#: workflows/templates/workflows/branding_settings.html:65
|
||||
#: workflows/templates/workflows/branding_settings.html:90
|
||||
msgid "Aktueller Briefkopf:"
|
||||
msgstr "Current letterhead:"
|
||||
|
||||
#: workflows/templates/workflows/branding_settings.html:70
|
||||
#: workflows/templates/workflows/branding_settings.html:96
|
||||
msgid "Erlaubte Formate: ICO, PNG, SVG, WEBP. Maximal 2 MB."
|
||||
msgstr ""
|
||||
|
||||
#: workflows/templates/workflows/branding_settings.html:99
|
||||
#, fuzzy
|
||||
#| msgid "Aktuelles Logo:"
|
||||
msgid "Aktuelles Favicon:"
|
||||
msgstr "Current logo:"
|
||||
|
||||
#: workflows/templates/workflows/branding_settings.html:104
|
||||
msgid ""
|
||||
"TUBCO bleibt als Standard erhalten, bis hier Werte geändert oder Dateien "
|
||||
"hochgeladen werden."
|
||||
@@ -1238,7 +1284,7 @@ msgstr ""
|
||||
"TUBCO remains the default until values are changed or files are uploaded "
|
||||
"here."
|
||||
|
||||
#: workflows/templates/workflows/branding_settings.html:71
|
||||
#: workflows/templates/workflows/branding_settings.html:105
|
||||
msgid "Branding speichern"
|
||||
msgstr "Save branding"
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ class AdminAuditLogAdmin(admin.ModelAdmin):
|
||||
|
||||
@admin.register(PortalBranding)
|
||||
class PortalBrandingAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'portal_title', 'company_name', 'support_email', 'default_language', 'updated_at')
|
||||
list_display = ('name', 'portal_title', 'company_name', 'company_domain', 'support_email', 'default_language', 'updated_at')
|
||||
|
||||
|
||||
@admin.register(PortalAppConfig)
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from email.utils import formataddr
|
||||
|
||||
from django.conf import settings
|
||||
from django.templatetags.static import static
|
||||
from django.utils.translation import get_language
|
||||
|
||||
from .models import PortalBranding
|
||||
|
||||
@@ -16,6 +18,12 @@ def get_portal_branding() -> PortalBranding:
|
||||
'company_name': 'TUBCO',
|
||||
'company_domain': 'tub.co',
|
||||
'support_email': 'info@tub.co',
|
||||
'sender_display_name': 'TUBCO',
|
||||
'login_subtitle': 'Bitte melden Sie sich mit Ihrem Benutzerkonto an.',
|
||||
'footer_text': 'TUBCO Onboarding & Offboarding Portal',
|
||||
'footer_text_en': 'TUBCO Onboarding & Offboarding Portal',
|
||||
'legal_notice': '',
|
||||
'legal_notice_en': '',
|
||||
'default_language': 'de',
|
||||
'primary_color': '#000078',
|
||||
'secondary_color': '#c0002b',
|
||||
@@ -40,6 +48,16 @@ def get_portal_logo_url() -> str:
|
||||
return static('workflows/img/tubco-logo.svg')
|
||||
|
||||
|
||||
def get_portal_favicon_url() -> str:
|
||||
branding = get_portal_branding()
|
||||
if branding.favicon_image:
|
||||
try:
|
||||
return branding.favicon_image.url
|
||||
except ValueError:
|
||||
pass
|
||||
return static('workflows/img/tubco-logo.svg')
|
||||
|
||||
|
||||
def get_portal_letterhead_path() -> Path:
|
||||
branding = get_portal_branding()
|
||||
if branding.pdf_letterhead:
|
||||
@@ -54,18 +72,31 @@ def get_portal_letterhead_path() -> Path:
|
||||
|
||||
def get_branding_context() -> dict[str, object]:
|
||||
branding = get_portal_branding()
|
||||
lang = (get_language() or branding.default_language or 'de').split('-')[0]
|
||||
footer_text = (branding.footer_text_en or '').strip() if lang == 'en' else ''
|
||||
legal_notice = (branding.legal_notice_en or '').strip() if lang == 'en' else ''
|
||||
if not footer_text:
|
||||
footer_text = (branding.footer_text or branding.portal_title).strip()
|
||||
if not legal_notice:
|
||||
legal_notice = (branding.legal_notice or '').strip()
|
||||
return {
|
||||
'portal_branding': branding,
|
||||
'portal_title': branding.portal_title,
|
||||
'portal_company_name': branding.company_name,
|
||||
'portal_email_domain': get_company_email_domain(),
|
||||
'portal_support_email': branding.support_email,
|
||||
'portal_sender_display_name': branding.sender_display_name or branding.company_name,
|
||||
'portal_login_subtitle': branding.login_subtitle,
|
||||
'portal_footer_text': footer_text,
|
||||
'portal_legal_notice': legal_notice,
|
||||
'portal_default_language': branding.default_language,
|
||||
'portal_primary_color': branding.primary_color,
|
||||
'portal_secondary_color': branding.secondary_color,
|
||||
'portal_logo_url': get_portal_logo_url(),
|
||||
'portal_favicon_url': get_portal_favicon_url(),
|
||||
'portal_has_custom_logo': bool(branding.logo_image),
|
||||
'portal_has_custom_letterhead': bool(branding.pdf_letterhead),
|
||||
'portal_has_custom_favicon': bool(branding.favicon_image),
|
||||
}
|
||||
|
||||
|
||||
@@ -78,9 +109,20 @@ def get_branding_email_copy() -> dict[str, str]:
|
||||
'company_domain': get_company_email_domain(),
|
||||
'portal_title': portal_title,
|
||||
'support_email': (branding.support_email or '').strip(),
|
||||
'sender_display_name': (branding.sender_display_name or company_name).strip(),
|
||||
}
|
||||
|
||||
|
||||
def get_branded_from_email(email_address: str | None) -> str | None:
|
||||
address = (email_address or '').strip()
|
||||
if not address:
|
||||
return None
|
||||
display_name = (get_branding_email_copy()['sender_display_name'] or '').strip()
|
||||
if not display_name:
|
||||
return address
|
||||
return formataddr((display_name, address))
|
||||
|
||||
|
||||
def get_default_notification_templates() -> dict[str, dict[str, str]]:
|
||||
from copy import deepcopy
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from django.conf import settings
|
||||
from django.core.mail import EmailMessage, get_connection
|
||||
|
||||
from .branding import get_branded_from_email
|
||||
from .models import SystemEmailConfig, WorkflowConfig
|
||||
|
||||
|
||||
@@ -66,7 +67,7 @@ def send_system_email(
|
||||
msg = EmailMessage(
|
||||
subject=subject,
|
||||
body=body,
|
||||
from_email=(from_email or smtp['from_email']),
|
||||
from_email=get_branded_from_email(from_email or smtp['from_email']) or (from_email or smtp['from_email']),
|
||||
to=to,
|
||||
connection=connection,
|
||||
)
|
||||
|
||||
@@ -178,9 +178,16 @@ class PortalBrandingForm(forms.ModelForm):
|
||||
'company_name',
|
||||
'company_domain',
|
||||
'support_email',
|
||||
'sender_display_name',
|
||||
'login_subtitle',
|
||||
'footer_text',
|
||||
'footer_text_en',
|
||||
'legal_notice',
|
||||
'legal_notice_en',
|
||||
'default_language',
|
||||
'logo_image',
|
||||
'pdf_letterhead',
|
||||
'favicon_image',
|
||||
'primary_color',
|
||||
'secondary_color',
|
||||
]
|
||||
@@ -189,9 +196,16 @@ class PortalBrandingForm(forms.ModelForm):
|
||||
'company_name': gettext_lazy('Firmenname'),
|
||||
'company_domain': gettext_lazy('Firmen-Domain'),
|
||||
'support_email': gettext_lazy('Support-E-Mail'),
|
||||
'sender_display_name': gettext_lazy('Absender-Anzeigename'),
|
||||
'login_subtitle': gettext_lazy('Login-Untertitel'),
|
||||
'footer_text': gettext_lazy('Footer-Text DE'),
|
||||
'footer_text_en': gettext_lazy('Footer-Text EN'),
|
||||
'legal_notice': gettext_lazy('Rechtlicher Hinweis DE'),
|
||||
'legal_notice_en': gettext_lazy('Rechtlicher Hinweis EN'),
|
||||
'default_language': gettext_lazy('Standardsprache'),
|
||||
'logo_image': gettext_lazy('Logo'),
|
||||
'pdf_letterhead': gettext_lazy('PDF-Briefkopf'),
|
||||
'favicon_image': gettext_lazy('Favicon'),
|
||||
'primary_color': gettext_lazy('Primärfarbe'),
|
||||
'secondary_color': gettext_lazy('Sekundärfarbe'),
|
||||
}
|
||||
@@ -200,6 +214,9 @@ class PortalBrandingForm(forms.ModelForm):
|
||||
'secondary_color': forms.TextInput(attrs={'type': 'color'}),
|
||||
'logo_image': forms.ClearableFileInput(attrs={'accept': '.svg,.png,.jpg,.jpeg,.webp'}),
|
||||
'pdf_letterhead': forms.ClearableFileInput(attrs={'accept': '.pdf'}),
|
||||
'favicon_image': forms.ClearableFileInput(attrs={'accept': '.ico,.png,.svg,.webp'}),
|
||||
'legal_notice': forms.Textarea(attrs={'rows': 3}),
|
||||
'legal_notice_en': forms.Textarea(attrs={'rows': 3}),
|
||||
}
|
||||
|
||||
def clean_logo_image(self):
|
||||
@@ -218,6 +235,14 @@ class PortalBrandingForm(forms.ModelForm):
|
||||
raise forms.ValidationError(_('Der PDF-Briefkopf darf maximal 10 MB groß sein.'))
|
||||
return letterhead
|
||||
|
||||
def clean_favicon_image(self):
|
||||
favicon = self.cleaned_data.get('favicon_image')
|
||||
if not favicon:
|
||||
return favicon
|
||||
if getattr(favicon, 'size', 0) > 2 * 1024 * 1024:
|
||||
raise forms.ValidationError(_('Das Favicon darf maximal 2 MB groß sein.'))
|
||||
return favicon
|
||||
|
||||
|
||||
class OnboardingRequestForm(forms.ModelForm):
|
||||
first_name = forms.CharField(label='Vorname', required=False)
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Generated by Django 5.1.5 on 2026-03-26 11:02
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('workflows', '0039_portalbranding_company_domain'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='portalbranding',
|
||||
name='favicon_image',
|
||||
field=models.FileField(blank=True, null=True, upload_to='branding/', validators=[django.core.validators.FileExtensionValidator(allowed_extensions=['ico', 'png', 'svg', 'webp'])]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='portalbranding',
|
||||
name='footer_text',
|
||||
field=models.CharField(blank=True, default='TUBCO Onboarding & Offboarding Portal', max_length=255),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='portalbranding',
|
||||
name='footer_text_en',
|
||||
field=models.CharField(blank=True, default='TUBCO Onboarding & Offboarding Portal', max_length=255),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='portalbranding',
|
||||
name='legal_notice',
|
||||
field=models.TextField(blank=True, default=''),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='portalbranding',
|
||||
name='legal_notice_en',
|
||||
field=models.TextField(blank=True, default=''),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='portalbranding',
|
||||
name='login_subtitle',
|
||||
field=models.CharField(blank=True, default='Bitte melden Sie sich mit Ihrem Benutzerkonto an.', max_length=255),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='portalbranding',
|
||||
name='sender_display_name',
|
||||
field=models.CharField(blank=True, default='TUBCO', max_length=255),
|
||||
),
|
||||
]
|
||||
@@ -31,6 +31,12 @@ class PortalBranding(models.Model):
|
||||
company_name = models.CharField(max_length=255, default='TUBCO')
|
||||
company_domain = models.CharField(max_length=120, blank=True, default='tub.co')
|
||||
support_email = models.EmailField(blank=True, default='info@tub.co')
|
||||
sender_display_name = models.CharField(max_length=255, blank=True, default='TUBCO')
|
||||
login_subtitle = models.CharField(max_length=255, blank=True, default='Bitte melden Sie sich mit Ihrem Benutzerkonto an.')
|
||||
footer_text = models.CharField(max_length=255, blank=True, default='TUBCO Onboarding & Offboarding Portal')
|
||||
footer_text_en = models.CharField(max_length=255, blank=True, default='TUBCO Onboarding & Offboarding Portal')
|
||||
legal_notice = models.TextField(blank=True, default='')
|
||||
legal_notice_en = models.TextField(blank=True, default='')
|
||||
default_language = models.CharField(
|
||||
max_length=10,
|
||||
choices=[('de', 'Deutsch'), ('en', 'English')],
|
||||
@@ -48,6 +54,12 @@ class PortalBranding(models.Model):
|
||||
null=True,
|
||||
validators=[FileExtensionValidator(allowed_extensions=['pdf'])],
|
||||
)
|
||||
favicon_image = models.FileField(
|
||||
upload_to='branding/',
|
||||
blank=True,
|
||||
null=True,
|
||||
validators=[FileExtensionValidator(allowed_extensions=['ico', 'png', 'svg', 'webp'])],
|
||||
)
|
||||
primary_color = models.CharField(max_length=20, blank=True, default='#000078')
|
||||
secondary_color = models.CharField(max_length=20, blank=True, default='#c0002b')
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
@@ -7,6 +7,30 @@ h1 { margin: 12px 0 6px; color: #000078; }
|
||||
.app-messages { margin-bottom: 12px; }
|
||||
.card { border: 1px solid #d8e3f0; border-radius: 12px; background: #fbfdff; padding: 12px; margin-bottom: 14px; transition: border-color 180ms cubic-bezier(0.2, 0.8, 0.2, 1), box-shadow 220ms cubic-bezier(0.2, 0.8, 0.2, 1), transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1); }
|
||||
.grid { display: grid; grid-template-columns: repeat(2, minmax(240px, 1fr)); gap: 10px; }
|
||||
.branding-sections { display: grid; gap: 14px; }
|
||||
.branding-block { border: 1px solid #dce5f1; border-radius: 16px; background: linear-gradient(180deg, rgba(255,255,255,0.98), rgba(246,250,255,0.94)); padding: 14px; box-shadow: inset 0 1px 0 rgba(255,255,255,0.92); }
|
||||
.branding-block-head { margin-bottom: 12px; }
|
||||
.branding-block-head h2 { margin: 0; color: #17345e; font-size: 18px; }
|
||||
.branding-block-head p { margin: 4px 0 0; color: #60738d; font-size: 13px; }
|
||||
.lang-pairs { align-items: start; }
|
||||
.lang-block { border: 1px solid #d9e4f1; border-radius: 14px; background: rgba(255,255,255,0.82); padding: 12px; }
|
||||
.lang-block h3 { margin: 0 0 10px; color: #223b63; font-size: 15px; }
|
||||
.branding-preview { max-width: 460px; margin-left: auto; border: 1px solid #dce5f1; border-radius: 18px; background:
|
||||
radial-gradient(circle at top right, rgba(59,112,234,0.10), transparent 30%),
|
||||
linear-gradient(180deg, #f9fbff, #eef4ff);
|
||||
padding: 10px; }
|
||||
.branding-preview-shell { border: 1px solid rgba(210, 221, 236, 0.95); border-radius: 18px; overflow: hidden; background: linear-gradient(180deg, rgba(255,255,255,0.99), rgba(247,250,255,0.96)); box-shadow: 0 8px 22px rgba(16, 32, 57, 0.05); }
|
||||
.branding-preview-header { display: flex; align-items: center; gap: 10px; padding: 10px 12px; border-bottom: 1px solid rgba(217, 227, 238, 0.9); }
|
||||
.branding-preview-logo { width: 64px; max-width: 100%; height: auto; display: block; object-fit: contain; filter: saturate(1.02); }
|
||||
.branding-preview-copy { display: grid; gap: 2px; min-width: 0; }
|
||||
.branding-preview-copy strong { color: #18335b; font-size: 13px; line-height: 1.2; }
|
||||
.branding-preview-copy span { color: #61738d; font-size: 12px; line-height: 1.3; }
|
||||
.branding-preview-band { display: flex; gap: 8px; padding: 10px 12px; }
|
||||
.branding-preview-chip { display: inline-flex; align-items: center; justify-content: center; min-width: 104px; padding: 5px 10px; border-radius: 999px; color: #fff; font-size: 10px; font-weight: 800; letter-spacing: 0.04em; text-transform: uppercase; background: #000078; box-shadow: inset 0 1px 0 rgba(255,255,255,0.16); }
|
||||
.branding-preview-chip-secondary { background: #c0002b; }
|
||||
.branding-preview-footer { padding: 0 12px 12px; }
|
||||
.branding-preview-footer-main { color: #20385f; font-size: 11px; font-weight: 700; line-height: 1.35; }
|
||||
.branding-preview-footer-legal { margin-top: 4px; color: #6c7f99; font-size: 10px; line-height: 1.4; }
|
||||
.backup-grid { grid-template-columns: minmax(280px, 720px); }
|
||||
label { display: block; margin-bottom: 4px; font-size: 12px; color: #334155; font-weight: 700; }
|
||||
input, select, textarea { width: 100%; box-sizing: border-box; border: 1px solid #cbd5e1; border-radius: 8px; padding: 8px 9px; background: #fff; transition: border-color 180ms cubic-bezier(0.2, 0.8, 0.2, 1), box-shadow 180ms cubic-bezier(0.2, 0.8, 0.2, 1), background-color 180ms cubic-bezier(0.2, 0.8, 0.2, 1); }
|
||||
@@ -56,8 +80,13 @@ th { background: #f6f9ff; color: #334155; }
|
||||
.bulk-note { color: #64748b; font-size: 12px; }
|
||||
.field label { display: block; font-weight: 600; margin-bottom: 6px; }
|
||||
.field input, .field select { min-height: 40px; }
|
||||
.field-full { grid-column: 1 / -1; }
|
||||
.mini { color: #64748b; font-size: 12px; }
|
||||
.table-controls input[type="text"], .table-controls select { width: 100%; min-height: 36px; padding: 7px 9px; border: 1px solid #cfd9e8; border-radius: 8px; box-sizing: border-box; }
|
||||
.table-controls input[type="checkbox"] { transform: scale(1.1); width: auto; }
|
||||
.actions { white-space: nowrap; }
|
||||
@media (max-width: 760px) { .grid { grid-template-columns: 1fr; } }
|
||||
@media (max-width: 760px) {
|
||||
.grid { grid-template-columns: 1fr; }
|
||||
.branding-preview-header { flex-direction: column; align-items: flex-start; }
|
||||
.branding-preview-band { flex-wrap: wrap; }
|
||||
}
|
||||
|
||||
@@ -122,6 +122,25 @@
|
||||
margin-right: auto !important;
|
||||
}
|
||||
|
||||
.app-site-footer {
|
||||
width: min(var(--app-shell-width), 100%);
|
||||
margin: 14px auto 0;
|
||||
padding: 0 10px 18px;
|
||||
color: #5f728d;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.app-site-footer-main {
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.app-site-footer-legal {
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.app-header,
|
||||
.app-header-in-shell {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<section class="login-shell-body">
|
||||
<div class="login-card">
|
||||
<h1>{% trans "Anmeldung" %}</h1>
|
||||
<p>{% trans "Bitte melden Sie sich mit Ihrem Benutzerkonto an." %}</p>
|
||||
<p>{{ portal_login_subtitle }}</p>
|
||||
|
||||
<form method="post" action="/accounts/login/">
|
||||
{% csrf_token %}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>{% block title %}{% endblock %}</title>
|
||||
<link rel="icon" href="{{ portal_favicon_url }}" />
|
||||
<link rel="stylesheet" href="{% static 'workflows/css/buttons.css' %}" />
|
||||
<link rel="stylesheet" href="{% static 'workflows/css/app_chrome.css' %}" />
|
||||
{% block extra_css %}{% endblock %}
|
||||
@@ -17,6 +18,12 @@
|
||||
{% block shell_header %}{% endblock %}
|
||||
{% block shell_body %}{% endblock %}
|
||||
</div>
|
||||
{% if portal_footer_text or portal_legal_notice %}
|
||||
<div class="app-site-footer">
|
||||
{% if portal_footer_text %}<div class="app-site-footer-main">{{ portal_footer_text }}</div>{% endif %}
|
||||
{% if portal_legal_notice %}<div class="app-site-footer-legal">{{ portal_legal_notice }}</div>{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="confirm-modal" id="app-confirm-modal" hidden aria-hidden="true">
|
||||
<div class="confirm-backdrop" data-confirm-close="1"></div>
|
||||
<div class="confirm-dialog" role="dialog" aria-modal="true" aria-labelledby="app-confirm-title" aria-describedby="app-confirm-message">
|
||||
|
||||
@@ -17,54 +17,150 @@
|
||||
<section class="card">
|
||||
<form method="post" action="{% url 'save_portal_branding' %}" enctype="multipart/form-data" class="stack-form">
|
||||
{% csrf_token %}
|
||||
<div class="grid two">
|
||||
<div class="field">
|
||||
<label for="{{ form.portal_title.id_for_label }}">{{ form.portal_title.label }}</label>
|
||||
{{ form.portal_title }}
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="{{ form.company_name.id_for_label }}">{{ form.company_name.label }}</label>
|
||||
{{ form.company_name }}
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="{{ form.company_domain.id_for_label }}">{{ form.company_domain.label }}</label>
|
||||
{{ form.company_domain }}
|
||||
<div class="hint">{% trans "Wird für E-Mail-Vorschläge und Domain-bezogene Standardtexte verwendet, z. B. tub.co." %}</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="{{ form.support_email.id_for_label }}">{{ form.support_email.label }}</label>
|
||||
{{ form.support_email }}
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="{{ form.default_language.id_for_label }}">{{ form.default_language.label }}</label>
|
||||
{{ form.default_language }}
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="{{ form.primary_color.id_for_label }}">{{ form.primary_color.label }}</label>
|
||||
{{ form.primary_color }}
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="{{ form.secondary_color.id_for_label }}">{{ form.secondary_color.label }}</label>
|
||||
{{ form.secondary_color }}
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="{{ form.logo_image.id_for_label }}">{{ form.logo_image.label }}</label>
|
||||
{{ form.logo_image }}
|
||||
<div class="hint">{% trans "Erlaubte Formate: SVG, PNG, JPG, JPEG, WEBP. Maximal 5 MB." %}</div>
|
||||
{% for error in form.logo_image.errors %}<div class="hint">{{ error }}</div>{% endfor %}
|
||||
{% if branding.logo_image %}
|
||||
<div class="hint">{% trans "Aktuelles Logo:" %} <a href="{{ branding.logo_image.url }}" target="_blank" rel="noopener">{% trans "öffnen" %}</a></div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="{{ form.pdf_letterhead.id_for_label }}">{{ form.pdf_letterhead.label }}</label>
|
||||
{{ form.pdf_letterhead }}
|
||||
<div class="hint">{% trans "Erlaubtes Format: PDF. Maximal 10 MB." %}</div>
|
||||
{% for error in form.pdf_letterhead.errors %}<div class="hint">{{ error }}</div>{% endfor %}
|
||||
{% if branding.pdf_letterhead %}
|
||||
<div class="hint">{% trans "Aktueller Briefkopf:" %} <a href="{{ branding.pdf_letterhead.url }}" target="_blank" rel="noopener">{% trans "öffnen" %}</a></div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="branding-sections">
|
||||
<section class="branding-block">
|
||||
<div class="branding-block-head">
|
||||
<h2>{% trans "Identität" %}</h2>
|
||||
<p>{% trans "Titel, Firmenname und zentrale Spracheinstellungen." %}</p>
|
||||
</div>
|
||||
<div class="grid two">
|
||||
<div class="field">
|
||||
<label for="{{ form.portal_title.id_for_label }}">{{ form.portal_title.label }}</label>
|
||||
{{ form.portal_title }}
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="{{ form.company_name.id_for_label }}">{{ form.company_name.label }}</label>
|
||||
{{ form.company_name }}
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="{{ form.company_domain.id_for_label }}">{{ form.company_domain.label }}</label>
|
||||
{{ form.company_domain }}
|
||||
<div class="hint">{% trans "Wird für E-Mail-Vorschläge und Domain-bezogene Standardtexte verwendet, z. B. tub.co." %}</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="{{ form.default_language.id_for_label }}">{{ form.default_language.label }}</label>
|
||||
{{ form.default_language }}
|
||||
</div>
|
||||
<div class="field field-full">
|
||||
<label for="{{ form.login_subtitle.id_for_label }}">{{ form.login_subtitle.label }}</label>
|
||||
{{ form.login_subtitle }}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="branding-block">
|
||||
<div class="branding-block-head">
|
||||
<h2>{% trans "Farben & Erscheinungsbild" %}</h2>
|
||||
<p>{% trans "Zentrale visuelle Markenwerte und Browser-Icon." %}</p>
|
||||
</div>
|
||||
<div class="grid two">
|
||||
<div class="field">
|
||||
<label for="{{ form.primary_color.id_for_label }}">{{ form.primary_color.label }}</label>
|
||||
{{ form.primary_color }}
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="{{ form.secondary_color.id_for_label }}">{{ form.secondary_color.label }}</label>
|
||||
{{ form.secondary_color }}
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="{{ form.logo_image.id_for_label }}">{{ form.logo_image.label }}</label>
|
||||
{{ form.logo_image }}
|
||||
<div class="hint">{% trans "Erlaubte Formate: SVG, PNG, JPG, JPEG, WEBP. Maximal 5 MB." %}</div>
|
||||
{% for error in form.logo_image.errors %}<div class="hint">{{ error }}</div>{% endfor %}
|
||||
{% if branding.logo_image %}
|
||||
<div class="hint">{% trans "Aktuelles Logo:" %} <a href="{{ branding.logo_image.url }}" target="_blank" rel="noopener">{% trans "öffnen" %}</a></div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="{{ form.favicon_image.id_for_label }}">{{ form.favicon_image.label }}</label>
|
||||
{{ form.favicon_image }}
|
||||
<div class="hint">{% trans "Erlaubte Formate: ICO, PNG, SVG, WEBP. Maximal 2 MB." %}</div>
|
||||
{% for error in form.favicon_image.errors %}<div class="hint">{{ error }}</div>{% endfor %}
|
||||
{% if branding.favicon_image %}
|
||||
<div class="hint">{% trans "Aktuelles Favicon:" %} <a href="{{ branding.favicon_image.url }}" target="_blank" rel="noopener">{% trans "öffnen" %}</a></div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="field field-full">
|
||||
<div class="branding-preview" id="branding-preview" data-default-logo="{{ portal_logo_url }}">
|
||||
<div class="branding-preview-shell">
|
||||
<div class="branding-preview-header">
|
||||
<img class="branding-preview-logo" id="branding-preview-logo" src="{{ portal_logo_url }}" alt="{{ portal_company_name }} Logo" />
|
||||
<div class="branding-preview-copy">
|
||||
<strong id="branding-preview-company">{{ branding.company_name }}</strong>
|
||||
<span id="branding-preview-title">{{ branding.portal_title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="branding-preview-band">
|
||||
<span class="branding-preview-chip" id="branding-preview-primary">{% trans "Primärfarbe" %}</span>
|
||||
<span class="branding-preview-chip branding-preview-chip-secondary" id="branding-preview-secondary">{% trans "Sekundärfarbe" %}</span>
|
||||
</div>
|
||||
<div class="branding-preview-footer">
|
||||
<div class="branding-preview-footer-main" id="branding-preview-footer">{{ branding.footer_text|default:branding.portal_title }}</div>
|
||||
<div class="branding-preview-footer-legal" id="branding-preview-legal">{{ branding.legal_notice }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="branding-block">
|
||||
<div class="branding-block-head">
|
||||
<h2>{% trans "Kommunikation" %}</h2>
|
||||
<p>{% trans "Absender, Support und PDF-Branding für ausgehende Kommunikation." %}</p>
|
||||
</div>
|
||||
<div class="grid two">
|
||||
<div class="field">
|
||||
<label for="{{ form.support_email.id_for_label }}">{{ form.support_email.label }}</label>
|
||||
{{ form.support_email }}
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="{{ form.sender_display_name.id_for_label }}">{{ form.sender_display_name.label }}</label>
|
||||
{{ form.sender_display_name }}
|
||||
<div class="hint">{% trans "Wird für ausgehende System-E-Mails als Anzeigename verwendet." %}</div>
|
||||
</div>
|
||||
<div class="field field-full">
|
||||
<label for="{{ form.pdf_letterhead.id_for_label }}">{{ form.pdf_letterhead.label }}</label>
|
||||
{{ form.pdf_letterhead }}
|
||||
<div class="hint">{% trans "Erlaubtes Format: PDF. Maximal 10 MB." %}</div>
|
||||
{% for error in form.pdf_letterhead.errors %}<div class="hint">{{ error }}</div>{% endfor %}
|
||||
{% if branding.pdf_letterhead %}
|
||||
<div class="hint">{% trans "Aktueller Briefkopf:" %} <a href="{{ branding.pdf_letterhead.url }}" target="_blank" rel="noopener">{% trans "öffnen" %}</a></div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="branding-block">
|
||||
<div class="branding-block-head">
|
||||
<h2>{% trans "Footer & Rechtliches" %}</h2>
|
||||
<p>{% trans "Gemeinsame Footer-Texte und rechtliche Hinweise für die Shell." %}</p>
|
||||
</div>
|
||||
<div class="grid two lang-pairs">
|
||||
<div class="lang-block">
|
||||
<h3>{% trans "Deutsch" %}</h3>
|
||||
<div class="field">
|
||||
<label for="{{ form.footer_text.id_for_label }}">{{ form.footer_text.label }}</label>
|
||||
{{ form.footer_text }}
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="{{ form.legal_notice.id_for_label }}">{{ form.legal_notice.label }}</label>
|
||||
{{ form.legal_notice }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="lang-block">
|
||||
<h3>{% trans "English" %}</h3>
|
||||
<div class="field">
|
||||
<label for="{{ form.footer_text_en.id_for_label }}">{{ form.footer_text_en.label }}</label>
|
||||
{{ form.footer_text_en }}
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="{{ form.legal_notice_en.id_for_label }}">{{ form.legal_notice_en.label }}</label>
|
||||
{{ form.legal_notice_en }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div class="toolbar" style="margin-top:1.25rem;">
|
||||
<div class="hint">{% trans "TUBCO bleibt als Standard erhalten, bis hier Werte geändert oder Dateien hochgeladen werden." %}</div>
|
||||
@@ -73,3 +169,59 @@
|
||||
</form>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_scripts %}
|
||||
<script>
|
||||
(() => {
|
||||
const byId = (id) => document.getElementById(id);
|
||||
const title = byId('{{ form.portal_title.id_for_label }}');
|
||||
const company = byId('{{ form.company_name.id_for_label }}');
|
||||
const footer = byId('{{ form.footer_text.id_for_label }}');
|
||||
const legal = byId('{{ form.legal_notice.id_for_label }}');
|
||||
const primary = byId('{{ form.primary_color.id_for_label }}');
|
||||
const secondary = byId('{{ form.secondary_color.id_for_label }}');
|
||||
const logo = byId('{{ form.logo_image.id_for_label }}');
|
||||
const previewLogo = byId('branding-preview-logo');
|
||||
const previewTitle = byId('branding-preview-title');
|
||||
const previewCompany = byId('branding-preview-company');
|
||||
const previewFooter = byId('branding-preview-footer');
|
||||
const previewLegal = byId('branding-preview-legal');
|
||||
const previewPrimary = byId('branding-preview-primary');
|
||||
const previewSecondary = byId('branding-preview-secondary');
|
||||
const preview = byId('branding-preview');
|
||||
if (!preview) return;
|
||||
|
||||
const defaultLogo = preview.dataset.defaultLogo || '';
|
||||
|
||||
function syncPreview() {
|
||||
if (previewTitle && title) previewTitle.textContent = title.value || '{{ branding.portal_title|escapejs }}';
|
||||
if (previewCompany && company) previewCompany.textContent = company.value || '{{ branding.company_name|escapejs }}';
|
||||
if (previewFooter && footer) previewFooter.textContent = footer.value || '{{ branding.footer_text|default:branding.portal_title|escapejs }}';
|
||||
if (previewLegal && legal) previewLegal.textContent = legal.value || '{{ branding.legal_notice|escapejs }}';
|
||||
if (previewPrimary && primary) previewPrimary.style.background = primary.value || '#000078';
|
||||
if (previewSecondary && secondary) previewSecondary.style.background = secondary.value || '#c0002b';
|
||||
}
|
||||
|
||||
[title, company, footer, legal, primary, secondary].forEach((input) => {
|
||||
if (input) input.addEventListener('input', syncPreview);
|
||||
});
|
||||
|
||||
if (logo && previewLogo) {
|
||||
logo.addEventListener('change', () => {
|
||||
const file = logo.files && logo.files[0];
|
||||
if (!file) {
|
||||
previewLogo.src = defaultLogo;
|
||||
return;
|
||||
}
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
previewLogo.src = event.target.result;
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
}
|
||||
|
||||
syncPreview();
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -176,8 +176,10 @@ docker compose exec -T web django-admin compilemessages</code></pre>
|
||||
<ul>
|
||||
<li>Portal-level branding is stored in the singleton model <code>PortalBranding</code>.</li>
|
||||
<li>Configured from Admin Apps → <code>Branding</code>.</li>
|
||||
<li>Current scope: portal title, company name, support email, default language, logo, PDF letterhead, and primary/secondary colors.</li>
|
||||
<li>Current scope: portal title, company name, company domain, support email, sender display name, login subtitle, footer/legal text, logo, favicon, PDF letterhead, and primary/secondary colors.</li>
|
||||
<li>Shared header/logo rendering now uses the branding context processor instead of hardcoded TUBCO asset references.</li>
|
||||
<li>The company domain now drives onboarding/offboarding email autofill and domain validation, so new customer deployments no longer require <code>@tub.co</code> code changes.</li>
|
||||
<li>Outgoing system mail sender names are now branded through the same layer.</li>
|
||||
<li>User invitation emails and welcome-template fallbacks also use the configured branding defaults.</li>
|
||||
</ul>
|
||||
|
||||
|
||||
@@ -178,7 +178,7 @@
|
||||
<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>Integrations:</strong> Nextcloud, SMTP, default routing addresses, notification rules, workflow rules, and remote backup target settings.</li>
|
||||
<li><strong>Branding:</strong> portal title, company name, logo, support email, default language, PDF letterhead, and basic brand colors.</li>
|
||||
<li><strong>Branding:</strong> portal title, company name, company domain, support email, sender display name, logo, favicon, default language, PDF letterhead, footer/legal text, and basic brand colors.</li>
|
||||
<li><strong>App Registry:</strong> platform-level registry for enabling, ordering, and relabeling landing-page apps without editing the home template.</li>
|
||||
<li><strong>Benutzer & 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>
|
||||
|
||||
Reference in New Issue
Block a user