modified a number of data models, view functions and Jinja templates to reflect changes in database schema

This commit is contained in:
eclipse 2025-08-19 22:02:21 +02:00
parent 56ec3051b6
commit 6b4f961053
7 changed files with 103 additions and 174 deletions

View File

@ -1,11 +1,12 @@
import sys import sys
from typing import List, Optional from typing import List, Optional, NewType
from sqlalchemy import ForeignKey, types, CheckConstraint from decimal import Decimal
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship 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 sqlalchemy.ext.associationproxy import AssociationProxy, association_proxy
from flask import url_for from flask import url_for
# these data models can be used with the generic functions from simple_view
SIMPLE_MODELS = [ SIMPLE_MODELS = [
{"name": "genre", "title": "Genres"}, {"name": "genre", "title": "Genres"},
{"name": "herausgeber", "title": "Herausgeber", "column": "Name"}, {"name": "herausgeber", "title": "Herausgeber", "column": "Name"},
@ -16,8 +17,24 @@ SIMPLE_MODELS = [
{"name": "werksform", "title": "Werksformen"} {"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""" """Base class for the_works' data models"""
class Base(DeclarativeBase): 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: def asdict(self) -> dict:
d = {} d = {}
for col in self.__table__.c: for col in self.__table__.c:
@ -26,10 +43,13 @@ class Base(DeclarativeBase):
else: else:
if isinstance(value := self.__getattribute__(col.key), str) and len(value) > 50: if isinstance(value := self.__getattribute__(col.key), str) and len(value) > 50:
d[col.key] = value[:48] + '...' d[col.key] = value[:48] + '...'
elif value is None:
d[col.key] = ""
else: else:
d[col.key] = value d[col.key] = value
return d return d
"""Display the data record in readable format"""
def __repr__(self) -> str: def __repr__(self) -> str:
return f"{type(self).__name__}({str(self.asdict())})" return f"{type(self).__name__}({str(self.asdict())})"
@ -61,7 +81,6 @@ class Pseudonym(Base):
ID: Mapped[int] = mapped_column(primary_key=True) ID: Mapped[int] = mapped_column(primary_key=True)
Pseudonym: Mapped[str] = mapped_column(CheckConstraint('Pseudonym <> ""', name='PseudonymNotEmptyConstraint'), nullable=False, unique=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') werk: Mapped[List['Werk']] = relationship(back_populates='pseudonym')
@ -91,7 +110,6 @@ class Verlag(Base):
ausgabe: Mapped[List['Ausgabe']] = relationship(back_populates='verlag') ausgabe: Mapped[List['Ausgabe']] = relationship(back_populates='verlag')
reihe: Mapped[List['Reihe']] = 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): class Werksform(Base):
@ -101,7 +119,6 @@ class Werksform(Base):
Werksform: Mapped[str] = mapped_column(CheckConstraint('Werksform <> ""', name='WerksformNotEmptyConstraint'), nullable=False, unique=True) Werksform: Mapped[str] = mapped_column(CheckConstraint('Werksform <> ""', name='WerksformNotEmptyConstraint'), nullable=False, unique=True)
ausgabe: Mapped[List['Ausgabe']] = relationship(back_populates='werksform') 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 # Classes that have more than one column and need their own view functions
@ -134,7 +151,6 @@ class Titelbild(Base):
# one-to-many # one-to-many
ausgabe: Mapped[List['Ausgabe']] = relationship(back_populates='titelbild') ausgabe: Mapped[List['Ausgabe']] = relationship(back_populates='titelbild')
werk: Mapped[List['Werk']] = relationship(back_populates='titelbild') #DELETE
def asdict_with_urls(self): def asdict_with_urls(self):
tb = self.asdict() tb = self.asdict()
@ -152,12 +168,12 @@ class Text(Base):
Untertitel: Mapped[Optional[str]] Untertitel: Mapped[Optional[str]]
# many-to-one # 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[int] = mapped_column(ForeignKey('Textform.ID'), nullable=False)
textform: Mapped['Textform'] = relationship(back_populates='text') 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 # one-to-many
veroeffentlichung: Mapped[List['Veroeffentlichung']] = relationship(back_populates='text') veroeffentlichung: Mapped[List['Veroeffentlichung']] = relationship(back_populates='text')
@ -172,16 +188,12 @@ class Veroeffentlichung(Base):
# regular columns # regular columns
ID: Mapped[int] = mapped_column(primary_key=True) 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[int] = mapped_column(ForeignKey('Text.ID'), nullable=False)
text: Mapped['Text'] = relationship(back_populates='veroeffentlichung') text: Mapped['Text'] = relationship(back_populates='veroeffentlichung')
Werk: Mapped[int] = mapped_column(ForeignKey('Werk.ID'), nullable=False) Werk: Mapped[int] = mapped_column(ForeignKey('Werk.ID'), nullable=False)
werk: Mapped['Werk'] = relationship(back_populates='veroeffentlichung') werk: Mapped['Werk'] = relationship(back_populates='veroeffentlichung')
AltTitel: Mapped[Optional[str]]
AltUntertitel: Mapped[Optional[str]]
class Werk(Base): class Werk(Base):
@ -191,26 +203,11 @@ class Werk(Base):
ID: Mapped[int] = mapped_column(primary_key=True) ID: Mapped[int] = mapped_column(primary_key=True)
Titel: Mapped[str] = mapped_column(CheckConstraint('Titel <> ""', name='WerkstitelNotEmptyConstraint'), nullable=False) Titel: Mapped[str] = mapped_column(CheckConstraint('Titel <> ""', name='WerkstitelNotEmptyConstraint'), nullable=False)
Untertitel: Mapped[Optional[str]] 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[int]] = mapped_column(ForeignKey('Reihe.ID'))
reihe: Mapped[Optional['Reihe']] = relationship(back_populates='werk') reihe: Mapped[Optional['Reihe']] = relationship(back_populates='werk')
Titelbild: Mapped[Optional[int]] = mapped_column(ForeignKey('Titelbild.ID')) #TODO DELETE Reihennummer: Mapped[Optional[str]]
titelbild: Mapped[Optional['Titelbild']] = relationship(back_populates='werk') #TODO DELETE Pseudonym: Mapped[int] = mapped_column(ForeignKey('Pseudonym.ID'))
Verlag: Mapped[Optional[int]] = mapped_column(ForeignKey('Verlag.ID')) #TODO DELETE pseudonym: Mapped['Pseudonym'] = relationship(back_populates='werk')
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
# one-to-many # one-to-many
ausgabe: Mapped[List['Ausgabe']] = relationship(back_populates='werk') ausgabe: Mapped[List['Ausgabe']] = relationship(back_populates='werk')
@ -228,23 +225,21 @@ class Ausgabe(Base):
# regular columns # regular columns
ID: Mapped[int] = mapped_column(primary_key=True) 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[int] = mapped_column(ForeignKey('Werk.ID'), nullable=False)
werk: Mapped["Werk"] = relationship(back_populates='ausgabe') werk: Mapped["Werk"] = relationship(back_populates='ausgabe')
Werksform: Mapped[int] = mapped_column(ForeignKey('Werksform.ID'), nullable=False) Werksform: Mapped[int] = mapped_column(ForeignKey('Werksform.ID'), nullable=False)
werksform: Mapped['Werksform'] = relationship(back_populates='ausgabe') 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 # Additional tables for many-to-many-relationships within the DB

View File

@ -53,7 +53,7 @@ Ausgabe bearbeiten
</label> </label>
<label> <label>
Preis Preis
<input id="form_Preis" name="form_Preis" aria-label="Preis" placeholder="kein Preis" value="{{ ausgabe['Preis'] or '' }}" /> <input id="form_Preis" name="form_Preis" aria-label="Preis" placeholder="kein Preis" type="number" min="0.01" max="99999.99" step="0.01" value="{{ ausgabe['Preis'] or '' }}" />
</label> </label>
<label> <label>
Titelbild Titelbild
@ -95,22 +95,22 @@ Ausgabe bearbeiten
<label> <label>
Erscheinungsdatum (TT-MM-JJJJ, MM-JJJJ, JJJJ oder leer) Erscheinungsdatum (TT-MM-JJJJ, MM-JJJJ, JJJJ oder leer)
<div class="grid"> <div class="grid">
<input type="number" min="1" max="31" id="form_Erscheinungstag" name="form_Erscheinungstag" aria-label="Erscheinungstag" placeholder="Tag" value="{{ ausgabe['Erscheinungsdatum'][8:] if ausgabe['Erscheinungsdatum'] }}" /> <input id="form_Erscheinungstag" name="form_Erscheinungstag" aria-label="Erscheinungstag" placeholder="Tag" value="{{ ausgabe['Erscheinungsdatum'][8:] if ausgabe['Erscheinungsdatum'] }}" type="number" min="1" max="31" />
<input type="number" min="1" max="12" id="form_Erscheinungsmonat" name="form_Erscheinungsmonat" aria-label="Erscheinungsmonat" placeholder="Monat" value="{{ ausgabe['Erscheinungsdatum'][5:7] if ausgabe['Erscheinungsdatum'] }}" /> <input id="form_Erscheinungsmonat" name="form_Erscheinungsmonat" aria-label="Erscheinungsmonat" placeholder="Monat" value="{{ ausgabe['Erscheinungsdatum'][5:7] if ausgabe['Erscheinungsdatum'] }}" type="number" min="1" max="12" />
<input type="number" min="1980" max="2100" id="form_Erscheinungsjahr" name="form_Erscheinungsjahr" aria-label="Erscheinungsjahr" placeholder="Jahr" value="{{ ausgabe['Erscheinungsdatum'][:4] if ausgabe['Erscheinungsdatum'] }}" /> <input id="form_Erscheinungsjahr" name="form_Erscheinungsjahr" aria-label="Erscheinungsjahr" placeholder="Jahr" value="{{ ausgabe['Erscheinungsdatum'][:4] if ausgabe['Erscheinungsdatum'] }}" type="number" min="1980" max="2100" />
</div> </div>
</label> </label>
<label> <label>
ISBN-13 ISBN-13
<input id="form_ISBN_13" name="form_ISBN_13" aria-label="ISBN-13" placeholder="keine ISBN-13" value="{{ ausgabe['ISBN_13'] or '' }}" /> <input id="form_ISBN_13" name="form_ISBN_13" aria-label="ISBN-13" placeholder="keine ISBN-13" value="{{ ausgabe['ISBN_13'] or '' }}" type="text" size="13" minlength="13" maxlength="13" />
</label> </label>
<label> <label>
ISBN-10 ISBN-10
<input id="form_ISBN_10" name="form_ISBN_10" aria-label="ISBN-10" placeholder="keine ISBN-10" value="{{ ausgabe['ISBN_10'] or '' }}" /> <input id="form_ISBN_10" name="form_ISBN_10" aria-label="ISBN-10" placeholder="keine ISBN-10" value="{{ ausgabe['ISBN_10'] or '' }}" type="text" size="10" minlength="10" maxlength="10" />
</label> </label>
<label> <label>
ISSN ISSN
<input id="form_ISSN" name="form_ISSN" aria-label="ISSN" placeholder="keine ISSN" value="{{ ausgabe['ISSN'] or '' }}" /> <input id="form_ISSN" name="form_ISSN" aria-label="ISSN" placeholder="keine ISSN" value="{{ ausgabe['ISSN'] or '' }}" type="text" size="8" maxlength="8" />
</label> </label>
</div> </div>
</section> </section>

View File

@ -19,7 +19,6 @@
<th>Werk</th> <th>Werk</th>
<th>Alt. Titel</th> <th>Alt. Titel</th>
<th>Alt. Untertitel</th> <th>Alt. Untertitel</th>
<th>Pseudonym</th>
<th colspan="2">Aktionen</th> <th colspan="2">Aktionen</th>
</tr> </tr>
</thead> </thead>
@ -30,8 +29,7 @@
<td title="Werk" data-display-werksform="{{ veroeffentlichung['wf_id'] }}">{{ veroeffentlichung["Werk"] }}</td> <td title="Werk" data-display-werksform="{{ veroeffentlichung['wf_id'] }}">{{ veroeffentlichung["Werk"] }}</td>
<td title="Alt. Titel">{{ veroeffentlichung["AltTitel"] }}</td> <td title="Alt. Titel">{{ veroeffentlichung["AltTitel"] }}</td>
<td title="Alt. Untertitel">{{ veroeffentlichung["AltUntertitel"] }}</td> <td title="Alt. Untertitel">{{ veroeffentlichung["AltUntertitel"] }}</td>
<td title="Pseudonym">{{ veroeffentlichung["Pseudonym"] }}</td> <td class="action action-update" data-id="{{ veroeffentlichung['id'] }}" data-text="{{ veroeffentlichung['t_id'] }}" data-werk="{{ veroeffentlichung['w_id'] }}" data-alttitel="{{ veroeffentlichung['AltTitel'] }}" data-altuntertitel="{{ veroeffentlichung['AltUntertitel_id'] }}"><a href="#" title="Veröffentlichung bearbeiten"><svg viewbox="0 0 24 24"><use href="#update" /></svg></a></td>
<td class="action action-update" data-id="{{ veroeffentlichung['id'] }}" data-text="{{ veroeffentlichung['t_id'] }}" data-werk="{{ veroeffentlichung['w_id'] }}" data-alttitel="{{ veroeffentlichung['AltTitel'] }}" data-altuntertitel="{{ veroeffentlichung['AltUntertitel_id'] }}" data-pseudonym="{{ veroeffentlichung['p_id'] }}"><a href="#" title="Veröffentlichung bearbeiten"><svg viewbox="0 0 24 24"><use href="#update" /></svg></a></td>
<td id="delete-{{ veroeffentlichung['id'] }}" class="action"><a onclick="return confirm('Eintrag wirklich löschen?');" href="{{ url_for('veroeffentlichung.delete', id=veroeffentlichung['id']) }}" title="Veröffentlichung löschen"><svg viewbox="0 0 24 24"><use href="#delete" /></svg></a></td> <td id="delete-{{ veroeffentlichung['id'] }}" class="action"><a onclick="return confirm('Eintrag wirklich löschen?');" href="{{ url_for('veroeffentlichung.delete', id=veroeffentlichung['id']) }}" title="Veröffentlichung löschen"><svg viewbox="0 0 24 24"><use href="#delete" /></svg></a></td>
</tr> </tr>
{% endfor %} {% endfor %}
@ -70,13 +68,6 @@
Alternativer Untertitel Alternativer Untertitel
<input id="form_AltUntertitel" name="form_AltUntertitel" aria-Label="Alternativer Untertitel" placeholder="Alternativen Untertitel eingeben" /> <input id="form_AltUntertitel" name="form_AltUntertitel" aria-Label="Alternativer Untertitel" placeholder="Alternativen Untertitel eingeben" />
</label> </label>
<label>
<span class="required">Pseudonym</span>
<select id="form_Pseudonym" name="form_Pseudonym" aria-label="Pseudonym" required>
<option selected value="">Pseudonym auswählen …</option>
{% for p in pseudonyme %}<option value="{{ p.ID }}">{{ p.Pseudonym }}</option>{% endfor %}
</select>
</label>
</article> </article>
</fieldset> </fieldset>
@ -97,7 +88,7 @@
window.onload = function () { window.onload = function () {
initDataTable("veroeffentlichung-table"); initDataTable("veroeffentlichung-table");
initCreateButton("veroeffentlichung-table", "Veröffentlichung hinzufügen …"); 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) }}"]);
} }
</script> </script>
{% endblock script %} {% endblock script %}

View File

@ -18,20 +18,11 @@
<tr> <tr>
<th>Titel</th> <th>Titel</th>
<th>Untertitel</th> <th>Untertitel</th>
<th>Werksform</th>
<th>Reihe</th> <th>Reihe</th>
<th>Reihennummer</th> <th>Reihennummer</th>
<th>Verlag</th>
<th>Preis</th>
<th>Erscheinungsdatum</th>
<th>ISBN_13</th>
<th>ISBN_10</th>
<th>ISSN</th>
<th>Genre(s)</th> <th>Genre(s)</th>
<th>Herausgeber:in(nen)</th> <th>Herausgeber:in(nen)</th>
<th>Titelbild</th> <th>Pseudonym</th>
<th>Klappentext</th>
<th>Anmerkungen</th>
<th colspan="2">Aktionen</th> <th colspan="2">Aktionen</th>
</tr> </tr>
</thead> </thead>
@ -40,33 +31,11 @@
<tr id="werk-{{ werk['id'] }}"> <tr id="werk-{{ werk['id'] }}">
<td title="Titel">{{ werk["Titel"] }}</td> <td title="Titel">{{ werk["Titel"] }}</td>
<td title="Untertitel">{{ werk["Untertitel"] }}</td> <td title="Untertitel">{{ werk["Untertitel"] }}</td>
<td title="Werksform">{{ werk["Werksform"] }}</td>
<td title="Reihe">{{ werk["Reihe"] }}</td> <td title="Reihe">{{ werk["Reihe"] }}</td>
<td title="Reihennummer">{{ werk["Reihennummer"] }}</td> <td title="Reihennummer">{{ werk["Reihennummer"] }}</td>
<td title="Verlag">{{ werk["Verlag"] }}</td>
<td title="Preis">{{ werk["Preis"] }}</td>
<td title="Erscheinungsdatum">{{ werk["Erscheinungsdatum"] }}</td>
<td title="ISBN_13">{{ werk["ISBN_13"] }}</td>
<td title="ISBN_10">{{ werk["ISBN_10"] }}</td>
<td title="ISSN">{{ werk["ISSN"] }}</td>
<td title="Genre(s)">{{ werk["Genre_list"] | join(", ") }}</td> <td title="Genre(s)">{{ werk["Genre_list"] | join(", ") }}</td>
<td title="Herausgeber:in(nen)">{{ werk["Herausgeber_list"] | join(", ") }}</td> <td title="Herausgeber:in(nen)">{{ werk["Herausgeber_list"] | join(", ") }}</td>
<td title="Titelbild"> <td title="Pseudonym">{{ werk["Pseudonym"] }}</td>
{% if werk["Titelbild"] %}
<div class="imageselect-entry" data-bild="{{ werk['Titelbild']['Bild'] }}">
<div class="imageselect-div">
<img src="{{ werk['Titelbild']['Thumbnail'] }}" width="128" height="128" alt="Titelbild (Thumbnail)" />
<span class="imageselect-label display-none">
{{ werk['Titelbild']['Dateiname'] }} ({{ werk['Titelbild']['Breite'] }} x {{ werk['Titelbild']['Hoehe'] }}, {{ werk['Titelbild']['Dateigroesse'] }} Bytes)
</span>
</div>
</div>
{% else %}
&#10008;
{% endif %}
</td>
<td title="Klappentext"{% if werk["Klappentext"] %} data-tooltip="{{ werk['Klappentext'] | replace('\n', ' &#13;&#10; ') | safe }}" data-placement="bottom">&#10004;{% else %}>&#10008;{% endif %}</td>
<td title="Anmerkungen">{{ werk["Anmerkungen"] }}</td>
<td class="action action-update" data-id="{{ werk['id'] }}"><a href="{{ url_for('werk.read', id=werk['id']) }}" title="Werk ansehen/bearbeiten"><svg viewbox="0 0 24 24"><use href="#update" /></svg></a></td> <td class="action action-update" data-id="{{ werk['id'] }}"><a href="{{ url_for('werk.read', id=werk['id']) }}" title="Werk ansehen/bearbeiten"><svg viewbox="0 0 24 24"><use href="#update" /></svg></a></td>
<td id="delete-{{ werk['id'] }}" class="action"><a onclick="return confirm('Eintrag wirklich löschen?');" href="{{ url_for('werk.delete', id=werk['id']) }}" title="Werk löschen"><svg viewbox="0 0 24 24"><use href="#delete" /></svg></a></td> <td id="delete-{{ werk['id'] }}" class="action"><a onclick="return confirm('Eintrag wirklich löschen?');" href="{{ url_for('werk.delete', id=werk['id']) }}" title="Werk löschen"><svg viewbox="0 0 24 24"><use href="#delete" /></svg></a></td>
</tr> </tr>
@ -75,29 +44,15 @@
</table> </table>
</div> </div>
<dialog id="imagepreview-modal" closedby="any">
<article>
<header>
<button class="modal-close" aria-label="close" rel="prev"></button>
<div class="imagepreview-details"></div>
</header>
<div class="imagepreview-div">
<img src="" />
</div>
</article>
</dialog>
{% endblock content %} {% endblock content %}
{% block script %} {% block script %}
<script src="{{ url_for('static', filename='js/datatables.js') }}"></script> <script src="{{ url_for('static', filename='js/datatables.js') }}"></script>
<script src="{{ url_for('static', filename='js/init_dt.js') }}"></script> <script src="{{ url_for('static', filename='js/init_dt.js') }}"></script>
<script src="{{ url_for('static', filename='js/imagepreview.js') }}"></script>
<script> <script>
window.onload = () => { window.onload = () => {
initDataTable("werk-table"); initDataTable("werk-table");
initCreateButton("werk-table", "Werk hinzufügen", "{{ url_for('werk.read', id=0) }}"); initCreateButton("werk-table", "Werk hinzufügen", "{{ url_for('werk.read', id=0) }}");
initImagepreview("imagepreview-modal");
} }
</script> </script>
{% endblock script %} {% endblock script %}

View File

@ -1,14 +1,14 @@
from flask import Blueprint, render_template, request, redirect, flash, url_for from flask import Blueprint, render_template, request, redirect, flash, url_for
from sqlalchemy import select from sqlalchemy import select
from the_works.database import db 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 = Blueprint("veroeffentlichung", __name__)
@bp.route("/veroeffentlichung/") @bp.route("/veroeffentlichung/")
@bp.route("/veroeffentlichung/all/") @bp.route("/veroeffentlichung/all/")
def 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 = [] veroeffentlichungen = []
for row in rows: for row in rows:
veroeffentlichungen.append({ veroeffentlichungen.append({
@ -17,13 +17,10 @@ def all():
"t_id": row.Veroeffentlichung.Text, "t_id": row.Veroeffentlichung.Text,
"Werk": row.Werk.Titel, "Werk": row.Werk.Titel,
"w_id": row.Veroeffentlichung.Werk, "w_id": row.Veroeffentlichung.Werk,
"wf_id": row.Werksform.ID,
"AltTitel": row.Veroeffentlichung.AltTitel or "", "AltTitel": row.Veroeffentlichung.AltTitel or "",
"AltUntertitel": row.Veroeffentlichung.AltUntertitel or "", "AltUntertitel": row.Veroeffentlichung.AltUntertitel or ""
"Pseudonym": row.Pseudonym.Pseudonym,
"p_id": row.Veroeffentlichung.Pseudonym,
}) })
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"]) @bp.route("/veroeffentlichung/create/", methods=["POST"])
def create(): def create():
@ -31,8 +28,7 @@ def create():
Text = request.form["form_Text"], Text = request.form["form_Text"],
Werk = request.form["form_Werk"], Werk = request.form["form_Werk"],
AltTitel = request.form["form_AltTitel"], AltTitel = request.form["form_AltTitel"],
AltUntertitel = request.form["form_AltUntertitel"], AltUntertitel = request.form["form_AltUntertitel"]
Pseudonym = request.form["form_Pseudonym"],
)) ))
db.session.commit() db.session.commit()
flash("Eintrag erfolgreich hinzugefügt") flash("Eintrag erfolgreich hinzugefügt")
@ -45,7 +41,6 @@ def update(id):
veroeffentlichung.Werk = request.form["form_Werk"] veroeffentlichung.Werk = request.form["form_Werk"]
veroeffentlichung.AltTitel = request.form["form_AltTitel"] veroeffentlichung.AltTitel = request.form["form_AltTitel"]
veroeffentlichung.AltUntertitel = request.form["form_AltUntertitel"] veroeffentlichung.AltUntertitel = request.form["form_AltUntertitel"]
veroeffentlichung.Pseudonym = request.form["form_Pseudonym"]
db.session.commit() db.session.commit()
flash("Eintrag erfolgreich geändert") flash("Eintrag erfolgreich geändert")
return redirect(url_for("veroeffentlichung.all"), code=303) return redirect(url_for("veroeffentlichung.all"), code=303)

View File

@ -1,7 +1,7 @@
from flask import Blueprint, render_template, request, redirect, flash, url_for from flask import Blueprint, render_template, request, redirect, flash, url_for
from sqlalchemy import select from sqlalchemy import select
from the_works.database import db 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__) bp = Blueprint("werk", __name__)
@ -10,7 +10,7 @@ bp = Blueprint("werk", __name__)
@bp.route("/werk/all/") @bp.route("/werk/all/")
def all(): def all():
# select all rows from table "Werk", ORM style # 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 # condense result into list of dicts
werke = [] werke = []
for row in rows: for row in rows:
@ -18,33 +18,20 @@ def all():
"id": row.Werk.ID, "id": row.Werk.ID,
"Titel": row.Werk.Titel, "Titel": row.Werk.Titel,
"Untertitel": row.Werk.Untertitel or "", "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 "", "Reihe": row.Reihe.Titel if row.Reihe else "",
"Reihennummer": row.Werk.Reihennummer or "", "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], "Herausgeber_list": [wh.herausgeber.Name for wh in row.Werk.herausgeber],
"Genre_list": [wg.genre.Genre for wg in row.Werk.genres], "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) 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)), 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 # all other ids -> read existing entry from DB and return as dict
w = db.session.get(Werk, id) w = db.session.get(Werk, id)
@ -54,7 +41,7 @@ def read(id):
werk["Genres"] = w.genre_ids werk["Genres"] = w.genre_ids
werk["Herausgeber"] = w.herausgeber_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"]) @bp.route("/werk/create/", methods=["POST"])
@ -62,18 +49,9 @@ def create():
werk = Werk( werk = Werk(
Titel = request.form["form_Titel"], Titel = request.form["form_Titel"],
Untertitel = request.form["form_Untertitel"] or None, 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, 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"]), Pseudonym = request.form["form_Pseudonym"],
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
) )
for g in request.form.getlist("form_Genre"): for g in request.form.getlist("form_Genre"):
werk.genre_ids.append(g) werk.genre_ids.append(g)
@ -93,18 +71,9 @@ def update(id):
# update values # update values
werk.Titel = request.form["form_Titel"] werk.Titel = request.form["form_Titel"]
werk.Untertitel = request.form["form_Untertitel"] or None 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.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.Pseudonym = request.form["form_Pseudonym"]
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 associated values: Genre # update associated values: Genre
form_set = set(map(int, request.form.getlist("form_Genre"))) form_set = set(map(int, request.form.getlist("form_Genre")))
@ -133,15 +102,3 @@ def delete(id):
db.session.commit() db.session.commit()
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):
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

36
tmp.md
View File

@ -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