Compare commits

..

13 Commits

15 changed files with 513 additions and 98 deletions
-1
View File
@@ -1,4 +1,3 @@
__pycache__/ __pycache__/
*.pyc *.pyc
*.sqlite3
+93 -4
View File
@@ -6,12 +6,15 @@ Muster-Implementation durch die Lehrkraft in python3 mit Django
## To get started ## To get started
## Vorbereitung der virtuellen Entwicklungsumgebung
in einem Projekt-Vezeichnis über dem eigentlichen SGTMensa-Projekt-Ordner
```bash ```bash
cd SGTMensa
python3 -m venv .venv python3 -m venv .venv
source .venv/bin/activate source .venv/bin/activate
pip install django pip install django
django-admin startproject mensa-core . pip install pillow
``` ```
Falls eine fish-Shell vorliegt: Falls eine fish-Shell vorliegt:
@@ -19,9 +22,27 @@ Falls eine fish-Shell vorliegt:
python3 -m venv .venv python3 -m venv .venv
source .venv/bin/activate.fish source .venv/bin/activate.fish
pip install django pip install django
django-admin startproject mensa-core . pip install pillow
``` ```
## Klonen des eigentlichen Projekts
```bash
git clone https://git.sgtlernen.de/mputzlocher/SGTMensa.git
```
## Starten des Servers
```bash
cd SGTMensa
python manage.py runserver
```
---
# Erweiterte Erklärung einzelner Schritte
### Schritt 1: Die Arbeitsumgebung isolieren (Virtual Environment) ### Schritt 1: Die Arbeitsumgebung isolieren (Virtual Environment)
Bevor du auch nur eine Zeile Code schreibst, erstelle eine eigene Umgebung. Das verhindert, dass sich verschiedene Projekte auf deinem Rechner gegenseitig stören. Bevor du auch nur eine Zeile Code schreibst, erstelle eine eigene Umgebung. Das verhindert, dass sich verschiedene Projekte auf deinem Rechner gegenseitig stören.
@@ -42,9 +63,10 @@ Installiere nur das Nötigste. Für den Start reicht Django.
```bash ```bash
pip install django pip install django
pip install pillow
``` ```
### Schritt 3: Das Projekt initialisieren ### Schritt 3: Das Projekt initialisieren (nur einmalig, beim ersten Start des Projekts)
Erstelle die Struktur eines Django-Projekts. Erstelle die Struktur eines Django-Projekts.
@@ -119,3 +141,70 @@ python manage.py runserver
Öffne deinen Browser unter `http://127.0.0.1:8000/admin`. Logge dich mit deinem Superuser ein. **Herzlichen Glückwunsch!** Du hast eine voll funktionsfähige Web-Anwendung, mit der du bereits Speisepläne und Bestellungen in einer Datenbank verwalten kannst. Öffne deinen Browser unter `http://127.0.0.1:8000/admin`. Logge dich mit deinem Superuser ein. **Herzlichen Glückwunsch!** Du hast eine voll funktionsfähige Web-Anwendung, mit der du bereits Speisepläne und Bestellungen in einer Datenbank verwalten kannst.
### Schritt 9: Eigentliche Entwicklungsarbeit
Folgende Befehle werden nacheinander ausgeführt:
#### Eigenen Projektbaum auf dem lokalen Rechen auf den aktuellen Stand bringen
Öffne ein Terminal und navigiere in das Projektverzeichnis, das bereits über `git clone` abgeholt worden ist.
zum Beispiel:
```bash
cd ~/projects/SGTMensa
```
Hole den aktuellen Stand ab:
```bash
git pull
```
Aktiviere die virtuelle Entwicklungsumgebung (falls noch nicht geschehen):
```bash
source .venv/bin/activate
```
Führe notwendige Migrationen der bestehenden Datenbank aus:
```bash
python manage.py makemigrations
python manage.py migrate
```
Starte den Server des Projekts:
```bash
python manage.py runserver
```
Jetzt bist Du startklar für die Entwicklungsarbeit und solltest den aktuellen Stand im Browser unter `http://localhost:8000/` abrufen können.
#### Änderungen vornehmen
Django erkennt selbstständig Veränderungen am Quellcode und startet den Webserver neu. Achte auf Fehlermeldungen!
#### Änderungen wieder auf den git-Server einspielen
Nach einem Entwicklungsschritt solltest Du Deine Änderungen von Deinem lokalen Rechner wieder auf den git-Server hochladen,
damit Dein Team auch sieht, was Du gemacht hast, und damit Deine Ergänzungen und Änderungen wieder in den gemeinsamen
Code integriert werden können.
Nachsehen der Änderungen:
```bash
git status
```
Dies zeigt alle veränderten Dateien an.
Veränderte Dateien ins Änderungspaket aufnehmen:
```bash
git add .
```
Veränderungen bestätigen und beschreiben:
```bash
git commit -m "Deine sinnvolle Beschreibung der Änderung in wenigen Worten"
```
Veränderungen auf den git-Server hochladen:
```bash
git push
```
BIN
View File
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 61 KiB

+30 -28
View File
@@ -3,33 +3,35 @@ from django.contrib import admin
# Register your models here. # Register your models here.
from .models import Person # Ersetze dies durch deine echten Klassennamen from .models import Person # Ersetze dies durch deine echten Klassennamen
class PersonInlineChildren(admin.TabularInline):
"""
Inline für das Feld ``children`` (Schüler eines Elternteils).
Nur bei Eltern (Rolle Mitarbeit/Chef) sichtbar.
"""
model = Person.children.through # Das durchschnittliche JoinModel
verbose_name = 'Kind'
verbose_name_plural = 'Kinder'
extra = 0 # Keine leeren Zeilen anzeigen
# ---------------------------------------------------------- # DELTE: Die Option, Kinder zuzuordnen soll zunächst nicht umgesetzt werden.
# **WICHTIG:** Hier wird der zu verwendende FK explizit genannt # class PersonInlineChildren(admin.TabularInline):
# ---------------------------------------------------------- # """
fk_name = 'person' # <-- legt fest, welcher FK gemeint ist # Inline für das Feld ``children`` (Schüler eines Elternteils).
# Nur bei Eltern (Rolle Mitarbeit/Chef) sichtbar.
def has_add_permission(self, request, obj=None): # """
# Erlaube Hinzufügen nur für Eltern (Rolle Eltern) # model = Person.children.through # Das durchschnittliche JoinModel
return obj and obj.rolle in ('eltern') # verbose_name = 'Kind'
# verbose_name_plural = 'Kinder'
def formfield_for_foreignkey(self, db_field, request, **kwargs): # extra = 0 # Keine leeren Zeilen anzeigen
""" #
Verhindere, dass ein Benutzer sich selbst als Kind hinzufügt. # # ----------------------------------------------------------
""" # # **WICHTIG:** Hier wird der zu verwendende FK explizit genannt
if db_field.name == "person": # # ----------------------------------------------------------
# Hier handelt es sich um den FK auf die Person (das Ziel) # fk_name = 'person' # <-- legt fest, welcher FK gemeint ist
kwargs["queryset"] = Person.objects.exclude(pk=self.instance.pk) #
return super().formfield_for_foreignkey(db_field, request, **kwargs) # def has_add_permission(self, request, obj=None):
# # Erlaube Hinzufügen nur für Eltern (Rolle Eltern)
# return obj and obj.rolle in ('eltern')
#
# def formfield_for_foreignkey(self, db_field, request, **kwargs):
# """
# Verhindere, dass ein Benutzer sich selbst als Kind hinzufügt.
# """
# if db_field.name == "person":
# # Hier handelt es sich um den FK auf die Person (das Ziel)
# kwargs["queryset"] = Person.objects.exclude(pk=self.instance.pk)
# return super().formfield_for_foreignkey(db_field, request, **kwargs)
@admin.register(Person) @admin.register(Person)
class PersonAdmin(admin.ModelAdmin): class PersonAdmin(admin.ModelAdmin):
@@ -37,8 +39,8 @@ class PersonAdmin(admin.ModelAdmin):
search_fields = ['user__username', 'klasse'] search_fields = ['user__username', 'klasse']
ordering = ('rolle',) ordering = ('rolle',)
# ---- Inline für Eltern (Rolle Mitarbeit/Chef) ---- # # ---- Inline für Eltern (Rolle Mitarbeit/Chef) ----
inlines = [PersonInlineChildren] # Zeigt das KinderInline nur bei passenden Rollen an # inlines = [PersonInlineChildren] # Zeigt das KinderInline nur bei passenden Rollen an
def has_change_permission(self, request, obj=None): def has_change_permission(self, request, obj=None):
""" """
+32
View File
@@ -0,0 +1,32 @@
# forms.py
from django import forms
from .models import Gericht
# Importiere alle Modelle, die für das Formular relevant sind
from .models import Kategorie
class GerichtForm(forms.ModelForm):
"""Definiert die Struktur und Validierungsregeln für das Gericht-Modell."""
class Meta:
model = Gericht
fields = [
'name',
'kategorie',
'preis',
'ist_vegetarisch',
'ist_allergene_frei',
'allergene',
'beschreibung', # Neues Feld für Zutaten/Rohstoffe
'ist_dauerangebot'
]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Wir können hier zusätzliche Validierungen oder Widgets einbinden
# Beispiel: Erzwinge das Betreten der Kategorie-Auswahl
self.fields['kategorie'].required = True
# Hier könntest du auch einen Fallback für den Preis hinzufügen, falls er nicht gesetzt ist
if 'initial' in kwargs: # Wird bei GET-Requests genutzt
self.initial['preis'] = 0.00
@@ -0,0 +1,14 @@
# Generated by Django 5.2.14 on 2026-06-12 09:13
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('mensa_app', '0005_alter_bestellung_id_alter_gericht_id_and_more'),
('mensa_app', '0009_alter_gericht_time_creation_and_more'),
]
operations = [
]
@@ -0,0 +1,38 @@
# Generated by Django 5.2.15 on 2026-06-12 10:50
import datetime
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mensa_app', '0010_merge_20260612_0913'),
]
operations = [
migrations.AlterModelOptions(
name='person',
options={},
),
migrations.AlterField(
model_name='gericht',
name='time_creation',
field=models.TimeField(default=datetime.datetime(2026, 6, 12, 10, 50, 49, 206132)),
),
migrations.AlterField(
model_name='gericht',
name='time_last_change',
field=models.TimeField(default=datetime.datetime(2026, 6, 12, 10, 50, 49, 206132)),
),
migrations.AlterField(
model_name='gerichtbild',
name='id',
field=models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
),
migrations.AlterField(
model_name='person',
name='rolle',
field=models.CharField(choices=[('schueler', 'Schüler'), ('eltern', 'Eltern'), ('lehrer', 'Lehrer'), ('mitarbeiter', 'Mensa-Mitarbeiter'), ('chef', 'Mensa-Leitung')], max_length=20),
),
]
@@ -0,0 +1,29 @@
# Generated by Django 5.2.14 on 2026-06-26 08:55
import datetime
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mensa_app', '0011_alter_person_options_alter_gericht_time_creation_and_more'),
]
operations = [
migrations.AddField(
model_name='gericht',
name='beschreibung',
field=models.TextField(blank=True, null=True),
),
migrations.AlterField(
model_name='gericht',
name='time_creation',
field=models.TimeField(default=datetime.datetime(2026, 6, 26, 8, 55, 13, 383962)),
),
migrations.AlterField(
model_name='gericht',
name='time_last_change',
field=models.TimeField(default=datetime.datetime(2026, 6, 26, 8, 55, 13, 383962)),
),
]
+52 -51
View File
@@ -1,10 +1,8 @@
import os
from django.db import models from django.db import models
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.utils import timezone from django.utils import timezone
import datetime import datetime
import os
from django.db import models
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.validators import FileExtensionValidator from django.core.validators import FileExtensionValidator
# Pillow ist für die Bildbearbeitung zuständig (installiere per pip install pillow) # Pillow ist für die Bildbearbeitung zuständig (installiere per pip install pillow)
@@ -19,55 +17,57 @@ class Person(models.Model):
rolle = models.CharField(max_length=20, choices=[('schueler', 'Schüler'), ('eltern', 'Eltern'), ('lehrer', 'Lehrer'), ('mitarbeiter','Mensa-Mitarbeiter'), ('chef','Mensa-Leitung')]) rolle = models.CharField(max_length=20, choices=[('schueler', 'Schüler'), ('eltern', 'Eltern'), ('lehrer', 'Lehrer'), ('mitarbeiter','Mensa-Mitarbeiter'), ('chef','Mensa-Leitung')])
klasse = models.CharField(max_length=4, blank=True, null=True) # Nur für Schüler relevant klasse = models.CharField(max_length=4, blank=True, null=True) # Nur für Schüler relevant
# ----------------------------------------------------------------------
# Neue ManytoMany Beziehung: „Elternteil ↔ Schüler“
# ----------------------------------------------------------------------
children = models.ManyToManyField(
'self',
blank=True,
related_name='parents', # → für einen Schüler: person.parents.all()
symmetrical=False, # Verhindert eine zirkuläre RückBeziehung (nicht benötigt)
help_text=(
"Eltern können hier die ihnen zugewiesenen Schüler hinzufügen. Für Schüler bleibt dieses Feld leer."
)
)
# ------------------------------------------------------------------ # DELETE: Die Eltern-Kinder Beziehung soll zunächst nicht umgesetzt werden.
# Optional: Hilfsmethode, die alle Kinder zurückgibt, ggf. mit einer Sortierung # # ----------------------------------------------------------------------
def get_children_sorted(self): # # Neue ManytoMany Beziehung: „Elternteil ↔ Schüler“
# Das `order_by('name')` ist ein Beispiel du kannst deine eigene Sortierung verwenden. # # ----------------------------------------------------------------------
return self.children.order_by('name') # children = models.ManyToManyField(
# 'self',
class Meta: # blank=True,
verbose_name_plural = "Personen" # related_name='parents', # → für einen Schüler: person.parents.all()
# symmetrical=False, # Verhindert eine zirkuläre RückBeziehung (nicht benötigt)
def __str__(self): # help_text=(
if self.children.exists(): # "Eltern können hier die ihnen zugewiesenen Schüler hinzufügen. Für Schüler bleibt dieses Feld leer."
# Wir sammeln alle KinderNamen in einem String, z.B. mit Komma getrennt. # )
children_names = ', '.join([c.name for c in self.children.all()]) # )
return f"{self.user.username} ({children_names}) ({self.rolle})" #
else: # # ------------------------------------------------------------------
return f"{self.user.username} ({self.rolle})" # # Optional: Hilfsmethode, die alle Kinder zurückgibt, ggf. mit einer Sortierung
# def get_children_sorted(self):
def clean(self): # # Das `order_by('name')` ist ein Beispiel du kannst deine eigene Sortierung verwenden.
""" # return self.children.order_by('name')
Überprüfung für die ManyToMany-Beziehung ``children``: #
- Nur Personen mit der Rolle 'mitarbeiter' oder 'chef' (Eltern) dürfen Kinder haben. # class Meta:
- Ein Schüler darf sich selbst nicht als Kind hinzufügen. # verbose_name_plural = "Personen"
""" #
super().clean() # ruft die Validierungen der Elternklasse auf (hier User/Model) # def __str__(self):
# if self.children.exists():
if self.rolle in ('mitarbeiter', 'chef', 'lehrer'): # # Wir sammeln alle KinderNamen in einem String, z.B. mit Komma getrennt.
# NichtEltern dürfen keine Kinder besitzen # children_names = ', '.join([c.name for c in self.children.all()])
if self.children.exists(): # return f"{self.user.username} ({children_names}) ({self.rolle})"
raise ValidationError( # else:
"Nur Benutzer mit der Rolle 'Eltern' " # return f"{self.user.username} ({self.rolle})"
"dürfen Schüler zuordnen (children)." #
) # def clean(self):
else: # """
# Eltern: Verhindere, dass sie sich selbst als Kind eintragen # Überprüfung für die ManyToMany-Beziehung ``children``:
if self in self.children.all(): # - Nur Personen mit der Rolle 'mitarbeiter' oder 'chef' (Eltern) dürfen Kinder haben.
raise ValidationError("Ein Benutzer darf nicht gleichzeitig Elternteil und eigener Kind sein.") # - Ein Schüler darf sich selbst nicht als Kind hinzufügen.
# """
# super().clean() # ruft die Validierungen der Elternklasse auf (hier User/Model)
#
# if self.rolle in ('mitarbeiter', 'chef', 'lehrer'):
# # NichtEltern dürfen keine Kinder besitzen
# if self.children.exists():
# raise ValidationError(
# "Nur Benutzer mit der Rolle 'Eltern' "
# "dürfen Schüler zuordnen (children)."
# )
# else:
# # Eltern: Verhindere, dass sie sich selbst als Kind eintragen
# if self in self.children.all():
# raise ValidationError("Ein Benutzer darf nicht gleichzeitig Elternteil und eigener Kind sein.")
@@ -128,6 +128,7 @@ class Gericht(models.Model):
) )
ist_vegetarisch = models.BooleanField(default=False) ist_vegetarisch = models.BooleanField(default=False)
ist_allergene_frei = models.BooleanField(default=False) ist_allergene_frei = models.BooleanField(default=False)
beschreibung = models.TextField(blank=True, null=True) # Zutaten/Rohstoffe Beschreibung (Textfeld für V1)
allergene = models.TextField(blank=True, default="") allergene = models.TextField(blank=True, default="")
preis = models.DecimalField(max_digits=5, decimal_places=2, default=0.00) preis = models.DecimalField(max_digits=5, decimal_places=2, default=0.00)
@@ -0,0 +1,120 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Neues Gericht hinzufügen</title>
<!-- Bootstrap CSS einbinden -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- OPTIONAL: Füge hier deinen eigenen, kleinen Custom-CSS-Link hinzu! -->
</head>
<body>
<div class="container mt-5 mb-5">
<!-- Der Haupttitel muss prominent sein und signalisiert den Zweck der Seite -->
<header class="mb-4 border-bottom pb-2">
<h1>🥗 Neues Gericht Speiseplan hinzufügen</h1>
<p class="text-muted">Bitte fülle alle notwendigen Felder aus, um das Gericht zu veröffentlichen.</p>
</header>
<!-- Die Hauptkarte für das Formular -->
<div class="card shadow p-4 mb-5" id="gericht-form-container">
{# ⚠️ Wichtig: enctype bleibt erhalten! #}
<form method="post" enctype="multipart/upload">
{% csrf_token %}
<!-- ==================== BLOCK 1: GRUNDDATEN (Kategorie, Name, Preis) ==================== -->
<div class="card mb-4 shadow-sm border-primary">
<div class="card-header bg-primary text-white p-3">Grunddaten &amp; Identifikation</div>
<div class="card-body row g-3">
<!-- Name (col-md-4) -->
<div class="col-lg-4 col-md-6 mb-3">
{{ form.name }}
{% if form.name.errors %}<p class="text-danger small mt-1">{{ form.name.errors }}</p>{% endif %}
</div>
<!-- Kategorie (Dropdown) -->
<div class="col-lg-4 col-md-6 mb-3">
{{ form.kategorie }}
{% if form.kategorie.errors %}<p class="text-danger small mt-1">{{ form.kategorie.errors }}</p>{% endif %}
</div>
<!-- Preis (Money Input) -->
<div class="col-lg-4 col-md-6 mb-3">
{{ form.preis }}
{% if form.preis.errors %}<p class="text-danger small mt-1">{{ form.preis.errors }}</p>{% endif %}
</div>
</div>
</div>
<!-- ==================== BLOCK 2: DETAILS & STATUS (Allergene, Flags) ==================== -->
<div class="card mb-4 shadow-sm border-warning">
<div class="card-header bg-warning text-dark p-3">Details und Statusprüfung</div>
<div class="card-body row g-3">
<!-- Switches (Checkboxen) - Nehmen wenig Platz ein -->
<div class="col-md-4 col-lg-2 mb-3">
{% if form.ist_vegetarisch %}
{{ form.ist_vegetarisch }} <label for="{{ form.ist_vegetarisch.id_for_label }}" class="form-check-label">Vegetarisch</label>
{% endif %}
</div>
<div class="col-md-4 col-lg-2 mb-3">
{% if form.ist_allergene_frei %}
{{ form.ist_allergene_frei }} <label for="{{ form.ist_allergene_frei.id_for_label }}" class="form-check-label">Allergenfrei</label>
{% endif %}
</div>
<!-- Dauerangebot -->
<div class="col-md-4 col-lg-2 mb-3">
{{ form.ist_dauerangebot }}
<label for="{{ form.ist_dauerangebot.id_for_label }}" class="form-check-label">Dauerangebot</label>
</div>
</div>
</div>
<!-- ==================== BLOCK 3: TEXTBEDARF (Beschreibung, Allergene) ==================== -->
<div class="card mb-4 shadow-sm border-info">
<div class="card-body p-3">
<h6 class="card-title text-info mb-3">Beschreibung &amp; Nährstoffe</h6>
<!-- Allergene (TextArea) -->
<div class="mb-3">
{{ form.allergene }}
{% if form.allergene.errors %}<p class="text-danger small mt-1">{{ form.allergene.errors }}</p>{% endif %}
</div>
<!-- Beschreibung (TextArea) -->
<div class="mb-3">
{{ form.beschreibung }}
{% if form.beschreibung.errors %}<p class="text-danger small mt-1">{{ form.beschreibung.errors }}</p>{% endif %}
</div>
</div>
</div>
<!-- ==================== BLOCK 4: BILDER & Upload (Media) ==================== -->
<div class="card mb-5 shadow-sm border-secondary">
<div class="card-header bg-secondary text-white p-3">Mediendateien hochladen</div>
<div class="card-body">
<div class="mb-3">
{{ form.bilder }}
{% if form.bilder.errors %}<p class="text-danger small mt-1">{{ form.bilder.errors }}</p>{% endif %}
</div>
</div>
</div>
<!-- Submit Button Container -->
<div class="d-grid gap-2">
<button type="submit" class="btn btn-lg btn-success">Gericht Speichern &amp; Veröffentlichen</button>
</div>
</form>
</div>
</div>
</body>
</html>
@@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Erfolg</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<div class="alert alert-success shadow p-5 text-center">
<h2 class="mb-3">🎉 Erfolgreich hinzugefügt! 🎉</h2>
<p class="lead">
Das Gericht <strong>{{ objekt_name }}</strong> wurde erfolgreich in das Menüsystem übernommen.
</p>
<p>
Du kannst nun die Details überprüfen und weitere Gerichte hinzufügen.
</p>
</div>
<div class="mt-4 text-center">
<!-- Der wichtigste Button, der zum Hauptbereichen zurückführt -->
<a href="{% url 'speisekarte' %}" class="btn btn-primary btn-lg">
&larr; Zurück zur Speisekarte &amp; Verwalten
</a>
</div>
</div>
</body>
</html>
+17
View File
@@ -2,9 +2,26 @@ from django.urls import path
from .views import GerichtListView from .views import GerichtListView
from .views import SpeiseplanView # Achte auf den neuen Klassennamen! from .views import SpeiseplanView # Achte auf den neuen Klassennamen!
from .views import BestellSummaryView from .views import BestellSummaryView
from .views import GerichtCreateView
urlpatterns = [ urlpatterns = [
path('speisekarte/', GerichtListView.as_view(), name='speisekarte'), path('speisekarte/', GerichtListView.as_view(), name='speisekarte'),
path('speiseplan/', SpeiseplanView.as_view(), name='speiseplan'), path('speiseplan/', SpeiseplanView.as_view(), name='speiseplan'),
path('bestellungen/summary/', BestellSummaryView.as_view(), name='bestell_summary'), path('bestellungen/summary/', BestellSummaryView.as_view(), name='bestell_summary'),
] ]
# ACHTUNG: Hier benötigen wir jetzt eine separate View für die Erfolgsmeldung!
# Wir nutzen hierfür ein sehr einfaches TemplateView.
from django.views.generic import TemplateView # Muss oben importiert werden
from .models import Gericht
class ErfolgTemplateView(TemplateView):
"""Zeigt einfach nur die Bestätigung an."""
template_name = 'mensa_app/add_gericht_success.html'
urlpatterns += [
# ... (vorherige Pfade bleiben) ...
path('gerichte/neu/', GerichtCreateView.as_view(), name='add_gericht'),
# NEU: Erfolgsmeldung nach erfolgreichem POST/redirect
path('erfolg/', ErfolgTemplateView.as_view(), name='success_message'),
]
+58 -13
View File
@@ -1,18 +1,20 @@
# views.py
from django.db.models import Count, Sum
from django.shortcuts import render from django.shortcuts import render
from django.utils import timezone
from datetime import datetime
from django.views.generic import ListView, TemplateView
from django.views.generic.edit import CreateView
from django.shortcuts import redirect, reverse # Importiere die Redirect-Funktion
from .forms import GerichtForm # Importiere dein neues Formular
from .models import Gericht, Menue, SpeiseplanTag, Bestellung
# Create your views here.
from django.views.generic import ListView
from .models import Gericht
class GerichtListView(ListView): class GerichtListView(ListView):
model = Gericht model = Gericht
template_name = 'mensa_app/gericht_liste.html' # Der Pfad zum Template template_name = 'mensa_app/gericht_liste.html' # Der Pfad zum Template
context_object_name = 'alle_gerichte' # Der Name, den wir im Template nutzen context_object_name = 'alle_gerichte' # Der Name, den wir im Template nutzen
from django.views.generic import TemplateView
from django.utils import timezone
from datetime import datetime
from .models import Menue, Gericht, SpeiseplanTag
class SpeiseplanView(TemplateView): class SpeiseplanView(TemplateView):
template_name = 'mensa_app/speiseplan.html' template_name = 'mensa_app/speiseplan.html'
@@ -36,6 +38,7 @@ class SpeiseplanView(TemplateView):
context['target_date'] = target_date context['target_date'] = target_date
context['menues_day'] = Menue.objects.filter(tag__datum=target_date) context['menues_day'] = Menue.objects.filter(tag__datum=target_date)
# 3. Dauerangebote laden (unabhängig vom Tag) # 3. Dauerangebote laden (unabhängig vom Tag)
context['dauerangebote'] = Gericht.objects.filter(ist_dauerangebot=True) context['dauerangebote'] = Gericht.objects.filter(ist_dauerangebot=True)
@@ -50,12 +53,6 @@ class SpeiseplanView(TemplateView):
return context return context
from django.views.generic import TemplateView
from django.utils import timezone
from django.db.models import Count, Sum
from datetime import datetime
from .models import Bestellung, SpeiseplanTag, Gericht, Menue
class BestellSummaryView(TemplateView): class BestellSummaryView(TemplateView):
template_name = 'mensa_app/bestell_summary.html' template_name = 'mensa_app/bestell_summary.html'
@@ -99,3 +96,51 @@ class BestellSummaryView(TemplateView):
context['total_umsatz'] = Bestellung.objects.filter(menue__tag__datum=target_date).aggregate(Sum('menue__preis'))['menue__preis__sum'] or 0 context['total_umsatz'] = Bestellung.objects.filter(menue__tag__datum=target_date).aggregate(Sum('menue__preis'))['menue__preis__sum'] or 0
return context return context
class GerichtCreateView(CreateView):
"""
View zur Erstellung eines neuen Gerichts.
Nutzt das benutzerdefinierte GerichtForm für die gesamte Steuerung.
"""
model = Gericht
form_class = GerichtForm # Verwendet unser Custom-Formular
template_name = 'mensa_app/add_gericht.html'
def get_success_url(self):
# Die Methode wird ausgeführt, nachdem die View erfolgreich ist
objekt = self.object # Gibt uns das gerade gespeicherte Objekt zurück
return reverse('speisekarte')
def form_valid(self, form):
"""
Die korrigierte Version der Form-Validierung.
Das 'form' Argument wird hier explizit als zweites Argument deklariert, um den TypeError zu verhindern.
"""
# 1.!!! WICHTIG: Das Aufrufen von super().form_valid() MUSS der allererste Befehl sein!
# Django speichert damit das Hauptobjekt (das Gericht) und übergibt es als self.object
super().form_valid(form)
# Jetzt sind wir sicher, dass das Objekt gespeichert wurde und Zugriff auf das neu erstellte Objekt haben:
newly_created_dish = self.object
# 2. Bilder verarbeiten (Der kritische Schritt nach der Speicherung)
uploaded_files = self.request.FILES.getlist('bilder') # Achtung: self.request wird hier genutzt!
if uploaded_files:
for file in uploaded_files:
try:
GerichtBild.objects.create(
gericht=newly_created_dish,
image=file,
sort_order=0 # Setze eine Standardreihenfolge
)
except Exception as e:
print(f"WARNUNG beim Speichern des Bildes {file.name}: {e}")
# Wir müssen danach explizit den Return-Wert festlegen, damit Django weitergeleitet werden kann.
return super().form_valid(form)
# Optional: Setzen des initialen Datenzustands (z.B. Datum, falls relevant)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
return context