moved Titelbild management to new page and new views
This commit is contained in:
parent
6c17b95ac2
commit
f84d24243e
@ -67,6 +67,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="titelbild-controls">
|
<div id="titelbild-controls">
|
||||||
<input type="file" id="titelbild-upload" name="form_Titelbild" aria-label="Titelbild" placeholder="kein Titelbild" accept="image/*"/>
|
<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="" />
|
<input type="hidden" id="titelbild-haschanged" name="form_Titelbild_haschanged" value="" />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</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))))
|
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>")
|
@bp.route("/titelbild/image/<int:id>")
|
||||||
def image(id):
|
def image(id):
|
||||||
titelbild = db.session.get(Titelbild, id)
|
titelbild = db.session.get(Titelbild, id)
|
||||||
@ -42,68 +37,86 @@ def thumbnail(id):
|
|||||||
#raise ValueError(message="requested thumbnail not found")
|
#raise ValueError(message="requested thumbnail not found")
|
||||||
|
|
||||||
|
|
||||||
# wrapper function; the actual magic happens in create_from_filestorage
|
|
||||||
@bp.route("/titelbild/create", methods=["POST"])
|
@bp.route("/titelbild/create", methods=["POST"])
|
||||||
def create():
|
def create():
|
||||||
id = create_from_filestorage(request.files["form_Titelbild"])
|
if request.files["form_Titelbild"].filename == "":
|
||||||
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 == "":
|
|
||||||
raise TypeError(message="FileStorage object expected")
|
raise TypeError(message="FileStorage object expected")
|
||||||
blob = f.read()
|
blob = request.files["form_Titelbild"].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_io:
|
||||||
with BytesIO(blob) as bytes_like:
|
# check if image is already in DB
|
||||||
if _check_duplicate(bytes_like):
|
# would use hashlib.file_digest() from the standard library instead of a makeshift function, but it's not available in Python 3.10
|
||||||
return False
|
#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
|
# get Image and thumbnail
|
||||||
bytes_like.seek(0)
|
bytes_io.seek(0)
|
||||||
img = Image.open(bytes_like)
|
img = Image.open(bytes_io)
|
||||||
|
w, h, img_format = img.width, img.height, img.format
|
||||||
# prepare values for database entry
|
img.thumbnail(dimensions)
|
||||||
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])
|
|
||||||
tn = BytesIO()
|
tn = BytesIO()
|
||||||
img.save(tn, format=tn_format)
|
img.save(tn, format=img_format)
|
||||||
titelbild.Thumbnail = tn.getvalue()
|
|
||||||
|
# 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
|
# add record to DB
|
||||||
db.session.add(titelbild)
|
db.session.add(titelbild)
|
||||||
db.session.flush()
|
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()
|
db.session.commit()
|
||||||
|
return redirect(url_for("titelbild.all"), code=303)
|
||||||
# return assigned ID
|
|
||||||
return id
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/titelbild/update/<int:id>", methods=["POST"])
|
@bp.route("/titelbild/update/<int:id>", methods=["POST"])
|
||||||
def update(id):
|
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)
|
return redirect(url_for("titelbild.all"), code=303)
|
||||||
|
|
||||||
|
|
||||||
@ -116,19 +129,6 @@ def delete(id):
|
|||||||
return redirect(url_for("titelbild.all"))
|
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
|
# code adapted from https://stackoverflow.com/a/76937680
|
||||||
def makeshift_digest(bytes_io):
|
def makeshift_digest(bytes_io):
|
||||||
h = hashlib.sha256()
|
h = hashlib.sha256()
|
||||||
@ -138,3 +138,13 @@ def makeshift_digest(bytes_io):
|
|||||||
h.update(mv[:n])
|
h.update(mv[:n])
|
||||||
return h.hexdigest()
|
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 sqlalchemy import select, insert, update, delete
|
||||||
from the_works.database import db
|
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.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 = Blueprint("werk", __name__)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/werk")
|
@bp.route("/werk")
|
||||||
@bp.route("/werk/all")
|
@bp.route("/werk/all")
|
||||||
def 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))
|
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
|
# condense result into list of dicts
|
||||||
werke = []
|
werke = []
|
||||||
for row in rows:
|
for row in rows:
|
||||||
werke.append({
|
werke.append({
|
||||||
"id": row.Werk.ID,
|
"id": row.Werk.ID,
|
||||||
"Titel": row.Werk.Titel,
|
"Titel": row.Werk.Titel,
|
||||||
@ -27,7 +28,6 @@ def all():
|
|||||||
"ISBN_10": row.Werk.ISBN_10 or "",
|
"ISBN_10": row.Werk.ISBN_10 or "",
|
||||||
"ISSN": row.Werk.ISSN or "",
|
"ISSN": row.Werk.ISSN or "",
|
||||||
"Preis": row.Werk.Preis 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 "",
|
"Titelbild": url_for("titelbild.thumbnail", id=row.Werk.Titelbild) if row.Werk.Titelbild else "",
|
||||||
"Klappentext": row.Werk.Klappentext or "",
|
"Klappentext": row.Werk.Klappentext or "",
|
||||||
"Anmerkungen": row.Werk.Anmerkungen or "",
|
"Anmerkungen": row.Werk.Anmerkungen or "",
|
||||||
@ -36,24 +36,24 @@ def all():
|
|||||||
})
|
})
|
||||||
return render_template("views/werk.html", werke=werke)
|
return render_template("views/werk.html", werke=werke)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/werk/read/<int:id>")
|
@bp.route("/werk/read/<int:id>")
|
||||||
def read(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
|
# id of zero -> return empty data
|
||||||
if id == 0:
|
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
|
# all other ids -> read existing entry from DB and return as dict
|
||||||
werk = db.session.get(Werk, id)
|
werk = db.session.get(Werk, id)
|
||||||
if not werk:
|
if not werk:
|
||||||
return "Werk not found", 404
|
raise ValueError(f"Werk with ID {id} not found")
|
||||||
werk = werk._asdict()
|
werk = werk._asdict()
|
||||||
# prepare Titelbild as dict including URLs for thumbnail and full pic
|
|
||||||
if werk["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)
|
||||||
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)
|
|
||||||
|
|
||||||
@bp.route("/werk/create", methods=["POST"])
|
@bp.route("/werk/create", methods=["POST"])
|
||||||
def create():
|
def create():
|
||||||
@ -64,12 +64,12 @@ def create():
|
|||||||
Verlag = request.form["form_Verlag"] or None,
|
Verlag = request.form["form_Verlag"] or None,
|
||||||
Reihe = request.form["form_Reihe"] or None,
|
Reihe = request.form["form_Reihe"] or None,
|
||||||
Reihennummer = request.form["form_Reihennummer"] 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_13 = request.form["form_ISBN_13"] or None,
|
||||||
ISBN_10 = request.form["form_ISBN_10"] or None,
|
ISBN_10 = request.form["form_ISBN_10"] or None,
|
||||||
ISSN = request.form["form_ISSN"] or None,
|
ISSN = request.form["form_ISSN"] or None,
|
||||||
Preis = request.form["form_Preis"] 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,
|
Klappentext = request.form["form_Klappentext"] or None,
|
||||||
Anmerkungen = request.form["form_Anmerkungen"] 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.Verlag = request.form["form_Verlag"] or None
|
||||||
werk.Reihe = request.form["form_Reihe"] or None
|
werk.Reihe = request.form["form_Reihe"] or None
|
||||||
werk.Reihennummer = request.form["form_Reihennummer"] 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_13 = request.form["form_ISBN_13"] or None
|
||||||
werk.ISBN_10 = request.form["form_ISBN_10"] or None
|
werk.ISBN_10 = request.form["form_ISBN_10"] or None
|
||||||
werk.ISSN = request.form["form_ISSN"] or None
|
werk.ISSN = request.form["form_ISSN"] or None
|
||||||
werk.Preis = request.form["form_Preis"] 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.Klappentext = request.form["form_Klappentext"] or None
|
||||||
werk.Anmerkungen = request.form["form_Anmerkungen"] 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
|
# update associated values: Genre
|
||||||
form_set = set(map(lambda g: int(g), request.form.getlist("form_Genre")))
|
form_set = set(map(lambda g: int(g), request.form.getlist("form_Genre")))
|
||||||
for g in set(werk.genres) - form_set:
|
for g in set(werk.genres) - form_set:
|
||||||
@ -125,7 +122,8 @@ def update(id):
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
flash("Eintrag erfolgreich geändert")
|
flash("Eintrag erfolgreich geändert")
|
||||||
return redirect(url_for("werk.all"))
|
return redirect(url_for("werk.all"))
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/werk/delete/<int:id>")
|
@bp.route("/werk/delete/<int:id>")
|
||||||
def delete(id):
|
def delete(id):
|
||||||
werk = db.session.get(Werk, id)
|
werk = db.session.get(Werk, id)
|
||||||
@ -134,7 +132,8 @@ def delete(id):
|
|||||||
flash("Eintrag erfolgreich gelöscht")
|
flash("Eintrag erfolgreich gelöscht")
|
||||||
return redirect(url_for("werk.all"))
|
return redirect(url_for("werk.all"))
|
||||||
|
|
||||||
def get_datum(jahr, monat, tag):
|
|
||||||
|
def _get_datum(jahr, monat, tag):
|
||||||
if tag != "":
|
if tag != "":
|
||||||
return "-".join([jahr, monat.zfill(2), tag.zfill(2)])
|
return "-".join([jahr, monat.zfill(2), tag.zfill(2)])
|
||||||
elif monat != "":
|
elif monat != "":
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user