from rest_framework import views, viewsets, permissions, status from rest_framework.response import Response from rest_framework.authtoken.models import Token from django.contrib.auth import authenticate, get_user_model from django.shortcuts import get_object_or_404 from django.core.mail import get_connection, EmailMultiAlternatives from .models import RegistrationDomainRule, RegistrationRestrictionSetting, GuestClaimToken, EmailVerificationToken, SMTPSetting from .serializers import ( UserSerializer, AdminUserSerializer, RegisterSerializer, RegistrationDomainRuleSerializer, RegistrationRestrictionSettingSerializer, SMTPSettingSerializer ) from apps.events.models import ShiftSignup User = get_user_model() def send_verification_email_to_user(user, v_token_str, request_host=None): smtp = SMTPSetting.get_solo() host_str = request_host or 'localhost:3000' verify_url = f"http://{host_str}/?verify_email={v_token_str}" subject = "E-Mail-Adresse bestätigen — Veranstaltungsschichtplaner" text_content = f"Hallo {user.get_display_name()},\n\nbitte bestätige deine E-Mail-Adresse durch Aufruf des folgenden Links:\n{verify_url}\n\nVielen Dank!" html_content = f"""

Willkommen beim Schichtplaner!

Hallo {user.get_display_name()},

vielen Dank für deine Registrierung. Bitte bestätige deine E-Mail-Adresse durch Klick auf den folgenden Button:

E-Mail-Adresse Bestätigen

Oder kopiere diesen Link in deinen Browser:
{verify_url}

""" print("==================================================") print(f"[E-MAIL BESTÄTIGUNG]: {verify_url}") print("==================================================") if smtp.is_active and smtp.host: try: connection = get_connection( host=smtp.host, port=smtp.port, username=smtp.username, password=smtp.password, use_tls=smtp.use_tls, use_ssl=smtp.use_ssl, fail_silently=False, ) msg = EmailMultiAlternatives( subject=subject, body=text_content, from_email=smtp.from_email or 'noreply@schichtplaner.local', to=[user.email], connection=connection ) msg.attach_alternative(html_content, "text/html") msg.send() return True, "E-Mail erfolgreich versendet." except Exception as e: return False, f"SMTP-Fehler: {str(e)}" return True, "Bestätigungslink in Konsole ausgegeben." class AdminUserViewSet(viewsets.ModelViewSet): queryset = User.objects.all().order_by('-date_joined') serializer_class = AdminUserSerializer permission_classes = [permissions.IsAdminUser] class RegisterView(views.APIView): permission_classes = [permissions.AllowAny] def post(self, request): serializer = RegisterSerializer(data=request.data) if serializer.is_valid(): user = serializer.save() user.is_approved = False user.is_email_verified = False user.is_active = True user.save() setting = RegistrationRestrictionSetting.get_solo() claim_token_str = request.data.get('claim_token') claimed_target_name = None if claim_token_str: try: claim_obj = GuestClaimToken.objects.get(token=claim_token_str, is_claimed=False) claim_obj.is_claimed = True claim_obj.save() # Assign target signup target_signup = claim_obj.signup if target_signup and not target_signup.user: target_signup.user = user target_signup.guest_name = None target_signup.guest_session_key = None target_signup.save() claimed_target_name = claim_obj.guest_name except Exception: pass # Reassign any guest shift signups to this newly created user! session_key = request.session.session_key display_name = user.get_display_name() guest_signups = ShiftSignup.objects.filter(user=None) assigned_count = 0 for signup in guest_signups: matches_session = session_key and signup.guest_session_key == session_key matches_display = signup.guest_name and signup.guest_name.strip().lower() == display_name.strip().lower() matches_claimed_name = claimed_target_name and signup.guest_name and signup.guest_name.strip().lower() == claimed_target_name.strip().lower() if matches_session or matches_display or matches_claimed_name: signup.user = user signup.guest_name = None signup.guest_session_key = None signup.save() assigned_count += 1 if setting.require_email_verification: user.is_approved = False user.is_email_verified = False user.is_active = True user.save() v_token = EmailVerificationToken.objects.create(user=user) req_host = request.get_host() send_verification_email_to_user(user, str(v_token.token), request_host=req_host) return Response({ 'requires_verification': True, 'verification_token': str(v_token.token), 'claimed_shifts_count': assigned_count, 'message': 'Konto erfolgreich erstellt! Bitte bestätige deine E-Mail-Adresse über den Link in der Bestätigungs-E-Mail oder warte auf die Admin-Freischaltung.' }, status=status.HTTP_201_CREATED) # Opt-Out: Email verification not required! user.is_email_verified = True if setting.require_admin_approval: user.is_approved = False user.is_active = False user.save() return Response({ 'requires_approval': True, 'claimed_shifts_count': assigned_count, 'message': 'Konto erfolgreich registriert! Ein Administrator muss dein Konto vor der ersten Anmeldung freischalten.' }, status=status.HTTP_201_CREATED) user.is_approved = True user.is_active = True user.save() token, _ = Token.objects.get_or_create(user=user) user_data = UserSerializer(user).data return Response({ 'token': token.key, 'user': user_data, 'claimed_shifts_count': assigned_count, 'message': f'Konto erfolgreich erstellt! {assigned_count} Gast-Schichten wurden deinem Konto zugewiesen.' if assigned_count > 0 else 'Konto erfolgreich erstellt!' }, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) class VerifyEmailView(views.APIView): permission_classes = [permissions.AllowAny] def post(self, request): token_str = request.data.get('token') if not token_str: return Response({'error': 'Token erforderlich.'}, status=status.HTTP_400_BAD_REQUEST) v_token = EmailVerificationToken.objects.filter(token=token_str, is_used=False).first() if not v_token: return Response({'error': 'Ungültiger oder bereits verwendeter Bestätigungslink.'}, status=status.HTTP_400_BAD_REQUEST) v_token.is_used = True v_token.save() user = v_token.user user.is_email_verified = True user.is_approved = True user.is_active = True user.save() token, _ = Token.objects.get_or_create(user=user) return Response({ 'token': token.key, 'user': UserSerializer(user).data, 'message': f'E-Mail-Adresse für {user.get_display_name()} wurde erfolgreich bestätigt! Du bist jetzt angemeldet.' }, status=status.HTTP_200_OK) class GenerateClaimLinkView(views.APIView): permission_classes = [permissions.IsAuthenticated] def post(self, request, signup_id): signup = get_object_or_404(ShiftSignup, pk=signup_id) if signup.user: return Response({'error': 'Diese Schicht ist bereits mit einem festen Benutzerkonto verknüpft.'}, status=status.HTTP_400_BAD_REQUEST) guest_name = signup.guest_name or "Gast" claim_obj, _ = GuestClaimToken.objects.get_or_create( signup=signup, created_by=request.user, defaults={'guest_name': guest_name} ) return Response({ 'token': str(claim_obj.token), 'guest_name': guest_name, 'event_title': signup.shift.task_area.event.title, 'shift_title': signup.shift.title, 'created_by': request.user.get_display_name() }) class ClaimInfoView(views.APIView): permission_classes = [permissions.AllowAny] def get(self, request, token_uuid): claim_obj = get_object_or_404(GuestClaimToken, token=token_uuid) return Response({ 'token': str(claim_obj.token), 'guest_name': claim_obj.guest_name, 'is_claimed': claim_obj.is_claimed, 'event_title': claim_obj.signup.shift.task_area.event.title if claim_obj.signup else 'Veranstaltung', 'shift_title': claim_obj.signup.shift.title if claim_obj.signup else 'Schicht', 'created_by': claim_obj.created_by.get_display_name() }) class LoginView(views.APIView): permission_classes = [permissions.AllowAny] def post(self, request): username = request.data.get('username') password = request.data.get('password') if not username or not password: return Response({'error': 'Benutzername und Passwort erforderlich.'}, status=status.HTTP_400_BAD_REQUEST) user_obj = None if '@' in username: try: user_obj = User.objects.get(email__iexact=username) username = user_obj.username except User.DoesNotExist: pass else: try: user_obj = User.objects.get(username__iexact=username) except User.DoesNotExist: pass if user_obj: if not (user_obj.is_approved or user_obj.is_email_verified or user_obj.is_superuser or user_obj.is_admin_user): return Response({ 'error': 'Anmeldung nicht möglich: Deine E-Mail-Adresse wurde noch nicht bestätigt und dein Konto wurde noch nicht vom Admin freigeschaltet.' }, status=status.HTTP_403_FORBIDDEN) user = authenticate(username=username, password=password) if not user: return Response({'error': 'Ungültige Anmeldedaten.'}, status=status.HTTP_401_UNAUTHORIZED) token, _ = Token.objects.get_or_create(user=user) return Response({ 'token': token.key, 'user': UserSerializer(user).data }) class MeView(views.APIView): permission_classes = [permissions.IsAuthenticated] def get(self, request): return Response(UserSerializer(request.user).data) def patch(self, request): serializer = UserSerializer(request.user, data=request.data, partial=True) if serializer.is_valid(): serializer.save() return Response(serializer.data) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) class RegistrationDomainRuleViewSet(viewsets.ModelViewSet): queryset = RegistrationDomainRule.objects.all() serializer_class = RegistrationDomainRuleSerializer permission_classes = [permissions.IsAdminUser] class RegistrationRestrictionSettingView(views.APIView): def get_permissions(self): if self.request.method == 'GET': return [permissions.AllowAny()] return [permissions.IsAdminUser()] def get(self, request): setting = RegistrationRestrictionSetting.get_solo() active_domains = list(RegistrationDomainRule.objects.filter(is_active=True).values_list('domain', flat=True)) return Response({ 'is_restriction_enabled': setting.is_restriction_enabled, 'require_admin_approval': setting.require_admin_approval, 'require_email_verification': setting.require_email_verification, 'active_domains': active_domains }) def post(self, request): setting = RegistrationRestrictionSetting.get_solo() if 'is_restriction_enabled' in request.data: setting.is_restriction_enabled = request.data['is_restriction_enabled'] if 'require_admin_approval' in request.data: setting.require_admin_approval = request.data['require_admin_approval'] if 'require_email_verification' in request.data: setting.require_email_verification = request.data['require_email_verification'] setting.save() return Response({ 'is_restriction_enabled': setting.is_restriction_enabled, 'require_admin_approval': setting.require_admin_approval, 'require_email_verification': setting.require_email_verification }) class SMTPSettingView(views.APIView): permission_classes = [permissions.IsAdminUser] def get(self, request): setting = SMTPSetting.get_solo() return Response(SMTPSettingSerializer(setting).data) def post(self, request): setting = SMTPSetting.get_solo() serializer = SMTPSettingSerializer(setting, data=request.data, partial=True) if serializer.is_valid(): serializer.save() return Response(serializer.data) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) class TestSMTPEmailView(views.APIView): permission_classes = [permissions.IsAdminUser] def post(self, request): smtp = SMTPSetting.get_solo() target_email = request.data.get('email') or request.user.email if not target_email: return Response({'error': 'E-Mail-Adresse erforderlich.'}, status=status.HTTP_400_BAD_REQUEST) try: connection = get_connection( host=smtp.host, port=smtp.port, username=smtp.username, password=smtp.password, use_tls=smtp.use_tls, use_ssl=smtp.use_ssl, fail_silently=False, ) msg = EmailMultiAlternatives( subject="Test E-Mail — SMTP Konfiguration", body=f"Hallo,\n\ndies ist eine Test-E-Mail zur Überprüfung der SMTP-Einstellungen für {request.user.get_display_name()}.", from_email=smtp.from_email or 'noreply@schichtplaner.local', to=[target_email], connection=connection ) msg.send() return Response({'message': f'Test E-Mail erfolgreich an {target_email} gesendet!'}) except Exception as e: return Response({'error': f'SMTP-Verbindungsfehler: {str(e)}'}, status=status.HTTP_400_BAD_REQUEST) class PendingUsersView(views.APIView): permission_classes = [permissions.IsAdminUser] def get(self, request): pending_users = User.objects.filter(is_approved=False, is_email_verified=False, is_superuser=False).order_by('-date_joined') return Response(UserSerializer(pending_users, many=True).data) class ApproveUserView(views.APIView): permission_classes = [permissions.IsAdminUser] def post(self, request, user_id): user = get_object_or_404(User, pk=user_id) user.is_approved = True user.is_active = True user.save() return Response({'message': f'Nutzer {user.get_display_name()} wurde erfolgreich freigeschaltet.'}) def delete(self, request, user_id): user = get_object_or_404(User, pk=user_id) name = user.get_display_name() user.delete() return Response({'message': f'Registrierung von {name} wurde abgelehnt und gelöscht.'})