diff --git a/the_works/models.py b/the_works/models.py
index 206895f..80cddbf 100644
--- a/the_works/models.py
+++ b/the_works/models.py
@@ -1,11 +1,12 @@
import sys
-from typing import List, Optional
-from sqlalchemy import ForeignKey, types, CheckConstraint
-from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
+from typing import List, Optional, NewType
+from decimal import Decimal
+from sqlalchemy import ForeignKey, CheckConstraint, types, String, Numeric
+from sqlalchemy.orm import DeclarativeBase, registry, Mapped, mapped_column, relationship
from sqlalchemy.ext.associationproxy import AssociationProxy, association_proxy
from flask import url_for
-
+# these data models can be used with the generic functions from simple_view
SIMPLE_MODELS = [
{"name": "genre", "title": "Genres"},
{"name": "herausgeber", "title": "Herausgeber", "column": "Name"},
@@ -16,8 +17,24 @@ SIMPLE_MODELS = [
{"name": "werksform", "title": "Werksformen"}
]
+# define some new data types (strings of specific lengths, numbers of different precision and scale)
+str13 = NewType("str13", str)
+str10 = NewType("str10", str)
+str8 = NewType("str8", str)
+price = NewType("price", Decimal)
+
+
"""Base class for the_works' data models"""
class Base(DeclarativeBase):
+ # register new data types
+ registry = registry(type_annotation_map={
+ str13: String(13),
+ str10: String(10),
+ str8: String(8),
+ price: Numeric(7, 2)
+ })
+
+ """Return the data record as a dict"""
def asdict(self) -> dict:
d = {}
for col in self.__table__.c:
@@ -26,10 +43,13 @@ class Base(DeclarativeBase):
else:
if isinstance(value := self.__getattribute__(col.key), str) and len(value) > 50:
d[col.key] = value[:48] + '...'
+ elif value is None:
+ d[col.key] = ""
else:
d[col.key] = value
return d
+ """Display the data record in readable format"""
def __repr__(self) -> str:
return f"{type(self).__name__}({str(self.asdict())})"
@@ -61,7 +81,6 @@ class Pseudonym(Base):
ID: Mapped[int] = mapped_column(primary_key=True)
Pseudonym: Mapped[str] = mapped_column(CheckConstraint('Pseudonym <> ""', name='PseudonymNotEmptyConstraint'), nullable=False, unique=True)
- veroeffentlichung: Mapped[List['Veroeffentlichung']] = relationship(back_populates='pseudonym') #TODO DELETE
werk: Mapped[List['Werk']] = relationship(back_populates='pseudonym')
@@ -91,7 +110,6 @@ class Verlag(Base):
ausgabe: Mapped[List['Ausgabe']] = relationship(back_populates='verlag')
reihe: Mapped[List['Reihe']] = relationship(back_populates='verlag')
- werk: Mapped[List['Werk']] = relationship(back_populates='verlag') #TODO DELETE
class Werksform(Base):
@@ -101,7 +119,6 @@ class Werksform(Base):
Werksform: Mapped[str] = mapped_column(CheckConstraint('Werksform <> ""', name='WerksformNotEmptyConstraint'), nullable=False, unique=True)
ausgabe: Mapped[List['Ausgabe']] = relationship(back_populates='werksform')
- werk: Mapped[List['Werk']] = relationship(back_populates='werksform') #TODO DELETE
# Classes that have more than one column and need their own view functions
@@ -134,7 +151,6 @@ class Titelbild(Base):
# one-to-many
ausgabe: Mapped[List['Ausgabe']] = relationship(back_populates='titelbild')
- werk: Mapped[List['Werk']] = relationship(back_populates='titelbild') #DELETE
def asdict_with_urls(self):
tb = self.asdict()
@@ -152,12 +168,12 @@ class Text(Base):
Untertitel: Mapped[Optional[str]]
# many-to-one
- Reihe: Mapped[Optional[int]] = mapped_column(ForeignKey('Reihe.ID'))
- reihe: Mapped[Optional["Reihe"]] = relationship(back_populates="text")
- Sprache: Mapped[int] = mapped_column(ForeignKey('Sprache.ID'), nullable=False)
- sprache: Mapped['Sprache'] = relationship(back_populates='text')
Textform: Mapped[int] = mapped_column(ForeignKey('Textform.ID'), nullable=False)
textform: Mapped['Textform'] = relationship(back_populates='text')
+ Sprache: Mapped[int] = mapped_column(ForeignKey('Sprache.ID'), nullable=False)
+ sprache: Mapped['Sprache'] = relationship(back_populates='text')
+ Reihe: Mapped[Optional[int]] = mapped_column(ForeignKey('Reihe.ID'))
+ reihe: Mapped[Optional["Reihe"]] = relationship(back_populates="text")
# one-to-many
veroeffentlichung: Mapped[List['Veroeffentlichung']] = relationship(back_populates='text')
@@ -172,16 +188,12 @@ class Veroeffentlichung(Base):
# regular columns
ID: Mapped[int] = mapped_column(primary_key=True)
- AltTitel: Mapped[Optional[str]]
- AltUntertitel: Mapped[Optional[str]]
-
- # many-to-one
- Pseudonym: Mapped[int] = mapped_column(ForeignKey('Pseudonym.ID'), nullable=False) #TODO DELETE
- pseudonym: Mapped['Pseudonym'] = relationship(back_populates='veroeffentlichung') #TODO DELETE
Text: Mapped[int] = mapped_column(ForeignKey('Text.ID'), nullable=False)
text: Mapped['Text'] = relationship(back_populates='veroeffentlichung')
Werk: Mapped[int] = mapped_column(ForeignKey('Werk.ID'), nullable=False)
werk: Mapped['Werk'] = relationship(back_populates='veroeffentlichung')
+ AltTitel: Mapped[Optional[str]]
+ AltUntertitel: Mapped[Optional[str]]
class Werk(Base):
@@ -191,26 +203,11 @@ class Werk(Base):
ID: Mapped[int] = mapped_column(primary_key=True)
Titel: Mapped[str] = mapped_column(CheckConstraint('Titel <> ""', name='WerkstitelNotEmptyConstraint'), nullable=False)
Untertitel: Mapped[Optional[str]]
- Reihennummer: Mapped[Optional[str]]
- Erscheinungsdatum: Mapped[Optional[str]] #TODO DELETE
- ISBN_13: Mapped[Optional[str]] #TODO DELETE
- ISBN_10: Mapped[Optional[str]] #TODO DELETE
- ISSN: Mapped[Optional[str]] #TODO DELETE
- Preis: Mapped[Optional[str]] #TODO DELETE
- Klappentext: Mapped[Optional[str]] #TODO DELETE
- Anmerkungen: Mapped[Optional[str]] #TODO DELETE
-
- # many-to-one
- Pseudonym: Mapped[int] = mapped_column(ForeignKey('Pseudonym.ID'))
- pseudonym: Mapped['Pseudonym'] = relationship(back_populates='werk')
Reihe: Mapped[Optional[int]] = mapped_column(ForeignKey('Reihe.ID'))
reihe: Mapped[Optional['Reihe']] = relationship(back_populates='werk')
- Titelbild: Mapped[Optional[int]] = mapped_column(ForeignKey('Titelbild.ID')) #TODO DELETE
- titelbild: Mapped[Optional['Titelbild']] = relationship(back_populates='werk') #TODO DELETE
- Verlag: Mapped[Optional[int]] = mapped_column(ForeignKey('Verlag.ID')) #TODO DELETE
- verlag: Mapped[Optional['Verlag']] = relationship(back_populates='werk') #TODO DELETE
- Werksform: Mapped[int] = mapped_column(ForeignKey('Werksform.ID'), nullable=False) #TODO DELETE
- werksform: Mapped['Werksform'] = relationship(back_populates='werk') #TODO DELETE
+ Reihennummer: Mapped[Optional[str]]
+ Pseudonym: Mapped[int] = mapped_column(ForeignKey('Pseudonym.ID'))
+ pseudonym: Mapped['Pseudonym'] = relationship(back_populates='werk')
# one-to-many
ausgabe: Mapped[List['Ausgabe']] = relationship(back_populates='werk')
@@ -228,23 +225,21 @@ class Ausgabe(Base):
# regular columns
ID: Mapped[int] = mapped_column(primary_key=True)
- Erscheinungsdatum: Mapped[Optional[str]]
- ISBN_13: Mapped[Optional[str]]
- ISBN_10: Mapped[Optional[str]]
- ISSN: Mapped[Optional[str]]
- Preis: Mapped[Optional[str]]
- Klappentext: Mapped[Optional[str]]
- Anmerkungen: Mapped[Optional[str]]
-
- # many-to-one
- Titelbild: Mapped[Optional[int]] = mapped_column(ForeignKey('Titelbild.ID'))
- titelbild: Mapped[Optional['Titelbild']] = relationship(back_populates='ausgabe')
- Verlag: Mapped[Optional[int]] = mapped_column(ForeignKey('Verlag.ID'))
- verlag: Mapped[Optional['Verlag']] = relationship(back_populates='ausgabe')
Werk: Mapped[int] = mapped_column(ForeignKey('Werk.ID'), nullable=False)
werk: Mapped["Werk"] = relationship(back_populates='ausgabe')
Werksform: Mapped[int] = mapped_column(ForeignKey('Werksform.ID'), nullable=False)
werksform: Mapped['Werksform'] = relationship(back_populates='ausgabe')
+ Titelbild: Mapped[Optional[int]] = mapped_column(ForeignKey('Titelbild.ID'))
+ titelbild: Mapped[Optional['Titelbild']] = relationship(back_populates='ausgabe')
+ ISBN_13: Mapped[Optional[str13]]
+ ISBN_10: Mapped[Optional[str10]]
+ ISSN: Mapped[Optional[str8]]
+ Verlag: Mapped[Optional[int]] = mapped_column(ForeignKey('Verlag.ID'))
+ verlag: Mapped[Optional['Verlag']] = relationship(back_populates='ausgabe')
+ Erscheinungsdatum: Mapped[Optional[str]]
+ Preis: Mapped[Optional[price]]
+ Klappentext: Mapped[Optional[str]]
+ Anmerkungen: Mapped[Optional[str]]
# Additional tables for many-to-many-relationships within the DB
diff --git a/the_works/templates/views/ausgabe_detail.html b/the_works/templates/views/ausgabe_detail.html
index 47ab9a7..8cda0d5 100644
--- a/the_works/templates/views/ausgabe_detail.html
+++ b/the_works/templates/views/ausgabe_detail.html
@@ -53,7 +53,7 @@ Ausgabe bearbeiten
-
- Pseudonym
-
-
@@ -97,7 +88,7 @@
window.onload = function () {
initDataTable("veroeffentlichung-table");
initCreateButton("veroeffentlichung-table", "Veröffentlichung hinzufügen …");
- initModal("veroeffentlichung-modal", ["form_Text", "form_Werk", "form_AltTitel", "form_AltUntertitel", "form_Pseudonym"], ["Neue Veröffentlichung", "Veröffentlichung bearbeiten"], ["{{ url_for('veroeffentlichung.create') }}", "{{ url_for('veroeffentlichung.update', id=-1) }}"]);
+ initModal("veroeffentlichung-modal", ["form_Text", "form_Werk", "form_AltTitel", "form_AltUntertitel"], ["Neue Veröffentlichung", "Veröffentlichung bearbeiten"], ["{{ url_for('veroeffentlichung.create') }}", "{{ url_for('veroeffentlichung.update', id=-1) }}"]);
}
{% endblock script %}
\ No newline at end of file
diff --git a/the_works/templates/views/werk.html b/the_works/templates/views/werk.html
index 19687f6..3ff4820 100644
--- a/the_works/templates/views/werk.html
+++ b/the_works/templates/views/werk.html
@@ -18,20 +18,11 @@
| Titel |
Untertitel |
- Werksform |
Reihe |
Reihennummer |
- Verlag |
- Preis |
- Erscheinungsdatum |
- ISBN_13 |
- ISBN_10 |
- ISSN |
Genre(s) |
Herausgeber:in(nen) |
- Titelbild |
- Klappentext |
- Anmerkungen |
+ Pseudonym |
Aktionen |
@@ -40,33 +31,11 @@
| {{ werk["Titel"] }} |
{{ werk["Untertitel"] }} |
- {{ werk["Werksform"] }} |
{{ werk["Reihe"] }} |
{{ werk["Reihennummer"] }} |
- {{ werk["Verlag"] }} |
- {{ werk["Preis"] }} |
- {{ werk["Erscheinungsdatum"] }} |
- {{ werk["ISBN_13"] }} |
- {{ werk["ISBN_10"] }} |
- {{ werk["ISSN"] }} |
{{ werk["Genre_list"] | join(", ") }} |
{{ werk["Herausgeber_list"] | join(", ") }} |
-
- {% if werk["Titelbild"] %}
-
-
- 
-
- {{ werk['Titelbild']['Dateiname'] }} ({{ werk['Titelbild']['Breite'] }} x {{ werk['Titelbild']['Hoehe'] }}, {{ werk['Titelbild']['Dateigroesse'] }} Bytes)
-
-
-
- {% else %}
- ✘
- {% endif %}
- |
- ✔{% else %}>✘{% endif %} |
- {{ werk["Anmerkungen"] }} |
+ {{ werk["Pseudonym"] }} |
|
|
@@ -75,29 +44,15 @@
-
-
{% endblock content %}
{% block script %}
-
{% endblock script %}
diff --git a/the_works/views/veroeffentlichung.py b/the_works/views/veroeffentlichung.py
index e220d94..e1532eb 100644
--- a/the_works/views/veroeffentlichung.py
+++ b/the_works/views/veroeffentlichung.py
@@ -1,14 +1,14 @@
from flask import Blueprint, render_template, request, redirect, flash, url_for
from sqlalchemy import select
from the_works.database import db
-from the_works.models import Veroeffentlichung, Text, Werk, Werksform, Pseudonym
+from the_works.models import Veroeffentlichung, Text, Werk
bp = Blueprint("veroeffentlichung", __name__)
@bp.route("/veroeffentlichung/")
@bp.route("/veroeffentlichung/all/")
def all():
- rows = db.session.execute(select(Veroeffentlichung, Text, Werk, Werksform, Pseudonym).join(Veroeffentlichung.text, isouter=True).join(Veroeffentlichung.werk, isouter=True).join(Veroeffentlichung.pseudonym, isouter=True).join(Werk.werksform))
+ rows = db.session.execute(select(Veroeffentlichung, Text, Werk).join(Veroeffentlichung.text, isouter=True).join(Veroeffentlichung.werk, isouter=True))
veroeffentlichungen = []
for row in rows:
veroeffentlichungen.append({
@@ -17,13 +17,10 @@ def all():
"t_id": row.Veroeffentlichung.Text,
"Werk": row.Werk.Titel,
"w_id": row.Veroeffentlichung.Werk,
- "wf_id": row.Werksform.ID,
"AltTitel": row.Veroeffentlichung.AltTitel or "",
- "AltUntertitel": row.Veroeffentlichung.AltUntertitel or "",
- "Pseudonym": row.Pseudonym.Pseudonym,
- "p_id": row.Veroeffentlichung.Pseudonym,
+ "AltUntertitel": row.Veroeffentlichung.AltUntertitel or ""
})
- return render_template("views/veroeffentlichung.html", veroeffentlichungen=veroeffentlichungen, texte=db.session.scalars(select(Text)), werke=db.session.scalars(select(Werk)), pseudonyme=db.session.scalars(select(Pseudonym)))
+ return render_template("views/veroeffentlichung.html", veroeffentlichungen=veroeffentlichungen, texte=db.session.scalars(select(Text)))
@bp.route("/veroeffentlichung/create/", methods=["POST"])
def create():
@@ -31,8 +28,7 @@ def create():
Text = request.form["form_Text"],
Werk = request.form["form_Werk"],
AltTitel = request.form["form_AltTitel"],
- AltUntertitel = request.form["form_AltUntertitel"],
- Pseudonym = request.form["form_Pseudonym"],
+ AltUntertitel = request.form["form_AltUntertitel"]
))
db.session.commit()
flash("Eintrag erfolgreich hinzugefügt")
@@ -45,7 +41,6 @@ def update(id):
veroeffentlichung.Werk = request.form["form_Werk"]
veroeffentlichung.AltTitel = request.form["form_AltTitel"]
veroeffentlichung.AltUntertitel = request.form["form_AltUntertitel"]
- veroeffentlichung.Pseudonym = request.form["form_Pseudonym"]
db.session.commit()
flash("Eintrag erfolgreich geändert")
return redirect(url_for("veroeffentlichung.all"), code=303)
diff --git a/the_works/views/werk.py b/the_works/views/werk.py
index 220e87a..dafa626 100644
--- a/the_works/views/werk.py
+++ b/the_works/views/werk.py
@@ -1,7 +1,7 @@
from flask import Blueprint, render_template, request, redirect, flash, url_for
from sqlalchemy import select
from the_works.database import db
-from the_works.models import Werk, Reihe, Verlag, Werksform, Genre, Herausgeber, Titelbild
+from the_works.models import Werk, Reihe, Pseudonym, Genre, Herausgeber
bp = Blueprint("werk", __name__)
@@ -10,7 +10,7 @@ bp = Blueprint("werk", __name__)
@bp.route("/werk/all/")
def all():
# select all rows from table "Werk", ORM style
- 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, Pseudonym).join(Werk.reihe, isouter=True).join(Werk.pseudonym, isouter=True))
# condense result into list of dicts
werke = []
for row in rows:
@@ -18,33 +18,20 @@ def all():
"id": row.Werk.ID,
"Titel": row.Werk.Titel,
"Untertitel": row.Werk.Untertitel or "",
- "Werksform": row.Werksform.Werksform if row.Werksform else "",
- "Verlag": row.Verlag.Verlag if row.Verlag else "",
"Reihe": row.Reihe.Titel if row.Reihe else "",
"Reihennummer": row.Werk.Reihennummer or "",
- "Erscheinungsdatum": row.Werk.Erscheinungsdatum or "",
- "ISBN_13": row.Werk.ISBN_13 or "",
- "ISBN_10": row.Werk.ISBN_10 or "",
- "ISSN": row.Werk.ISSN or "",
- "Preis": row.Werk.Preis or "",
-# "Titelbild": url_for("titelbild.thumbnail", id=row.Werk.Titelbild) if row.Werk.Titelbild else "",
- "Titelbild": db.session.get(Titelbild, row.Werk.Titelbild).asdict_with_urls() if row.Werk.Titelbild else "",
- "Klappentext": row.Werk.Klappentext or "",
- "Anmerkungen": row.Werk.Anmerkungen or "",
"Herausgeber_list": [wh.herausgeber.Name for wh in row.Werk.herausgeber],
"Genre_list": [wg.genre.Genre for wg in row.Werk.genres],
+ "Pseudonym": row.Pseudonym.Pseudonym if row.Pseudonym else ""
})
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)), titelbilder=titelbilder)
+ return render_template("views/werk_detail.html", werk={"ID": 0, "Erscheinungsdatum": ""}, reihen=db.session.scalars(select(Reihe)), genres=db.session.scalars(select(Genre)), hrsg=db.session.scalars(select(Herausgeber)), pseudonyme=db.session.scalars(select(Pseudonym)))
# all other ids -> read existing entry from DB and return as dict
w = db.session.get(Werk, id)
@@ -54,7 +41,7 @@ def read(id):
werk["Genres"] = w.genre_ids
werk["Herausgeber"] = w.herausgeber_ids
- 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)
+ return render_template("views/werk_detail.html", werk=werk, reihen=db.session.scalars(select(Reihe)), genres=db.session.scalars(select(Genre)), hrsg=db.session.scalars(select(Herausgeber)), pseudonyme=db.session.scalars(select(Pseudonym)))
@bp.route("/werk/create/", methods=["POST"])
@@ -62,18 +49,9 @@ def create():
werk = Werk(
Titel = request.form["form_Titel"],
Untertitel = request.form["form_Untertitel"] or None,
- Werksform = request.form["form_Werksform"],
- 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"]),
- 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 = request.form["form_Titelbild"] or None,
- Klappentext = request.form["form_Klappentext"] or None,
- Anmerkungen = request.form["form_Anmerkungen"] or None
+ Pseudonym = request.form["form_Pseudonym"],
)
for g in request.form.getlist("form_Genre"):
werk.genre_ids.append(g)
@@ -93,18 +71,9 @@ def update(id):
# update values
werk.Titel = request.form["form_Titel"]
werk.Untertitel = request.form["form_Untertitel"] or None
- werk.Werksform = request.form["form_Werksform"]
- 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.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
+ werk.Pseudonym = request.form["form_Pseudonym"]
# update associated values: Genre
form_set = set(map(int, request.form.getlist("form_Genre")))
@@ -133,15 +102,3 @@ def delete(id):
db.session.commit()
flash("Eintrag erfolgreich gelöscht")
return redirect(url_for("werk.all"))
-
-
-def _get_datum(jahr, monat, tag):
- if tag != "":
- return "-".join([jahr, monat.zfill(2), tag.zfill(2)])
- elif monat != "":
- return "-".join([jahr, monat.zfill(2)])
- elif jahr != "":
- return jahr
- else:
- return None
-
diff --git a/tmp.md b/tmp.md
index 7a83eb1..562006a 100644
--- a/tmp.md
+++ b/tmp.md
@@ -57,7 +57,43 @@ Sprache, (Genres) Reihe, R.No, (Genres), (Hrsg) IS?Nx3, Preis, Titel
+Stand der neuen DB
+- Basisdaten sind vorhanden, Ausnahme: Titelbild
+- Text ist auf dem alten Stand
+- es fehlt
+ - Veroeffentlichung
+ - Titelbild
+ - Werk
+ - Ausgabe
+- die Python views sind alle upgedated (theoretisch)
+- zu ändernde Jinja-Templates
+ - x veroeffentlichung
+ - x werk
+ - werk_detail
+ - ausgabe_detail
+ - Darstellung von Preis updaten und validaten
+
+- nice to have: Darstellung und Validation von IS*N verbessern (auf Basis der jew. Spezifikation)
+ - https://en.wikipedia.org/wiki/ISBN, https://en.wikipedia.org/wiki/ISSN
+ - isbnlib (Python package)
+ - isbnlib-dnb
+- nice to have: Metadaten zu meinen Werken direkt aus einer externen DB holen
+ - zB DNB
+ - keine Cover o. Klappentexte, Daten sind unsauber (zB Preis)
+ - Demo-Abruf-Seite: https://dnb-sru-demo.streamlit.app/
+ - ist kompliziert; vllt reicht es auch, einmal per Hand alle Werke von Tobias Radloff / Paul Jansen abzurufen und in die DB zu übernehmen?
+ - https://services.dnb.de/sru/dnb?version=1.1&operation=searchRetrieve&query=atr%3DTobias%20and%20atr%3DRadloff
+ - buchhandel.de
+ - keine frei verfügbare API, aber [Standardsuche](https://buchhandel.de/suche)
+ - openlibrary.org: ein Treffer [Amoralisch](https://openlibrary.org/works/OL26179908W/Philip_Strasser_in_Amoralisch?edition=key%3A/books/OL35329529M)
+ - Google Books: null Treffer
+ - VLB hat ne [REST-API](https://vlb.de/hilfe/datenbezug/rest-api), aber die kostet
+ - booklooker: taugt nicht (ist ein Marktplatz und kein Katalog)
+ - ISBNdb.com -> kennt 9 Bücher von mir
+ - kostet, gibt aber free 7-day trial
+ - [Amazon Product Advertising API](https://webservices.amazon.com/paapi5/documentation/)
+ - kostet nix