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:
Oystein Kristoffer Tveit 2023-05-01 01:37:52 +02:00
parent f72188b063
commit e57c638683
Signed by: oysteikt
GPG Key ID: 9F2F7D8250F35146
26 changed files with 651 additions and 227 deletions

185
data/iso639_1.csv Normal file
View File

@ -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
1 code name
2 aa Afar
3 ab Abkhazian
4 ae Avestan
5 af Afrikaans
6 ak Akan
7 am Amharic
8 an Aragonese
9 ar Arabic
10 as Assamese
11 av Avaric
12 ay Aymara
13 az Azerbaijani
14 ba Bashkir
15 be Belarusian
16 bg Bulgarian
17 bh Bihari
18 bi Bislama
19 bm Bambara
20 bn Bengali
21 bo Tibetan
22 br Breton
23 bs Bosnian
24 ca Catalan
25 ce Chechen
26 ch Chamorro
27 co Corsican
28 cr Cree
29 cs Czech
30 cu Church Slavic
31 cv Chuvash
32 cy Welsh
33 da Danish
34 de German
35 dv Divehi
36 dz Dzongkha
37 ee Ewe
38 el Greek
39 en English
40 eo Esperanto
41 es Spanish
42 et Estonian
43 eu Basque
44 fa Persian
45 ff Fulah
46 fi Finnish
47 fj Fijian
48 fo Faroese
49 fr French
50 fy Western Frisian
51 ga Irish
52 gd Gaelic
53 gl Galician
54 gn Guarani
55 gu Gujarati
56 gv Manx
57 ha Hausa
58 he Hebrew
59 hi Hindi
60 ho Hiri Motu
61 hr Croatian
62 ht Haitian
63 hu Hungarian
64 hy Armenian
65 hz Herero
66 ia Interlingua
67 id Indonesian
68 ie Interlingue
69 ig Igbo
70 ii Sichuan Yi
71 ik Inupiaq
72 io Ido
73 is Icelandic
74 it Italian
75 iu Inuktitut
76 ja Japanese
77 jv Javanese
78 ka Georgian
79 kg Kongo
80 ki Kikuyu
81 kj Kwanyama
82 kk Kazakh
83 kl Kalaallisut
84 km Central Khmer
85 kn Kannada
86 ko Korean
87 kr Kanuri
88 ks Kashmiri
89 ku Kurdish
90 kv Komi
91 kw Cornish
92 ky Kirghiz
93 la Latin
94 lb Luxembourgish
95 lg Ganda
96 li Limburgan
97 ln Lingala
98 lo Lao
99 lt Lithuanian
100 lu Luba-Katanga
101 lv Latvian
102 mg Malagasy
103 mh Marshallese
104 mi Maori
105 mk Macedonian
106 ml Malayalam
107 mn Mongolian
108 mr Marathi
109 ms Malay
110 mt Maltese
111 my Burmese
112 na Nauru
113 nb Norwegian Bokmål
114 nd North Ndebele
115 ne Nepali
116 ng Ndonga
117 nl Dutch
118 nn Norwegian Nynorsk
119 no Norwegian
120 nr South Ndebele
121 nv Navajo
122 ny Chichewa
123 oc Occitan
124 oj Ojibwa
125 om Oromo
126 or Oriya
127 os Ossetian
128 pa Panjabi
129 pi Pali
130 pl Polish
131 ps Pushto
132 pt Portuguese
133 qu Quechua
134 rm Romansh
135 rn Rundi
136 ro Romanian
137 ru Russian
138 rw Kinyarwanda
139 sa Sanskrit
140 sc Sardinian
141 sd Sindhi
142 se Northern Sami
143 sg Sango
144 si Sinhala
145 sk Slovak
146 sl Slovenian
147 sm Samoan
148 sn Shona
149 so Somali
150 sq Albanian
151 sr Serbian
152 ss Swati
153 st Southern Sotho
154 su Sundanese
155 sv Swedish
156 sw Swahili
157 ta Tamil
158 te Telugu
159 tg Tajik
160 th Thai
161 ti Tigrinya
162 tk Turkmen
163 tl Tagalog
164 tn Tswana
165 to Tonga
166 tr Turkish
167 ts Tsonga
168 tt Tatar
169 tw Twi
170 ty Tahitian
171 ug Uighur
172 uk Ukrainian
173 ur Urdu
174 uz Uzbek
175 ve Venda
176 vi Vietnamese
177 vo Volapük
178 wa Walloon
179 wo Wolof
180 xh Xhosa
181 yi Yiddish
182 yo Yoruba
183 za Zhuang
184 zh Chinese
185 zu Zulu

2
run.sh
View File

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

View File

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

View File

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

40
worblehat/flaskapp.py Normal file
View File

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

View File

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

40
worblehat/models/Base.py Normal file
View File

@ -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})>"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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()}'

View File

@ -0,0 +1,2 @@
from .UidMixin import UidMixin
from .UniqueNameMixin import UniqueNameMixin

View File

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

View File

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

View File

@ -0,0 +1,2 @@
from .Item_Author import Item_Author
from .Item_Category import Item_Category

View File

@ -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.")