switched handling of all simple tables (ID and one other column) to generic class-based view and template

This commit is contained in:
eclipse 2025-07-28 22:46:33 +02:00
parent f0a162461d
commit f4b0ec6045
20 changed files with 217 additions and 1033 deletions

View File

@ -1,12 +1,10 @@
from flask import Flask
# this import is not strictly necessary but it forces pipreqs-to include dotenv when generating `requirements.txt`
import dotenv
import dotenv # this import is not strictly necessary but it forces pipreqs-to include dotenv when generating `requirements.txt`
from the_works.database import init_db
from the_works.views import home, text, werk, verlag, sprache, textform, werksform, genre, pseudonym, reihe, herausgeber, veroeffentlichung, titelbild
from the_works.models import CLASSVIEWABLE_MODELS
from the_works.views import home, text, werk, reihe, veroeffentlichung, titelbild
from the_works.views.view import VIEWS, ViewAll, ViewCreate, ViewUpdate, ViewDelete
#from flask_debugtoolbar import DebugToolbarExtension
def create_app(config=None):
app = Flask(__name__)
@ -24,23 +22,28 @@ def create_app(config=None):
# some #DEBUG output
print(f"Current Environment: {app.config['ENV'] if 'ENV' in app.config.keys() else 'ENV is not set'}") #DEBUG
print(app.config) #DEBUG
# initialize database
init_db(app)
# register blueprints
app.register_blueprint(genre.bp)
app.register_blueprint(herausgeber.bp)
# add url rules for class-based views
for m in CLASSVIEWABLE_MODELS:
for v in VIEWS:
route = f"/{m["name"]}/{v['name']}{v.get('route_params', '')}"
class_ = globals()[f"View{v['name'].capitalize()}"]
view_name = f"{m["name"]}.{v['name']}"
kwargs = {key: value for key, value in m.items() if key is not "name"}
app.add_url_rule(
route,
endpoint=view_name,
view_func=class_.as_view(view_name, m["name"], **kwargs)
)
# register blueprints for remaining views
app.register_blueprint(home.bp)
app.register_blueprint(pseudonym.bp)
app.register_blueprint(reihe.bp)
app.register_blueprint(sprache.bp)
app.register_blueprint(text.bp)
app.register_blueprint(textform.bp)
app.register_blueprint(verlag.bp)
app.register_blueprint(werk.bp)
app.register_blueprint(werksform.bp)
app.register_blueprint(veroeffentlichung.bp)
app.register_blueprint(titelbild.bp)

View File

@ -1,11 +1,22 @@
import sys
from typing import List, Optional
from sqlalchemy import ForeignKey, types, CheckConstraint
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship, validates
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
from sqlalchemy.ext.associationproxy import AssociationProxy, association_proxy
from flask import url_for
CLASSVIEWABLE_MODELS = [
{"name": "genre", "title": "Genres"},
{"name": "herausgeber", "title": "Herausgeber", "column": "Name"},
{"name": "pseudonym", "title": "Pseudonyme"},
{"name": "sprache", "title": "Sprachen"},
{"name": "textform", "title": "Textformen"},
{"name": "verlag", "title": "Verlage"},
{"name": "werksform", "title": "Werksformen"}
]
"""Base class for the_works' data models"""
class Base(DeclarativeBase):
def asdict(self) -> dict:
d = {}
@ -23,6 +34,8 @@ class Base(DeclarativeBase):
return f"{type(self).__name__}({str(self.asdict())})"
# Classes for models with a single column (plus ID) which can be handled by the generic class-based view
class Genre(Base):
__tablename__ = 'Genre'
@ -69,6 +82,40 @@ class Textform(Base):
text: Mapped[List['Text']] = relationship(back_populates='textform')
class Verlag(Base):
__tablename__ = 'Verlag'
ID: Mapped[int] = mapped_column(primary_key=True)
Verlag: Mapped[str] = mapped_column(CheckConstraint('Verlag <> ""', name='VerlagNotEmptyConstraint'), unique=True)
reihe: Mapped[List['Reihe']] = relationship(back_populates='verlag')
werk: Mapped[List['Werk']] = relationship(back_populates='verlag')
class Werksform(Base):
__tablename__ = 'Werksform'
ID: Mapped[int] = mapped_column(primary_key=True)
Werksform: Mapped[str] = mapped_column(CheckConstraint('Werksform <> ""', name='WerksformNotEmptyConstraint'), unique=True)
werk: Mapped[List['Werk']] = relationship(back_populates='werksform')
# Classes that have more than one column and need dedicated view functions
class Reihe(Base):
__tablename__ = 'Reihe'
ID: Mapped[int] = mapped_column(primary_key=True)
Titel: Mapped[str] = mapped_column(CheckConstraint('Titel <> ""', name='ReihentitelNotEmptyConstraint'))
Verlag: Mapped[Optional[str]] = mapped_column(ForeignKey('Verlag.ID'))
verlag: Mapped['Verlag'] = relationship(back_populates='reihe')
text: Mapped[List['Text']] = relationship(back_populates='reihe')
werk: Mapped[List['Werk']] = relationship(back_populates='reihe')
class Titelbild(Base):
__tablename__ = 'Titelbild'
@ -91,38 +138,6 @@ class Titelbild(Base):
return tb
class Verlag(Base):
__tablename__ = 'Verlag'
ID: Mapped[int] = mapped_column(primary_key=True)
Verlag: Mapped[str] = mapped_column(CheckConstraint('Verlag <> ""', name='VerlagNotEmptyConstraint'), unique=True)
reihe: Mapped[List['Reihe']] = relationship(back_populates='verlag')
werk: Mapped[List['Werk']] = relationship(back_populates='verlag')
class Werksform(Base):
__tablename__ = 'Werksform'
ID: Mapped[int] = mapped_column(primary_key=True)
Werksform: Mapped[str] = mapped_column(CheckConstraint('Werksform <> ""', name='WerksformNotEmptyConstraint'), unique=True)
werk: Mapped[List['Werk']] = relationship(back_populates='werksform')
class Reihe(Base):
__tablename__ = 'Reihe'
ID: Mapped[int] = mapped_column(primary_key=True)
Titel: Mapped[str] = mapped_column(CheckConstraint('Titel <> ""', name='ReihentitelNotEmptyConstraint'))
Verlag: Mapped[Optional[str]] = mapped_column(ForeignKey('Verlag.ID'))
verlag: Mapped['Verlag'] = relationship(back_populates='reihe')
text: Mapped[List['Text']] = relationship(back_populates='reihe')
werk: Mapped[List['Werk']] = relationship(back_populates='reihe')
class Text(Base):
__tablename__ = 'Text'
@ -147,7 +162,6 @@ class Text(Base):
genre_ids: AssociationProxy[List["Genre"]] = association_proxy("genres", "Genre", creator=lambda genre_id: Text_Genre(Genre=genre_id))
class Werk(Base):
__tablename__ = 'Werk'
@ -201,6 +215,8 @@ class Veroeffentlichung(Base):
werk: Mapped['Werk'] = relationship(back_populates='veroeffentlichung')
# Additional tables for many-to-many-relationships within the DB
class Text_Genre(Base):
__tablename__ = 'Text_Genre'

View File

@ -1,121 +0,0 @@
from the_works.database import db
from sqlalchemy.orm import relationship
from sqlalchemy.ext.associationproxy import association_proxy
from flask import url_for
import sys
# add method to sqlalchemy.orm.decl_api.Model
def _asdict(self):
d = {}
for col in self.__table__.c:
if type(col.type) == db.types.BLOB:
d[col.key] = "Blob (NULL)" if self.__getattribute__(col.key) == None else f"Blob ({sys.getsizeof(self.__getattribute__(col.key))} Bytes)"
else:
value = str(self.__getattribute__(col.key))
d[col.key] = value[:50] + '...' if len(value) > 50 else value
return d
db.Model._asdict = _asdict
# override repr() method from sqlalchemy.orm.decl_api.Model
def __repr__(self):
return f"{type(self).__name__}({str(self._asdict())})"
db.Model.__repr__ = __repr__
class Text(db.Model):
__table__ = db.Model.metadata.tables['Text']
reihe = relationship("Reihe", back_populates="text")
textform = relationship("Textform", back_populates="text")
sprache = relationship("Sprache", back_populates="text")
veroeffentlichung = relationship("Veroeffentlichung", back_populates="text")
text_genre = relationship("Text_Genre", back_populates="text", cascade="save-update, merge, delete, delete-orphan")
genres = association_proxy("text_genre", "Genre")
class Werk(db.Model):
__table__ = db.Model.metadata.tables['Werk']
reihe = relationship("Reihe", back_populates="werk")
verlag = relationship("Verlag", back_populates="werk")
werksform = relationship("Werksform", back_populates="werk")
veroeffentlichung = relationship("Veroeffentlichung", back_populates="werk")
werk_genre = relationship("Werk_Genre", back_populates="werk", cascade="save-update, merge, delete, delete-orphan")
# titelbild = relationship("Titelbild", back_populates="werk", cascade="all, delete-orphan", single_parent=True)
titelbild = relationship("Titelbild", back_populates="werk")
genres = association_proxy("werk_genre", "Genre")
werk_herausgeber = relationship("Werk_Herausgeber", back_populates="werk", cascade="save-update, merge, delete, delete-orphan")
herausgeber = association_proxy("werk_herausgeber", "Herausgeber")
class Veroeffentlichung(db.Model):
__table__ = db.Model.metadata.tables['Veroeffentlichung']
text = relationship("Text", back_populates="veroeffentlichung")
werk = relationship("Werk", back_populates="veroeffentlichung")
pseudonym = relationship("Pseudonym", back_populates="veroeffentlichung")
class Reihe(db.Model):
__table__ = db.Model.metadata.tables['Reihe']
text = relationship("Text", back_populates="reihe")
werk = relationship("Werk", back_populates="reihe")
verlag = relationship("Verlag", back_populates="reihe")
class Verlag(db.Model):
__table__ = db.Model.metadata.tables['Verlag']
werk = relationship("Werk", back_populates="verlag")
reihe = relationship("Reihe", back_populates="verlag")
class Sprache(db.Model):
__table__ = db.Model.metadata.tables['Sprache']
text = relationship("Text", back_populates="sprache")
class Textform(db.Model):
__table__ = db.Model.metadata.tables['Textform']
text = relationship("Text", back_populates="textform")
class Werksform(db.Model):
__table__ = db.Model.metadata.tables['Werksform']
werk = relationship("Werk", back_populates="werksform")
class Genre(db.Model):
__table__ = db.Model.metadata.tables['Genre']
text_genre = relationship("Text_Genre", back_populates="genre")
werk_genre = relationship("Werk_Genre", back_populates="genre")
class Pseudonym(db.Model):
__table__ = db.Model.metadata.tables['Pseudonym']
veroeffentlichung = relationship("Veroeffentlichung", back_populates="pseudonym")
class Herausgeber(db.Model):
__table__ = db.Model.metadata.tables['Herausgeber']
werk_herausgeber = relationship("Werk_Herausgeber", back_populates="herausgeber")
class Titelbild(db.Model):
__table__ = db.Model.metadata.tables['Titelbild']
werk = relationship("Werk", back_populates="titelbild")
def _asdict_with_urls(self):
tb = self._asdict()
tb["Bild"] = url_for("titelbild.image", id=self.ID)
tb["Thumbnail"] = url_for("titelbild.thumbnail", id=self.ID)
return tb
class Text_Genre(db.Model):
__table__ = db.Model.metadata.tables['Text_Genre']
text = relationship("Text", back_populates="text_genre")
genre = relationship("Genre", back_populates="text_genre")
def __init__(self, genre: int):
self.Genre = genre
class Werk_Genre(db.Model):
__table__ = db.Model.metadata.tables['Werk_Genre']
werk = relationship("Werk", back_populates="werk_genre")
genre = relationship("Genre", back_populates="werk_genre")
def __init__(self, genre: int):
self.Genre = genre
class Werk_Herausgeber(db.Model):
__table__ = db.Model.metadata.tables['Werk_Herausgeber']
werk = relationship("Werk", back_populates="werk_herausgeber")
herausgeber = relationship("Herausgeber", back_populates="werk_herausgeber")
def __init__(self, hrsg: int):
self.Herausgeber = hrsg

View File

@ -1,125 +0,0 @@
# this is the verbatim output of `sqlacodegen --generator tables sqlite:///path/to/the_works.sqlite`, run inside project root
from sqlalchemy import Column, ForeignKey, Integer, LargeBinary, MetaData, Table, Text
metadata = MetaData()
t_Genre = Table(
'Genre', metadata,
Column('ID', Integer, primary_key=True),
Column('Genre', Text, nullable=False)
)
t_Herausgeber = Table(
'Herausgeber', metadata,
Column('ID', Integer, primary_key=True),
Column('Name', Text, nullable=False)
)
t_Pseudonym = Table(
'Pseudonym', metadata,
Column('ID', Integer, primary_key=True),
Column('Pseudonym', Text, nullable=False)
)
t_Sprache = Table(
'Sprache', metadata,
Column('ID', Integer, primary_key=True),
Column('Sprache', Text, nullable=False)
)
t_Textform = Table(
'Textform', metadata,
Column('ID', Integer, primary_key=True),
Column('Textform', Text, nullable=False)
)
t_Titelbild = Table(
'Titelbild', metadata,
Column('ID', Integer, primary_key=True),
Column('Mimetype', Text, nullable=False),
Column('Dateiname', Text, nullable=False),
Column('Dateigroesse', Integer, nullable=False),
Column('Breite', Integer, nullable=False),
Column('Hoehe', Integer, nullable=False),
Column('Bild', LargeBinary, nullable=False),
Column('Thumbnail', LargeBinary, nullable=False),
Column('sha256', Text, nullable=False, unique=True)
)
t_Verlag = Table(
'Verlag', metadata,
Column('ID', Integer, primary_key=True),
Column('Verlag', Text, nullable=False)
)
t_Werksform = Table(
'Werksform', metadata,
Column('ID', Integer, primary_key=True),
Column('Werksform', Text, nullable=False)
)
t_Reihe = Table(
'Reihe', metadata,
Column('ID', Integer, primary_key=True),
Column('Titel', Text, nullable=False),
Column('Verlag', ForeignKey('Verlag.ID'))
)
t_Text = Table(
'Text', metadata,
Column('ID', Integer, primary_key=True),
Column('Titel', Text, nullable=False),
Column('Untertitel', Text),
Column('Reihe', ForeignKey('Reihe.ID')),
Column('Textform', ForeignKey('Textform.ID')),
Column('Sprache', ForeignKey('Sprache.ID'))
)
t_Werk = Table(
'Werk', metadata,
Column('ID', Integer, primary_key=True),
Column('Titel', Text, nullable=False),
Column('Untertitel', Text),
Column('Werksform', ForeignKey('Werksform.ID')),
Column('Verlag', ForeignKey('Verlag.ID')),
Column('Reihe', ForeignKey('Reihe.ID')),
Column('Reihennummer', Text),
Column('Erscheinungsdatum', Text),
Column('ISBN_13', Text),
Column('ISBN_10', Text),
Column('ISSN', Text),
Column('Preis', Text),
Column('Titelbild', ForeignKey('Titelbild.ID')),
Column('Klappentext', Text),
Column('Anmerkungen', Text)
)
t_Text_Genre = Table(
'Text_Genre', metadata,
Column('Text', ForeignKey('Text.ID'), primary_key=True),
Column('Genre', ForeignKey('Genre.ID'), primary_key=True)
)
t_Veroeffentlichung = Table(
'Veroeffentlichung', metadata,
Column('ID', Integer, primary_key=True),
Column('Text', ForeignKey('Text.ID'), nullable=False),
Column('Werk', ForeignKey('Werk.ID'), nullable=False),
Column('AltTitel', Text),
Column('AltUntertitel', Text),
Column('Pseudonym', ForeignKey('Pseudonym.ID'), nullable=False)
)
t_Werk_Genre = Table(
'Werk_Genre', metadata,
Column('Werk', ForeignKey('Werk.ID'), primary_key=True),
Column('Genre', ForeignKey('Genre.ID'), primary_key=True)
)
t_Werk_Herausgeber = Table(
'Werk_Herausgeber', metadata,
Column('Herausgeber', ForeignKey('Herausgeber.ID'), primary_key=True),
Column('Werk', ForeignKey('Werk.ID'), primary_key=True)
)

View File

@ -1,70 +0,0 @@
{% extends 'base.html' %}
{% block title %}Genres{% endblock title %}
{% block head %}
<link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='css/datatables.css') }}">
{% endblock head %}
{% block heading %}Genres{% endblock heading %}
{% block content %}
{% include "_icons.svg" %}
<table id="genre-table">
<thead>
<tr>
<th>Genre</th>
<th colspan="2">Aktionen</th>
</tr>
</thead>
<tbody>
{% for genre in genres %}
<tr id="genre-{{ genre['ID'] }}">
<td title="Genre">{{ genre["Genre"] }}</td>
<td class="action action-update" data-id="{{ genre['ID'] }}" data-genre="{{genre['Genre'] }}"><a href="#" title="Genre bearbeiten"><svg viewbox="0 0 24 24"><use href="#update" /></svg></a></td>
<td id="delete-{{ genre['ID'] }}" class="action"><a onclick="return confirm('Eintrag wirklich löschen?');" href="{{ url_for('genre.delete', id=genre['ID']) }}" title="Genre löschen"><svg viewbox="0 0 24 24"><use href="#delete" /></svg></a></td>
</tr>
{% endfor %}
</tbody>
</table>
<dialog aria-labelledby="dialog-heading" id="genre-modal">
<article>
<form id="genre_detail_form" method="post" action="{{ url_for('genre.create') }}">
<header>
<button class="modal-close" aria-label="close" formmethod="dialog" formnovalidate rel="prev"></button>
<h1 id="dialog-heading" />
</header>
<fieldset>
<article>
<label>
<span class="required">Genre</span>
<input id="form_Genre" name="form_Genre" aria-Label="Genre" required autofocus />
</label>
</article>
</fieldset>
<footer class="grid">
<button id="form_submit" type="submit" formmethod="post" formaction="{{ url_for('genre.create') }}">OK</button>
<button class="secondary" aria-label="close" formmethod="dialog" formnovalidate>Abbrechen</button>
</footer>
</form>
</article>
</dialog>
{% endblock content %}
{% block 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/modal.js') }}"></script>
<script>
window.onload = function () {
initDataTable("genre-table");
initCreateButton("genre-table", "Genre hinzufügen …");
initModal("genre-modal", "form_Genre", ["Neues Genre", "Genre bearbeiten"], ["{{ url_for('genre.create') }}", "{{ url_for('genre.update', id=-1) }}"]);
}
</script>
{% endblock script %}

View File

@ -1,70 +0,0 @@
{% extends 'base.html' %}
{% block title %}Herausgeber:innen{% endblock title %}
{% block head %}
<link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='css/datatables.css') }}">
{% endblock head %}
{% block heading %}Herausgeber:innen{% endblock heading %}
{% block content %}
{% include "_icons.svg" %}
<table id="herausgeber-table">
<thead>
<tr>
<th>Name</th>
<th colspan="2">Aktionen</th>
</tr>
</thead>
<tbody>
{% for hrsg in herausgeber %}
<tr id="hrsg-{{ hrsg['ID'] }}">
<td title="Name">{{ hrsg["Name"] }}</td>
<td class="action action-update" data-id="{{ hrsg['ID'] }}" data-name="{{hrsg['Name'] }}"><a href="#" title="Herausgeber:in bearbeiten"><svg viewbox="0 0 24 24"><use href="#update" /></svg></a></td>
<td id="delete-{{ hrsg['ID'] }}" class="action"><a onclick="return confirm('Eintrag wirklich löschen?');" href="{{ url_for('herausgeber.delete', id=hrsg['ID']) }}" title="Herausgeber:in löschen"><svg viewbox="0 0 24 24"><use href="#delete" /></svg></a></td>
</tr>
{% endfor %}
</tbody>
</table>
<dialog aria-labelledby="dialog-heading" id="herausgeber-modal">
<article>
<form id="herausgeber_detail_form" method="dialog">
<header>
<button class="modal-close" aria-label="close" rel="prev" formnovalidate></button>
<h1 id="dialog-heading">#</h1>
</header>
<fieldset>
<article>
<label>
<span class="required">Name</span>
<input id="form_Name" name="form_Name" aria-Label="Name" required autofocus />
</label>
</article>
</fieldset>
<footer class="grid">
<button id="form_submit" type="submit" formmethod="post" formaction="{{ url_for('herausgeber.create') }}">OK</button>
<button class="secondary" aria-label="close" formmethod="dialog" formnovalidate>Abbrechen</button>
</footer>
</form>
</article>
</dialog>
{% endblock content %}
{% block 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/modal.js') }}"></script>
<script>
window.onload = function () {
initDataTable("herausgeber-table");
initCreateButton("herausgeber-table", "Herausgeber:in hinzufügen …");
initModal("herausgeber-modal", "form_Name", ["Neue:r Herausgeber:in", "Herausgeber:in bearbeiten"], ["{{ url_for('herausgeber.create') }}", "{{ url_for('herausgeber.update', id=-1) }}"]);
}
</script>
{% endblock script %}

View File

@ -1,70 +0,0 @@
{% extends 'base.html' %}
{% block title %}Pseudonyme{% endblock title %}
{% block head %}
<link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='css/datatables.css') }}">
{% endblock head %}
{% block heading %}Pseudonyme{% endblock heading %}
{% block content %}
{% include "_icons.svg" %}
<table id="pseudonym-table">
<thead>
<tr>
<th>Pseudonym</th>
<th colspan="2">Aktionen</th>
</tr>
</thead>
<tbody>
{% for pseudonym in pseudonyme %}
<tr id="pseudonym-{{ pseudonym['ID'] }}">
<td title="Pseudonym">{{ pseudonym["Pseudonym"] }}</td>
<td class="action action-update" data-id="{{ pseudonym['ID'] }}" data-pseudonym="{{pseudonym['Pseudonym'] }}"><a href="#" title="Pseudonym bearbeiten"><svg viewbox="0 0 24 24"><use href="#update" /></svg></a></td>
<td id="delete-{{ pseudonym['ID'] }}" class="action"><a onclick="return confirm('Eintrag wirklich löschen?');" href="{{ url_for('pseudonym.delete', id=pseudonym['ID']) }}" title="Pseudonym löschen"><svg viewbox="0 0 24 24"><use href="#delete" /></svg></a></td>
</tr>
{% endfor %}
</tbody>
</table>
<dialog aria-labelledby="dialog-heading" id="pseudonym-modal">
<article>
<form id="pseudonym_detail_form" method="dialog">
<header>
<button class="modal-close" aria-label="close" rel="prev" formnovalidate></button>
<h1 id="dialog-heading">#</h1>
</header>
<fieldset>
<article>
<label>
<span class="required">Pseudonym</span>
<input id="form_Pseudonym" name="form_Pseudonym" aria-Label="Pseudonym" required autofocus />
</label>
</article>
</fieldset>
<footer class="grid">
<button id="form_submit" type="submit" formmethod="post" formaction="{{ url_for('pseudonym.create') }}">OK</button>
<button class="secondary" aria-label="close" formmethod="dialog" formnovalidate>Abbrechen</button>
</footer>
</form>
</article>
</dialog>
{% endblock content %}
{% block 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/modal.js') }}"></script>
<script>
window.onload = function () {
initDataTable("pseudonym-table");
initCreateButton("pseudonym-table", "Pseudonym hinzufügen …");
initModal("pseudonym-modal", "form_Pseudonym", ["Neues Pseudonym", "Pseudonym bearbeiten"], ["{{ url_for('pseudonym.create') }}", "{{ url_for('pseudonym.update', id=-1) }}"]);
}
</script>
{% endblock script %}

View File

@ -0,0 +1,84 @@
{% extends 'base.html' %}
{% block title %}{{ title }}{% endblock title %}
{% block head %}
<link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='css/datatables.css') }}" />
{% endblock head %}
{% block heading %}{{ title }}{% endblock heading %}
{% block content %}
{% include "_icons.svg" %}
<table id="{{ model }}-table">
<thead>
<tr>
<th>{{ model | capitalize }}</th>
<th colspan="2">Aktionen</th>
</tr>
</thead>
<tbody>
{% for item in items %}
<tr id="{{ model }}-{{ item['ID'] }}">
<td title="{{ model | capitalize }}">{{ item[column] }}</td>
<td class="action action-update" data-id="{{ item['ID'] }}" data-{{ column }}="{{item[column] }}">
<a href="#" title="{{ model | capitalize }} bearbeiten">
<svg viewbox="0 0 24 24"><use href="#update" /></svg>
</a>
</td>
<td id="delete-{{ item['ID'] }}" class="action">
<a onclick="return confirm('Eintrag wirklich löschen?');" href="{{ url_for(model+'.delete', id=item['ID']) }}" title="{{ model | capitalize }} löschen"><svg viewbox="0 0 24 24"><use href="#delete" /></svg></a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<dialog aria-labelledby="dialog-heading" id="{{ model }}-modal">
<article>
<form id="{{ model }}_detail_form" method="post" action="{{ url_for(model + '.create') }}">
<header>
<button class="modal-close" aria-label="close" formmethod="dialog" formnovalidate rel="prev"></button>
<h1 id="dialog-heading"></h1>
</header>
<fieldset>
<article>
<label>
<span class="required">{{ model | capitalize }}</span>
<input id="form_{{ column | capitalize }}" name="form_{{ column | capitalize }}" aria-Label="{{ column | capitalize }}" required autofocus />
</label>
</article>
</fieldset>
<footer class="grid">
<button id="form_submit" type="submit" formmethod="post" formaction="{{ url_for(model + '.create') }}">OK</button>
<button class="secondary" aria-label="close" formmethod="dialog" formnovalidate>Abbrechen</button>
</footer>
</form>
</article>
</dialog>
{% endblock content %}
{% block 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/modal.js') }}"></script>
<script>
window.onload = function () {
initDataTable("{{ model }}-table");
initCreateButton("{{ model }}-table", "{{ model | capitalize }} hinzufügen …");
initModal(
"{{ model }}-modal",
"form_{{ column | capitalize }}",
["Neues {{ model | capitalize }}", "{{ model | capitalize }} bearbeiten"],
[
"{{ url_for(model + '.create') }}",
"{{ url_for(model + '.update', id=-1) }}",
]
);
};
</script>
{% endblock script %}

View File

@ -1,70 +0,0 @@
{% extends 'base.html' %}
{% block title %}Sprachen{% endblock title %}
{% block head %}
<link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='css/datatables.css') }}">
{% endblock head %}
{% block heading %}Sprachen{% endblock heading %}
{% block content %}
{% include "_icons.svg" %}
<table id="sprache-table">
<thead>
<tr>
<th>Sprachen</th>
<th colspan="2">Aktionen</th>
</tr>
</thead>
<tbody>
{% for sprache in sprachen %}
<tr id="sprache-{{ sprache['ID'] }}">
<td title="Sprache">{{ sprache["Sprache"] }}</td>
<td class="action action-update" data-id="{{ sprache['ID'] }}" data-sprache="{{sprache['Sprache'] }}"><a href="#" title="Sprache bearbeiten"><svg viewbox="0 0 24 24"><use href="#update" /></svg></a></td>
<td id="delete-{{ sprache['ID'] }}" class="action"><a onclick="return confirm('Eintrag wirklich löschen?');" href="{{ url_for('sprache.delete', id=sprache['ID']) }}" title="Sprache löschen"><svg viewbox="0 0 24 24"><use href="#delete" /></svg></a></td>
</tr>
{% endfor %}
</tbody>
</table>
<dialog aria-labelledby="dialog-heading" id="sprache-modal">
<article>
<form id="sprache_detail_form" method="dialog">
<header>
<button class="modal-close" aria-label="close" rel="prev" formnovalidate></button>
<h1 id="dialog-heading">#</h1>
</header>
<fieldset>
<article>
<label>
<span class="required">Sprache</span>
<input id="form_Sprache" name="form_Sprache" aria-Label="Sprache" required autofocus />
</label>
</article>
</fieldset>
<footer class="grid">
<button id="form_submit" type="submit" formmethod="post" formaction="{{ url_for('sprache.create') }}">OK</button>
<button class="secondary" aria-label="close" formmethod="dialog" formnovalidate>Abbrechen</button>
</footer>
</form>
</article>
</dialog>
{% endblock content %}
{% block 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/modal.js') }}"></script>
<script>
window.onload = function () {
initDataTable("sprache-table");
initCreateButton("sprache-table", "Sprache hinzufügen …");
initModal("sprache-modal", "form_Sprache", ["Neue Sprache", "Sprache bearbeiten"], ["{{ url_for('sprache.create') }}", "{{ url_for('sprache.update', id=-1) }}"]);
}
</script>
{% endblock script %}

View File

@ -1,70 +0,0 @@
{% extends 'base.html' %}
{% block title %}Textformen{% endblock title %}
{% block head %}
<link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='css/datatables.css') }}">
{% endblock head %}
{% block heading %}Textformen{% endblock heading %}
{% block content %}
{% include "_icons.svg" %}
<table id="textform-table">
<thead>
<tr>
<th>Textformen</th>
<th colspan="2">Aktionen</th>
</tr>
</thead>
<tbody>
{% for textform in textformen %}
<tr id="textform-{{ textform['ID'] }}">
<td title="Textform">{{ textform["Textform"] }}</td>
<td class="action action-update" data-id="{{ textform['ID'] }}" data-textform="{{textform['Textform'] }}"><a href="#" title="Textform bearbeiten"><svg viewbox="0 0 24 24"><use href="#update" /></svg></a></td>
<td id="delete-{{ textform['ID'] }}" class="action"><a onclick="return confirm('Eintrag wirklich löschen?');" href="{{ url_for('textform.delete', id=textform['ID']) }}" title="Textform löschen"><svg viewbox="0 0 24 24"><use href="#delete" /></svg></a></td>
</tr>
{% endfor %}
</tbody>
</table>
<dialog aria-labelledby="dialog-heading" id="textform-modal">
<article>
<form id="textform_detail_form" method="dialog">
<header>
<button class="modal-close" aria-label="close" rel="prev" formnovalidate></button>
<h1 id="dialog-heading">#</h1>
</header>
<fieldset>
<article>
<label>
<span class="required">Textform</span>
<input id="form_Textform" name="form_Textform" aria-Label="Textform" required autofocus />
</label>
</article>
</fieldset>
<footer class="grid">
<button id="form_submit" type="submit" formmethod="post" formaction="{{ url_for('textform.create') }}">OK</button>
<button class="secondary" aria-label="close" formmethod="dialog" formnovalidate>Abbrechen</button>
</footer>
</form>
</article>
</dialog>
{% endblock content %}
{% block 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/modal.js') }}"></script>
<script>
window.onload = function () {
initDataTable("textform-table");
initCreateButton("textform-table", "Textform hinzufügen …");
initModal("textform-modal", "form_Textform", ["Neue Textform", "Textform bearbeiten"], ["{{ url_for('textform.create') }}", "{{ url_for('textform.update', id=-1) }}"]);
}
</script>
{% endblock script %}

View File

@ -1,70 +0,0 @@
{% extends 'base.html' %}
{% block title %}Verlage{% endblock title %}
{% block head %}
<link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='css/datatables.css') }}">
{% endblock head %}
{% block heading %}Verlage{% endblock heading %}
{% block content %}
{% include "_icons.svg" %}
<table id="verlag-table">
<thead>
<tr>
<th>Verlag</th>
<th colspan="2">Aktionen</th>
</tr>
</thead>
<tbody>
{% for verlag in verlage %}
<tr id="verlag-{{ verlag['ID'] }}">
<td title="Verlag">{{ verlag["Verlag"] }}</td>
<td class="action action-update" data-id="{{ verlag['ID'] }}" data-verlag="{{verlag['Verlag'] }}"><a href="#" title="Verlag bearbeiten"><svg viewbox="0 0 24 24"><use href="#update" /></svg></a></td>
<td id="delete-{{ verlag['ID'] }}" class="action"><a onclick="return confirm('Eintrag wirklich löschen?');" href="{{ url_for('verlag.delete', id=verlag['ID']) }}" title="Verlag löschen"><svg viewbox="0 0 24 24"><use href="#delete" /></svg></a></td>
</tr>
{% endfor %}
</tbody>
</table>
<dialog aria-labelledby="dialog-heading" id="verlag-modal">
<article>
<form id="verlag_detail_form" method="dialog">
<header>
<button class="modal-close" aria-label="close" rel="prev" formnovalidate></button>
<h1 id="dialog-heading">#</h1>
</header>
<fieldset>
<article>
<label>
<span class="required">Name des Verlags</span>
<input id="form_Verlag" name="form_Verlag" aria-Label="Verlagsname" required autofocus />
</label>
</article>
</fieldset>
<footer class="grid">
<button id="form_submit" type="submit" formmethod="post" formaction="{{ url_for('verlag.create') }}">OK</button>
<button class="secondary" aria-label="close" formmethod="dialog" formnovalidate>Abbrechen</button>
</footer>
</form>
</article>
</dialog>
{% endblock content %}
{% block 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/modal.js') }}"></script>
<script>
window.onload = function () {
initDataTable("verlag-table");
initCreateButton("verlag-table", "Verlag hinzufügen …");
initModal("verlag-modal", "form_Verlag", ["Neuer Verlag", "Verlag bearbeiten"], ["{{ url_for('verlag.create') }}", "{{ url_for('verlag.update', id=-1) }}"]);
}
</script>
{% endblock script %}

View File

@ -1,70 +0,0 @@
{% extends 'base.html' %}
{% block title %}Werksformen{% endblock title %}
{% block head %}
<link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='css/datatables.css') }}">
{% endblock head %}
{% block heading %}Werksformen{% endblock heading %}
{% block content %}
{% include "_icons.svg" %}
<table id="werksform-table">
<thead>
<tr>
<th>Werksformen</th>
<th colspan="2">Aktionen</th>
</tr>
</thead>
<tbody>
{% for werksform in werksformen %}
<tr id="werksform-{{ werksform['ID'] }}">
<td title="Werksform">{{ werksform["Werksform"] }}</td>
<td class="action action-update" data-id="{{ werksform['ID'] }}" data-werksform="{{werksform['Werksform'] }}"><a href="#" title="Werksform bearbeiten"><svg viewbox="0 0 24 24"><use href="#update" /></svg></a></td>
<td id="delete-{{ werksform['ID'] }}" class="action"><a onclick="return confirm('Eintrag wirklich löschen?');" href="{{ url_for('werksform.delete', id=werksform['ID']) }}" title="Werksform löschen"><svg viewbox="0 0 24 24"><use href="#delete" /></svg></a></td>
</tr>
{% endfor %}
</tbody>
</table>
<dialog aria-labelledby="dialog-heading" id="werksform-modal">
<article>
<form id="werksform_detail_form" method="dialog" >
<header>
<button class="modal-close" aria-label="close" rel="prev" formnovalidate></button>
<h1 id="dialog-heading">#</h1>
</header>
<fieldset>
<article>
<label>
<span class="required">Werksform</span>
<input id="form_Werksform" name="form_Werksform" aria-Label="Werksform" required autofocus/>
</label>
</article>
</fieldset>
<footer class="grid">
<button id="form_submit" type="submit" formmethod="post" formaction="#">OK</button>
<button class="secondary" aria-label="close" formmethod="dialog" formnovalidate>Abbrechen</button>
</footer>
</form>
</article>
</dialog>
{% endblock content %}
{% block 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/modal.js') }}"></script>
<script>
window.onload = function () {
initDataTable("werksform-table");
initCreateButton("werksform-table", "Werksform hinzufügen …");
initModal("werksform-modal", "form_Werksform", ["Neue Werksform", "Werksform bearbeiten"], ["{{ url_for('werksform.create') }}", "{{ url_for('werksform.update', id=-1) }}"]);
}
</script>
{% endblock script %}

View File

@ -1,38 +0,0 @@
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 Genre
bp = Blueprint("genre", __name__)
@bp.route("/genre")
@bp.route("/genre/all")
def all():
return render_template("views/genre.html", genres=db.session.scalars(select(Genre)))
@bp.route("/genre/create", methods=["POST"])
def create():
db.session.add(Genre(Genre=request.form.get("form_Genre", default=None)))
db.session.commit()
flash("Eintrag erfolgreich hinzugefügt")
return redirect(url_for("genre.all"), code=303)
@bp.route("/genre/update/<int:id>", methods=["POST"])
def update(id):
genre = db.session.get(Genre, id)
if genre:
genre.Genre = request.form.get("form_Genre", default=None)
db.session.commit()
flash("Eintrag erfolgreich geändert")
else:
flash("Fehler beim Ändern: Eintrag nicht vorhanden")
return redirect(url_for("genre.all"), code=303)
@bp.route("/genre/delete/<int:id>")
def delete(id):
genre = db.session.get(Genre, id)
db.session.delete(genre)
db.session.commit()
flash("Eintrag erfolgreich gelöscht")
return redirect(url_for("genre.all"))

View File

@ -1,35 +0,0 @@
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 Herausgeber
bp = Blueprint("herausgeber", __name__)
@bp.route("/herausgeber")
@bp.route("/herausgeber/all")
def all():
return render_template("views/herausgeber.html", herausgeber=db.session.scalars(select(Herausgeber)))
@bp.route("/herausgeber/create", methods=["POST"])
def create():
db.session.add(Herausgeber(Name = request.form["form_Name"]))
db.session.commit()
flash("Eintrag erfolgreich hinzugefügt")
return redirect(url_for("herausgeber.all"), code=303)
@bp.route("/herausgeber/update/<int:id>", methods=["POST"])
def update(id):
herausgeber = db.session.get(Herausgeber, id)
herausgeber.Name = request.form["form_Name"]
db.session.commit()
flash("Eintrag erfolgreich geändert")
return redirect(url_for("herausgeber.all"), code=303)
@bp.route("/herausgeber/delete/<int:id>")
def delete(id):
herausgeber = db.session.get(Herausgeber, id)
db.session.delete(herausgeber)
db.session.commit()
flash("Eintrag erfolgreich gelöscht")
return redirect(url_for("herausgeber.all"))

View File

@ -1,35 +0,0 @@
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 Pseudonym
bp = Blueprint("pseudonym", __name__)
@bp.route("/pseudonym")
@bp.route("/pseudonym/all")
def all():
return render_template("views/pseudonym.html", pseudonyme=db.session.scalars(select(Pseudonym)))
@bp.route("/pseudonym/create", methods=["POST"])
def create():
db.session.add(Pseudonym(Pseudonym = request.form["form_Pseudonym"]))
db.session.commit()
flash("Eintrag erfolgreich hinzugefügt")
return redirect(url_for("pseudonym.all"), code=303)
@bp.route("/pseudonym/update/<int:id>", methods=["POST"])
def update(id):
pseudonym = db.session.get(Pseudonym, id)
pseudonym.Pseudonym = request.form["form_Pseudonym"]
db.session.commit()
flash("Eintrag erfolgreich geändert")
return redirect(url_for("pseudonym.all"), code=303)
@bp.route("/pseudonym/delete/<int:id>")
def delete(id):
pseudonym = db.session.get(Pseudonym, id)
db.session.delete(pseudonym)
db.session.commit()
flash("Eintrag erfolgreich gelöscht")
return redirect(url_for("pseudonym.all"))

View File

@ -1,35 +0,0 @@
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 Sprache
bp = Blueprint("sprache", __name__)
@bp.route("/sprache")
@bp.route("/sprache/all")
def all():
return render_template("views/sprache.html", sprachen=db.session.scalars(select(Sprache)))
@bp.route("/sprache/create", methods=["POST"])
def create():
db.session.add(Sprache(Sprache = request.form["form_Sprache"]))
db.session.commit()
flash("Eintrag erfolgreich hinzugefügt")
return redirect(url_for("sprache.all"), code=303)
@bp.route("/sprache/update/<int:id>", methods=["POST"])
def update(id):
sprache = db.session.get(Sprache, id)
sprache.Sprache = request.form["form_Sprache"]
db.session.commit()
flash("Eintrag erfolgreich geändert")
return redirect(url_for("sprache.all"), code=303)
@bp.route("/sprache/delete/<int:id>")
def delete(id):
sprache = db.session.get(Sprache, id)
db.session.delete(sprache)
db.session.commit()
flash("Eintrag erfolgreich gelöscht")
return redirect(url_for("sprache.all"))

View File

@ -1,35 +0,0 @@
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 Textform
bp = Blueprint("textform", __name__)
@bp.route("/textform")
@bp.route("/textform/all")
def all():
return render_template("views/textform.html", textformen=db.session.scalars(select(Textform)))
@bp.route("/textform/create", methods=["POST"])
def create():
db.session.add(Textform(Textform = request.form["form_Textform"]))
db.session.commit()
flash("Eintrag erfolgreich hinzugefügt")
return redirect(url_for("textform.all"), code=303)
@bp.route("/textform/update/<int:id>", methods=["POST"])
def update(id):
textform = db.session.get(Textform, id)
textform.Textform = request.form["form_Textform"]
db.session.commit()
flash("Eintrag erfolgreich geändert")
return redirect(url_for("textform.all"), code=303)
@bp.route("/textform/delete/<int:id>")
def delete(id):
textform = db.session.get(Textform, id)
db.session.delete(textform)
db.session.commit()
flash("Eintrag erfolgreich gelöscht")
return redirect(url_for("textform.all"))

View File

@ -1,35 +0,0 @@
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 Verlag
bp = Blueprint("verlag", __name__)
@bp.route("/verlag")
@bp.route("/verlag/all")
def all():
return render_template("views/verlag.html", verlage=db.session.scalars(select(Verlag)))
@bp.route("/verlag/create", methods=["POST"])
def create():
db.session.add(Verlag(Verlag = request.form["form_Verlag"]))
db.session.commit()
flash("Eintrag erfolgreich hinzugefügt")
return redirect(url_for("verlag.all"), code=303)
@bp.route("/verlag/update/<int:id>", methods=["POST"])
def update(id):
verlag = db.session.get(Verlag, id)
verlag.Verlag = request.form["form_Verlag"]
db.session.commit()
flash("Eintrag erfolgreich geändert")
return redirect(url_for("verlag.all"), code=303)
@bp.route("/verlag/delete/<int:id>")
def delete(id):
verlag = db.session.get(Verlag, id)
db.session.delete(verlag)
db.session.commit()
flash("Eintrag erfolgreich gelöscht")
return redirect(url_for("verlag.all"))

65
the_works/views/view.py Normal file
View File

@ -0,0 +1,65 @@
from abc import ABC
from flask import render_template, request, redirect, flash, url_for
from flask.views import View
from sqlalchemy import select
from the_works.database import db
VIEWS = [
{"name": "all"},
{"name": "create"},
{"name": "update", "route_params": "/<int:id>"},
{"name": "delete", "route_params": "/<int:id>"}
]
class ViewBase(View, ABC):
init_every_request = False
def __init__(self, model, **kwargs):
self.model = model.lower()
self.Model = self.model.capitalize()
module_ = __import__("the_works.models", fromlist=[self.Model])
self.class_ = getattr(module_, self.Model)
self.title = kwargs["title"]
self.column = kwargs.get("column", self.Model)
class ViewAll(ViewBase):
methods = ["GET"]
def dispatch_request(self):
return render_template(f"views/classviewable.html", items=db.session.scalars(select(self.class_)), title=self.title, model=self.model, column=self.column)
class ViewCreate(ViewBase):
methods = ["POST"]
def dispatch_request(self):
kwargs = {self.column: request.form.get(f"form_{self.column}", default=None)}
db.session.add(self.class_(**kwargs))
db.session.commit()
flash("Eintrag erfolgreich hinzugefügt")
return redirect(url_for(f"{self.model}.all"), code=303)
class ViewUpdate(ViewBase):
methods = ["POST"]
def dispatch_request(self, id: int):
record = db.session.get(self.class_, id)
if record:
setattr(record, self.column, request.form.get(f"form_{self.column}", default=None))
db.session.commit()
flash("Eintrag erfolgreich geändert")
else:
flash("Fehler beim Ändern: Eintrag nicht vorhanden")
return redirect(url_for(f"{self.model}.all"), code=303)
class ViewDelete(ViewBase):
methods = ["GET"]
def dispatch_request(self, id: int):
record = db.session.get(self.class_, id)
db.session.delete(record)
db.session.commit()
flash("Eintrag erfolgreich gelöscht")
return redirect(url_for(f"{self.model}.all"))

View File

@ -1,35 +0,0 @@
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 Werksform
bp = Blueprint("werksform", __name__)
@bp.route("/werksform")
@bp.route("/werksform/all")
def all():
return render_template("views/werksform.html", werksformen=db.session.scalars(select(Werksform)))
@bp.route("/werksform/create", methods=["POST"])
def create():
db.session.add(Werksform(Werksform = request.form["form_Werksform"]))
db.session.commit()
flash("Eintrag erfolgreich hinzugefügt")
return redirect(url_for("werksform.all"), code=303)
@bp.route("/werksform/update/<int:id>", methods=["POST"])
def update(id):
werksform = db.session.get(Werksform, id)
werksform.Werksform = request.form["form_Werksform"]
db.session.commit()
flash("Eintrag erfolgreich geändert")
return redirect(url_for("werksform.all"), code=303)
@bp.route("/werksform/delete/<int:id>")
def delete(id):
werksform = db.session.get(Werksform, id)
db.session.delete(werksform)
db.session.commit()
flash("Eintrag erfolgreich gelöscht")
return redirect(url_for("werksform.all"))