From e119b5c91498b4d5339256018bddb5c665bf2b99 Mon Sep 17 00:00:00 2001 From: Matthias Nagel Date: Tue, 30 Sep 2025 20:05:30 +0200 Subject: [PATCH] feat: Implementierung von Phase 2 (Benutzer- und Rollenmodell) und Anpassung des Geburtsdatumsformats --- accounts/__pycache__/forms.cpython-313.pyc | Bin 0 -> 2251 bytes accounts/__pycache__/models.cpython-313.pyc | Bin 201 -> 2540 bytes accounts/__pycache__/urls.cpython-313.pyc | Bin 259 -> 795 bytes accounts/__pycache__/views.cpython-313.pyc | Bin 207 -> 2284 bytes accounts/forms.py | 22 +++++++ accounts/migrations/0001_initial.py | 58 ++++++++++++++++++ .../__pycache__/0001_initial.cpython-313.pyc | Bin 0 -> 4480 bytes accounts/models.py | 31 +++++++++- .../templates/accounts/invitation_code.html | 10 +++ accounts/templates/accounts/login.html | 10 +++ accounts/templates/accounts/register.html | 10 +++ accounts/templates/base.html | 10 +++ accounts/urls.py | 8 ++- accounts/views.py | 44 ++++++++++++- .../__pycache__/settings.cpython-313.pyc | Bin 2585 -> 2632 bytes .../__pycache__/wsgi.cpython-313.pyc | Bin 0 -> 686 bytes baseball_organisator/settings.py | 2 + db.sqlite3 | Bin 131072 -> 143360 bytes docs/phase2.md | 27 ++++++++ 19 files changed, 227 insertions(+), 5 deletions(-) create mode 100644 accounts/__pycache__/forms.cpython-313.pyc create mode 100644 accounts/forms.py create mode 100644 accounts/migrations/0001_initial.py create mode 100644 accounts/migrations/__pycache__/0001_initial.cpython-313.pyc create mode 100644 accounts/templates/accounts/invitation_code.html create mode 100644 accounts/templates/accounts/login.html create mode 100644 accounts/templates/accounts/register.html create mode 100644 accounts/templates/base.html create mode 100644 baseball_organisator/__pycache__/wsgi.cpython-313.pyc create mode 100644 docs/phase2.md diff --git a/accounts/__pycache__/forms.cpython-313.pyc b/accounts/__pycache__/forms.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..29c917aa423116406d8cf7bf5b385a5a432f3fd7 GIT binary patch literal 2251 zcma)6O=ufO6rPoKrIl=FZOc(oyOB4+NvznB+7d%j@Y2uj> zi)hj?VNor@fyShl&T7Bk-3i%CM3cJjH{+qUcM5VCL^5d}m!VT>H!jjKzJqr^Bsi2C22#g69{ zaxOedR)n;)z(qgC0#g7+^(2rPlS1y*y-83_*Hdk?Q1;XcEi<+B^Y@l#mNL~5xRJA4 z$5*apAlbbIiyAg9>P5pdc(_D8hPA|_PW~n>crKSsSHERgW|3ivxN-w|zaVEBa~SWP zaj3iCc(Zo^L^{kxms%@6Uapus#jE2xT=bx-DnFYoI~AI(*hYz3+3U=?N#FNqHgC8z zZ&;S@u##b$uHiW>YZMAj)%M)1-xrzHyFBhg_C4}Luhwd&qmIJ&+y_9`$cB<=C~93% z8_HN+8GCZ$nKHR9ZiS@SJHqqSWFvK|o;uY?ovx=&e^;!fPS;Y?>(@3CM;eKVdSarH zI9X4ee4ZR>BqyFGCu);R&yvfv{^hMO8Om(Qqq@adZ@P!}FM#uX@yl z3s@6P_!D*ziQFdGUoMJ1AsapY4=&ul@T5?C^J2Z{(%Q@mQI>|-k+x|8n4N_|#?|}+ z$p0HXFp|3T7j`R)g0A29w})WwWd+a;?h*S3)WIMVGzo@TyfbyDLV+#o(PY+JilyT` z*%_bHjQUmVkGBD^gxMRpZ5E*eK9r9ZGe;|#qwk>%>(20C&h?r0YSq(&v&)XbojrkQ z554B=pm{kEOww@DhFP>ErX@bo9Z2RX^^DCSL=)w56*?W$&f|W{{o>#;%W%Dbm1xB< zEgsLC%qwF9Q{J~~8Fwku?P?`Y8JAZL*S+nqqM!AJw9F72JA@>KWE2U`Q!YWlQ45}n zRphe6sK8PQvW|7ZMMTF=0E-CyB8FP&A6}O>2CPpbfhEq>*4O&Fehr5JQ-Scl>n(Jb zF|h0{l*MM?8Ga=PY@m7r@A#g(;9_H_MS^K`&jSHi%Fu&*_wPNSjbmr(@KfGidv{|X z`T6K)qu<8A{^*CGSLyxW;O6aiYUa5QN{z!QV+{TkH{r9_{~X a`0?;I0d{*>5-P%@+*kTz{RKhZhxRY++x#5> literal 0 HcmV?d00001 diff --git a/accounts/__pycache__/models.cpython-313.pyc b/accounts/__pycache__/models.cpython-313.pyc index 01b7431cbd26e1a0a3acfc3bc8aae5afb997a719..90cbcddf1697d54b43d1f7557e924bc3cd89aceb 100644 GIT binary patch literal 2540 zcma)8O>7fK6rT0&`rok)X<|a!CO}HJQWGjdMW6-I1SFCY(ri+d#?@+VPc}=|yJ2=6 zn(&uX1*gCPL0hSCEK(2Q#H9y%YL9U^IGTcl)I*h9+rYVfZ|zWG3}V)LJM-R~w{PCh zH(pO9q6Eglwue)nNre1?i;W@IChIdWxlUA~3PWU2U_uAZ14Ds9k%|6X9FhiQCi`<~ zD9C~tvV*8{8&QKe?3qP6@7Q!W|C|-_%HmW0hBY45 zEdp5}kCR&$FD+hL-P-yvmAR3)mZ+q5mQy<)q`EH4tI6iY`784`2X3AG_GG1fPZ@s6 zJxk;FChkt$@A_5wMOp6sba`Z~GNP47w8{uAkI-dfx?;?hjoFGZUpD5KPo69Ho+~HM zFNe>s!w1P7GOWaTSkr91NHvW|H4S_%SvZet+L@AW)g(zxn>3m0S*A^GN7ERV7LN=- zD36)cDzGH3o-V!+&l zN(iJrD*XVH*Fq)X)u^=N83s*YqW#ebUwl9o`vmN5Kr7N1X%0Z>b;Lpet_yki*cvu3 zoJu}R>9jkh1bM`CHMls;S<3J%T!f=Cym)v7kpUv&={%Ze6zaPG1*!xXri&-MvySFl z(R9P9fr4MDJPamhb;~U9*3D_?-Vk6xyL~_w$SO4Y#l?%OTQd(c9k=4&#lLU*DN|7n zt|$lZcb1iba%SLiVl|drJbdNw&95HB-UCh8U^*9-v(6|@w@8!+?BcIaHiT(*s#OO7S;}L_9l|Ikl>CQ zNc2zXRYYLezdU-(VbnD2Av))GHV^e5QTvY!9OY68?ns_MP#J?PRDoqL?umcmI2P6I zDr*ng#!HJ~G}$gBb-3{k@{nK;xTk;sZp6}+*!Gp!_S-JtMr{AWfX``PiM8MSa_Pf+ zhwdKwOMK3F*Zj}(-fpQZ$PTQv;$EhWm^ z3a543aJutwjR2^1Lmy3bSMi@=o_M6DP{>uI6R)jHo@u#$G5U2_jf{K(|Izs8s5>AJ ze^f>4L+mJ+#ZiX1L3l*=J|gXpNayb)`9z8c7lf$oY_Hz!v?6VG$+-rhyy6Z V2*kx8#s_9bM#g&#GDU1a4gfX*9JBxc diff --git a/accounts/__pycache__/urls.cpython-313.pyc b/accounts/__pycache__/urls.cpython-313.pyc index 32d90d7c057f2c5269e20d864920e13738343d3b..a3046a622944ce33bc0c074055db14a01c05d817 100644 GIT binary patch literal 795 zcmZutJ#W)M7(T~#66d3-WUHinNEJ)*l2cnTgrY8N2|_4`Scnu|l9TP$@kQrLQM0ji z%!0&UKrD>>j93g2Co(aWsVIMdJ2y@w#0~HJywCeS@5imG)e?};zyH(!q5<$r1=+LK zVD(#qj{pIrwLwc`+5td0q_=Y|o#_$-i4BR(1JKnT17vjdP9Gs0;XW^6<)ojJP(0Zx%d@Iit1svaTCCSO@DOr|t<Yi4EsxXfCb&X8x|97z0(YKWi-Bk@r3@||%pA{ym%i8NR6tSeUXtKTpiE1+5Vkt;0 z$#}^K;jorvrj{4`X|mqpPRUBlOV8IUEy^h_Vg}0IVklw-Qj<-XVpMp543LguAt3RA ZnURt4E`!Wn2H_7ZO#DoZ+(jU_000p79?Jj# diff --git a/accounts/__pycache__/views.cpython-313.pyc b/accounts/__pycache__/views.cpython-313.pyc index 62a796bb0d0d92777a10acd36f8a3e9ae80780bc..15cb9fc91404cb7791e88ebf2ba281c88e1a0426 100644 GIT binary patch literal 2284 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 literal 0 HcmV?d00001 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 248bdfe443a3d9c1a420d3085f9e1a6e278514d6..d8a7ea715093a00901b23718e4b26d775ce27628 100644 GIT binary patch delta 122 zcmbO!azcdnGcPX}0}$9t-N|@8kyn!O!$kGB9KljW;_1?wGMgnBOW7ohGE;>2GcPX}0}#YW-pM#Lkyn!O%|!LLEa_63(wk)%OWBzHG{rZsWEW$ce1RjL ZOADxw5r~VKHd}HsF>+jF5Gv9D@&WIX5rO~! diff --git a/baseball_organisator/__pycache__/wsgi.cpython-313.pyc b/baseball_organisator/__pycache__/wsgi.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1cf99bc2539771f9b56778dffaf847ebbb8614f2 GIT binary patch literal 686 zcmZuvPiqu06i;T>Zke@C#|p-5kY8Ow$)!kdlVs!A{=?!SYvnk+uoM1eS%)&-NiZN;@oXq*mEnYMc3Ni%yT$@6}8V(dLi># z3&+2LS5c=+5+z4GBO|5BkQo>9Rr!6-E}q; zK$szn1eq|+&r*`*kdncG6@}mlvs}r+z}0)^s^>}r;nK0^X9=RDE6xoW zaRK*<0SLy{7UmJ9sY*)bt#wGfXXj;iDJ5GHLG?y#`L3gTwzp+5C z(r|UK;tY$Ua`Ex(1)k`ptLwfPGHY)8>tl@nd39Wy-b6U~jn=;-?_F>bd_~Kj(DLa@ Wc)HgAf_8p1S3fuJo;37z2l@{&lFa!4 literal 0 HcmV?d00001 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 cb112970e0f770caf868ba5172424834b21ae1d9..e3016ee9044cf9f87e74f41595f54374c088371c 100644 GIT binary patch literal 143360 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