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
185
data/iso639_1.csv
Normal file
185
data/iso639_1.csv
Normal 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
|
|
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 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 sqlalchemy.orm import scoped_session, sessionmaker
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from flask_sqlalchemy import 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)
|
||||
db = SQLAlchemy()
|
40
worblehat/flaskapp.py
Normal file
40
worblehat/flaskapp.py
Normal 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))
|
34
worblehat/models/Author.py
Normal file
34
worblehat/models/Author.py
Normal 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
40
worblehat/models/Base.py
Normal 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})>"
|
@ -1,16 +1,31 @@
|
||||
from sqlalchemy import Column, Integer, String, ForeignKey, Boolean
|
||||
from sqlalchemy.orm import relationship
|
||||
from worblehat.database import Base
|
||||
from __future__ import annotations
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from sqlalchemy import Text
|
||||
from sqlalchemy.orm import (
|
||||
Mapped,
|
||||
mapped_column,
|
||||
relationship,
|
||||
)
|
||||
|
||||
class Bookcase(Base):
|
||||
__tablename__ = 'bookcases'
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String(10), nullable=False)
|
||||
description = Column(String(255))
|
||||
from .Base import Base
|
||||
from .mixins import (
|
||||
UidMixin,
|
||||
UniqueNameMixin,
|
||||
)
|
||||
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):
|
||||
return '<Bookcase %r>' % self.name
|
||||
locations: Mapped[list[BookcaseLocation]] = relationship(back_populates='bookcase')
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
description: str | None = None,
|
||||
):
|
||||
self.name = name
|
||||
self.description = description
|
||||
|
||||
|
62
worblehat/models/BookcaseItem.py
Normal file
62
worblehat/models/BookcaseItem.py
Normal 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
|
40
worblehat/models/BookcaseLocation.py
Normal file
40
worblehat/models/BookcaseLocation.py
Normal 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
|
@ -1,18 +1,34 @@
|
||||
from sqlalchemy import Column, Integer, String, ForeignKey, Boolean, Table
|
||||
from sqlalchemy.orm import relationship
|
||||
from worblehat.database import Base
|
||||
from __future__ import annotations
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
category_association = Table('category_association', Base.metadata,
|
||||
Column('category_id', Integer, ForeignKey('categories.id')),
|
||||
Column('item_id', Integer, ForeignKey('items.id'))
|
||||
from sqlalchemy import Text
|
||||
from sqlalchemy.orm import (
|
||||
Mapped,
|
||||
mapped_column,
|
||||
relationship,
|
||||
)
|
||||
|
||||
class Category(Base):
|
||||
__tablename__ = 'categories'
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String(80), unique=True)
|
||||
description = Column(String(255))
|
||||
from .Base import Base
|
||||
from .mixins import (
|
||||
UidMixin,
|
||||
UniqueNameMixin,
|
||||
)
|
||||
from .xref_tables import Item_Category
|
||||
if TYPE_CHECKING:
|
||||
from .BookcaseItem import BookcaseItem
|
||||
|
||||
items = relationship('Item', secondary=category_association, back_populates='categories')
|
||||
def __repr__(self):
|
||||
return '<Category %r>' % self.name
|
||||
class Category(Base, UidMixin, UniqueNameMixin):
|
||||
description: Mapped[str | None] = mapped_column(Text)
|
||||
|
||||
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.orm import relationship
|
||||
from worblehat.database import Base
|
||||
# from sqlalchemy import Column, Integer, String, ForeignKey, Boolean
|
||||
# from sqlalchemy.orm import relationship
|
||||
|
||||
class Language(Base):
|
||||
__tablename__ = 'languages'
|
||||
id = Column(Integer, primary_key=True)
|
||||
from sqlalchemy import String
|
||||
from sqlalchemy.orm import (
|
||||
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):
|
||||
return '<Language %r>' % self.name
|
||||
class Language(Base, UidMixin, UniqueNameMixin):
|
||||
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 sqlalchemy.orm import relationship
|
||||
from worblehat.database import Base
|
||||
from __future__ import annotations
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from sqlalchemy import Text
|
||||
from sqlalchemy.orm import (
|
||||
Mapped,
|
||||
mapped_column,
|
||||
relationship,
|
||||
)
|
||||
|
||||
class MediaType(Base):
|
||||
__tablename__ = 'media_types'
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String(80), nullable=False)
|
||||
description = Column(String(255))
|
||||
items = relationship('Item', back_populates='media', lazy=True)
|
||||
from .Base import Base
|
||||
from .mixins import UidMixin, UniqueNameMixin
|
||||
if TYPE_CHECKING:
|
||||
from .BookcaseItem import BookcaseItem
|
||||
|
||||
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.description = description
|
||||
|
||||
def __repr__(self):
|
||||
return '<MediaType %r>' % self.name
|
||||
|
||||
|
8
worblehat/models/__init__.py
Normal file
8
worblehat/models/__init__.py
Normal 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
|
30
worblehat/models/mixins/UidMixin.py
Normal file
30
worblehat/models/mixins/UidMixin.py
Normal 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()
|
30
worblehat/models/mixins/UniqueNameMixin.py
Normal file
30
worblehat/models/mixins/UniqueNameMixin.py
Normal 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()
|
6
worblehat/models/mixins/XrefMixin.py
Normal file
6
worblehat/models/mixins/XrefMixin.py
Normal 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()}'
|
2
worblehat/models/mixins/__init__.py
Normal file
2
worblehat/models/mixins/__init__.py
Normal file
@ -0,0 +1,2 @@
|
||||
from .UidMixin import UidMixin
|
||||
from .UniqueNameMixin import UniqueNameMixin
|
15
worblehat/models/xref_tables/Item_Author.py
Normal file
15
worblehat/models/xref_tables/Item_Author.py
Normal 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)
|
15
worblehat/models/xref_tables/Item_Category.py
Normal file
15
worblehat/models/xref_tables/Item_Category.py
Normal 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)
|
2
worblehat/models/xref_tables/__init__.py
Normal file
2
worblehat/models/xref_tables/__init__.py
Normal file
@ -0,0 +1,2 @@
|
||||
from .Item_Author import Item_Author
|
||||
from .Item_Category import Item_Category
|
42
worblehat/seed_test_data.py
Normal file
42
worblehat/seed_test_data.py
Normal 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.")
|
Loading…
Reference in New Issue
Block a user