Compare commits

2 Commits

28 changed files with 118 additions and 451 deletions

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Binary file not shown.

View File

@@ -3,48 +3,7 @@ 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): admin.site.register(Person)
"""
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
# ----------------------------------------------------------
# **WICHTIG:** Hier wird der zu verwendende FK explizit genannt
# ----------------------------------------------------------
fk_name = 'person' # <-- legt fest, welcher FK gemeint ist
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)
class PersonAdmin(admin.ModelAdmin):
list_display = ('user', 'rolle', 'klasse')
search_fields = ['user__username', 'klasse']
ordering = ('rolle',)
# ---- Inline für Eltern (Rolle Mitarbeit/Chef) ----
inlines = [PersonInlineChildren] # Zeigt das KinderInline nur bei passenden Rollen an
def has_change_permission(self, request, obj=None):
"""
Optional: Verhindere, dass ein Schüler seine eigene Rolle oder die Zuordnung ändert.
"""
return super().has_change_permission(request, obj)
from .models import Schulwoche # Ersetze dies durch deine echten Klassennamen from .models import Schulwoche # Ersetze dies durch deine echten Klassennamen
@@ -60,7 +19,7 @@ admin.site.register(Kategorie)
from .models import Gericht # Ersetze dies durch deine echten Klassennamen from .models import Gericht # Ersetze dies durch deine echten Klassennamen
# admin.site.register(Gericht) admin.site.register(Gericht)
from .models import Menue # Ersetze dies durch deine echten Klassennamen from .models import Menue # Ersetze dies durch deine echten Klassennamen
@@ -70,21 +29,3 @@ from .models import Bestellung # Ersetze dies durch deine echten Klassennamen
admin.site.register(Bestellung) admin.site.register(Bestellung)
from .models import Bewertung
admin.site.register(Bewertung)
from .models import GerichtBild
admin.site.register(GerichtBild)
class GerichtBildInline(admin.TabularInline):
model = GerichtBild
extra = 0 # Keine leeren Zeilen anzeigen (kann später auf 1 erhöht werden)
readonly_fields = ('image',) # Optional: Nur zur Anzeige, nicht editierbar
@admin.register(Gericht)
class GerichtAdmin(admin.ModelAdmin):
list_display = ('name', 'kategorie')
inlines = [GerichtBildInline] # Das Inline erscheint direkt unter jedem Gericht

View File

@@ -1,52 +0,0 @@
# Generated by Django 6.0.5 on 2026-05-19 20:13
import datetime
import django.core.validators
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mensa_app', '0004_alter_gericht_time_creation_and_more'),
]
operations = [
migrations.AlterField(
model_name='gericht',
name='time_creation',
field=models.TimeField(default=datetime.datetime(2026, 5, 19, 20, 13, 52, 449584)),
),
migrations.AlterField(
model_name='gericht',
name='time_last_change',
field=models.TimeField(default=datetime.datetime(2026, 5, 19, 20, 13, 52, 449584)),
),
migrations.CreateModel(
name='GerichtBild',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('image', models.ImageField(help_text='Bitte nur JPEG-Dateien hochladen.', upload_to='gerichte_bilder/', validators=[django.core.validators.FileExtensionValidator(['jpg', 'jpeg'])])),
('sort_order', models.PositiveIntegerField(default=0)),
('gericht', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='bilder', to='mensa_app.gericht')),
],
),
migrations.CreateModel(
name='Bewertung',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('sterne', models.IntegerField(choices=[(1, '★☆☆☆☆'), (2, '★★☆☆☆'), (3, '★★★☆☆'), (4, '★★★★☆'), (5, '★★★★★')], default=3)),
('kommentar', models.TextField(blank=True, null=True)),
('datum', models.DateTimeField(auto_now_add=True)),
('ist_verifiziert', models.BooleanField(default=False, help_text='Wird automatisch auf True gesetzt, wenn eine bezahlte Bestellung vorliegt.')),
('gericht', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='bewertungen', to='mensa_app.gericht')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='bewertungen', to='mensa_app.person')),
],
options={
'verbose_name': 'Bewertung',
'verbose_name_plural': 'Bewertungen',
'unique_together': {('user', 'gericht')},
},
),
]

View File

@@ -1,24 +0,0 @@
# Generated by Django 6.0.5 on 2026-05-19 20:24
import datetime
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mensa_app', '0005_alter_gericht_time_creation_and_more'),
]
operations = [
migrations.AlterField(
model_name='gericht',
name='time_creation',
field=models.TimeField(default=datetime.datetime(2026, 5, 19, 20, 24, 22, 989880)),
),
migrations.AlterField(
model_name='gericht',
name='time_last_change',
field=models.TimeField(default=datetime.datetime(2026, 5, 19, 20, 24, 22, 989880)),
),
]

View File

@@ -1,32 +0,0 @@
# Generated by Django 6.0.5 on 2026-05-19 20:27
import datetime
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mensa_app', '0006_alter_gericht_time_creation_and_more'),
]
operations = [
migrations.AlterModelOptions(
name='gerichtbild',
options={'verbose_name': 'Bild', 'verbose_name_plural': 'Bilder'},
),
migrations.AlterField(
model_name='gericht',
name='time_creation',
field=models.TimeField(default=datetime.datetime(2026, 5, 19, 20, 27, 3, 59858)),
),
migrations.AlterField(
model_name='gericht',
name='time_last_change',
field=models.TimeField(default=datetime.datetime(2026, 5, 19, 20, 27, 3, 59858)),
),
migrations.AlterUniqueTogether(
name='gerichtbild',
unique_together={('image', 'gericht')},
),
]

View File

@@ -1,30 +0,0 @@
# Generated by Django 6.0.5 on 2026-05-19 20:43
import datetime
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mensa_app', '0007_alter_gerichtbild_options_and_more'),
]
operations = [
migrations.AlterField(
model_name='gericht',
name='time_creation',
field=models.TimeField(default=datetime.datetime(2026, 5, 19, 20, 43, 37, 765352)),
),
migrations.AlterField(
model_name='gericht',
name='time_last_change',
field=models.TimeField(default=datetime.datetime(2026, 5, 19, 20, 43, 37, 765352)),
),
migrations.AlterField(
model_name='gerichtbild',
name='image',
field=models.ImageField(help_text='Bitte nur JPEG-Dateien (.jpg/.jpeg) hochladen.', upload_to='gerichte_bilder/', validators=[django.core.validators.FileExtensionValidator(['jpg', 'jpeg'])]),
),
]

View File

@@ -1,24 +0,0 @@
# Generated by Django 6.0.5 on 2026-05-19 21:49
import datetime
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mensa_app', '0008_alter_gericht_time_creation_and_more'),
]
operations = [
migrations.AlterField(
model_name='gericht',
name='time_creation',
field=models.TimeField(default=datetime.datetime(2026, 5, 19, 21, 49, 30, 971985)),
),
migrations.AlterField(
model_name='gericht',
name='time_last_change',
field=models.TimeField(default=datetime.datetime(2026, 5, 19, 21, 49, 30, 971985)),
),
]

View File

@@ -3,73 +3,18 @@ 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.validators import FileExtensionValidator
# Pillow ist für die Bildbearbeitung zuständig (installiere per pip install pillow)
from PIL import Image as PilImage
# Optional: Wenn du später Thumbnails mit sorl-thumbnail nutzen möchtest:
# from sorl.thumbnail import get_thumbnail
class Person(models.Model): class Person(models.Model):
"""Repräsentiert Schüler oder Lehrer.""" """Repräsentiert Schüler oder Lehrer."""
user = models.OneToOneField(User, on_delete=models.CASCADE) user = models.OneToOneField(User, on_delete=models.CASCADE)
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'), ('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."
)
)
# ------------------------------------------------------------------
# Optional: Hilfsmethode, die alle Kinder zurückgibt, ggf. mit einer Sortierung
def get_children_sorted(self):
# Das `order_by('name')` ist ein Beispiel du kannst deine eigene Sortierung verwenden.
return self.children.order_by('name')
class Meta: class Meta:
verbose_name_plural = "Personen" verbose_name_plural = "Personen"
def __str__(self): def __str__(self):
if self.children.exists():
# 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})" return f"{self.user.username} ({self.rolle})"
def clean(self):
"""
Überprüfung für die ManyToMany-Beziehung ``children``:
- Nur Personen mit der Rolle 'mitarbeiter' oder 'chef' (Eltern) dürfen Kinder haben.
- 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.")
class Schulwoche(models.Model): class Schulwoche(models.Model):
"""Repräsentiert eine Schulwoche mit einem eindeutigen Datum.""" """Repräsentiert eine Schulwoche mit einem eindeutigen Datum."""
@@ -188,167 +133,3 @@ class Bestellung(models.Model):
verbose_name = "Bestellung" verbose_name = "Bestellung"
verbose_name_plural = "Bestellungen" verbose_name_plural = "Bestellungen"
from django.core.exceptions import ValidationError
class Bewertung(models.Model):
"""
Repräsentiert die Bewertung eines Gerichts durch einen Nutzer.
"""
class Sterne(models.IntegerChoices):
EINER = 1, '★☆☆☆☆'
ZWEI = 2, '★★☆☆☆'
DREI = 3, '★★★☆☆'
VIER = 4, '★★★★☆'
FUENF = 5, '★★★★★'
user = models.ForeignKey('Person', on_delete=models.CASCADE, related_name='bewertungen')
gericht = models.ForeignKey('Gericht', on_delete=models.CASCADE, related_name='bewertungen')
sterne = models.IntegerField(choices=Sterne.choices, default=3)
kommentar = models.TextField(blank=True, null=True)
datum = models.DateTimeField(auto_now_add=True)
# Das Feld für die Verifizierung
ist_verifiziert = models.BooleanField(
default=False,
help_text="Wird automatisch auf True gesetzt, wenn eine bezahlte Bestellung vorliegt."
)
class Meta:
# Verhindert, dass ein Nutzer dasselbe Gericht mehrfach bewertet
unique_together = ('user', 'gericht')
verbose_name = "Bewertung"
verbose_name_plural = "Bewertungen"
def __str__(self):
return f"{self.user.user.username} bewertet {self.gericht.name} mit {self.sterne} Sternen"
def clean(self):
"""
Hier können wir zusätzliche Validierungen einbauen.
"""
if self.sterne < 1 or self.sterne > 5:
raise ValidationError("Die Bewertung muss zwischen 1 und 5 Sternen liegen.")
def check_verifizierung(self):
"""
Ein hilfreicher Service-Method, um den Status der Verifizierung
automatisch zu prüfen.
"""
# Wir prüfen, ob es eine Bestellung für diesen User und dieses Gericht gibt,
# die bereits als 'bezahlt' markiert ist.
exists = Bestellung.objects.filter(
person=self.user,
menue__gericht=self.gericht,
bezahlt=True
).exists()
if exists:
self.ist_verifiziert = True
return self.ist_verifiziert
class GerichtBild(models.Model):
"""
Speichert ein hochgeladenes Bild für ein Gericht.
- Kann später beliebig erweitert werden (z.B. Beschreibung, Sortierreihenfolge).
"""
gericht = models.ForeignKey(
'Gericht', # Verweis auf das Gericht
on_delete=models.CASCADE, # Wenn das Gericht gelöscht wird, lösche auch die Bilder
related_name='bilder' # -> Gericht.bilder.all()
)
image = models.ImageField( # Typ: BildDatei (JPEG)
upload_to='gerichte_bilder/', # Unterordner im MEDIA_ROOT
validators=[FileExtensionValidator(['jpg', 'jpeg'])], # Nur JPEG zulassen
help_text="Bitte nur JPEG-Dateien (.jpg/.jpeg) hochladen."
)
# Optional: Sortierreihenfolge, wenn mehrere Bilder angezeigt werden sollen
sort_order = models.PositiveIntegerField(default=0)
class Meta:
# Verhindert, dass ein Bild mehrfach zu einem Gericht angelegt wird
unique_together = ('image', 'gericht')
verbose_name = "Bild"
verbose_name_plural = "Bilder"
def clean(self):
"""
Zusätzliche Validierung wird vor dem Speichern aufgerufen,
wenn du .full_clean() oder ModelForm nutzt.
"""
# Prüfe ob die Datei tatsächlich ein JPEG ist (optional, da Validator schon hilft)
_, ext = os.path.splitext(self.image.name.lower())
if ext not in ('.jpg', '.jpeg'):
raise ValidationError('Nur JPEG-Dateien sind erlaubt.')
def _resize_image_if_needed(self):
"""
Verkleinert das hochgeladene Bild auf maximal 640×480px
(beibehält Seitenverhältnis). Überschreibt die Originaldatei.
"""
# Sicherstellen, dass ein Dateiname vorhanden ist und die Datei lesbar ist
if not self.image or not self.image.name:
return
try:
# Öffne das Bild mit Pillow .open('rb') liefert eine FileInstanz
with self.image.open('rb') as img_file:
pil_img = PilImage.open(img_file)
pil_img = pil_img.convert('RGB')
# thumbnail passt das Bild in die Grenzen (640×480) und behält
# das Seitenverhältnis bei.
pil_img.thumbnail((640, 480), PilImage.LANCZOS)
# Schreibe das verkleinerte Bild zurück in dieselbe Datei.
# storage = self.image.field.storage # z.B. FileSystemStorage
storage = self.image.field.storage
path = self.image.path
with storage.open(path, 'wb') as out_f:
pil_img.save(out_f, format='JPEG', quality=85)
except Exception as exc:
# Im ProduktivBetrieb wäre hier ein Logging sinnvoll.
print(f'Fehler beim Verkleinern von {self.image.name}: {exc}')
def save(self, *args, **kwargs):
"""
Überschreibe das Speichern, um das Bild automatisch auf 640×480 px
(oder darunter) zu verkleinern. Das Ergebnis überschreibt die OriginalDatei.
"""
# 1⃣ Führe die normale save() aus nötig, damit wir später den Pfad haben
super().save(*args, **kwargs)
# 2⃣ Führe die Verkleinerung nur dann aus, wenn tatsächlich ein Bild da ist.
if self.image and self.image.name:
self._resize_image_if_needed()
def delete(self, *args, **kwargs):
"""
Erweiterte DeleteLogik:
1. Versuche die Datei aus dem Storage zu löschen (falls sie existiert).
2. Führe eigene Aktionen durch (z.B. Logging).
3. Rufe die StandardDeleteMethode auf, um den DBEintrag zu entfernen.
"""
# --- 1⃣ Versuche die Datei zu löschen -------------------------------------------------
storage = self.image.field.storage # z.B. FileSystemStorage
path = self.image.path # vollständiger Pfad im Media-Ordner
try:
if storage.exists(path):
# Optional: Backup, Logging usw.
print(f"[DELETE] Bild entfernt: {path}")
storage.delete(path) # tatsächliches Löschen der Datei
except Exception as exc:
# Im ProduktivBetrieb besser mit logging.exception() umgehen
print(f"Warnung konnte die Bilddatei nicht löschen ({exc})")
# --- 2⃣ Rufe die normale Django-DeleteLogik auf ------------------------------------
super().delete(*args, **kwargs) # löscht den DBEintrag

View File

@@ -0,0 +1,55 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mensa-Speisekarte</title>
<!-- Bootstrap CSS für schnelles Styling -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card shadow">
<div class='card-header bg-primary text-white text-center py-3'>
<h1><i class="bi bi-egg-fried"></i> Unsere Speisekarte</h1>
</div>
<div class="card-body">
<table class="table table-hover">
<thead class="table-dark">
<tr>
<th>Gericht</th>
<th>Kategorie</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{% for gericht in alle_gerichte %}
<tr>
<td><strong>{{ gericht.name }}</strong></td>
<td><span class="badge bg-info text-dark">{{ gericht.kategorie.name }}</span></td>
<td>
{% if gericht.ist_dauerangebot %}
<span class="badge bg-success">Dauerangebot</span>
{% else %}
<span class="badge bg-secondary">Tagesangebot</span>
{% endif %}
</td>
</tr>
{% empty %}
<tr>
<td colspan="3" class="text-center text-muted">Keine Gerichte im System gefunden.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

8
mensa_app/urls.py Normal file
View File

@@ -0,0 +1,8 @@
from django.urls import path
from .views import GerichtListView
from .views import SpeiseplanView # Achte auf den neuen Klassennamen!
urlpatterns = [
path('speisekarte/', GerichtListView.as_view(), name='gericht_list'),
path('speiseplan/', SpeiseplanView.as_view(), name='speiseplan'),
]

View File

@@ -1,3 +1,50 @@
from django.shortcuts import render from django.shortcuts import render
# Create your views here. # Create your views here.
from django.views.generic import ListView
from .models import Gericht
class GerichtListView(ListView):
model = Gericht
template_name = 'mensa_app/gericht_liste.html' # Der Pfad zum Template
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):
template_name = 'mensa_app/speiseplan.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# 1. Datum aus der URL holen (z.B. ?datum=2023-10-27)
# Wenn kein Datum angegeben ist, nehmen wir heute.
date_str = self.request.GET.get('datum')
if date_str:
try:
target_date = datetime.strptime(date_str, '%Y-%m-%d').date()
except ValueError:
target_date = timezone.glob.now().date()
else:
target_date = timezone.now().date()
# 2. Menüs für diesen spezifischen Tag laden
# Wir suchen alle Menüs, deren Tag das target_date hat
context['target_date'] = target_date
context['menues_day'] = Menue.objects.filter(tag__datum=target_date)
# 3. Dauerangebote laden (unabhängig vom Tag)
context['dauerangebote'] = Gericht.objects.filter(ist_dauerangebot=True)
# 4. Pager-Logik: Vorherigen und nächsten Tag finden
# Wir suchen in der Tabelle SpeiseplanTag nach dem Tag davor/danach
prev_tag = SpeiseplanTag.objects.filter(datum__lt=target_date).order_by('-datum').first()
next_tag = SpeiseplanTag.objects.filter(datum__gt=target_date).order_by('datum').first()
context['prev_date'] = prev_tag.datum.strftime('%Y-%m-%d') if prev_tag else None
context['next_date'] = next_tag.datum.strftime('%Y-%m-%d') if next_tag else None
return context

View File

@@ -10,13 +10,11 @@ For the full list of settings and their values, see
https://docs.djangoproject.com/en/6.0/ref/settings/ https://docs.djangoproject.com/en/6.0/ref/settings/
""" """
import os
from pathlib import Path from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'. # Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent BASE_DIR = Path(__file__).resolve().parent.parent
MEDIA_ROOT = os.path.join(BASE_DIR, 'media') # Ordner, in dem die OriginalDateien landen
MEDIA_URL = '/media/' # URL-Pfad für den Zugriff (bspw. http://localhost:8000/media/...)
# Quick-start development settings - unsuitable for production # Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/6.0/howto/deployment/checklist/ # See https://docs.djangoproject.com/en/6.0/howto/deployment/checklist/

View File

@@ -15,10 +15,9 @@ Including another URLconf
2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
""" """
from django.contrib import admin from django.contrib import admin
from django.urls import path from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [ urlpatterns = [
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) path('', include('mensa_app.urls')), # Schaltet die App-URLs frei
]