Bilder im Datenmodell angelegt, upload, Anzeige und delete
funktionieren.
This commit is contained in:
@@ -3,6 +3,16 @@ from django.contrib.auth.models import User
|
||||
from django.utils import timezone
|
||||
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):
|
||||
"""Repräsentiert Schüler oder Lehrer."""
|
||||
user = models.OneToOneField(User, on_delete=models.CASCADE)
|
||||
@@ -193,3 +203,107 @@ class Bewertung(models.Model):
|
||||
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: Bild‑Datei (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 × 480 px
|
||||
(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 File‑Instanz
|
||||
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 Produktiv‑Betrieb 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 Original‑Datei.
|
||||
"""
|
||||
# 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 Delete‑Logik:
|
||||
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 Standard‑Delete‑Methode auf, um den DB‑Eintrag 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 Produktiv‑Betrieb besser mit logging.exception() umgehen
|
||||
print(f"Warnung – konnte die Bilddatei nicht löschen ({exc})")
|
||||
|
||||
# --- 2️⃣ Rufe die normale Django-Delete‑Logik auf ------------------------------------
|
||||
super().delete(*args, **kwargs) # löscht den DB‑Eintrag
|
||||
|
||||
Reference in New Issue
Block a user