moved Titelbild management to new page and new views

This commit is contained in:
eclipse 2025-06-01 13:01:44 +02:00
parent 6c17b95ac2
commit f84d24243e
3 changed files with 97 additions and 87 deletions

View File

@ -67,6 +67,7 @@
</div>
<div id="titelbild-controls">
<input type="file" id="titelbild-upload" name="form_Titelbild" aria-label="Titelbild" placeholder="kein Titelbild" accept="image/*"/>
<small>max. Dateigröße: 2 MB</small>
<input type="hidden" id="titelbild-haschanged" name="form_Titelbild_haschanged" value="" />
</div>
</section>

View File

@ -17,11 +17,6 @@ def all():
return render_template("views/titelbild.html", titelbilder=map(lambda t: t._asdict_with_urls(), db.session.scalars(select(Titelbild))))
#@bp.route("/titelbild/read/<int:id>")
#def read(id):
# return
@bp.route("/titelbild/image/<int:id>")
def image(id):
titelbild = db.session.get(Titelbild, id)
@ -42,68 +37,86 @@ def thumbnail(id):
#raise ValueError(message="requested thumbnail not found")
# wrapper function; the actual magic happens in create_from_filestorage
@bp.route("/titelbild/create", methods=["POST"])
def create():
id = create_from_filestorage(request.files["form_Titelbild"])
if id:
flash("Eintrag erfolgreich hinzugefügt")
else:
if request.files["form_Titelbild"].filename == "":
raise TypeError(message="FileStorage object expected")
blob = request.files["form_Titelbild"].read()
with BytesIO(blob) as bytes_io:
# check if image is already in DB
# would use hashlib.file_digest() from the standard library instead of a makeshift function, but it's not available in Python 3.10
#if db.session.scalar(select(Titelbild).where(Titelbild.sha256 == hashlib.file_digest(bytes_like, "sha256"))):
sha256 = makeshift_digest(bytes_io)
if db.session.scalar(select(Titelbild).where(Titelbild.sha256 == sha256)):
flash("Eintrag ist bereits in Datenbank vorhanden")
return redirect(url_for("titelbild.all"), code=303)
# take a FileStorage object with an image and, if it does not yet exist in the DB, create a new Titelbild record around it; return record id
def create_from_filestorage(f):
if f.filename == "":
raise TypeError(message="FileStorage object expected")
blob = f.read()
filesize = len(blob)
print(f"filesize of {f.filename} is {filesize}")
# use BytesIO as a wrapper around the byte stream
with BytesIO(blob) as bytes_like:
if _check_duplicate(bytes_like):
return False
# open image in memory
bytes_like.seek(0)
img = Image.open(bytes_like)
# prepare values for database entry
bytes_like.seek(0)
titelbild = Titelbild(
Mimetype = img.get_format_mimetype(),
Dateiname = secure_filename(f.filename),
Dateigroesse = filesize,
Breite = img.width,
Hoehe = img.height,
Bild = bytes_like.getvalue(),
# I'd rather use hashlib.file_digest() instead of a makeshift function, but alas, it's not available in Python 3.10
#sha256 = hashlib.file_digest(bytes_like, "sha256")
sha256 = makeshift_digest(bytes_like)
)
# add thumbnail to table entry
tn_format = img.format
img.thumbnail([128, 128])
# get Image and thumbnail
bytes_io.seek(0)
img = Image.open(bytes_io)
w, h, img_format = img.width, img.height, img.format
img.thumbnail(dimensions)
tn = BytesIO()
img.save(tn, format=tn_format)
titelbild.Thumbnail = tn.getvalue()
img.save(tn, format=img_format)
# build new record
titelbild = Titelbild(
Dateiname = request.files["form_Titelbild"].filename,
Dateigroesse = len(blob),
Mimetype = img.get_format_mimetype(),
Breite = w,
Hoehe = h,
Bild = bytes_io.getvalue(),
Thumbnail = tn.getvalue(),
sha256 = sha256
)
# add record to DB
db.session.add(titelbild)
db.session.flush()
id = titelbild.ID
if titelbild.ID:
flash("Eintrag erfolgreich hinzugefügt")
else:
flash("Fehler beim Eintragen in die Datenbank")
db.session.commit()
# return assigned ID
return id
return redirect(url_for("titelbild.all"), code=303)
@bp.route("/titelbild/update/<int:id>", methods=["POST"])
def update(id):
# not written yet
if request.files["form_Titelbild"].filename == "":
raise TypeError(message="FileStorage object expected")
blob = request.files["form_Titelbild"].read()
with BytesIO(blob) as bytes_io:
# make sure image does not exist in database yet (including the record to be updated )
sha256 = makeshift_digest(bytes_io)
if db.session.scalar(select(Titelbild).where(Titelbild.sha256 == sha256)):
flash("Eintrag ist bereits in Datenbank vorhanden")
return redirect(url_for("titelbild.all"), code=303)
# get Image and thumbnail
bytes_io.seek(0)
img = Image.open(bytes_io)
w, h, img_format = img.width, img.height, img.format
img.thumbnail([128, 128])
tn = BytesIO()
img.save(tn, format=img_format)
# update existing record
current_tb = db.session.get(Titelbild, id)
current_tb.Dateiname = request.files["form_Titelbild"].filename
current_tb.Dateigroesse = len(blob)
current_tb.Mimetype = img.get_format_mimetype()
current_tb.Breite = w
current_tb.Hoehe = h
current_tb.Bild = bytes_io.getvalue()
current_tb.Thumbnail = tn.getvalue()
current_tb.sha256 = sha256
db.session.commit()
flash("Eintrag erfolgreich geändert")
return redirect(url_for("titelbild.all"), code=303)
@ -116,19 +129,6 @@ def delete(id):
return redirect(url_for("titelbild.all"))
# returns id of record with same checksum or False if there is none
def _check_duplicate(bytes_like):
bytes_like.seek(0)
# would use hashlib.file_digest() from the standard library instead of a makeshift function, but it's not available in Python 3.10
#duplicate = db.session.scalar(select(Titelbild).where(Titelbild.sha256 == hashlib.file_digest(bytes_like, "sha256")))
duplicate = db.session.scalar(select(Titelbild).where(Titelbild.sha256 == makeshift_digest(bytes_like)))
if duplicate:
return duplicate.ID
else:
return False
# code adapted from https://stackoverflow.com/a/76937680
def makeshift_digest(bytes_io):
h = hashlib.sha256()
@ -138,3 +138,13 @@ def makeshift_digest(bytes_io):
h.update(mv[:n])
return h.hexdigest()
# takes a BytesIO object containing an image and creates a Pillow thumbnail from it
def _process_image_bytes(bytes_io, dimensions=[128, 128]):
bytes_io.seek(0)
img = Image.open(bytes_io)
tn_format = img.format
img.thumbnail(dimensions)
tn = BytesIO()
img.save(tn, format=tn_format)
return (tn.getvalue(), tn_format)

View File

@ -2,10 +2,11 @@ from flask import Blueprint, render_template, request, redirect, flash, url_for
from sqlalchemy import select, insert, update, delete
from the_works.database import db
from the_works.models import Werk, Reihe, Verlag, Werksform, Werk_Genre, Genre, Werk_Herausgeber, Herausgeber, Titelbild
from the_works.views import titelbild as cover
from the_works.views import titelbild as tb
bp = Blueprint("werk", __name__)
@bp.route("/werk")
@bp.route("/werk/all")
def all():
@ -27,7 +28,6 @@ def all():
"ISBN_10": row.Werk.ISBN_10 or "",
"ISSN": row.Werk.ISSN or "",
"Preis": row.Werk.Preis or "",
# Titelbild: return URL for the thumbnail
"Titelbild": url_for("titelbild.thumbnail", id=row.Werk.Titelbild) if row.Werk.Titelbild else "",
"Klappentext": row.Werk.Klappentext or "",
"Anmerkungen": row.Werk.Anmerkungen or "",
@ -36,24 +36,24 @@ def all():
})
return render_template("views/werk.html", werke=werke)
@bp.route("/werk/read/<int:id>")
def read(id):
# prepare Titelbilder as dict including URLs for thumbnail and full pic
titelbilder = map(lambda t: t._asdict_with_urls(), db.session.scalars(select(Titelbild)))
# id of zero -> return empty data
if id == 0:
return render_template("views/werk_detail.html", werk={"ID": 0, "Erscheinungsdatum": ""}, reihen=db.session.scalars(select(Reihe)), verlage=db.session.scalars(select(Verlag)), werksformen=db.session.scalars(select(Werksform)), genres=db.session.scalars(select(Genre)), hrsg=db.session.scalars(select(Herausgeber)), titelbild=None)
return render_template("views/werk_detail.html", werk={"ID": 0, "Erscheinungsdatum": ""}, reihen=db.session.scalars(select(Reihe)), verlage=db.session.scalars(select(Verlag)), werksformen=db.session.scalars(select(Werksform)), genres=db.session.scalars(select(Genre)), hrsg=db.session.scalars(select(Herausgeber)), titelbilder=titelbilder)
# all other ids -> read existing entry from DB and return as dict
werk = db.session.get(Werk, id)
if not werk:
return "Werk not found", 404
raise ValueError(f"Werk with ID {id} not found")
werk = werk._asdict()
# prepare Titelbild as dict including URLs for thumbnail and full pic
if werk["Titelbild"]:
titelbild = db.session.get(Titelbild, werk["Titelbild"])._asdict()
titelbild["Bild"] = url_for("titelbild.image", id=titelbild["ID"])
titelbild["Thumbnail"] = url_for("titelbild.thumbnail", id=titelbild["ID"])
else:
titelbild = None
return render_template("views/werk_detail.html", werk=werk, reihen=db.session.scalars(select(Reihe)), verlage=db.session.scalars(select(Verlag)), werksformen=db.session.scalars(select(Werksform)), genres=db.session.scalars(select(Genre)), hrsg=db.session.scalars(select(Herausgeber)), titelbild=titelbild)
return render_template("views/werk_detail.html", werk=werk, reihen=db.session.scalars(select(Reihe)), verlage=db.session.scalars(select(Verlag)), werksformen=db.session.scalars(select(Werksform)), genres=db.session.scalars(select(Genre)), hrsg=db.session.scalars(select(Herausgeber)), titelbilder=titelbilder)
@bp.route("/werk/create", methods=["POST"])
def create():
@ -64,12 +64,12 @@ def create():
Verlag = request.form["form_Verlag"] or None,
Reihe = request.form["form_Reihe"] or None,
Reihennummer = request.form["form_Reihennummer"] or None,
Erscheinungsdatum = get_datum(request.form["form_Erscheinungsjahr"], request.form["form_Erscheinungsmonat"], request.form["form_Erscheinungstag"]),
Erscheinungsdatum = _get_datum(request.form["form_Erscheinungsjahr"], request.form["form_Erscheinungsmonat"], request.form["form_Erscheinungstag"]),
ISBN_13 = request.form["form_ISBN_13"] or None,
ISBN_10 = request.form["form_ISBN_10"] or None,
ISSN = request.form["form_ISSN"] or None,
Preis = request.form["form_Preis"] or None,
Titelbild = cover.create(request.files["form_Titelbild"]) if request.files["form_Titelbild"].filename else None,
Titelbild = request.form["form_Titelbild"] or None,
Klappentext = request.form["form_Klappentext"] or None,
Anmerkungen = request.form["form_Anmerkungen"] or None
)
@ -95,18 +95,15 @@ def update(id):
werk.Verlag = request.form["form_Verlag"] or None
werk.Reihe = request.form["form_Reihe"] or None
werk.Reihennummer = request.form["form_Reihennummer"] or None
werk.Erscheinungsdatum = get_datum(request.form["form_Erscheinungsjahr"], request.form["form_Erscheinungsmonat"], request.form["form_Erscheinungstag"])
werk.Erscheinungsdatum = _get_datum(request.form["form_Erscheinungsjahr"], request.form["form_Erscheinungsmonat"], request.form["form_Erscheinungstag"])
werk.ISBN_13 = request.form["form_ISBN_13"] or None
werk.ISBN_10 = request.form["form_ISBN_10"] or None
werk.ISSN = request.form["form_ISSN"] or None
werk.Preis = request.form["form_Preis"] or None
werk.Titelbild = request.form["form_Titelbild"] or None
werk.Klappentext = request.form["form_Klappentext"] or None
werk.Anmerkungen = request.form["form_Anmerkungen"] or None
# update Titelbild
if request.form["form_Titelbild_haschanged"]:
werk.Titelbild = cover.create(request.files["form_Titelbild"]) if request.files["form_Titelbild"].filename else None
# update associated values: Genre
form_set = set(map(lambda g: int(g), request.form.getlist("form_Genre")))
for g in set(werk.genres) - form_set:
@ -126,6 +123,7 @@ def update(id):
flash("Eintrag erfolgreich geändert")
return redirect(url_for("werk.all"))
@bp.route("/werk/delete/<int:id>")
def delete(id):
werk = db.session.get(Werk, id)
@ -134,7 +132,8 @@ def delete(id):
flash("Eintrag erfolgreich gelöscht")
return redirect(url_for("werk.all"))
def get_datum(jahr, monat, tag):
def _get_datum(jahr, monat, tag):
if tag != "":
return "-".join([jahr, monat.zfill(2), tag.zfill(2)])
elif monat != "":