feat: Implementierung von Phase 4 (Terminverwaltung und Google Maps API) und Korrektur der Dashboard-Logik
This commit is contained in:
parent
05da0c94ac
commit
ea8439e616
Binary file not shown.
Binary file not shown.
@ -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 = '/'
|
||||||
|
|||||||
@ -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.
BIN
calendars/__pycache__/forms.cpython-313.pyc
Normal file
BIN
calendars/__pycache__/forms.cpython-313.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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
7
calendars/forms.py
Normal 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']
|
||||||
55
calendars/migrations/0001_initial.py
Normal file
55
calendars/migrations/0001_initial.py
Normal 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')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
BIN
calendars/migrations/__pycache__/0001_initial.cpython-313.pyc
Normal file
BIN
calendars/migrations/__pycache__/0001_initial.cpython-313.pyc
Normal file
Binary file not shown.
@ -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}"
|
||||||
11
calendars/templates/calendars/event_confirm_delete.html
Normal file
11
calendars/templates/calendars/event_confirm_delete.html
Normal 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 %}
|
||||||
10
calendars/templates/calendars/event_form.html
Normal file
10
calendars/templates/calendars/event_form.html
Normal 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 %}
|
||||||
@ -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'),
|
||||||
]
|
]
|
||||||
@ -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')
|
||||||
Binary file not shown.
Binary file not shown.
29
dashboard/templates/dashboard/dashboard.html
Normal file
29
dashboard/templates/dashboard/dashboard.html
Normal 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 %}
|
||||||
@ -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'),
|
||||||
]
|
]
|
||||||
@ -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)
|
||||||
|
|||||||
BIN
db.sqlite3
BIN
db.sqlite3
Binary file not shown.
29
docs/phase4.md
Normal file
29
docs/phase4.md
Normal 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.
|
||||||
Loading…
x
Reference in New Issue
Block a user