Misc changes:
- Update database models to the new declarative mapping syntax See https://docs.sqlalchemy.org/en/20/orm/mapping_styles.html#orm-declarative-mapping - Make use of Flask-SQLAlchemy to manage database connections - Remove cli command interface in favor of just deleting the sqlite file and reseeding when no tables are detected. - Add list of iso639_1 language codes and names, and add to seed list - Other changes to file structure.
This commit is contained in:
parent
f72188b063
commit
e57c638683
|
@ -0,0 +1,185 @@
|
||||||
|
code,name
|
||||||
|
aa,Afar
|
||||||
|
ab,Abkhazian
|
||||||
|
ae,Avestan
|
||||||
|
af,Afrikaans
|
||||||
|
ak,Akan
|
||||||
|
am,Amharic
|
||||||
|
an,Aragonese
|
||||||
|
ar,Arabic
|
||||||
|
as,Assamese
|
||||||
|
av,Avaric
|
||||||
|
ay,Aymara
|
||||||
|
az,Azerbaijani
|
||||||
|
ba,Bashkir
|
||||||
|
be,Belarusian
|
||||||
|
bg,Bulgarian
|
||||||
|
bh,Bihari
|
||||||
|
bi,Bislama
|
||||||
|
bm,Bambara
|
||||||
|
bn,Bengali
|
||||||
|
bo,Tibetan
|
||||||
|
br,Breton
|
||||||
|
bs,Bosnian
|
||||||
|
ca,Catalan
|
||||||
|
ce,Chechen
|
||||||
|
ch,Chamorro
|
||||||
|
co,Corsican
|
||||||
|
cr,Cree
|
||||||
|
cs,Czech
|
||||||
|
cu,Church Slavic
|
||||||
|
cv,Chuvash
|
||||||
|
cy,Welsh
|
||||||
|
da,Danish
|
||||||
|
de,German
|
||||||
|
dv,Divehi
|
||||||
|
dz,Dzongkha
|
||||||
|
ee,Ewe
|
||||||
|
el,Greek
|
||||||
|
en,English
|
||||||
|
eo,Esperanto
|
||||||
|
es,Spanish
|
||||||
|
et,Estonian
|
||||||
|
eu,Basque
|
||||||
|
fa,Persian
|
||||||
|
ff,Fulah
|
||||||
|
fi,Finnish
|
||||||
|
fj,Fijian
|
||||||
|
fo,Faroese
|
||||||
|
fr,French
|
||||||
|
fy,Western Frisian
|
||||||
|
ga,Irish
|
||||||
|
gd,Gaelic
|
||||||
|
gl,Galician
|
||||||
|
gn,Guarani
|
||||||
|
gu,Gujarati
|
||||||
|
gv,Manx
|
||||||
|
ha,Hausa
|
||||||
|
he,Hebrew
|
||||||
|
hi,Hindi
|
||||||
|
ho,Hiri Motu
|
||||||
|
hr,Croatian
|
||||||
|
ht,Haitian
|
||||||
|
hu,Hungarian
|
||||||
|
hy,Armenian
|
||||||
|
hz,Herero
|
||||||
|
ia,Interlingua
|
||||||
|
id,Indonesian
|
||||||
|
ie,Interlingue
|
||||||
|
ig,Igbo
|
||||||
|
ii,Sichuan Yi
|
||||||
|
ik,Inupiaq
|
||||||
|
io,Ido
|
||||||
|
is,Icelandic
|
||||||
|
it,Italian
|
||||||
|
iu,Inuktitut
|
||||||
|
ja,Japanese
|
||||||
|
jv,Javanese
|
||||||
|
ka,Georgian
|
||||||
|
kg,Kongo
|
||||||
|
ki,Kikuyu
|
||||||
|
kj,Kwanyama
|
||||||
|
kk,Kazakh
|
||||||
|
kl,Kalaallisut
|
||||||
|
km,Central Khmer
|
||||||
|
kn,Kannada
|
||||||
|
ko,Korean
|
||||||
|
kr,Kanuri
|
||||||
|
ks,Kashmiri
|
||||||
|
ku,Kurdish
|
||||||
|
kv,Komi
|
||||||
|
kw,Cornish
|
||||||
|
ky,Kirghiz
|
||||||
|
la,Latin
|
||||||
|
lb,Luxembourgish
|
||||||
|
lg,Ganda
|
||||||
|
li,Limburgan
|
||||||
|
ln,Lingala
|
||||||
|
lo,Lao
|
||||||
|
lt,Lithuanian
|
||||||
|
lu,Luba-Katanga
|
||||||
|
lv,Latvian
|
||||||
|
mg,Malagasy
|
||||||
|
mh,Marshallese
|
||||||
|
mi,Maori
|
||||||
|
mk,Macedonian
|
||||||
|
ml,Malayalam
|
||||||
|
mn,Mongolian
|
||||||
|
mr,Marathi
|
||||||
|
ms,Malay
|
||||||
|
mt,Maltese
|
||||||
|
my,Burmese
|
||||||
|
na,Nauru
|
||||||
|
nb,Norwegian Bokmål
|
||||||
|
nd,North Ndebele
|
||||||
|
ne,Nepali
|
||||||
|
ng,Ndonga
|
||||||
|
nl,Dutch
|
||||||
|
nn,Norwegian Nynorsk
|
||||||
|
no,Norwegian
|
||||||
|
nr,South Ndebele
|
||||||
|
nv,Navajo
|
||||||
|
ny,Chichewa
|
||||||
|
oc,Occitan
|
||||||
|
oj,Ojibwa
|
||||||
|
om,Oromo
|
||||||
|
or,Oriya
|
||||||
|
os,Ossetian
|
||||||
|
pa,Panjabi
|
||||||
|
pi,Pali
|
||||||
|
pl,Polish
|
||||||
|
ps,Pushto
|
||||||
|
pt,Portuguese
|
||||||
|
qu,Quechua
|
||||||
|
rm,Romansh
|
||||||
|
rn,Rundi
|
||||||
|
ro,Romanian
|
||||||
|
ru,Russian
|
||||||
|
rw,Kinyarwanda
|
||||||
|
sa,Sanskrit
|
||||||
|
sc,Sardinian
|
||||||
|
sd,Sindhi
|
||||||
|
se,Northern Sami
|
||||||
|
sg,Sango
|
||||||
|
si,Sinhala
|
||||||
|
sk,Slovak
|
||||||
|
sl,Slovenian
|
||||||
|
sm,Samoan
|
||||||
|
sn,Shona
|
||||||
|
so,Somali
|
||||||
|
sq,Albanian
|
||||||
|
sr,Serbian
|
||||||
|
ss,Swati
|
||||||
|
st,Southern Sotho
|
||||||
|
su,Sundanese
|
||||||
|
sv,Swedish
|
||||||
|
sw,Swahili
|
||||||
|
ta,Tamil
|
||||||
|
te,Telugu
|
||||||
|
tg,Tajik
|
||||||
|
th,Thai
|
||||||
|
ti,Tigrinya
|
||||||
|
tk,Turkmen
|
||||||
|
tl,Tagalog
|
||||||
|
tn,Tswana
|
||||||
|
to,Tonga
|
||||||
|
tr,Turkish
|
||||||
|
ts,Tsonga
|
||||||
|
tt,Tatar
|
||||||
|
tw,Twi
|
||||||
|
ty,Tahitian
|
||||||
|
ug,Uighur
|
||||||
|
uk,Ukrainian
|
||||||
|
ur,Urdu
|
||||||
|
uz,Uzbek
|
||||||
|
ve,Venda
|
||||||
|
vi,Vietnamese
|
||||||
|
vo,Volapük
|
||||||
|
wa,Walloon
|
||||||
|
wo,Wolof
|
||||||
|
xh,Xhosa
|
||||||
|
yi,Yiddish
|
||||||
|
yo,Yoruba
|
||||||
|
za,Zhuang
|
||||||
|
zh,Chinese
|
||||||
|
zu,Zulu
|
|
2
run.sh
2
run.sh
|
@ -2,4 +2,4 @@
|
||||||
|
|
||||||
# FLASK_APP=app.py FLASK_DEBUG=1 FLASK_ENV=development python3 -m flask run --host=localhost --port=5000 --debugger --reload
|
# FLASK_APP=app.py FLASK_DEBUG=1 FLASK_ENV=development python3 -m flask run --host=localhost --port=5000 --debugger --reload
|
||||||
|
|
||||||
flask --app worblehat --debug run --host=localhost --port=5000 --debugger --reload
|
flask --app worblehat.flaskapp --debug run --host=localhost --port=5000 --debugger --reload
|
||||||
|
|
|
@ -1,103 +0,0 @@
|
||||||
from flask import Flask
|
|
||||||
from flask_sqlalchemy import SQLAlchemy
|
|
||||||
from flask_admin import Admin
|
|
||||||
from flask_admin.contrib.sqla import ModelView
|
|
||||||
|
|
||||||
from os import environ, path
|
|
||||||
from dotenv import load_dotenv
|
|
||||||
|
|
||||||
from worblehat.database import db_session, init_db, drop_db
|
|
||||||
|
|
||||||
def create_app():
|
|
||||||
app = Flask(__name__, instance_relative_config=True)
|
|
||||||
app.config.from_object('config.Config')
|
|
||||||
configure_database(app)
|
|
||||||
configure_admin(app)
|
|
||||||
configure_blueprints(app)
|
|
||||||
|
|
||||||
@app.route('/')
|
|
||||||
def index():
|
|
||||||
return 'Hello World!'
|
|
||||||
|
|
||||||
return app
|
|
||||||
|
|
||||||
|
|
||||||
def configure_database(app):
|
|
||||||
@app.cli.command("initdb")
|
|
||||||
def initdb_command():
|
|
||||||
init_db()
|
|
||||||
print("Initialized the database.")
|
|
||||||
|
|
||||||
@app.cli.command("resetdb")
|
|
||||||
def resetdb_command():
|
|
||||||
drop_db()
|
|
||||||
print("Cleared the database.")
|
|
||||||
init_db()
|
|
||||||
from worblehat.models.MediaType import MediaType
|
|
||||||
from worblehat.models.Bookcase import Bookcase
|
|
||||||
from worblehat.models.Location import Location
|
|
||||||
from worblehat.models.Language import Language
|
|
||||||
|
|
||||||
media_types = [
|
|
||||||
MediaType(name='Book', description='A physical book'),
|
|
||||||
MediaType(name='Comic', description='A comic book'),
|
|
||||||
MediaType(name='Video Game', description='A digital game for computers or games consoles'),
|
|
||||||
MediaType(name='Tabletop Game', description='A physical game with cards, boards or similar')
|
|
||||||
]
|
|
||||||
|
|
||||||
bookcases = [
|
|
||||||
Bookcase(name='A', description='The first bookcase'),
|
|
||||||
Bookcase(name='B', description='The second bookcase'),
|
|
||||||
]
|
|
||||||
|
|
||||||
locations = [
|
|
||||||
Location(name='1-1', description='The first location', bookcase=bookcases[0]),
|
|
||||||
Location(name='1-2', description='The second location', bookcase=bookcases[0]),
|
|
||||||
Location(name='1-1', description='The first location', bookcase=bookcases[1]),
|
|
||||||
Location(name='1-2', description='The second location', bookcase=bookcases[1]),
|
|
||||||
]
|
|
||||||
|
|
||||||
languages = [
|
|
||||||
Language(name='English', shortname='en'),
|
|
||||||
Language(name='Norwegian', shortname='no'),
|
|
||||||
Language(name='Japanese', shortname='ja'),
|
|
||||||
Language(name='Swedish', shortname='sv'),
|
|
||||||
Language(name='German', shortname='de'),
|
|
||||||
Language(name='Russian', shortname='ru'),
|
|
||||||
Language(name='Danish', shortname='da')
|
|
||||||
]
|
|
||||||
|
|
||||||
db_session.add_all(media_types)
|
|
||||||
db_session.add_all(bookcases)
|
|
||||||
db_session.add_all(locations)
|
|
||||||
db_session.add_all(languages)
|
|
||||||
db_session.commit()
|
|
||||||
print("Added media types, bookcases and locations.")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@app.teardown_appcontext
|
|
||||||
def shutdown_session(exception=None):
|
|
||||||
db_session.remove()
|
|
||||||
|
|
||||||
def configure_admin(app):
|
|
||||||
admin = Admin(app, name='Worblehat', template_mode='bootstrap3')
|
|
||||||
|
|
||||||
from worblehat.models.Category import Category
|
|
||||||
from worblehat.models.Item import Item
|
|
||||||
from worblehat.models.MediaType import MediaType
|
|
||||||
from worblehat.models.Location import Location
|
|
||||||
from worblehat.models.Bookcase import Bookcase
|
|
||||||
|
|
||||||
admin.add_view(ModelView(Category, db_session))
|
|
||||||
admin.add_view(ModelView(Item, db_session))
|
|
||||||
admin.add_view(ModelView(MediaType, db_session))
|
|
||||||
admin.add_view(ModelView(Location, db_session))
|
|
||||||
admin.add_view(ModelView(Bookcase, db_session))
|
|
||||||
|
|
||||||
def configure_blueprints(app):
|
|
||||||
from worblehat.blueprints.main import main
|
|
||||||
blueprints = [main]
|
|
||||||
|
|
||||||
for bp in blueprints:
|
|
||||||
app.register_blueprint(bp)
|
|
|
@ -1,29 +1,3 @@
|
||||||
from sqlalchemy import create_engine
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
from sqlalchemy.orm import scoped_session, sessionmaker
|
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
|
||||||
|
|
||||||
|
db = SQLAlchemy()
|
||||||
engine = create_engine('sqlite:///db.sqlite', convert_unicode=True)
|
|
||||||
db_session = scoped_session(sessionmaker(autocommit=False,
|
|
||||||
autoflush=False,
|
|
||||||
bind=engine))
|
|
||||||
Base = declarative_base()
|
|
||||||
Base.query = db_session.query_property()
|
|
||||||
|
|
||||||
def init_db():
|
|
||||||
# import all modules here that might define models so that
|
|
||||||
# they will be registered properly on the metadata. Otherwise
|
|
||||||
# you will have to import them first before calling init_db()
|
|
||||||
|
|
||||||
from .models.Category import Category
|
|
||||||
from .models.Item import Item
|
|
||||||
from .models.MediaType import MediaType
|
|
||||||
from .models.Location import Location
|
|
||||||
from .models.Bookcase import Bookcase
|
|
||||||
from .models.Language import Language
|
|
||||||
|
|
||||||
Base.metadata.create_all(bind=engine)
|
|
||||||
|
|
||||||
|
|
||||||
def drop_db():
|
|
||||||
Base.metadata.drop_all(bind=engine)
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
from flask import Flask
|
||||||
|
from flask_admin import Admin
|
||||||
|
from flask_admin.contrib.sqla import ModelView
|
||||||
|
from sqlalchemy import inspect
|
||||||
|
|
||||||
|
from os import environ, path
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
from .database import db
|
||||||
|
from .models import *
|
||||||
|
|
||||||
|
from .blueprints.main import main
|
||||||
|
from .seed_test_data import seed_data
|
||||||
|
|
||||||
|
def create_app():
|
||||||
|
app = Flask(__name__, instance_relative_config=True)
|
||||||
|
app.config.from_object('config.Config')
|
||||||
|
|
||||||
|
db.init_app(app)
|
||||||
|
|
||||||
|
with app.app_context():
|
||||||
|
if not inspect(db.engine).has_table('Bookcase'):
|
||||||
|
Base.metadata.create_all(db.engine)
|
||||||
|
seed_data(db)
|
||||||
|
|
||||||
|
configure_admin(app)
|
||||||
|
|
||||||
|
app.register_blueprint(main)
|
||||||
|
|
||||||
|
return app
|
||||||
|
|
||||||
|
def configure_admin(app):
|
||||||
|
admin = Admin(app, name='Worblehat', template_mode='bootstrap3')
|
||||||
|
admin.add_view(ModelView(Author, db.session))
|
||||||
|
admin.add_view(ModelView(Bookcase, db.session))
|
||||||
|
admin.add_view(ModelView(BookcaseItem, db.session))
|
||||||
|
admin.add_view(ModelView(BookcaseLocation, db.session))
|
||||||
|
admin.add_view(ModelView(Category, db.session))
|
||||||
|
admin.add_view(ModelView(Language, db.session))
|
||||||
|
admin.add_view(ModelView(MediaType, db.session))
|
|
@ -0,0 +1,34 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from sqlalchemy import (
|
||||||
|
Integer,
|
||||||
|
ForeignKey,
|
||||||
|
)
|
||||||
|
from sqlalchemy.orm import (
|
||||||
|
Mapped,
|
||||||
|
mapped_column,
|
||||||
|
relationship,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .Base import Base
|
||||||
|
from .mixins import (
|
||||||
|
UidMixin,
|
||||||
|
UniqueNameMixin,
|
||||||
|
)
|
||||||
|
from .xref_tables import Item_Author
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .BookcaseItem import BookcaseItem
|
||||||
|
|
||||||
|
class Author(Base, UidMixin, UniqueNameMixin):
|
||||||
|
items: Mapped[set[BookcaseItem]] = relationship(
|
||||||
|
secondary = Item_Author.__table__,
|
||||||
|
back_populates = 'authors',
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
):
|
||||||
|
self.name = name
|
|
@ -0,0 +1,40 @@
|
||||||
|
from sqlalchemy import MetaData
|
||||||
|
from sqlalchemy.orm import (
|
||||||
|
DeclarativeBase,
|
||||||
|
declared_attr,
|
||||||
|
)
|
||||||
|
from sqlalchemy.orm.collections import (
|
||||||
|
InstrumentedDict,
|
||||||
|
InstrumentedList,
|
||||||
|
InstrumentedSet,
|
||||||
|
)
|
||||||
|
|
||||||
|
class Base(DeclarativeBase):
|
||||||
|
metadata = MetaData(
|
||||||
|
naming_convention={
|
||||||
|
"ix": "ix_%(column_0_label)s",
|
||||||
|
"uq": "uq_%(table_name)s_%(column_0_name)s",
|
||||||
|
"ck": "ck_%(table_name)s_`%(constraint_name)s`",
|
||||||
|
"fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
|
||||||
|
"pk": "pk_%(table_name)s"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
@declared_attr.directive
|
||||||
|
def __tablename__(cls) -> str:
|
||||||
|
return cls.__name__
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
columns = ", ".join(
|
||||||
|
f"{k}={repr(v)}" for k, v in self.__dict__.items() if not any([
|
||||||
|
k.startswith("_"),
|
||||||
|
|
||||||
|
# Ensure that we don't try to print out the entire list of
|
||||||
|
# relationships, which could create an infinite loop
|
||||||
|
isinstance(v, Base),
|
||||||
|
isinstance(v, InstrumentedList),
|
||||||
|
isinstance(v, InstrumentedSet),
|
||||||
|
isinstance(v, InstrumentedDict),
|
||||||
|
])
|
||||||
|
)
|
||||||
|
return f"<{self.__class__.__name__}({columns})>"
|
|
@ -1,16 +1,31 @@
|
||||||
from sqlalchemy import Column, Integer, String, ForeignKey, Boolean
|
from __future__ import annotations
|
||||||
from sqlalchemy.orm import relationship
|
from typing import TYPE_CHECKING
|
||||||
from worblehat.database import Base
|
|
||||||
|
|
||||||
|
from sqlalchemy import Text
|
||||||
|
from sqlalchemy.orm import (
|
||||||
|
Mapped,
|
||||||
|
mapped_column,
|
||||||
|
relationship,
|
||||||
|
)
|
||||||
|
|
||||||
class Bookcase(Base):
|
from .Base import Base
|
||||||
__tablename__ = 'bookcases'
|
from .mixins import (
|
||||||
id = Column(Integer, primary_key=True)
|
UidMixin,
|
||||||
name = Column(String(10), nullable=False)
|
UniqueNameMixin,
|
||||||
description = Column(String(255))
|
)
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .BookcaseLocation import BookcaseLocation
|
||||||
|
|
||||||
locations = relationship('Location', back_populates='bookcase')
|
class Bookcase(Base, UidMixin, UniqueNameMixin):
|
||||||
|
description: Mapped[str | None] = mapped_column(Text)
|
||||||
|
|
||||||
def __repr__(self):
|
locations: Mapped[list[BookcaseLocation]] = relationship(back_populates='bookcase')
|
||||||
return '<Bookcase %r>' % self.name
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
description: str | None = None,
|
||||||
|
):
|
||||||
|
self.name = name
|
||||||
|
self.description = description
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from sqlalchemy import (
|
||||||
|
Integer,
|
||||||
|
String,
|
||||||
|
ForeignKey,
|
||||||
|
)
|
||||||
|
from sqlalchemy.orm import (
|
||||||
|
Mapped,
|
||||||
|
mapped_column,
|
||||||
|
relationship,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .Base import Base
|
||||||
|
from .mixins import (
|
||||||
|
UidMixin,
|
||||||
|
UniqueNameMixin,
|
||||||
|
)
|
||||||
|
from .xref_tables import (
|
||||||
|
Item_Category,
|
||||||
|
Item_Author,
|
||||||
|
)
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .Author import Author
|
||||||
|
from .BookcaseLocation import BookcaseLocation
|
||||||
|
from .Category import Category
|
||||||
|
from .Language import Language
|
||||||
|
from .MediaType import MediaType
|
||||||
|
|
||||||
|
class BookcaseItem(Base, UidMixin, UniqueNameMixin):
|
||||||
|
# NOTE: This is kept non-unique in case we have
|
||||||
|
# multiple copies of the same book.
|
||||||
|
isbn: Mapped[int | None] = mapped_column(String, index=True)
|
||||||
|
owner: Mapped[str] = mapped_column(String, default='PVV')
|
||||||
|
|
||||||
|
fk_media_type_uid: Mapped[int] = mapped_column(Integer, ForeignKey('MediaType.uid'))
|
||||||
|
fk_bookcase_location_uid: Mapped[int | None] = mapped_column(Integer, ForeignKey('BookcaseLocation.uid'))
|
||||||
|
fk_language_uid: Mapped[int] = mapped_column(Integer, ForeignKey('Language.uid'))
|
||||||
|
|
||||||
|
media_type: Mapped[MediaType] = relationship(back_populates='items')
|
||||||
|
location: Mapped[BookcaseLocation] = relationship(back_populates='items')
|
||||||
|
language: Mapped[Language] = relationship()
|
||||||
|
|
||||||
|
categories: Mapped[set[Category]] = relationship(
|
||||||
|
secondary = Item_Category.__table__,
|
||||||
|
back_populates = 'items',
|
||||||
|
)
|
||||||
|
authors: Mapped[set[Author]] = relationship(
|
||||||
|
secondary = Item_Author.__table__,
|
||||||
|
back_populates = 'items',
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
isbn: int | None = None,
|
||||||
|
owner: str = 'PVV',
|
||||||
|
):
|
||||||
|
self.name = name
|
||||||
|
self.isbn = isbn
|
||||||
|
self.owner = owner
|
|
@ -0,0 +1,40 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from sqlalchemy import (
|
||||||
|
Integer,
|
||||||
|
ForeignKey,
|
||||||
|
Text,
|
||||||
|
)
|
||||||
|
from sqlalchemy.orm import (
|
||||||
|
Mapped,
|
||||||
|
mapped_column,
|
||||||
|
relationship,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .Base import Base
|
||||||
|
from .mixins import (
|
||||||
|
UidMixin,
|
||||||
|
UniqueNameMixin,
|
||||||
|
)
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .Bookcase import Bookcase
|
||||||
|
from .BookcaseItem import BookcaseItem
|
||||||
|
|
||||||
|
class BookcaseLocation(Base, UidMixin, UniqueNameMixin):
|
||||||
|
description: Mapped[str | None] = mapped_column(Text)
|
||||||
|
|
||||||
|
fk_bookcase_uid: Mapped[int] = mapped_column(Integer, ForeignKey('Bookcase.uid'))
|
||||||
|
|
||||||
|
bookcase: Mapped[Bookcase] = relationship(back_populates='locations')
|
||||||
|
items: Mapped[set[BookcaseItem]] = relationship(back_populates='location')
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
bookcase: Bookcase,
|
||||||
|
description: str | None = None,
|
||||||
|
):
|
||||||
|
self.name = name
|
||||||
|
self.bookcase = bookcase
|
||||||
|
self.description = description
|
|
@ -1,18 +1,34 @@
|
||||||
from sqlalchemy import Column, Integer, String, ForeignKey, Boolean, Table
|
from __future__ import annotations
|
||||||
from sqlalchemy.orm import relationship
|
from typing import TYPE_CHECKING
|
||||||
from worblehat.database import Base
|
|
||||||
|
|
||||||
category_association = Table('category_association', Base.metadata,
|
from sqlalchemy import Text
|
||||||
Column('category_id', Integer, ForeignKey('categories.id')),
|
from sqlalchemy.orm import (
|
||||||
Column('item_id', Integer, ForeignKey('items.id'))
|
Mapped,
|
||||||
|
mapped_column,
|
||||||
|
relationship,
|
||||||
)
|
)
|
||||||
|
|
||||||
class Category(Base):
|
from .Base import Base
|
||||||
__tablename__ = 'categories'
|
from .mixins import (
|
||||||
id = Column(Integer, primary_key=True)
|
UidMixin,
|
||||||
name = Column(String(80), unique=True)
|
UniqueNameMixin,
|
||||||
description = Column(String(255))
|
)
|
||||||
|
from .xref_tables import Item_Category
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .BookcaseItem import BookcaseItem
|
||||||
|
|
||||||
items = relationship('Item', secondary=category_association, back_populates='categories')
|
class Category(Base, UidMixin, UniqueNameMixin):
|
||||||
def __repr__(self):
|
description: Mapped[str | None] = mapped_column(Text)
|
||||||
return '<Category %r>' % self.name
|
|
||||||
|
items: Mapped[set[BookcaseItem]] = relationship(
|
||||||
|
secondary=Item_Category.__table__,
|
||||||
|
back_populates='categories',
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
description: str | None = None,
|
||||||
|
):
|
||||||
|
self.name = name
|
||||||
|
self.description = description
|
|
@ -1,27 +0,0 @@
|
||||||
from sqlalchemy import Column, Integer, String, ForeignKey, Boolean
|
|
||||||
from sqlalchemy.orm import relationship
|
|
||||||
from worblehat.database import Base
|
|
||||||
from .Language import Language
|
|
||||||
|
|
||||||
class Item(Base):
|
|
||||||
__tablename__ = 'items'
|
|
||||||
id = Column(Integer, primary_key=True)
|
|
||||||
|
|
||||||
title = Column(String(255), nullable=False)
|
|
||||||
owner = Column(String(255))
|
|
||||||
isbn = Column(String(255))
|
|
||||||
|
|
||||||
media_id = Column(Integer, ForeignKey('media_types.id'), nullable=False)
|
|
||||||
media = relationship('MediaType', back_populates='items')
|
|
||||||
|
|
||||||
location_id = Column(Integer, ForeignKey('locations.id'), nullable=False)
|
|
||||||
location = relationship('Location', back_populates='items')
|
|
||||||
|
|
||||||
language_id = Column(Integer, ForeignKey('languages.id'), nullable=False)
|
|
||||||
language = relationship('Language', back_populates='items')
|
|
||||||
|
|
||||||
categories = relationship('Category', secondary='category_association', back_populates='items')
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return '<Item %r / %r>' % (self.media.name, self.title)
|
|
||||||
|
|
|
@ -1,16 +1,23 @@
|
||||||
from sqlalchemy import Column, Integer, String, ForeignKey, Boolean
|
# from sqlalchemy import Column, Integer, String, ForeignKey, Boolean
|
||||||
from sqlalchemy.orm import relationship
|
# from sqlalchemy.orm import relationship
|
||||||
from worblehat.database import Base
|
|
||||||
|
|
||||||
class Language(Base):
|
from sqlalchemy import String
|
||||||
__tablename__ = 'languages'
|
from sqlalchemy.orm import (
|
||||||
id = Column(Integer, primary_key=True)
|
Mapped,
|
||||||
|
mapped_column,
|
||||||
|
)
|
||||||
|
|
||||||
name = Column(String(32), nullable=False)
|
|
||||||
shortname = Column(String(2), nullable=False)
|
|
||||||
flag = Column(String(3))
|
|
||||||
|
|
||||||
items = relationship('Item', back_populates='language')
|
from .Base import Base
|
||||||
|
from .mixins import UidMixin, UniqueNameMixin
|
||||||
|
|
||||||
def __repr__(self):
|
class Language(Base, UidMixin, UniqueNameMixin):
|
||||||
return '<Language %r>' % self.name
|
iso639_1_code: Mapped[str] = mapped_column(String(2))
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
iso639_1_code: str,
|
||||||
|
):
|
||||||
|
self.name = name
|
||||||
|
self.iso639_1_code = iso639_1_code
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
from sqlalchemy import Column, Integer, String, ForeignKey, Boolean
|
|
||||||
from sqlalchemy.orm import relationship
|
|
||||||
from worblehat.database import Base
|
|
||||||
|
|
||||||
|
|
||||||
class Location(Base):
|
|
||||||
__tablename__ = 'locations'
|
|
||||||
id = Column(Integer, primary_key=True)
|
|
||||||
name = Column(String(100), nullable=False)
|
|
||||||
description = Column(String(255))
|
|
||||||
|
|
||||||
bookcase_id = Column(Integer, ForeignKey('bookcases.id'), nullable=False)
|
|
||||||
bookcase = relationship('Bookcase', back_populates='locations')
|
|
||||||
|
|
||||||
items = relationship('Item', back_populates='location')
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return '<Location %s %s>' % (self.bookcase.name, self.name)
|
|
||||||
|
|
|
@ -1,19 +1,29 @@
|
||||||
from sqlalchemy import Column, Integer, String, ForeignKey, Boolean
|
from __future__ import annotations
|
||||||
from sqlalchemy.orm import relationship
|
from typing import TYPE_CHECKING
|
||||||
from worblehat.database import Base
|
|
||||||
|
|
||||||
|
from sqlalchemy import Text
|
||||||
|
from sqlalchemy.orm import (
|
||||||
|
Mapped,
|
||||||
|
mapped_column,
|
||||||
|
relationship,
|
||||||
|
)
|
||||||
|
|
||||||
class MediaType(Base):
|
from .Base import Base
|
||||||
__tablename__ = 'media_types'
|
from .mixins import UidMixin, UniqueNameMixin
|
||||||
id = Column(Integer, primary_key=True)
|
if TYPE_CHECKING:
|
||||||
name = Column(String(80), nullable=False)
|
from .BookcaseItem import BookcaseItem
|
||||||
description = Column(String(255))
|
|
||||||
items = relationship('Item', back_populates='media', lazy=True)
|
|
||||||
|
|
||||||
def __init__(self, name, description):
|
class MediaType(Base, UidMixin, UniqueNameMixin):
|
||||||
|
description: Mapped[str | None] = mapped_column(Text)
|
||||||
|
|
||||||
|
items: Mapped[set[BookcaseItem]] = relationship(back_populates='media_type')
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
description: str | None = None,
|
||||||
|
):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.description = description
|
self.description = description
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return '<MediaType %r>' % self.name
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
from .Author import Author
|
||||||
|
from .Base import Base
|
||||||
|
from .Bookcase import Bookcase
|
||||||
|
from .BookcaseItem import BookcaseItem
|
||||||
|
from .BookcaseLocation import BookcaseLocation
|
||||||
|
from .Category import Category
|
||||||
|
from .Language import Language
|
||||||
|
from .MediaType import MediaType
|
|
@ -0,0 +1,30 @@
|
||||||
|
from typing_extensions import Self
|
||||||
|
|
||||||
|
from sqlalchemy import Integer
|
||||||
|
from sqlalchemy.orm import (
|
||||||
|
Mapped,
|
||||||
|
mapped_column,
|
||||||
|
)
|
||||||
|
|
||||||
|
from worblehat.database import db
|
||||||
|
|
||||||
|
class UidMixin(object):
|
||||||
|
uid: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_by_uid(cls, uid: int) -> Self | None:
|
||||||
|
"""
|
||||||
|
NOTE:
|
||||||
|
This is a flask_sqlalchemy specific method.
|
||||||
|
It will not work outside of a request context.
|
||||||
|
"""
|
||||||
|
return db.session.query(cls).where(cls.uid == uid).one_or_none()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_by_uid_or_404(cls, uid: int) -> Self:
|
||||||
|
"""
|
||||||
|
NOTE:
|
||||||
|
This is a flask_sqlalchemy specific method.
|
||||||
|
It will not work outside of a request context.
|
||||||
|
"""
|
||||||
|
return db.session.query(cls).where(cls.uid == uid).one_or_404()
|
|
@ -0,0 +1,30 @@
|
||||||
|
from typing_extensions import Self
|
||||||
|
|
||||||
|
from sqlalchemy import Text
|
||||||
|
from sqlalchemy.orm import (
|
||||||
|
Mapped,
|
||||||
|
mapped_column,
|
||||||
|
)
|
||||||
|
|
||||||
|
from worblehat.database import db
|
||||||
|
|
||||||
|
class UniqueNameMixin(object):
|
||||||
|
name: Mapped[str] = mapped_column(Text, unique=True, index=True)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_by_name(cls, name: str) -> Self | None:
|
||||||
|
"""
|
||||||
|
NOTE:
|
||||||
|
This is a flask_sqlalchemy specific method.
|
||||||
|
It will not work outside of a request context.
|
||||||
|
"""
|
||||||
|
return db.session.query(cls).where(cls.name == name).one_or_none()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_by_uid_or_404(cls, name: str) -> Self:
|
||||||
|
"""
|
||||||
|
NOTE:
|
||||||
|
This is a flask_sqlalchemy specific method.
|
||||||
|
It will not work outside of a request context.
|
||||||
|
"""
|
||||||
|
return db.session.query(cls).where(cls.name == name).one_or_404()
|
|
@ -0,0 +1,6 @@
|
||||||
|
from sqlalchemy.orm import declared_attr
|
||||||
|
|
||||||
|
class XrefMixin(object):
|
||||||
|
@declared_attr.directive
|
||||||
|
def __tablename__(cls) -> str:
|
||||||
|
return f'xref_{cls.__name__.lower()}'
|
|
@ -0,0 +1,2 @@
|
||||||
|
from .UidMixin import UidMixin
|
||||||
|
from .UniqueNameMixin import UniqueNameMixin
|
|
@ -0,0 +1,15 @@
|
||||||
|
from sqlalchemy import (
|
||||||
|
Integer,
|
||||||
|
ForeignKey,
|
||||||
|
)
|
||||||
|
from sqlalchemy.orm import (
|
||||||
|
Mapped,
|
||||||
|
mapped_column,
|
||||||
|
)
|
||||||
|
|
||||||
|
from ..Base import Base
|
||||||
|
from ..mixins.XrefMixin import XrefMixin
|
||||||
|
|
||||||
|
class Item_Author(Base, XrefMixin):
|
||||||
|
fk_item_uid: Mapped[int] = mapped_column(ForeignKey('BookcaseItem.uid'), primary_key=True)
|
||||||
|
fk_author_uid: Mapped[int] = mapped_column(ForeignKey('Author.uid'), primary_key=True)
|
|
@ -0,0 +1,15 @@
|
||||||
|
from sqlalchemy import (
|
||||||
|
Integer,
|
||||||
|
ForeignKey,
|
||||||
|
)
|
||||||
|
from sqlalchemy.orm import (
|
||||||
|
Mapped,
|
||||||
|
mapped_column,
|
||||||
|
)
|
||||||
|
|
||||||
|
from ..Base import Base
|
||||||
|
from ..mixins.XrefMixin import XrefMixin
|
||||||
|
|
||||||
|
class Item_Category(Base, XrefMixin):
|
||||||
|
fk_item_uid: Mapped[int] = mapped_column(ForeignKey('BookcaseItem.uid'), primary_key=True)
|
||||||
|
fk_category_uid: Mapped[int] = mapped_column(ForeignKey('Category.uid'), primary_key=True)
|
|
@ -0,0 +1,2 @@
|
||||||
|
from .Item_Author import Item_Author
|
||||||
|
from .Item_Category import Item_Category
|
|
@ -0,0 +1,42 @@
|
||||||
|
import csv
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
|
||||||
|
from .models import (
|
||||||
|
Bookcase,
|
||||||
|
BookcaseLocation,
|
||||||
|
Language,
|
||||||
|
MediaType,
|
||||||
|
)
|
||||||
|
|
||||||
|
def seed_data(db: SQLAlchemy):
|
||||||
|
media_types = [
|
||||||
|
MediaType(name='Book', description='A physical book'),
|
||||||
|
MediaType(name='Comic', description='A comic book'),
|
||||||
|
MediaType(name='Video Game', description='A digital game for computers or games consoles'),
|
||||||
|
MediaType(name='Tabletop Game', description='A physical game with cards, boards or similar')
|
||||||
|
]
|
||||||
|
|
||||||
|
bookcases = [
|
||||||
|
Bookcase(name='A', description='The first bookcase'),
|
||||||
|
Bookcase(name='B', description='The second bookcase'),
|
||||||
|
]
|
||||||
|
|
||||||
|
locations = [
|
||||||
|
BookcaseLocation(name='1-1', description='The first location', bookcase=bookcases[0]),
|
||||||
|
BookcaseLocation(name='1-2', description='The second location', bookcase=bookcases[0]),
|
||||||
|
BookcaseLocation(name='2-1', description='The third location', bookcase=bookcases[1]),
|
||||||
|
BookcaseLocation(name='2-2', description='The fourth location', bookcase=bookcases[1]),
|
||||||
|
]
|
||||||
|
|
||||||
|
with open(Path(__file__).parent.parent / 'data' / 'iso639_1.csv') as f:
|
||||||
|
reader = csv.reader(f)
|
||||||
|
languages = [Language(name, code) for (code, name) in reader]
|
||||||
|
|
||||||
|
db.session.add_all(media_types)
|
||||||
|
db.session.add_all(bookcases)
|
||||||
|
db.session.add_all(locations)
|
||||||
|
db.session.add_all(languages)
|
||||||
|
db.session.commit()
|
||||||
|
print("Added test media types, bookcases and locations.")
|
Loading…
Reference in New Issue