From f84d24243ec3b2dc59f847baf95d47d608adb1e2 Mon Sep 17 00:00:00 2001 From: eclipse Date: Sun, 1 Jun 2025 13:01:44 +0200 Subject: [PATCH] moved Titelbild management to new page and new views --- the_works/templates/views/titelbild.html | 1 + the_works/views/titelbild.py | 140 ++++++++++++----------- the_works/views/werk.py | 43 ++++--- 3 files changed, 97 insertions(+), 87 deletions(-) diff --git a/the_works/templates/views/titelbild.html b/the_works/templates/views/titelbild.html index ba6a1e0..7802fc2 100644 --- a/the_works/templates/views/titelbild.html +++ b/the_works/templates/views/titelbild.html @@ -67,6 +67,7 @@
+ max. Dateigröße: 2 MB
diff --git a/the_works/views/titelbild.py b/the_works/views/titelbild.py index 0e3532c..85e0b2e 100644 --- a/the_works/views/titelbild.py +++ b/the_works/views/titelbild.py @@ -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/") -#def read(id): -# return - - @bp.route("/titelbild/image/") 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: - 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 == "": + if request.files["form_Titelbild"].filename == "": raise TypeError(message="FileStorage object expected") - blob = f.read() - filesize = len(blob) - print(f"filesize of {f.filename} is {filesize}") + blob = request.files["form_Titelbild"].read() - # use BytesIO as a wrapper around the byte stream - with BytesIO(blob) as bytes_like: - if _check_duplicate(bytes_like): - return False + 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) - # 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/", 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) diff --git a/the_works/views/werk.py b/the_works/views/werk.py index c8f2d28..8cfd565 100644 --- a/the_works/views/werk.py +++ b/the_works/views/werk.py @@ -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(): @@ -13,7 +14,7 @@ def all(): rows = db.session.execute(select(Werk, Reihe, Verlag, Werksform).join(Werk.reihe, isouter=True).join(Werk.verlag, isouter=True).join(Werk.werksform, isouter=True)) # condense result into list of dicts werke = [] - for row in rows: + for row in rows: werke.append({ "id": row.Werk.ID, "Titel": row.Werk.Titel, @@ -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/") 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: @@ -125,7 +122,8 @@ def update(id): db.session.commit() flash("Eintrag erfolgreich geändert") return redirect(url_for("werk.all")) - + + @bp.route("/werk/delete/") 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 != "":