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'
|
||||
|
||||
AUTH_USER_MODEL = 'accounts.CustomUser'
|
||||
|
||||
LOGIN_REDIRECT_URL = '/'
|
||||
|
||||
@ -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')),
|
||||
]
|
||||
|
||||
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 .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.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
|
||||
|
||||
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.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
|
||||
|
||||
urlpatterns = [
|
||||
# Add your URLs here
|
||||
path('', views.dashboard, name='dashboard'),
|
||||
]
|
||||
@ -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)
|
||||
|
||||
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