from django.contrib.auth import get_user_model from django.shortcuts import render from django.utils.encoding import force_bytes from django.utils.http import urlsafe_base64_encode from django.utils.translation import gettext as _ from django.urls import reverse from django.contrib.auth.tokens import default_token_generator from .branding import get_branding_email_copy from .forms import UserManagementCreateForm from .models import AdminAuditLog from .emailing import send_system_email from .roles import ROLE_GROUP_NAMES, ROLE_LABELS, ROLE_PLATFORM_OWNER, ROLE_SUPER_ADMIN, get_user_role_key def user_management_rows(*, display_user_name_fn): user_model = get_user_model() role_order = { ROLE_PLATFORM_OWNER: 0, ROLE_SUPER_ADMIN: 0, 'admin': 1, 'it_staff': 2, 'staff': 3, } rows = [] for user in user_model.objects.all().order_by('-is_active', 'username'): role_key = get_user_role_key(user) rows.append( { 'user': user, 'role_key': role_key, 'role_label': str(ROLE_LABELS[role_key]), 'role_sort': role_order.get(role_key, 99), 'display_name': display_user_name_fn(user), } ) rows.sort(key=lambda item: (not item['user'].is_active, item['role_sort'], item['user'].username.lower())) return rows def render_user_management(request, *, create_form=None, status_code: int = 200, audit_action_label_fn, display_user_name_fn): recent_user_events = list( AdminAuditLog.objects.select_related('actor') .filter(action__in=['user_created', 'user_updated', 'user_password_reset_sent', 'user_deleted']) .order_by('-created_at', '-id')[:12] ) for row in recent_user_events: row.action_label = audit_action_label_fn(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(include_product_owner=include_product_owner), 'rows': user_management_rows(display_user_name_fn=display_user_name_fn), '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) def would_remove_last_super_admin(user, new_role_key: str | None = None, new_is_active: bool | None = None, deleting: bool = False) -> bool: if get_user_role_key(user) != ROLE_SUPER_ADMIN or not user.is_active: return False if super_admin_user_count() > 1: return False if deleting: return True if new_role_key is not None and new_role_key != ROLE_SUPER_ADMIN: return True if new_is_active is not None and not new_is_active: return True 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, display_user_name_fn) -> 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) 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 %(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_fn(target_user), 'portal_title': branding_copy['portal_title'], '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_fn(target_user), 'url': reset_url, } send_system_email(subject=subject, body=body, to=[email])