feat: Implementierung von Phase 2 (Benutzer- und Rollenmodell) und Anpassung des Geburtsdatumsformats

This commit is contained in:
Matthias Nagel 2025-09-30 20:05:30 +02:00
parent bf27894513
commit e119b5c914
19 changed files with 227 additions and 5 deletions

Binary file not shown.

22
accounts/forms.py Normal file
View File

@ -0,0 +1,22 @@
from django import forms
from .models import InvitationCode, CustomUser
class InvitationCodeForm(forms.Form):
code = forms.CharField(max_length=255, label="Einladungscode")
def clean_code(self):
code = self.cleaned_data.get('code')
try:
invitation_code = InvitationCode.objects.get(code=code)
if not invitation_code.is_valid():
raise forms.ValidationError("Dieser Einladungscode ist nicht mehr gültig.")
except InvitationCode.DoesNotExist:
raise forms.ValidationError("Ungültiger Einladungscode.")
return code
class CustomUserCreationForm(forms.ModelForm):
password = forms.CharField(widget=forms.PasswordInput)
birth_date = forms.DateField(input_formats=['%d.%m.%Y'], widget=forms.DateInput(format='%d.%m.%Y'))
class Meta:
model = CustomUser
fields = ('username', 'first_name', 'last_name', 'email', 'birth_date', 'player_number', 'password')

View File

@ -0,0 +1,58 @@
# Generated by Django 5.2.6 on 2025-09-30 17:59
import django.contrib.auth.models
import django.contrib.auth.validators
import django.db.models.deletion
import django.utils.timezone
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
]
operations = [
migrations.CreateModel(
name='CustomUser',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('birth_date', models.DateField(blank=True, null=True)),
('player_number', models.IntegerField(default=999)),
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
],
options={
'verbose_name': 'user',
'verbose_name_plural': 'users',
'abstract': False,
},
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
migrations.CreateModel(
name='InvitationCode',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('code', models.CharField(max_length=255, unique=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('is_active', models.BooleanField(default=True)),
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@ -1,3 +1,32 @@
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.utils import timezone
import datetime
# Create your models here.
class CustomUser(AbstractUser):
birth_date = models.DateField(null=True, blank=True)
player_number = models.IntegerField(default=999)
@property
def age(self):
if not self.birth_date:
return None
today = datetime.date.today()
return today.year - self.birth_date.year - ((today.month, today.day) < (self.birth_date.month, self.birth_date.day))
class InvitationCode(models.Model):
code = models.CharField(max_length=255, unique=True)
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE, null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
is_active = models.BooleanField(default=True)
def is_valid(self):
if not self.is_active:
return False
two_weeks_ago = timezone.now() - datetime.timedelta(weeks=2)
if self.created_at < two_weeks_ago:
return False
return True
def __str__(self):
return self.code

View File

@ -0,0 +1,10 @@
{% extends "base.html" %}
{% block content %}
<h2>Enter Invitation Code</h2>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Submit</button>
</form>
{% endblock %}

View File

@ -0,0 +1,10 @@
{% extends "base.html" %}
{% block content %}
<h2>Login</h2>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Login</button>
</form>
{% endblock %}

View File

@ -0,0 +1,10 @@
{% extends "base.html" %}
{% block content %}
<h2>Register</h2>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Register</button>
</form>
{% endblock %}

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<title>Baseball Organisator</title>
</head>
<body>
{% block content %}
{% endblock %}
</body>
</html>

View File

@ -1,6 +1,10 @@
from django.urls import path
from . import views
from django.contrib.auth import views as auth_views
urlpatterns = [
# Add your URLs here
]
path('invitation/', views.invitation_code_view, name='invitation_code'),
path('register/', views.register_view, name='register'),
path('login/', auth_views.LoginView.as_view(template_name='accounts/login.html'), name='login'),
path('logout/', auth_views.LogoutView.as_view(), name='logout'),
]

View File

@ -1,3 +1,43 @@
from django.shortcuts import render
from django.shortcuts import render, redirect
from .forms import InvitationCodeForm, CustomUserCreationForm
from .models import InvitationCode
# Create your views here.
def invitation_code_view(request):
if request.method == 'POST':
form = InvitationCodeForm(request.POST)
if form.is_valid():
code = form.cleaned_data['code']
request.session['invitation_code'] = code
return redirect('register')
else:
form = InvitationCodeForm()
return render(request, 'accounts/invitation_code.html', {'form': form})
def register_view(request):
invitation_code_str = request.session.get('invitation_code')
if not invitation_code_str:
return redirect('invitation_code')
try:
invitation_code = InvitationCode.objects.get(code=invitation_code_str)
if not invitation_code.is_valid():
# Handle invalid code, maybe redirect with a message
return redirect('invitation_code')
except InvitationCode.DoesNotExist:
return redirect('invitation_code')
if request.method == 'POST':
form = CustomUserCreationForm(request.POST)
if form.is_valid():
user = form.save(commit=False)
user.set_password(form.cleaned_data['password'])
user.save()
invitation_code.is_active = False
invitation_code.user = user
invitation_code.save()
# Log the user in and redirect to the dashboard
return redirect('login') # Or wherever you want to redirect after registration
else:
form = CustomUserCreationForm()
return render(request, 'accounts/register.html', {'form': form})

Binary file not shown.

View File

@ -124,3 +124,5 @@ STATIC_URL = 'static/'
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
AUTH_USER_MODEL = 'accounts.CustomUser'

Binary file not shown.

27
docs/phase2.md Normal file
View File

@ -0,0 +1,27 @@
## Phase 2: Benutzer- und Rollenmodell
In dieser Phase liegt der Fokus auf der Erstellung der Benutzerprofile und der Definition der verschiedenen Rollen.
Schritt 1: Benutzer- und Profilmodelle erstellen
Navigiere in das accounts-Verzeichnis und öffne models.py.
Erstelle ein benutzerdefiniertes User-Modell, das von AbstractUser erbt, um es an die spezifischen Anforderungen anzupassen (z. B. für birthdate).
Füge die folgenden Felder hinzu: first_name, last_name, birth_date (Geburtsdatum), und player_number mit einem Standardwert von 999.
Das Alter soll dynamisch aus dem Geburtsdatum berechnet werden. Die Rollen Player, Parent, Child, Coach, AssistantCoach, HeadCoach werden nicht als boolesche Felder gespeichert, sondern durch die Beziehungen zu anderen Modellen abgeleitet.
Schritt 2: Einladungscode-Modell erstellen
Erstelle in accounts/models.py ein Modell namens InvitationCode mit Feldern für den Code selbst, das Erstellungsdatum, die Gültigkeit (2 Wochen) und einen Status (aktiv/deaktiviert).
Verknüpfe dieses Modell mit dem User-Modell.
Schritt 3: Benutzerauthentifizierung und Logik implementieren
Erstelle in der accounts-App Logik für die Registrierung mittels Einladungscode.
Erstelle Views und URLs für die Code-Eingabe, Benutzerdatenerstellung und den Login.
Sorge dafür, dass der Einladungscode nach der Verwendung erlischt.