From ea8439e616f935538546ccaf4a4b7b6e07a0c8c1 Mon Sep 17 00:00:00 2001 From: Matthias Nagel Date: Wed, 1 Oct 2025 08:32:56 +0200 Subject: [PATCH] feat: Implementierung von Phase 4 (Terminverwaltung und Google Maps API) und Korrektur der Dashboard-Logik --- .../__pycache__/settings.cpython-313.pyc | Bin 2632 -> 2665 bytes .../__pycache__/urls.cpython-313.pyc | Bin 1346 -> 1336 bytes baseball_organisator/settings.py | 2 + baseball_organisator/urls.py | 2 +- calendars/__pycache__/admin.cpython-313.pyc | Bin 205 -> 609 bytes calendars/__pycache__/forms.cpython-313.pyc | Bin 0 -> 805 bytes calendars/__pycache__/models.cpython-313.pyc | Bin 202 -> 3219 bytes calendars/__pycache__/urls.cpython-313.pyc | Bin 260 -> 691 bytes calendars/__pycache__/views.cpython-313.pyc | Bin 208 -> 2938 bytes calendars/admin.py | 6 +- calendars/forms.py | 7 +++ calendars/migrations/0001_initial.py | 55 ++++++++++++++++++ .../__pycache__/0001_initial.cpython-313.pyc | Bin 0 -> 3151 bytes calendars/models.py | 36 +++++++++++- .../calendars/event_confirm_delete.html | 11 ++++ calendars/templates/calendars/event_form.html | 10 ++++ calendars/urls.py | 6 +- calendars/views.py | 40 ++++++++++++- dashboard/__pycache__/urls.cpython-313.pyc | Bin 260 -> 341 bytes dashboard/__pycache__/views.cpython-313.pyc | Bin 208 -> 1381 bytes dashboard/templates/dashboard/dashboard.html | 29 +++++++++ dashboard/urls.py | 4 +- dashboard/views.py | 29 ++++++++- db.sqlite3 | Bin 204800 -> 241664 bytes docs/phase4.md | 29 +++++++++ 25 files changed, 257 insertions(+), 9 deletions(-) create mode 100644 calendars/__pycache__/forms.cpython-313.pyc create mode 100644 calendars/forms.py create mode 100644 calendars/migrations/0001_initial.py create mode 100644 calendars/migrations/__pycache__/0001_initial.cpython-313.pyc create mode 100644 calendars/templates/calendars/event_confirm_delete.html create mode 100644 calendars/templates/calendars/event_form.html create mode 100644 dashboard/templates/dashboard/dashboard.html create mode 100644 docs/phase4.md diff --git a/baseball_organisator/__pycache__/settings.cpython-313.pyc b/baseball_organisator/__pycache__/settings.cpython-313.pyc index d8a7ea715093a00901b23718e4b26d775ce27628..1008b139769463dd396a06c646f9ec388200d401 100644 GIT binary patch delta 107 zcmX>h@=}EFGcPX}0}$*yaVLY7cOstzSbQF%Pi_2 OxY_ub8o7)3fXV@1YZ5E~ delta 70 zcmdnNb%=}iGcPX}0}zBr-pRPUk@o{LPZd{6VsS=NeqvFI{^bA6dl>mA?_o)ikeOJ1 YL0!w=kS{7jA9MZ7?b05e_|00000 diff --git a/baseball_organisator/settings.py b/baseball_organisator/settings.py index 20139e2..e0c5073 100644 --- a/baseball_organisator/settings.py +++ b/baseball_organisator/settings.py @@ -126,3 +126,5 @@ STATIC_URL = 'static/' DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' AUTH_USER_MODEL = 'accounts.CustomUser' + +LOGIN_REDIRECT_URL = '/' diff --git a/baseball_organisator/urls.py b/baseball_organisator/urls.py index 40f07c6..0119cc1 100644 --- a/baseball_organisator/urls.py +++ b/baseball_organisator/urls.py @@ -22,5 +22,5 @@ urlpatterns = [ path('accounts/', include('accounts.urls')), path('clubs/', include('clubs.urls')), path('calendars/', include('calendars.urls')), - path('dashboard/', include('dashboard.urls')), + path('', include('dashboard.urls')), ] diff --git a/calendars/__pycache__/admin.cpython-313.pyc b/calendars/__pycache__/admin.cpython-313.pyc index d34c1f409ae4aae40bc4b0da2d0289797072ca22..5169540a09227880dd6d4c50b101318d1fa2dc91 100644 GIT binary patch literal 609 zcmbVKF-yZh6n@v_Vhpq>ofO2bS(>SvI$4wyL>h4_$hA4r)Z`-XQo+&5O`VU3^(#154KQaLVa0>whqy;`f zQiGrr7_E|Tpy8Wn`V^^ep;dzpX;-H9S#Gb+_>^^wM$uKrzg*@*F1nD*nzs-Zd3%o^z2a`v_SqVi75GkEL8W8rxhBll;J4R+VtTM3mKgnaDBjqJCOs zrCG=miL@xt(y*k#)@P=SbFOUUQ7m(gcQ7ebP}^QVOSzW{7V*S8#B9J%bMAGSsj5 delta 134 zcmaFJa+WdaGcPX}0}xn9-pQ~B(vLwL7+``jJ_`UDQyGF8f*HLTycmlZ6&TW)G?`z5 zq%|3Du_mVEX6E^6GTq`!$x6&i&(}-N&nqd)Oe$gqDww>2F;I#F$Y2EGVi4m4Gb1D8 LJqCp$HXsK8ydW9f diff --git a/calendars/__pycache__/forms.cpython-313.pyc b/calendars/__pycache__/forms.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..50227b40330fe2215a3e1c8610f5904a5eb6c714 GIT binary patch literal 805 zcmZWnO>Yx15ViL!X=t1B5!4otB2L{)_tYD3qlZ?lfW1a?ovfQ~@$QzfgTx=Gkhs91 z;y3UwIPw#a)t)%zhLj?%%%Zvpo*H&r&b-_q{zI{61S}k69XQvNRmrB597!!)mFRcJ|&AAo^v&u z_5br6V19zXYNcItoGYCf*AoJ(+k7OXwU7@dRvDDuLFo)X=AI@eRiToilw+ln7f_w5 z*H$G%Y1B|^Eh-qxGB?szkc_0p?MpC8eM8%4?_5BK2YM(}-7F)Vv+rxY_o2HjK18sI zKj0w&l{}0ixJ$u3f*!$rf?BXaj8sk117fWO(0!Rzgmd=W^TH#3%?KMQsnFU**{LkY z70|14QQZjBbY45Ptfc^DDM~#Wt=3pC9p$;pgtB K?k`5XF8v2ZZNEkU literal 0 HcmV?d00001 diff --git a/calendars/__pycache__/models.cpython-313.pyc b/calendars/__pycache__/models.cpython-313.pyc index 915d57d20d0fb0cc65652f2b647c1f3343bef60e..895e3a3b40a55353fcfe359aa3ef1f0ccd634604 100644 GIT binary patch literal 3219 zcma)8O>7&-6`m!R-N3NGV>iJQ$&-;iRN3ARwIK=_S*~IUY`YANk2#i)&hI8iaV>oZ19EIpgS9a+3+iN z4Hdn~$PONnl!U)x7pTdhr*P^xWvj?n^82a~0(W)4?yN+}Ex6qpVUk9eN0XR$Oe*)GY#ub(j#Q6W|}MvLQ6@w6tqI2g|cJhOu8A$ zo3$KIWvEdReu{hYCms>BEggJE9V#M>nubFQx@A;o9vrpz;n^|xcmDz8lJqgjlGQ!t ze&=+*Yy5zqglC^K=nW(aX9>XjraYKm9ROr70qtt=g+af2__-JQx(<Ym{(p28Aq$@_1r#UrP zzLfsHQ7tc=J$1K6*~UULAiibVdBZ7#?TkW!QO?B>itD^&Gsi4jcZ9#jOtYL5zN*1E z6~4PQ8%*ruwJK#o=Egmml%b{Ox{wW4pDmCsH$2Qm(nG>Lemw? zC{i>14zq95I}T0f3{G=~Y3epB8djMbj?L0}1AtM0L({#WOjS47Yp{_Bxj0JU(HKtr zr$D~0lTJ9+4y*9n8f_;p!mo2O^F{V%J8LwvMmt+t)8#b=0zTouS4ec(r7(r7|@OY|0&H=mWz=fe8pdtKjw+sw$N1>W$upsGJBb*^HKp)-v4?fvZ=vt5+7U z3E6=NbzR0zqEG_w3Si6YhU2huuI5kxhluuZlBWOw9j<#Egk6(ec1^}Gq+Ej0%YO&t z>kmjL5^qN)T9Jt@Z!eoB5L_2n( z6+5vtx}9mp-m3qoGj_Z^Hq#oL**@1Cd$S>rb`r-v(f^=to&Vc(GjX}T(g_c@!xOFW z#N)N?cR#!J>8-}<&zs?!^~LA1*LUhaA;~xXi~w<+2ir2ed&x ze%(I;IjGGm3dT1nT6@{aJ;Nn#(erBoC@5Gq4ljF(TlPX)kcEq(WLU^Q1Q73Fj57rZ z=FOdpKp>z31-fq3OeX{N0MB!a!VXHfmRU(g4mBOZpM3|2Q0!{ehM;i7=sPwqJLP-y zZOfrW+6xSEtU@Uke7#b(Tx90xI)GX)f=61f%gn_A7O=E*HeyCf%hvy-|TkvZ@ z0ICSS$yRJ~>s&LI2Ix(lZci<=rWXF5Yfio0kS9CQk#_WWD|&ouWP7n0ov$ysXr61u z=eCVz{JXzi?zt8BJ>b?svFSR%`DSiidx=BcH;VV=ZxDQ=xG#*57J%9w))W>g29qHX zLZ_`#4!+r$1T};o$_NY?)>+q&n{QmTD-~moQz#)=msOqz%W@Seh+4KV0A^L1DyC-D znX_|eX3xyMK6`HN>`dR&B3PnkRd*m|9mvO6mC?Oj6$m^trNe6~)>6pIP#?oREmd%< z3W$=?v+M*cejz{iGF5#4PN{x^#tgr9|FqZ_*fy1P@76nt3QCD82pWoiE5g7L5vT~jEs!;7-WjrfE)lr>m6bM diff --git a/calendars/__pycache__/urls.cpython-313.pyc b/calendars/__pycache__/urls.cpython-313.pyc index a9724599fa62b8932371bc22d589ba7aff583af1..e553981eb5972fcdc1b09e450e17c7a73709153c 100644 GIT binary patch literal 691 zcmb7AK}*~~7@gT<-DI<)q!R~Hdm4^=3w!6HPdRB>>u!(?5-3Gf^(SfeR=PF-^&apC(8)VpYva>C5+H- z*^G}i0)q^|5ps}&H_$rfcp5ncN7*Q>D_ogIJNOxL)E#AWtRMTKZW{kIBRD^*PSKfY zD7m`??#lnlJKfriTL<@pd%DZgO%$$qMy+xw%{82h#q35kY~R%M%y9Q<;Dz-b z_am-Af(-GZEY&r+|0QQI?mZ;RN;KM)(MHTxz9?sg(pUM4{DT40;Fn^?gXlXNz&MmO zNh|Cy(g~=?eDaou`|JP`?a_$sQQzMUxkrO`MB|W?kJM*@OL;_Ota|rzrZqdE+w<8n ze+a|mOGO@BR|>}X0$CU6(ccMF)W2HadZ{@h%$d}j6Xsmsd~y0gsIPyj+7JEl@mgxl y3TrmC8p3Myt);WNP~YX;)06qsUJ&*|YOe@;rEjmEKN0HoRY|QDz8Jg;yRHFXv#SUI delta 152 zcmdnY+QOv%nU|M~0SNph?_|UR>Bk@r3@||%pA{ym%i8NR6tSeUXtKTpiE1+5Vkt;0 z$#}^K;jorvrj{4`X|mqpPRUBlOV8IUEy^h_Vg}0IVklw-Qj_m8#;WiD86X|SLO|jJ ZGb1D8T?U!E48k8+nE06*xr;z<0RTHaACCY4 diff --git a/calendars/__pycache__/views.cpython-313.pyc b/calendars/__pycache__/views.cpython-313.pyc index 5efd986a91af29efd08bb8c96a1778823f9c0360..9ced3183359d1b24612237f0428793eb9eba6d54 100644 GIT binary patch literal 2938 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 literal 0 HcmV?d00001 diff --git a/calendars/models.py b/calendars/models.py index 71a8362..29228dd 100644 --- a/calendars/models.py +++ b/calendars/models.py @@ -1,3 +1,37 @@ from django.db import models +from django.conf import settings +import urllib.parse -# Create your models here. +class Event(models.Model): + title = models.CharField(max_length=255) + description = models.TextField(blank=True) + start_time = models.DateTimeField() + end_time = models.DateTimeField(null=True, blank=True) + location_address = models.CharField(max_length=255) + maps_shortlink = models.URLField(blank=True, editable=False) + team = models.ForeignKey('clubs.Team', on_delete=models.CASCADE, related_name='events') + + def save(self, *args, **kwargs): + if self.location_address and not self.maps_shortlink: + self.maps_shortlink = f"https://www.google.com/maps/search/?api=1&query={urllib.parse.quote(self.location_address)}" + super().save(*args, **kwargs) + + def __str__(self): + return self.title + +class Training(Event): + pass + +class Game(Event): + opponent = models.CharField(max_length=255) + meeting_minutes_before_game = models.PositiveIntegerField(default=60) + season = models.CharField(max_length=255, blank=True) + min_players = models.PositiveIntegerField(default=9) + +class GameResult(models.Model): + game = models.OneToOneField(Game, on_delete=models.CASCADE, related_name='result') + # A simple way to store inning results as a string. A more complex solution could use a JSONField or separate Inning model. + inning_results = models.CharField(max_length=255, help_text="Comma-separated scores per inning, e.g., '1-0,0-2,3-1'") + + def __str__(self): + return f"Result for {self.game}" \ No newline at end of file diff --git a/calendars/templates/calendars/event_confirm_delete.html b/calendars/templates/calendars/event_confirm_delete.html new file mode 100644 index 0000000..5ffc6e5 --- /dev/null +++ b/calendars/templates/calendars/event_confirm_delete.html @@ -0,0 +1,11 @@ +{% extends "base.html" %} + +{% block content %} +

Delete Event

+

Are you sure you want to delete "{{ object.title }}"?

+
+ {% csrf_token %} + + Cancel +
+{% endblock %} diff --git a/calendars/templates/calendars/event_form.html b/calendars/templates/calendars/event_form.html new file mode 100644 index 0000000..adec35c --- /dev/null +++ b/calendars/templates/calendars/event_form.html @@ -0,0 +1,10 @@ +{% extends "base.html" %} + +{% block content %} +

{% if object %}Edit Event{% else %}Create Event{% endif %}

+
+ {% csrf_token %} + {{ form.as_p }} + +
+{% endblock %} diff --git a/calendars/urls.py b/calendars/urls.py index bbbd7cc..fae091b 100644 --- a/calendars/urls.py +++ b/calendars/urls.py @@ -2,5 +2,7 @@ from django.urls import path from . import views urlpatterns = [ - # Add your URLs here -] + path('event/add/', views.EventCreateView.as_view(), name='event-add'), + path('event//', views.EventUpdateView.as_view(), name='event-update'), + path('event//delete/', views.EventDeleteView.as_view(), name='event-delete'), +] \ No newline at end of file diff --git a/calendars/views.py b/calendars/views.py index 91ea44a..9f9aa4f 100644 --- a/calendars/views.py +++ b/calendars/views.py @@ -1,3 +1,41 @@ from django.shortcuts import render +from django.views.generic.edit import CreateView, UpdateView, DeleteView +from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin +from django.urls import reverse_lazy +from .models import Event +from .forms import EventForm -# Create your views here. +class CoachCheckMixin(UserPassesTestMixin): + def test_func(self): + user = self.request.user + if user.is_superuser: + return True + team = self.get_object().team + return user == team.head_coach or user in team.assistant_coaches.all() + +class EventCreateView(LoginRequiredMixin, CreateView): + model = Event + form_class = EventForm + template_name = 'calendars/event_form.html' + success_url = reverse_lazy('dashboard') + + def get_form(self, form_class=None): + form = super().get_form(form_class) + user = self.request.user + if not user.is_superuser: + coached_teams = user.coached_teams.all() + assisted_teams = user.assisted_teams.all() + teams = coached_teams | assisted_teams + form.fields['team'].queryset = teams + return form + +class EventUpdateView(LoginRequiredMixin, CoachCheckMixin, UpdateView): + model = Event + form_class = EventForm + template_name = 'calendars/event_form.html' + success_url = reverse_lazy('dashboard') + +class EventDeleteView(LoginRequiredMixin, CoachCheckMixin, DeleteView): + model = Event + template_name = 'calendars/event_confirm_delete.html' + success_url = reverse_lazy('dashboard') \ No newline at end of file diff --git a/dashboard/__pycache__/urls.cpython-313.pyc b/dashboard/__pycache__/urls.cpython-313.pyc index 7d70fae40980992e2c27ab4ac68935d9836bbb37..31b1563126e0fc08b4633f19ac7631daa4372c85 100644 GIT binary patch delta 206 zcmZo+y2_;fnU|M~0SK0#xRX%=q#uJgFu(?7e0G|sE~^#8sKgM=62lzC0h342!K}e- zmdyH$MXc%Unj8~-q|I(I+~Q10EY3*EPb^B&WW2?amzbOCr^$AUJ0&YIFFjwcv?!;z zh#6=^5y+Y%RuI8Hu_3{X56EH!;$jsb@qw9Bk@r3@||%pA{ym%W~*56tSeUXtGYsla}<;WWB|m zl9ia3p08J0lv7;93{-lHp@?O>7%Q6rTOJTRZlab*O1lS)~ba71AeDBSB^WJ>#?auP~ zGl0v+??>K10pPJvQjrrdyUxLVU;|rff-MP46@lfZyrp0z#!6G&(y$g|wW)6z*rq9)bf`N)8Z9 zmOs2oF#DNX?*j+4g!MTIr8)>@{wQtX8;c{^QX{#~T4IA20dS<2nyAk#bC3LgdDNv) z;j#KNK?!V$8lRJtgHYu$G2j1o_}>`KmTg6F8tN6$Hq*%V3n_B+OW^(29IcVuwvjNF zuA$)=^Bb)L(6-XZ)?$iI*H-xyr^qpEbwg@hNcGdz*5+AcTX*DFr7+W8PWK&P8%`#z z>oTZjwwX$KQ?Y1;XtnD>Kja`IteuV@vNME2Ogq#cAf_WR3FOoBJxIK61aYrEM>UTQ zf@*=OLxM0fJV+p=7-#swEbm6p^-!-PP9;o%LBJLuAwHpVW7ek#(}iH9FC(eQaHEi-Pbd+oE2*&8>s~ZK^+5>xD5&4U(GL2EqIwq+)D^)I?!(aM7me^K z5%s%1x<_hvc3E~#4i+sC!%2SSuMv23_S~17Uu=%9ALa|Aw@-9nEsky+84G)K|Mt{a z8XHUBTMx?*%ZKNezRNzy?tiwI<4&dW)LI=|t5fUB*t#;Y)<-uFxvjJ|F0D;V>*Lb; zq*NPiKhc3!IVxIT+Yd_Le*Vk$q}-g8ZcK_dNALb&6pzfsZnX7pz8Vk}}*U`S`uWPS-!q{(=T zttd4wCAG*;lj)W~N>*ZCdcIz9Mt)IAa%o9%5i?NDN`}uMrIUM^9aK1Ma`RJ4b5iY! ZIDkToKwJ!Bd|+l|WW2|qT*LDashboard + + {% if user.coached_teams.all or user.assisted_teams.all %} + Create New Event + {% endif %} + +

Your Team's Events

+ {% if events %} +
    + {% for event in events %} +
  • + {{ event.title }} ({{ event.start_time }}) +

    {{ event.description }}

    +

    Location: {{ event.location_address }}

    + View on Map + {% if user == event.team.head_coach or user in event.team.assistant_coaches.all %} + Edit + Delete + {% endif %} +
  • + {% endfor %} +
+ {% else %} +

No events found for your team.

+ {% endif %} +{% endblock %} \ No newline at end of file diff --git a/dashboard/urls.py b/dashboard/urls.py index bbbd7cc..ca81b57 100644 --- a/dashboard/urls.py +++ b/dashboard/urls.py @@ -2,5 +2,5 @@ from django.urls import path from . import views urlpatterns = [ - # Add your URLs here -] + path('', views.dashboard, name='dashboard'), +] \ No newline at end of file diff --git a/dashboard/views.py b/dashboard/views.py index 91ea44a..0a2015e 100644 --- a/dashboard/views.py +++ b/dashboard/views.py @@ -1,3 +1,30 @@ from django.shortcuts import render +from django.contrib.auth.decorators import login_required +from calendars.models import Event +from clubs.models import Team -# Create your views here. +@login_required +def dashboard(request): + user = request.user + events = [] + + # Get teams for players + player_teams = [] + if hasattr(user, 'team') and user.team: + player_teams = [user.team] + + # Get teams for coaches + coached_teams = user.coached_teams.all() + assisted_teams = user.assisted_teams.all() + + # Combine all teams and remove duplicates + from itertools import chain + all_teams = list(set(chain(player_teams, coached_teams, assisted_teams))) + + if all_teams: + events = Event.objects.filter(team__in=all_teams).order_by('start_time') + + context = { + 'events': events, + } + return render(request, 'dashboard/dashboard.html', context) diff --git a/db.sqlite3 b/db.sqlite3 index 985bf467237844bce8c56f20a3f595a64fe2c72b..57f311177bc52363e015ce491b1adb11999e148d 100644 GIT binary patch delta 3095 zcmai0Z*1Gf73Wd3Nl_%vPGT#I?TA()ONuN@vL$hbl~vZXbrOo8_wL@i)5$lulUIaiJ6cBxg4l_^)z8>c?|^@l=zH?9CIorl zn&lXQx8O~6RGDE>?_P>|naRi3o(oX5_r0<>X>1 znM|f6Ra13U&i5z#Gx1b1o=l0!!IA#{kz{{gYB)2T99nsK%aNu|PI#Y$ufb2?DfqH* z6&{EC;Ri4QUx2*ue#Q8muxSJ5iFuvSlCr31`tsGjS3r8%OpJi`<#!XY5E8{AUA%CE zfCg0HHTV|%3)})}*eBe8--G+$Z{SZW#@oW-b}G;XoN#(kMmn`weghR~tHHB!LE$!1 z0e=m`>-siRf$kb+(TYmxL|$i?_ucacPulnKGCevj@_@U_y~Dl6{hE7@JHzRm!X4zs zxD>aQV|<_bKCT#=ue^~0VZbk{%J-$JigX(VVt~&nc|}(obihVu=45TQhW2luKnU=1 zF6R(akOBd~;gFWJWGe->0d75g=#Tv$YBOkh*=KB>#En{ zp@Vivok>IrglpyONV85tOd#*Jn7g=Lk}#vsX1I#VC1qAF7nCh=MCl4S#b1?RP2bv! zeBDu3Cphcl>IlY=KNPQP43`3%v%}`BOTd_;p{$4)=!_y#AYAL6stW9lMvym_Y3w9N z3(nN;i+C7O6T^0Uxx33H%SAsD@3Hx=4YC6{5*5+K4~GyvI?&LJRk1GO%9H;(I|W{- z7@vdH?YqDgJ|cj#1UT!x75NhQlrTMsig6mO zY7c`Sg4Lme;1*31e_Ar$TJ>t=XW$1nkAl^kM@b(U=PnR}XN`d8VXyEMzs(o+o@XvH z?ezZU_nP-My-NMovnGUinRtfZ%x|t3=RK@3I!OCxv%rwe?iq52d3l;-R)0KAzQ|DM z)p%gZm|E?9l)Oa#a)CU@S`Qr2LJ+WXrS;@>xb$867og?m=UUwMCs(M=EW;5nCj5xI z=9}>!yQkUzPzDfKP3f_`iXIsG zgg%eQQf&e29PKKQQg=d!PX+F#{h16nE$i}hUbzzoVQKr10De;JaNk}}>Co7&iL6^} zZAEkRL68wt&3cd}dsfN(QYE)g7RUVa9NCtbnjF7>Dk~ny?#&*^PVUYgYz$>(jXOtL z(2J6JEDM6==ghK#ZA(Ed~koQgMOUS-twqKXXSq$64Jwydj+ zI=MJS^MMBe*Ky|rabo7NJQ*f;?lj*nke%h)qShw4gA(o>k4C|nCmKXs-uk)e zuREbSov3ny;%0Ol$l>xnAcEY;xpY|P!!!d=z^-9(3O%jtsI{( z+SgW4M3I$QrD$Gw1a!o{mU;JGVlvt*21|-un%8jXO(>+ake8oOilzEckJNea)adDs zVcH*yfis1M9m1Pk+af$1_{q305{iQOYYL0%|HrMdZ{ no>#R;&8`CJHzRw%CiBu9xd%j7FCQayz*_&3@#S`_db#}%9D>fY delta 414 zcmZp8z}IkqXM(h#B?AM45fH;5XrS>GP{8G#>OhH&1L#&GhxGw?U^r}Dq!Kh1w@vy{O#zKIj!C%fD4 zWme%8o6PRuxv}vW&t@^-cT8fO{B8{VZ~1TXpWW*(U%s+~u48;Xf10X2bcH9hgRW^>+eJUf^ynLaTtV)()x%dEe#k(+tDc`)N|b|yx< zjg3}}+t&v&b}+Voi)GyYEtZL!Nf+o7VTSt*{Goh@_&9mb@S5_pbL((+a;UQFvsJLJ zXEkM6!6Lyd#uUt0$8djQ Av;Y7A diff --git a/docs/phase4.md b/docs/phase4.md new file mode 100644 index 0000000..4871f47 --- /dev/null +++ b/docs/phase4.md @@ -0,0 +1,29 @@ +## Phase 4: Terminverwaltung und Google Maps API + +Hier wird die Kalenderfunktionalität implementiert, einschließlich der verschiedenen Termintypen und der Integration von Google Maps. + + Schritt 1: Terminkategorien und Basismodell erstellen + + Navigiere in das calendars-Verzeichnis und öffne models.py. + + Erstelle ein Event-Modell als Basis für alle Termine, mit Feldern wie title, description, start_time, end_time (optional), location_address und Maps_shortlink. + + Erstelle Training und Game als Submodelle von Event unter Verwendung von Multi-Table-Inheritance. + + Schritt 2: Besondere Logik für Game implementieren + + Füge dem Game-Modell die Felder opponent, meeting_minutes_before_game und die season hinzu. + + Implementiere Logik für die Mindestanzahl von Spielern (Default 9, kann von Coaches überschrieben werden). + + Erstelle ein Modell GameResult zur Speicherung der Ergebnisse pro Inning. + + Schritt 3: Google Maps Integration + + Implementiere eine Logik im calendars App, die eine gegebene Adresse in einen Google Maps Shortlink umwandelt. Dies erfordert die Nutzung einer API (oder eine einfache URL-Erstellung basierend auf den Adressdaten). + + Schritt 4: Dashboard-Views und Zugriffsrechte + + Entwickle die Views in der dashboard-App, die basierend auf der Rolle des eingeloggten Benutzers (Elternteil, Spieler, Coach) die entsprechenden Termine und "Widgets" anzeigen. + + Implementiere die Zugriffslogik, sodass ein Coach nur Termine für seine Gliederung (Mannschaft oder Team) anlegen, absagen oder bearbeiten kann.