moved Titelbild management to new page and new views
This commit is contained in:
parent
6c17b95ac2
commit
f84d24243e
@ -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>
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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 != "":
|
||||
|
||||
Loading…
Reference in New Issue
Block a user