feat: Implementierung von Phase 4 (Terminverwaltung und Google Maps API) und Korrektur der Dashboard-Logik

This commit is contained in:
Matthias Nagel 2025-10-01 08:32:56 +02:00
parent 05da0c94ac
commit ea8439e616
25 changed files with 257 additions and 9 deletions

View File

@ -126,3 +126,5 @@ STATIC_URL = 'static/'
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
AUTH_USER_MODEL = 'accounts.CustomUser' AUTH_USER_MODEL = 'accounts.CustomUser'
LOGIN_REDIRECT_URL = '/'

View File

@ -22,5 +22,5 @@ urlpatterns = [
path('accounts/', include('accounts.urls')), path('accounts/', include('accounts.urls')),
path('clubs/', include('clubs.urls')), path('clubs/', include('clubs.urls')),
path('calendars/', include('calendars.urls')), path('calendars/', include('calendars.urls')),
path('dashboard/', include('dashboard.urls')), path('', include('dashboard.urls')),
] ]

Binary file not shown.

View File

@ -1,3 +1,7 @@
from django.contrib import admin from django.contrib import admin
from .models import Event, Training, Game, GameResult
# Register your models here. admin.site.register(Event)
admin.site.register(Training)
admin.site.register(Game)
admin.site.register(GameResult)

7
calendars/forms.py Normal file
View File

@ -0,0 +1,7 @@
from django import forms
from .models import Event
class EventForm(forms.ModelForm):
class Meta:
model = Event
fields = ['title', 'description', 'start_time', 'end_time', 'location_address', 'team']

View File

@ -0,0 +1,55 @@
# Generated by Django 5.2.6 on 2025-10-01 06:21
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('clubs', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Event',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=255)),
('description', models.TextField(blank=True)),
('start_time', models.DateTimeField()),
('end_time', models.DateTimeField(blank=True, null=True)),
('location_address', models.CharField(max_length=255)),
('maps_shortlink', models.URLField(blank=True, editable=False)),
('team', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='events', to='clubs.team')),
],
),
migrations.CreateModel(
name='Game',
fields=[
('event_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='calendars.event')),
('opponent', models.CharField(max_length=255)),
('meeting_minutes_before_game', models.PositiveIntegerField(default=60)),
('season', models.CharField(blank=True, max_length=255)),
('min_players', models.PositiveIntegerField(default=9)),
],
bases=('calendars.event',),
),
migrations.CreateModel(
name='Training',
fields=[
('event_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='calendars.event')),
],
bases=('calendars.event',),
),
migrations.CreateModel(
name='GameResult',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('inning_results', models.CharField(help_text="Comma-separated scores per inning, e.g., '1-0,0-2,3-1'", max_length=255)),
('game', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='result', to='calendars.game')),
],
),
]

View File

@ -1,3 +1,37 @@
from django.db import models 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}"

View File

@ -0,0 +1,11 @@
{% extends "base.html" %}
{% block content %}
<h2>Delete Event</h2>
<p>Are you sure you want to delete "{{ object.title }}"?</p>
<form method="post">
{% csrf_token %}
<button type="submit">Confirm Delete</button>
<a href="{% url 'dashboard' %}">Cancel</a>
</form>
{% endblock %}

View File

@ -0,0 +1,10 @@
{% extends "base.html" %}
{% block content %}
<h2>{% if object %}Edit Event{% else %}Create Event{% endif %}</h2>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Save</button>
</form>
{% endblock %}

View File

@ -2,5 +2,7 @@ from django.urls import path
from . import views from . import views
urlpatterns = [ urlpatterns = [
# Add your URLs here path('event/add/', views.EventCreateView.as_view(), name='event-add'),
] path('event/<int:pk>/', views.EventUpdateView.as_view(), name='event-update'),
path('event/<int:pk>/delete/', views.EventDeleteView.as_view(), name='event-delete'),
]

View File

@ -1,3 +1,41 @@
from django.shortcuts import render 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')

View File

@ -0,0 +1,29 @@
{% extends "base.html" %}
{% block content %}
<h2>Dashboard</h2>
{% if user.coached_teams.all or user.assisted_teams.all %}
<a href="{% url 'event-add' %}">Create New Event</a>
{% endif %}
<h3>Your Team's Events</h3>
{% if events %}
<ul>
{% for event in events %}
<li>
<strong>{{ event.title }}</strong> ({{ event.start_time }})
<p>{{ event.description }}</p>
<p>Location: {{ event.location_address }}</p>
<a href="{{ event.maps_shortlink }}" target="_blank">View on Map</a>
{% if user == event.team.head_coach or user in event.team.assistant_coaches.all %}
<a href="{% url 'event-update' event.pk %}">Edit</a>
<a href="{% url 'event-delete' event.pk %}">Delete</a>
{% endif %}
</li>
{% endfor %}
</ul>
{% else %}
<p>No events found for your team.</p>
{% endif %}
{% endblock %}

View File

@ -2,5 +2,5 @@ from django.urls import path
from . import views from . import views
urlpatterns = [ urlpatterns = [
# Add your URLs here path('', views.dashboard, name='dashboard'),
] ]

View File

@ -1,3 +1,30 @@
from django.shortcuts import render 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)

Binary file not shown.

29
docs/phase4.md Normal file
View File

@ -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.