snapshot: preserve branding foundation and platform owner split
This commit is contained in:
@@ -25,7 +25,8 @@ from django.utils.translation import get_language, override
|
||||
from django.urls import reverse
|
||||
|
||||
from .backup_ops import create_backup_bundle, delete_backup_bundle, list_backup_bundles, verify_backup_bundle
|
||||
from .forms import OffboardingRequestForm, OnboardingRequestForm, UserManagementCreateForm
|
||||
from .branding import get_branding_email_copy, get_default_notification_templates
|
||||
from .forms import OffboardingRequestForm, OnboardingRequestForm, PortalBrandingForm, UserManagementCreateForm
|
||||
from .form_builder import (
|
||||
DEFAULT_FIELD_ORDER,
|
||||
LOCKED_FIELD_RULES,
|
||||
@@ -34,12 +35,11 @@ from .form_builder import (
|
||||
ONBOARDING_PAGE_ORDER,
|
||||
ensure_form_field_configs,
|
||||
)
|
||||
from .models import AdminAuditLog, EmployeeProfile, FormFieldConfig, FormOption, IntroChecklistItem, NotificationRule, NotificationTemplate, OffboardingRequest, OnboardingIntroductionSession, OnboardingRequest, ScheduledWelcomeEmail, SystemEmailConfig, WorkflowConfig
|
||||
from .models import AdminAuditLog, EmployeeProfile, FormFieldConfig, FormOption, IntroChecklistItem, NotificationRule, NotificationTemplate, OffboardingRequest, OnboardingIntroductionSession, OnboardingRequest, PortalBranding, ScheduledWelcomeEmail, SystemEmailConfig, WorkflowConfig
|
||||
from .emailing import send_system_email
|
||||
from .roles import ROLE_GROUP_NAMES, ROLE_LABELS, ROLE_SUPER_ADMIN, assign_user_role, get_user_role_key, get_user_role_label, user_has_capability
|
||||
from .roles import ROLE_GROUP_NAMES, ROLE_LABELS, ROLE_PLATFORM_OWNER, ROLE_SUPER_ADMIN, assign_user_role, get_user_role_key, get_user_role_label, user_has_capability
|
||||
from .services import get_email_test_redirect, is_email_test_mode, is_nextcloud_enabled, upload_to_nextcloud
|
||||
from .tasks import (
|
||||
DEFAULT_NOTIFICATION_TEMPLATES,
|
||||
_generate_onboarding_intro_pdf,
|
||||
_generate_onboarding_intro_session_pdf,
|
||||
build_intro_sections_for_request,
|
||||
@@ -341,6 +341,7 @@ def home(request):
|
||||
def _user_management_rows():
|
||||
user_model = get_user_model()
|
||||
role_order = {
|
||||
ROLE_PLATFORM_OWNER: 0,
|
||||
ROLE_SUPER_ADMIN: 0,
|
||||
'admin': 1,
|
||||
'it_staff': 2,
|
||||
@@ -372,19 +373,30 @@ def _render_user_management(request, create_form=None, status_code: int = 200):
|
||||
row.action_label = _audit_action_label(row.action)
|
||||
role_key = (row.details or {}).get('role')
|
||||
row.role_label = str(ROLE_LABELS[role_key]) if role_key in ROLE_LABELS else role_key
|
||||
include_product_owner = get_user_role_key(request.user) == ROLE_PLATFORM_OWNER
|
||||
return render(
|
||||
request,
|
||||
'workflows/user_management.html',
|
||||
{
|
||||
'create_form': create_form or UserManagementCreateForm(),
|
||||
'create_form': create_form or UserManagementCreateForm(include_product_owner=include_product_owner),
|
||||
'rows': _user_management_rows(),
|
||||
'role_choices': [(key, str(ROLE_LABELS[key])) for key in ROLE_GROUP_NAMES],
|
||||
'role_choices': [
|
||||
(key, str(ROLE_LABELS[key]))
|
||||
for key in ROLE_GROUP_NAMES
|
||||
if include_product_owner or key != ROLE_PLATFORM_OWNER
|
||||
],
|
||||
'include_product_owner': include_product_owner,
|
||||
'recent_user_events': recent_user_events,
|
||||
},
|
||||
status=status_code,
|
||||
)
|
||||
|
||||
|
||||
def _platform_owner_user_count() -> int:
|
||||
user_model = get_user_model()
|
||||
return sum(1 for user in user_model.objects.all() if get_user_role_key(user) == ROLE_PLATFORM_OWNER and user.is_active)
|
||||
|
||||
|
||||
def _super_admin_user_count() -> int:
|
||||
user_model = get_user_model()
|
||||
return sum(1 for user in user_model.objects.all() if get_user_role_key(user) == ROLE_SUPER_ADMIN and user.is_active)
|
||||
@@ -404,6 +416,20 @@ def _would_remove_last_super_admin(user, new_role_key: str | None = None, new_is
|
||||
return False
|
||||
|
||||
|
||||
def _would_remove_last_platform_owner(user, new_role_key: str | None = None, new_is_active: bool | None = None, deleting: bool = False) -> bool:
|
||||
if get_user_role_key(user) != ROLE_PLATFORM_OWNER or not user.is_active:
|
||||
return False
|
||||
if _platform_owner_user_count() > 1:
|
||||
return False
|
||||
if deleting:
|
||||
return True
|
||||
if new_role_key is not None and new_role_key != ROLE_PLATFORM_OWNER:
|
||||
return True
|
||||
if new_is_active is not None and not new_is_active:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _send_user_access_email(request, target_user, *, invitation: bool) -> None:
|
||||
email = (target_user.email or '').strip()
|
||||
if not email:
|
||||
@@ -413,17 +439,19 @@ def _send_user_access_email(request, target_user, *, invitation: bool) -> None:
|
||||
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)
|
||||
branding_copy = get_branding_email_copy()
|
||||
|
||||
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'
|
||||
'für Sie wurde ein Benutzerkonto im %(portal_title)s 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),
|
||||
'portal_title': branding_copy['portal_title'],
|
||||
'url': reset_url,
|
||||
}
|
||||
else:
|
||||
@@ -447,10 +475,67 @@ def user_management_page(request):
|
||||
return _render_user_management(request)
|
||||
|
||||
|
||||
@_require_capability('manage_product_branding')
|
||||
def portal_branding_page(request):
|
||||
branding, created = PortalBranding.objects.get_or_create(name='Default')
|
||||
form = PortalBrandingForm(instance=branding)
|
||||
return render(
|
||||
request,
|
||||
'workflows/branding_settings.html',
|
||||
{
|
||||
'form': form,
|
||||
'branding': branding,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@_require_capability('manage_product_branding')
|
||||
@require_POST
|
||||
def save_portal_branding(request):
|
||||
branding, created = PortalBranding.objects.get_or_create(name='Default')
|
||||
form = PortalBrandingForm(request.POST, request.FILES, instance=branding)
|
||||
if not form.is_valid():
|
||||
messages.error(request, _('Branding konnte nicht gespeichert werden. Bitte prüfen Sie die Eingaben.'))
|
||||
return render(
|
||||
request,
|
||||
'workflows/branding_settings.html',
|
||||
{
|
||||
'form': form,
|
||||
'branding': branding,
|
||||
},
|
||||
status=400,
|
||||
)
|
||||
|
||||
branding = form.save()
|
||||
_audit(
|
||||
request,
|
||||
'portal_branding_saved',
|
||||
target_type='portal_branding',
|
||||
target_id=branding.id,
|
||||
target_label=branding.portal_title,
|
||||
details={
|
||||
'company_name': branding.company_name,
|
||||
'support_email': branding.support_email,
|
||||
'default_language': branding.default_language,
|
||||
'has_custom_logo': bool(branding.logo_image),
|
||||
'has_custom_letterhead': bool(branding.pdf_letterhead),
|
||||
},
|
||||
)
|
||||
messages.success(request, _('Portal-Branding wurde gespeichert.'))
|
||||
return render(
|
||||
request,
|
||||
'workflows/branding_settings.html',
|
||||
{
|
||||
'form': PortalBrandingForm(instance=branding),
|
||||
'branding': branding,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@_require_capability('manage_users')
|
||||
@require_POST
|
||||
def create_user_from_admin(request):
|
||||
form = UserManagementCreateForm(request.POST)
|
||||
form = UserManagementCreateForm(request.POST, include_product_owner=(get_user_role_key(request.user) == ROLE_PLATFORM_OWNER))
|
||||
if not form.is_valid():
|
||||
messages.error(request, _('Benutzer konnte nicht erstellt werden. Bitte prüfen Sie die Eingaben.'))
|
||||
return _render_user_management(request, create_form=form, status_code=400)
|
||||
@@ -481,10 +566,20 @@ def update_user_from_admin(request, user_id: int):
|
||||
if role_key not in ROLE_GROUP_NAMES:
|
||||
messages.error(request, _('Ungültige Rolle.'))
|
||||
return redirect('user_management_page')
|
||||
if role_key == ROLE_PLATFORM_OWNER and get_user_role_key(request.user) != ROLE_PLATFORM_OWNER:
|
||||
messages.error(request, _('Nur Platform Owner dürfen diese Rolle vergeben.'))
|
||||
return redirect('user_management_page')
|
||||
|
||||
if target_user == request.user and (role_key != ROLE_SUPER_ADMIN or not is_active):
|
||||
current_role = get_user_role_key(request.user)
|
||||
if target_user == request.user and current_role == ROLE_PLATFORM_OWNER and (role_key != ROLE_PLATFORM_OWNER or not is_active):
|
||||
messages.error(request, _('Der aktuell angemeldete Platform Owner kann sich hier nicht selbst sperren oder herabstufen.'))
|
||||
return redirect('user_management_page')
|
||||
if target_user == request.user and current_role == ROLE_SUPER_ADMIN and (role_key != ROLE_SUPER_ADMIN or not is_active):
|
||||
messages.error(request, _('Der aktuell angemeldete Super Admin kann sich hier nicht selbst sperren oder herabstufen.'))
|
||||
return redirect('user_management_page')
|
||||
if _would_remove_last_platform_owner(target_user, new_role_key=role_key, new_is_active=is_active):
|
||||
messages.error(request, _('Der letzte aktive Platform Owner kann nicht deaktiviert oder herabgestuft werden.'))
|
||||
return redirect('user_management_page')
|
||||
if _would_remove_last_super_admin(target_user, new_role_key=role_key, new_is_active=is_active):
|
||||
messages.error(request, _('Der letzte aktive Super Admin kann nicht deaktiviert oder herabgestuft werden.'))
|
||||
return redirect('user_management_page')
|
||||
@@ -535,9 +630,16 @@ def delete_user_from_admin(request, user_id: int):
|
||||
user_model = get_user_model()
|
||||
target_user = get_object_or_404(user_model, id=user_id)
|
||||
|
||||
current_role = get_user_role_key(request.user)
|
||||
if target_user == request.user and current_role == ROLE_PLATFORM_OWNER:
|
||||
messages.error(request, _('Der aktuell angemeldete Platform Owner kann sich hier nicht selbst löschen.'))
|
||||
return redirect('user_management_page')
|
||||
if target_user == request.user:
|
||||
messages.error(request, _('Der aktuell angemeldete Super Admin kann sich hier nicht selbst löschen.'))
|
||||
return redirect('user_management_page')
|
||||
if _would_remove_last_platform_owner(target_user, deleting=True):
|
||||
messages.error(request, _('Der letzte aktive Platform Owner kann nicht gelöscht werden.'))
|
||||
return redirect('user_management_page')
|
||||
if _would_remove_last_super_admin(target_user, deleting=True):
|
||||
messages.error(request, _('Der letzte aktive Super Admin kann nicht gelöscht werden.'))
|
||||
return redirect('user_management_page')
|
||||
@@ -1584,11 +1686,11 @@ def welcome_emails_page(request):
|
||||
rows = ScheduledWelcomeEmail.objects.select_related('onboarding_request').order_by('-send_at', '-id')[:200]
|
||||
config, _ = WorkflowConfig.objects.get_or_create(name='Default')
|
||||
welcome_template = NotificationTemplate.objects.filter(key='onboarding_welcome').first()
|
||||
default_welcome = DEFAULT_NOTIFICATION_TEMPLATES.get('onboarding_welcome', {})
|
||||
default_subject = (default_welcome.get('subject') or 'Willkommen bei TUB/CO, {{ FULL_NAME }}').strip()
|
||||
default_body = (default_welcome.get('body') or 'Hallo {{ FULL_NAME }}, willkommen bei TUB/CO.').strip()
|
||||
default_subject_en = (default_welcome.get('subject_en') or 'Welcome to TUB/CO, {{ FULL_NAME }}').strip()
|
||||
default_body_en = (default_welcome.get('body_en') or 'Hello {{ FULL_NAME }}, welcome to TUB/CO.').strip()
|
||||
default_welcome = get_default_notification_templates().get('onboarding_welcome', {})
|
||||
default_subject = (default_welcome.get('subject') or '').strip()
|
||||
default_body = (default_welcome.get('body') or '').strip()
|
||||
default_subject_en = (default_welcome.get('subject_en') or '').strip()
|
||||
default_body_en = (default_welcome.get('body_en') or '').strip()
|
||||
subject_value = (welcome_template.subject_template if welcome_template else '').strip() or default_subject
|
||||
body_value = (welcome_template.body_template if welcome_template else '').strip() or default_body
|
||||
subject_value_en = (welcome_template.subject_template_en if welcome_template else '').strip() or default_subject_en
|
||||
@@ -1650,11 +1752,11 @@ def save_welcome_email_settings(request):
|
||||
subject_en = request.POST.get('welcome_subject_en')
|
||||
body_en = request.POST.get('welcome_body_en')
|
||||
if subject is not None or body is not None or subject_en is not None or body_en is not None:
|
||||
default_welcome = DEFAULT_NOTIFICATION_TEMPLATES.get('onboarding_welcome', {})
|
||||
default_subject = (default_welcome.get('subject') or 'Willkommen bei TUB/CO, {{ FULL_NAME }}').strip()
|
||||
default_body = (default_welcome.get('body') or 'Hallo {{ FULL_NAME }}, willkommen bei TUB/CO.').strip()
|
||||
default_subject_en = (default_welcome.get('subject_en') or 'Welcome to TUB/CO, {{ FULL_NAME }}').strip()
|
||||
default_body_en = (default_welcome.get('body_en') or 'Hello {{ FULL_NAME }}, welcome to TUB/CO.').strip()
|
||||
default_welcome = get_default_notification_templates().get('onboarding_welcome', {})
|
||||
default_subject = (default_welcome.get('subject') or '').strip()
|
||||
default_body = (default_welcome.get('body') or '').strip()
|
||||
default_subject_en = (default_welcome.get('subject_en') or '').strip()
|
||||
default_body_en = (default_welcome.get('body_en') or '').strip()
|
||||
subject_clean = (subject or '').strip() or default_subject
|
||||
body_clean = (body or '').strip() or default_body
|
||||
subject_clean_en = (subject_en or '').strip() or default_subject_en
|
||||
|
||||
Reference in New Issue
Block a user