Matthias Nagel bc4b8a1e7f Feat: Implementierung des Spieler- und Eltern-Verifizierungsprozesses
Fügt einen umfassenden Verifizierungsprozess für neu erstellte Spieler
und zugeordnete Eltern hinzu. Dies ersetzt das frühere Einladungscode-System.

Wesentliche Änderungen:
- **`CustomUser` Modell:** Erweitert um `is_verified` (Standard `False`) und
  `verification_code` (UUID) Felder. `is_active` ist nun standardmäßig `False`
  bis zur Verifizierung. Das `InvitationCode`-Modell wurde entfernt.
- **E-Mail-Utility (`accounts/utils.py`):** Eine neue Funktion `send_verification_email`
  sendet oder simuliert E-Mails (basierend auf `settings.MTP_EMAIL_SEND`).
  Simulierte E-Mails werden im `.mbox`-Format in `tmp_mails/` gespeichert.
- **`settings.py`:** `DEFAULT_FROM_EMAIL` wurde hinzugefügt.
- **`PlayerCreateView` (`accounts/views.py`):**
    - Generiert `verification_code` für neue Spieler und Eltern.
    - Setzt das Passwort für neue Benutzer auf unbrauchbar (`set_unusable_password`).
    - Löst den Versand von Verifizierungs-E-Mails aus.
- **`verify_account` View (`accounts/views.py`):**
    - Eine neue View, die über einen Link in der E-Mail aufgerufen wird.
    - Ermöglicht Spielern, ein Passwort festzulegen.
    - Ermöglicht Eltern, einen eindeutigen Benutzernamen und ein Passwort festzulegen.
    - Setzt `is_active` und `is_verified` auf `True` und invalidiert den
      Verifizierungscode nach erfolgreicher Einrichtung.
    - Loggt den Benutzer nach erfolgreicher Verifizierung direkt ein und zeigt
      eine Erfolgsmeldung an.
    - Behebt ein Problem bei der Bestimmung von Eltern-Benutzern.
- **Formulare (`accounts/forms.py`):** Neue `PlayerVerificationForm` und
  `ParentVerificationForm` für den Verifizierungsprozess.
- **E-Mail-Templates:** Neue Text- und HTML-Templates für Spieler- und
  Eltern-Verifizierungs-E-Mails (`accounts/templates/accounts/email/`).
- **Verifizierungs-Template:** Neues Template für die Verifizierungsseite
  (`accounts/templates/accounts/verify_account.html`).
- **URLs (`accounts/urls.py`):** Entfernung der alten `invitation_code` und
  `register` URLs, Hinzufügung der neuen `verify_account` URL.
- **Datenbankmigrationen:** Migrationen für die Änderungen am `CustomUser`-Modell
  erstellt und angewendet.
- **Temporäres Verzeichnis:** `tmp_mails/` Verzeichnis für E-Mail-Simulation erstellt.
2025-11-23 16:34:25 +01:00

129 lines
5.4 KiB
Python

from django.shortcuts import render, redirect
from django.urls import reverse_lazy
from django.views.generic.edit import CreateView
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.contrib.auth import views as auth_views, login
from django.contrib import messages
from django.db.models import Q
from django.http import JsonResponse
from .forms import CustomUserCreationForm, CustomUserChangeForm, PlayerCreationForm, PlayerVerificationForm, ParentVerificationForm
from .models import CustomUser
from .utils import send_verification_email
import uuid
@login_required
def edit_profile(request):
if request.method == 'POST':
form = CustomUserChangeForm(request.POST, instance=request.user)
if form.is_valid():
form.save()
return redirect('edit_profile') # Or wherever you want to redirect
else:
form = CustomUserChangeForm(instance=request.user)
return render(request, 'accounts/edit_profile.html', {'form': form})
class HeadCoachCheckMixin(UserPassesTestMixin):
def test_func(self):
return self.request.user.is_superuser or self.request.user.coached_teams.exists()
class PlayerCreateView(LoginRequiredMixin, HeadCoachCheckMixin, CreateView):
model = CustomUser
form_class = PlayerCreationForm
template_name = 'accounts/player_form.html'
success_url = reverse_lazy('dashboard')
def form_valid(self, form):
# Create player user
player = form.save(commit=False)
player.is_active = False # Player is inactive until verified
player.set_unusable_password() # Password must be set via verification
player.verification_code = uuid.uuid4()
player.save()
# Send verification email to player
send_verification_email(player, self.request, is_parent=False)
# Handle parents
for i in ['1', '2']:
search_identifier = form.cleaned_data.get(f'parent{i}_search')
new_email = form.cleaned_data.get(f'parent{i}_new')
if search_identifier:
import re
match = re.search(r'\((\w+)\)', search_identifier)
if match:
username = match.group(1)
try:
parent_user = CustomUser.objects.get(username=username)
player.parents.add(parent_user)
except CustomUser.DoesNotExist:
form.add_error(f'parent{i}_search', 'User not found.')
else:
try:
parent_user = CustomUser.objects.get(Q(username=search_identifier) | Q(email=search_identifier))
player.parents.add(parent_user)
except CustomUser.DoesNotExist:
form.add_error(f'parent{i}_search', 'User not found.')
except CustomUser.MultipleObjectsReturned:
form.add_error(f'parent{i}_search', 'Multiple users found. Please be more specific.')
elif new_email:
parent_user, created = CustomUser.objects.get_or_create(
email=new_email,
defaults={'username': new_email, 'is_active': False}
)
if created:
parent_user.set_unusable_password()
parent_user.verification_code = uuid.uuid4()
parent_user.save()
# Send verification email to new parent
send_verification_email(parent_user, self.request, is_parent=True)
player.parents.add(parent_user)
if form.errors:
return self.form_invalid(form)
return redirect(self.success_url)
class MyLoginView(auth_views.LoginView):
def get(self, request, *args, **kwargs):
if request.user.is_authenticated:
return redirect('dashboard')
return super().get(request, *args, **kwargs)
def user_search(request):
q = request.GET.get('q', '')
users = CustomUser.objects.filter(last_name__istartswith=q).values('username', 'first_name', 'last_name')
results = []
for user in users:
results.append(f"{user['last_name']}, {user['first_name']} ({user['username']})")
return JsonResponse(results, safe=False)
def verify_account(request, verification_code):
user = get_object_or_404(CustomUser, verification_code=verification_code, is_verified=False)
# Determine if user is a parent (has no team) or player
is_parent = user.team is None
FormClass = ParentVerificationForm if is_parent else PlayerVerificationForm
if request.method == 'POST':
form = FormClass(request.POST)
if form.is_valid():
user.set_password(form.cleaned_data['password'])
if is_parent:
user.username = form.cleaned_data['username']
user.is_active = True
user.is_verified = True
user.verification_code = None # Invalidate the code
user.save()
login(request, user) # Log the user in
messages.success(request, 'Your account has been verified! You are now logged in.')
return redirect('dashboard')
else:
form = FormClass()
return render(request, 'accounts/verify_account.html', {'form': form, 'is_parent': is_parent})