diff --git a/accounts/__pycache__/forms.cpython-313.pyc b/accounts/__pycache__/forms.cpython-313.pyc new file mode 100644 index 0000000..29c917a Binary files /dev/null and b/accounts/__pycache__/forms.cpython-313.pyc differ diff --git a/accounts/__pycache__/models.cpython-313.pyc b/accounts/__pycache__/models.cpython-313.pyc index 01b7431..90cbcdd 100644 Binary files a/accounts/__pycache__/models.cpython-313.pyc and b/accounts/__pycache__/models.cpython-313.pyc differ diff --git a/accounts/__pycache__/urls.cpython-313.pyc b/accounts/__pycache__/urls.cpython-313.pyc index 32d90d7..a3046a6 100644 Binary files a/accounts/__pycache__/urls.cpython-313.pyc and b/accounts/__pycache__/urls.cpython-313.pyc differ diff --git a/accounts/__pycache__/views.cpython-313.pyc b/accounts/__pycache__/views.cpython-313.pyc index 62a796b..15cb9fc 100644 Binary files a/accounts/__pycache__/views.cpython-313.pyc and b/accounts/__pycache__/views.cpython-313.pyc differ diff --git a/accounts/forms.py b/accounts/forms.py new file mode 100644 index 0000000..d67316f --- /dev/null +++ b/accounts/forms.py @@ -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') diff --git a/accounts/migrations/0001_initial.py b/accounts/migrations/0001_initial.py new file mode 100644 index 0000000..502164f --- /dev/null +++ b/accounts/migrations/0001_initial.py @@ -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)), + ], + ), + ] diff --git a/accounts/migrations/__pycache__/0001_initial.cpython-313.pyc b/accounts/migrations/__pycache__/0001_initial.cpython-313.pyc new file mode 100644 index 0000000..930b79c Binary files /dev/null and b/accounts/migrations/__pycache__/0001_initial.cpython-313.pyc differ diff --git a/accounts/models.py b/accounts/models.py index 71a8362..b4ff1bc 100644 --- a/accounts/models.py +++ b/accounts/models.py @@ -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 \ No newline at end of file diff --git a/accounts/templates/accounts/invitation_code.html b/accounts/templates/accounts/invitation_code.html new file mode 100644 index 0000000..4a4a4bb --- /dev/null +++ b/accounts/templates/accounts/invitation_code.html @@ -0,0 +1,10 @@ +{% extends "base.html" %} + +{% block content %} +

Enter Invitation Code

+
+ {% csrf_token %} + {{ form.as_p }} + +
+{% endblock %} diff --git a/accounts/templates/accounts/login.html b/accounts/templates/accounts/login.html new file mode 100644 index 0000000..a2aec78 --- /dev/null +++ b/accounts/templates/accounts/login.html @@ -0,0 +1,10 @@ +{% extends "base.html" %} + +{% block content %} +

Login

+
+ {% csrf_token %} + {{ form.as_p }} + +
+{% endblock %} diff --git a/accounts/templates/accounts/register.html b/accounts/templates/accounts/register.html new file mode 100644 index 0000000..b245b46 --- /dev/null +++ b/accounts/templates/accounts/register.html @@ -0,0 +1,10 @@ +{% extends "base.html" %} + +{% block content %} +

Register

+
+ {% csrf_token %} + {{ form.as_p }} + +
+{% endblock %} diff --git a/accounts/templates/base.html b/accounts/templates/base.html new file mode 100644 index 0000000..1e9ad1c --- /dev/null +++ b/accounts/templates/base.html @@ -0,0 +1,10 @@ + + + + Baseball Organisator + + + {% block content %} + {% endblock %} + + diff --git a/accounts/urls.py b/accounts/urls.py index bbbd7cc..af5d7a2 100644 --- a/accounts/urls.py +++ b/accounts/urls.py @@ -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'), +] \ No newline at end of file diff --git a/accounts/views.py b/accounts/views.py index 91ea44a..a7cbea1 100644 --- a/accounts/views.py +++ b/accounts/views.py @@ -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}) \ No newline at end of file diff --git a/baseball_organisator/__pycache__/settings.cpython-313.pyc b/baseball_organisator/__pycache__/settings.cpython-313.pyc index 248bdfe..d8a7ea7 100644 Binary files a/baseball_organisator/__pycache__/settings.cpython-313.pyc and b/baseball_organisator/__pycache__/settings.cpython-313.pyc differ diff --git a/baseball_organisator/__pycache__/wsgi.cpython-313.pyc b/baseball_organisator/__pycache__/wsgi.cpython-313.pyc new file mode 100644 index 0000000..1cf99bc Binary files /dev/null and b/baseball_organisator/__pycache__/wsgi.cpython-313.pyc differ diff --git a/baseball_organisator/settings.py b/baseball_organisator/settings.py index 38f42e8..20139e2 100644 --- a/baseball_organisator/settings.py +++ b/baseball_organisator/settings.py @@ -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' diff --git a/db.sqlite3 b/db.sqlite3 index cb11297..e3016ee 100644 Binary files a/db.sqlite3 and b/db.sqlite3 differ diff --git a/docs/phase2.md b/docs/phase2.md new file mode 100644 index 0000000..7ae751d --- /dev/null +++ b/docs/phase2.md @@ -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.