Compare commits

...

4 Commits

6 changed files with 162 additions and 29 deletions
BIN
View File
Binary file not shown.
-1
View File
@@ -18,7 +18,6 @@ class GerichtForm(forms.ModelForm):
'ist_allergene_frei', 'ist_allergene_frei',
'allergene', 'allergene',
'beschreibung', # Neues Feld für Zutaten/Rohstoffe 'beschreibung', # Neues Feld für Zutaten/Rohstoffe
'bilder', # Das Bild-Feld
'ist_dauerangebot' 'ist_dauerangebot'
] ]
@@ -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)),
),
]
+105 -17
View File
@@ -1,32 +1,120 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="de"> <html lang="de">
<head> <head>
<!-- (Standard Head und Bootstrap CDN einbinden) --> <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> </head>
<body class="bg-light">
<div class="container mt-5">
<h1>Neue Speiseart hinzufügen</h1>
{# !!! KRITISCH WICHTIG: enctype muss für Dateiuploads stehen !!! #} <body>
<form method="post" enctype="multipart/form-data"> <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 %} {% csrf_token %}
<!-- Der Standardway, alle Felder mit Bootstrap zu stylen (für Anfänger am besten) --> <!-- ==================== BLOCK 1: GRUNDDATEN (Kategorie, Name, Preis) ==================== -->
{# Man kann die komplexe Struktur durch eine einfache Schleife ersetzen #} <div class="card mb-4 shadow-sm border-primary">
{% for field in form %} <div class="card-header bg-primary text-white p-3">Grunddaten &amp; Identifikation</div>
<div class="mb-3"> <div class="card-body row g-3">
<label for="{{ field.id_for_label }}" class="form-label">{{ field.label }}</label>
{{ field }}
<!-- Fehlerhinweis des Feldes wird hier automatisch gerendert --> <!-- Name (col-md-4) -->
{% if field.errors %} <div class="col-lg-4 col-md-6 mb-3">
<p class="text-danger small mt-1">{{ field.errors }}</p> {{ 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 %} {% endif %}
</div> </div>
{% endfor %}
<button type="submit" class="btn btn-success mt-4">Gericht Speichern & Veröffentlichen</button> <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> </form>
</div> </div>
</div>
</body> </body>
</html> </html>
+1
View File
@@ -2,6 +2,7 @@ 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'),
+24 -8
View File
@@ -5,7 +5,7 @@ from django.utils import timezone
from datetime import datetime from datetime import datetime
from django.views.generic import ListView, TemplateView from django.views.generic import ListView, TemplateView
from django.views.generic.edit import CreateView from django.views.generic.edit import CreateView
from django.shortcuts import redirect # Importiere die Redirect-Funktion from django.shortcuts import redirect, reverse # Importiere die Redirect-Funktion
from .forms import GerichtForm # Importiere dein neues Formular from .forms import GerichtForm # Importiere dein neues Formular
from .models import Gericht, Menue, SpeiseplanTag, Bestellung from .models import Gericht, Menue, SpeiseplanTag, Bestellung
@@ -113,16 +113,32 @@ class GerichtCreateView(CreateView):
def form_valid(self, form): def form_valid(self, form):
""" """
Übersteuert die Standard-Logik: Die korrigierte Version der Form-Validierung.
1. Speichert zuerst das Gericht (über super()). Das 'form' Argument wird hier explizit als zweites Argument deklariert, um den TypeError zu verhindern.
2. Leitet danach auf ein success/confirmation Pattern um.
""" """
super().form_valid() # Das eigentliche Speichern des Objekts # 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)
# Hier ist der Haken an Django: Die View wird beim Erfolg automatisch weitergeleitet. # Jetzt sind wir sicher, dass das Objekt gespeichert wurde und Zugriff auf das neu erstellte Objekt haben:
# Wir verwenden einen kleinen Trick, damit wir den Namen im Kontext haben. newly_created_dish = self.object
return redirect('success_nach_gerichterstellung') # Leiten auf eine temporäre URL um # 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) # Optional: Setzen des initialen Datenzustands (z.B. Datum, falls relevant)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):