Misc changes:
- Rename BookcaseLocation -> BookcaseShelf - Remove `name` from BookcaseShelfs, and use columns and rows to index them instead - Add `amount` to BookcaseItem, and make isbn unique - Remove ability to use scanner tool without database - Add a lot of real bookshelfs to seeding data
This commit is contained in:
parent
98eed07417
commit
4e868c3324
|
@ -32,7 +32,7 @@ def configure_admin(app):
|
||||||
admin.add_view(ModelView(Author, db.session))
|
admin.add_view(ModelView(Author, db.session))
|
||||||
admin.add_view(ModelView(Bookcase, db.session))
|
admin.add_view(ModelView(Bookcase, db.session))
|
||||||
admin.add_view(ModelView(BookcaseItem, db.session))
|
admin.add_view(ModelView(BookcaseItem, db.session))
|
||||||
admin.add_view(ModelView(BookcaseLocation, db.session))
|
admin.add_view(ModelView(BookcaseShelf, db.session))
|
||||||
admin.add_view(ModelView(Category, db.session))
|
admin.add_view(ModelView(Category, db.session))
|
||||||
admin.add_view(ModelView(Language, db.session))
|
admin.add_view(ModelView(Language, db.session))
|
||||||
admin.add_view(ModelView(MediaType, db.session))
|
admin.add_view(ModelView(MediaType, db.session))
|
|
@ -14,12 +14,12 @@ from .mixins import (
|
||||||
UniqueNameMixin,
|
UniqueNameMixin,
|
||||||
)
|
)
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .BookcaseLocation import BookcaseLocation
|
from .BookcaseShelf import BookcaseShelf
|
||||||
|
|
||||||
class Bookcase(Base, UidMixin, UniqueNameMixin):
|
class Bookcase(Base, UidMixin, UniqueNameMixin):
|
||||||
description: Mapped[str | None] = mapped_column(Text)
|
description: Mapped[str | None] = mapped_column(Text)
|
||||||
|
|
||||||
locations: Mapped[list[BookcaseLocation]] = relationship(back_populates='bookcase')
|
shelfs: Mapped[list[BookcaseShelf]] = relationship(back_populates='bookcase')
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
|
|
@ -3,6 +3,7 @@ from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from sqlalchemy import (
|
from sqlalchemy import (
|
||||||
Integer,
|
Integer,
|
||||||
|
SmallInteger,
|
||||||
String,
|
String,
|
||||||
ForeignKey,
|
ForeignKey,
|
||||||
)
|
)
|
||||||
|
@ -23,23 +24,22 @@ from .xref_tables import (
|
||||||
)
|
)
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .Author import Author
|
from .Author import Author
|
||||||
from .BookcaseLocation import BookcaseLocation
|
from .BookcaseShelf import BookcaseShelf
|
||||||
from .Category import Category
|
from .Category import Category
|
||||||
from .Language import Language
|
from .Language import Language
|
||||||
from .MediaType import MediaType
|
from .MediaType import MediaType
|
||||||
|
|
||||||
class BookcaseItem(Base, UidMixin, UniqueNameMixin):
|
class BookcaseItem(Base, UidMixin, UniqueNameMixin):
|
||||||
# NOTE: This is kept non-unique in case we have
|
isbn: Mapped[int] = mapped_column(String, unique=True, index=True)
|
||||||
# multiple copies of the same book.
|
|
||||||
isbn: Mapped[int | None] = mapped_column(String, index=True)
|
|
||||||
owner: Mapped[str] = mapped_column(String, default='PVV')
|
owner: Mapped[str] = mapped_column(String, default='PVV')
|
||||||
|
amount: Mapped[int] = mapped_column(SmallInteger, default=1)
|
||||||
|
|
||||||
fk_media_type_uid: Mapped[int] = mapped_column(Integer, ForeignKey('MediaType.uid'))
|
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_bookcase_shelf_uid: Mapped[int | None] = mapped_column(Integer, ForeignKey('BookcaseShelf.uid'))
|
||||||
fk_language_uid: Mapped[int] = mapped_column(Integer, ForeignKey('Language.uid'))
|
fk_language_uid: Mapped[int] = mapped_column(Integer, ForeignKey('Language.uid'))
|
||||||
|
|
||||||
media_type: Mapped[MediaType] = relationship(back_populates='items')
|
media_type: Mapped[MediaType] = relationship(back_populates='items')
|
||||||
location: Mapped[BookcaseLocation] = relationship(back_populates='items')
|
shelf: Mapped[BookcaseShelf] = relationship(back_populates='items')
|
||||||
language: Mapped[Language] = relationship()
|
language: Mapped[Language] = relationship()
|
||||||
|
|
||||||
categories: Mapped[set[Category]] = relationship(
|
categories: Mapped[set[Category]] = relationship(
|
||||||
|
|
|
@ -4,7 +4,9 @@ from typing import TYPE_CHECKING
|
||||||
from sqlalchemy import (
|
from sqlalchemy import (
|
||||||
Integer,
|
Integer,
|
||||||
ForeignKey,
|
ForeignKey,
|
||||||
|
SmallInteger,
|
||||||
Text,
|
Text,
|
||||||
|
UniqueConstraint,
|
||||||
)
|
)
|
||||||
from sqlalchemy.orm import (
|
from sqlalchemy.orm import (
|
||||||
Mapped,
|
Mapped,
|
||||||
|
@ -13,28 +15,39 @@ from sqlalchemy.orm import (
|
||||||
)
|
)
|
||||||
|
|
||||||
from .Base import Base
|
from .Base import Base
|
||||||
from .mixins import (
|
from .mixins import UidMixin
|
||||||
UidMixin,
|
|
||||||
UniqueNameMixin,
|
|
||||||
)
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .Bookcase import Bookcase
|
from .Bookcase import Bookcase
|
||||||
from .BookcaseItem import BookcaseItem
|
from .BookcaseItem import BookcaseItem
|
||||||
|
|
||||||
class BookcaseLocation(Base, UidMixin, UniqueNameMixin):
|
# NOTE: Booshelfs are 0 indexed for both rows and columns,
|
||||||
|
# where cell 0-0 is placed in the lower right corner.
|
||||||
|
|
||||||
|
class BookcaseShelf(Base, UidMixin):
|
||||||
|
__table_args__ = (
|
||||||
|
UniqueConstraint(
|
||||||
|
'column',
|
||||||
|
'fk_bookcase_uid',
|
||||||
|
'row',
|
||||||
|
),
|
||||||
|
)
|
||||||
description: Mapped[str | None] = mapped_column(Text)
|
description: Mapped[str | None] = mapped_column(Text)
|
||||||
|
row: Mapped[int] = mapped_column(SmallInteger)
|
||||||
|
column: Mapped[int] = mapped_column(SmallInteger)
|
||||||
|
|
||||||
fk_bookcase_uid: Mapped[int] = mapped_column(Integer, ForeignKey('Bookcase.uid'))
|
fk_bookcase_uid: Mapped[int] = mapped_column(Integer, ForeignKey('Bookcase.uid'))
|
||||||
|
|
||||||
bookcase: Mapped[Bookcase] = relationship(back_populates='locations')
|
bookcase: Mapped[Bookcase] = relationship(back_populates='shelfs')
|
||||||
items: Mapped[set[BookcaseItem]] = relationship(back_populates='location')
|
items: Mapped[set[BookcaseItem]] = relationship(back_populates='shelf')
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
name: str,
|
row: int,
|
||||||
|
column: int,
|
||||||
bookcase: Bookcase,
|
bookcase: Bookcase,
|
||||||
description: str | None = None,
|
description: str | None = None,
|
||||||
):
|
):
|
||||||
self.name = name
|
self.row = row
|
||||||
|
self.column = column
|
||||||
self.bookcase = bookcase
|
self.bookcase = bookcase
|
||||||
self.description = description
|
self.description = description
|
|
@ -12,7 +12,7 @@ from .Base import Base
|
||||||
from .mixins import UidMixin, UniqueNameMixin
|
from .mixins import UidMixin, UniqueNameMixin
|
||||||
|
|
||||||
class Language(Base, UidMixin, UniqueNameMixin):
|
class Language(Base, UidMixin, UniqueNameMixin):
|
||||||
iso639_1_code: Mapped[str] = mapped_column(String(2))
|
iso639_1_code: Mapped[str] = mapped_column(String(2), unique=True, index=True)
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
|
|
@ -2,7 +2,7 @@ from .Author import Author
|
||||||
from .Base import Base
|
from .Base import Base
|
||||||
from .Bookcase import Bookcase
|
from .Bookcase import Bookcase
|
||||||
from .BookcaseItem import BookcaseItem
|
from .BookcaseItem import BookcaseItem
|
||||||
from .BookcaseLocation import BookcaseLocation
|
from .BookcaseShelf import BookcaseShelf
|
||||||
from .Category import Category
|
from .Category import Category
|
||||||
from .Language import Language
|
from .Language import Language
|
||||||
from .MediaType import MediaType
|
from .MediaType import MediaType
|
|
@ -5,7 +5,7 @@ from flask_sqlalchemy import SQLAlchemy
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
Bookcase,
|
Bookcase,
|
||||||
BookcaseLocation,
|
BookcaseShelf,
|
||||||
Language,
|
Language,
|
||||||
MediaType,
|
MediaType,
|
||||||
)
|
)
|
||||||
|
@ -19,15 +19,100 @@ def seed_data(db: SQLAlchemy):
|
||||||
]
|
]
|
||||||
|
|
||||||
bookcases = [
|
bookcases = [
|
||||||
Bookcase(name='A', description='The first bookcase'),
|
Bookcase(name='Unnamed A', description='White case across dibbler'),
|
||||||
Bookcase(name='B', description='The second bookcase'),
|
Bookcase(name='Unnamed B', description='Math case in the working room'),
|
||||||
|
Bookcase(name='Unnamed C', description='Large case in the working room'),
|
||||||
|
Bookcase(name='Unnamed D', description='White comics case in the hallway'),
|
||||||
|
Bookcase(name='Unnamed E', description='Wooden comics case in the hallway'),
|
||||||
]
|
]
|
||||||
|
|
||||||
locations = [
|
shelfs = [
|
||||||
BookcaseLocation(name='1-1', description='The first location', bookcase=bookcases[0]),
|
BookcaseShelf(row=0, column=0, bookcase=bookcases[0]),
|
||||||
BookcaseLocation(name='1-2', description='The second location', bookcase=bookcases[0]),
|
BookcaseShelf(row=1, column=0, bookcase=bookcases[0]),
|
||||||
BookcaseLocation(name='2-1', description='The third location', bookcase=bookcases[1]),
|
BookcaseShelf(row=2, column=0, bookcase=bookcases[0]),
|
||||||
BookcaseLocation(name='2-2', description='The fourth location', bookcase=bookcases[1]),
|
BookcaseShelf(row=3, column=0, bookcase=bookcases[0], description="Hacking"),
|
||||||
|
BookcaseShelf(row=4, column=0, bookcase=bookcases[0], description="Hacking"),
|
||||||
|
|
||||||
|
BookcaseShelf(row=0, column=1, bookcase=bookcases[0]),
|
||||||
|
BookcaseShelf(row=1, column=1, bookcase=bookcases[0]),
|
||||||
|
BookcaseShelf(row=2, column=1, bookcase=bookcases[0], description="DOS"),
|
||||||
|
BookcaseShelf(row=3, column=1, bookcase=bookcases[0], description="Food for thought"),
|
||||||
|
BookcaseShelf(row=4, column=1, bookcase=bookcases[0], description="CPP"),
|
||||||
|
|
||||||
|
BookcaseShelf(row=0, column=2, bookcase=bookcases[0]),
|
||||||
|
BookcaseShelf(row=1, column=2, bookcase=bookcases[0]),
|
||||||
|
BookcaseShelf(row=2, column=2, bookcase=bookcases[0], description="E = mc2"),
|
||||||
|
BookcaseShelf(row=3, column=2, bookcase=bookcases[0], description="OBJECTION!"),
|
||||||
|
BookcaseShelf(row=4, column=2, bookcase=bookcases[0], description="/home"),
|
||||||
|
|
||||||
|
BookcaseShelf(row=0, column=3, bookcase=bookcases[0]),
|
||||||
|
BookcaseShelf(row=1, column=3, bookcase=bookcases[0], description="Big indonisian island"),
|
||||||
|
BookcaseShelf(row=2, column=3, bookcase=bookcases[0]),
|
||||||
|
BookcaseShelf(row=3, column=3, bookcase=bookcases[0], description="Div science"),
|
||||||
|
BookcaseShelf(row=4, column=3, bookcase=bookcases[0], description="/home"),
|
||||||
|
|
||||||
|
BookcaseShelf(row=0, column=4, bookcase=bookcases[0]),
|
||||||
|
BookcaseShelf(row=1, column=4, bookcase=bookcases[0]),
|
||||||
|
BookcaseShelf(row=2, column=4, bookcase=bookcases[0], description="(not) computer vision"),
|
||||||
|
BookcaseShelf(row=3, column=4, bookcase=bookcases[0], description="Low voltage"),
|
||||||
|
BookcaseShelf(row=4, column=4, bookcase=bookcases[0], description="/home"),
|
||||||
|
|
||||||
|
BookcaseShelf(row=0, column=5, bookcase=bookcases[0]),
|
||||||
|
BookcaseShelf(row=1, column=5, bookcase=bookcases[0]),
|
||||||
|
BookcaseShelf(row=2, column=5, bookcase=bookcases[0], description="/home"),
|
||||||
|
BookcaseShelf(row=3, column=5, bookcase=bookcases[0], description="/home"),
|
||||||
|
|
||||||
|
BookcaseShelf(row=0, column=0, bookcase=bookcases[1]),
|
||||||
|
BookcaseShelf(row=1, column=0, bookcase=bookcases[1], description="Kjellerarealer og komodovaraner"),
|
||||||
|
BookcaseShelf(row=2, column=0, bookcase=bookcases[1]),
|
||||||
|
BookcaseShelf(row=3, column=0, bookcase=bookcases[1], description="Quick mafs"),
|
||||||
|
BookcaseShelf(row=4, column=0, bookcase=bookcases[1]),
|
||||||
|
|
||||||
|
BookcaseShelf(row=0, column=0, bookcase=bookcases[2]),
|
||||||
|
BookcaseShelf(row=1, column=0, bookcase=bookcases[2]),
|
||||||
|
BookcaseShelf(row=2, column=0, bookcase=bookcases[2], description="AI"),
|
||||||
|
BookcaseShelf(row=3, column=0, bookcase=bookcases[2], description="X86"),
|
||||||
|
BookcaseShelf(row=4, column=0, bookcase=bookcases[2], description="Humanoira"),
|
||||||
|
BookcaseShelf(row=5, column=0, bookcase=bookcases[2], description="Hvem monterte rørforsterker?"),
|
||||||
|
|
||||||
|
BookcaseShelf(row=0, column=1, bookcase=bookcases[2]),
|
||||||
|
BookcaseShelf(row=1, column=1, bookcase=bookcases[2], description="Div data"),
|
||||||
|
BookcaseShelf(row=2, column=1, bookcase=bookcases[2], description="Chemistry"),
|
||||||
|
BookcaseShelf(row=3, column=1, bookcase=bookcases[2], description="Soviet Phys. Techn. Phys"),
|
||||||
|
BookcaseShelf(row=4, column=1, bookcase=bookcases[2], description="Digitalteknikk"),
|
||||||
|
BookcaseShelf(row=5, column=1, bookcase=bookcases[2], description="Material"),
|
||||||
|
|
||||||
|
BookcaseShelf(row=0, column=2, bookcase=bookcases[2]),
|
||||||
|
BookcaseShelf(row=1, column=2, bookcase=bookcases[2], description="Assembler / APL"),
|
||||||
|
BookcaseShelf(row=2, column=2, bookcase=bookcases[2], description="Internet"),
|
||||||
|
BookcaseShelf(row=3, column=2, bookcase=bookcases[2], description="Algorithms"),
|
||||||
|
BookcaseShelf(row=4, column=2, bookcase=bookcases[2], description="Soviet Physics Jetp"),
|
||||||
|
BookcaseShelf(row=5, column=2, bookcase=bookcases[2], description="Død og pine"),
|
||||||
|
|
||||||
|
BookcaseShelf(row=0, column=3, bookcase=bookcases[2]),
|
||||||
|
BookcaseShelf(row=1, column=3, bookcase=bookcases[2], description="Web"),
|
||||||
|
BookcaseShelf(row=2, column=3, bookcase=bookcases[2], description="Div languages"),
|
||||||
|
BookcaseShelf(row=3, column=3, bookcase=bookcases[2], description="Python"),
|
||||||
|
BookcaseShelf(row=4, column=3, bookcase=bookcases[2], description="D&D Minis"),
|
||||||
|
BookcaseShelf(row=5, column=3, bookcase=bookcases[2], description="Perl"),
|
||||||
|
|
||||||
|
BookcaseShelf(row=0, column=4, bookcase=bookcases[2]),
|
||||||
|
BookcaseShelf(row=1, column=4, bookcase=bookcases[2], description="Knuth on programming"),
|
||||||
|
BookcaseShelf(row=2, column=4, bookcase=bookcases[2], description="Div languages"),
|
||||||
|
BookcaseShelf(row=3, column=4, bookcase=bookcases[2], description="Typesetting"),
|
||||||
|
BookcaseShelf(row=4, column=4, bookcase=bookcases[2]),
|
||||||
|
|
||||||
|
BookcaseShelf(row=0, column=0, bookcase=bookcases[3]),
|
||||||
|
BookcaseShelf(row=0, column=1, bookcase=bookcases[3]),
|
||||||
|
BookcaseShelf(row=0, column=2, bookcase=bookcases[3]),
|
||||||
|
BookcaseShelf(row=0, column=3, bookcase=bookcases[3]),
|
||||||
|
BookcaseShelf(row=0, column=4, bookcase=bookcases[3]),
|
||||||
|
|
||||||
|
BookcaseShelf(row=0, column=0, bookcase=bookcases[4]),
|
||||||
|
BookcaseShelf(row=0, column=1, bookcase=bookcases[4]),
|
||||||
|
BookcaseShelf(row=0, column=2, bookcase=bookcases[4]),
|
||||||
|
BookcaseShelf(row=0, column=3, bookcase=bookcases[4]),
|
||||||
|
BookcaseShelf(row=0, column=4, bookcase=bookcases[4], description="Religion"),
|
||||||
]
|
]
|
||||||
|
|
||||||
with open(Path(__file__).parent.parent / 'data' / 'iso639_1.csv') as f:
|
with open(Path(__file__).parent.parent / 'data' / 'iso639_1.csv') as f:
|
||||||
|
@ -36,7 +121,7 @@ def seed_data(db: SQLAlchemy):
|
||||||
|
|
||||||
db.session.add_all(media_types)
|
db.session.add_all(media_types)
|
||||||
db.session.add_all(bookcases)
|
db.session.add_all(bookcases)
|
||||||
db.session.add_all(locations)
|
db.session.add_all(shelfs)
|
||||||
db.session.add_all(languages)
|
db.session.add_all(languages)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
print("Added test media types, bookcases and locations.")
|
print("Added test media types, bookcases and shelfs.")
|
|
@ -0,0 +1,45 @@
|
||||||
|
import isbnlib
|
||||||
|
|
||||||
|
from sqlalchemy import select
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from ..models import (
|
||||||
|
Author,
|
||||||
|
BookcaseItem,
|
||||||
|
Language,
|
||||||
|
)
|
||||||
|
|
||||||
|
def is_valid_pvv_isbn(isbn: str) -> bool:
|
||||||
|
try:
|
||||||
|
int(isbn)
|
||||||
|
except ValueError:
|
||||||
|
return False
|
||||||
|
return len(isbn) == 8
|
||||||
|
|
||||||
|
def is_valid_isbn(isbn: str) -> bool:
|
||||||
|
return any([
|
||||||
|
isbnlib.is_isbn10(isbn),
|
||||||
|
isbnlib.is_isbn13(isbn),
|
||||||
|
])
|
||||||
|
|
||||||
|
def create_bookcase_item_from_isbn(isbn: str, sql_session: Session) -> BookcaseItem | None:
|
||||||
|
metadata = isbnlib.meta(isbn, 'openl')
|
||||||
|
if len(metadata.keys()) == 0:
|
||||||
|
return None
|
||||||
|
|
||||||
|
bookcase_item = BookcaseItem(
|
||||||
|
name = metadata.get('Title'),
|
||||||
|
isbn = int(isbn.replace('-', '')),
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(authors := metadata.get('Authors')) > 0:
|
||||||
|
for author in authors:
|
||||||
|
bookcase_item.authors.add(Author(author))
|
||||||
|
|
||||||
|
if (language := metadata.get('Language')):
|
||||||
|
bookcase_item.language = sql_session.scalars(
|
||||||
|
select(Language)
|
||||||
|
.where(Language.iso639_1_code == language)
|
||||||
|
).one()
|
||||||
|
|
||||||
|
return bookcase_item
|
|
@ -1,31 +0,0 @@
|
||||||
import isbnlib
|
|
||||||
|
|
||||||
from ..models import (
|
|
||||||
Author,
|
|
||||||
BookcaseItem,
|
|
||||||
)
|
|
||||||
|
|
||||||
def is_valid_isbn(isbn: str) -> bool:
|
|
||||||
return any([
|
|
||||||
isbnlib.is_isbn10(isbn),
|
|
||||||
isbnlib.is_isbn13(isbn),
|
|
||||||
])
|
|
||||||
|
|
||||||
def create_bookcase_item_from_isbn(isbn: str) -> BookcaseItem | None:
|
|
||||||
metadata = isbnlib.meta(isbn)
|
|
||||||
if len(metadata.keys()) == 0:
|
|
||||||
return None
|
|
||||||
|
|
||||||
bookcase_item = BookcaseItem(
|
|
||||||
name = metadata.get('Title'),
|
|
||||||
isbn = int(isbn.replace('-', '')),
|
|
||||||
)
|
|
||||||
|
|
||||||
if len(authors := metadata.get('Authors')) > 0:
|
|
||||||
for author in authors:
|
|
||||||
bookcase_item.authors.add(Author(author))
|
|
||||||
|
|
||||||
if (language := metadata.get('language')):
|
|
||||||
bookcase_item.language = language
|
|
||||||
|
|
||||||
return bookcase_item
|
|
|
@ -1,5 +1,6 @@
|
||||||
from cmd import Cmd
|
from cmd import Cmd
|
||||||
from typing import Any, Set
|
from typing import Any
|
||||||
|
from textwrap import dedent
|
||||||
|
|
||||||
from sqlalchemy import (
|
from sqlalchemy import (
|
||||||
create_engine,
|
create_engine,
|
||||||
|
@ -9,7 +10,7 @@ from sqlalchemy.orm import (
|
||||||
Session,
|
Session,
|
||||||
)
|
)
|
||||||
|
|
||||||
from worblehat.services.item import (
|
from worblehat.services.bookcase_item import (
|
||||||
create_bookcase_item_from_isbn,
|
create_bookcase_item_from_isbn,
|
||||||
is_valid_isbn,
|
is_valid_isbn,
|
||||||
)
|
)
|
||||||
|
@ -43,13 +44,11 @@ class _InteractiveItemSelector(Cmd):
|
||||||
self,
|
self,
|
||||||
cls: type,
|
cls: type,
|
||||||
sql_session: Session,
|
sql_session: Session,
|
||||||
items: Set[Any],
|
|
||||||
default: Any | None = None,
|
default: Any | None = None,
|
||||||
):
|
):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.cls = cls
|
self.cls = cls
|
||||||
self.sql_session = sql_session
|
self.sql_session = sql_session
|
||||||
self.items = items
|
|
||||||
self.default_item = default
|
self.default_item = default
|
||||||
if default is not None:
|
if default is not None:
|
||||||
self.prompt = f'Select {cls.__name__} [{default.name}]> '
|
self.prompt = f'Select {cls.__name__} [{default.name}]> '
|
||||||
|
@ -61,13 +60,10 @@ class _InteractiveItemSelector(Cmd):
|
||||||
self.result = self.default_item
|
self.result = self.default_item
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if self.sql_session is not None:
|
result = self.sql_session.scalars(
|
||||||
result = self.sql_session.scalars(
|
select(self.cls)
|
||||||
select(self.cls)
|
.where(self.cls.name == arg),
|
||||||
.where(self.cls.name == arg),
|
).all()
|
||||||
).all()
|
|
||||||
else:
|
|
||||||
result = [x for x in self.items if x.name == arg]
|
|
||||||
|
|
||||||
if len(result) != 1:
|
if len(result) != 1:
|
||||||
print(f'No such {self.cls.__name__} found: {arg}')
|
print(f'No such {self.cls.__name__} found: {arg}')
|
||||||
|
@ -77,13 +73,10 @@ class _InteractiveItemSelector(Cmd):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def completenames(self, text: str, *_) -> list[str]:
|
def completenames(self, text: str, *_) -> list[str]:
|
||||||
if self.sql_session is not None:
|
return self.sql_session.scalars(
|
||||||
return self.sql_session.scalars(
|
select(self.cls.name)
|
||||||
select(self.cls.name)
|
.where(self.cls.name.like(f'{text}%')),
|
||||||
.where(self.cls.name.like(f'{text}%'))
|
).all()
|
||||||
).all()
|
|
||||||
else:
|
|
||||||
return [x for x in self.items if x.name.startswith(text)]
|
|
||||||
|
|
||||||
|
|
||||||
class BookScanTool(Cmd):
|
class BookScanTool(Cmd):
|
||||||
|
@ -94,13 +87,6 @@ Start by entering a command, or entering an ISBN of a new item.
|
||||||
Type "help" to see list of commands
|
Type "help" to see list of commands
|
||||||
"""
|
"""
|
||||||
|
|
||||||
sql_session = None
|
|
||||||
|
|
||||||
bookcases: set[Bookcase] = set()
|
|
||||||
bookcase_locations: set[BookcaseLocation] = set()
|
|
||||||
bookcase_items: set[BookcaseItem] = set()
|
|
||||||
media_types: set[MediaType] = set()
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
|
@ -108,32 +94,39 @@ Type "help" to see list of commands
|
||||||
engine = create_engine(Config.SQLALCHEMY_DATABASE_URI)
|
engine = create_engine(Config.SQLALCHEMY_DATABASE_URI)
|
||||||
self.sql_session = Session(engine)
|
self.sql_session = Session(engine)
|
||||||
except Exception:
|
except Exception:
|
||||||
print('Warning: could not connect to database. Saving to database has been disabled.')
|
print('Error: could not connect to database.')
|
||||||
return
|
exit(1)
|
||||||
|
|
||||||
try:
|
print(f"Connected to database at '{Config.SQLALCHEMY_DATABASE_URI}'")
|
||||||
self.bookcases = set(self.sql_session.scalars(select(Bookcase)).all())
|
|
||||||
self.bookcase_locations = set(self.sql_session.scalars(select(BookcaseLocation)).all())
|
|
||||||
self.bookcase_items = set(self.sql_session.scalars(select(BookcaseItem)).all())
|
|
||||||
self.media_types = set(self.sql_session.scalars(select(MediaType)).all())
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
print('Warning: could not prefill data from sql database. Saving to database has been disabled.')
|
|
||||||
self.sql_session.close()
|
|
||||||
self.sql_session = None
|
|
||||||
return
|
|
||||||
|
|
||||||
print('Note: Successfully connected to database')
|
|
||||||
|
|
||||||
|
|
||||||
def do_list_bookcases(self, arg: str):
|
def do_list_bookcases(self, _: str):
|
||||||
self.columnize([repr(bc) for bc in self.bookcases])
|
"""Usage: list_bookcases"""
|
||||||
|
bookcase_shelfs = self.sql_session.scalars(
|
||||||
|
select(BookcaseShelf)
|
||||||
|
.join(Bookcase)
|
||||||
|
.order_by(
|
||||||
|
Bookcase.name,
|
||||||
|
BookcaseShelf.column,
|
||||||
|
BookcaseShelf.row,
|
||||||
|
)
|
||||||
|
).all()
|
||||||
|
|
||||||
|
bookcase_uid = None
|
||||||
|
for shelf in bookcase_shelfs:
|
||||||
|
if shelf.bookcase.uid != bookcase_uid:
|
||||||
|
print(shelf.bookcase.name)
|
||||||
|
bookcase_uid = shelf.bookcase.uid
|
||||||
|
|
||||||
|
name = f"r{shelf.row}-c{shelf.column}"
|
||||||
|
if shelf.description is not None:
|
||||||
|
name += f" [{shelf.description}]"
|
||||||
|
|
||||||
|
print(f' {name} - {sum(i.amount for i in shelf.items)} items')
|
||||||
|
|
||||||
|
|
||||||
def do_add_bookcase(self, arg: str):
|
def do_add_bookcase(self, arg: str):
|
||||||
"""
|
"""Usage: add_bookcase <name> [description]"""
|
||||||
Usage: add_bookcase <name> [description]
|
|
||||||
"""
|
|
||||||
arg = arg.split(' ')
|
arg = arg.split(' ')
|
||||||
if len(arg) < 1:
|
if len(arg) < 1:
|
||||||
print('Usage: add_bookcase <name> [description]')
|
print('Usage: add_bookcase <name> [description]')
|
||||||
|
@ -142,82 +135,105 @@ Type "help" to see list of commands
|
||||||
name = arg[0]
|
name = arg[0]
|
||||||
description = ' '.join(arg[1:])
|
description = ' '.join(arg[1:])
|
||||||
|
|
||||||
if any([bc.name == name for bc in self.bookcases]):
|
if self.sql_session.scalars(
|
||||||
|
select(Bookcase)
|
||||||
|
.where(Bookcase.name == name)
|
||||||
|
).one_or_none() is not None:
|
||||||
print(f'Error: a bookcase with name {name} already exists')
|
print(f'Error: a bookcase with name {name} already exists')
|
||||||
return
|
return
|
||||||
|
|
||||||
bookcase = Bookcase(name, description)
|
bookcase = Bookcase(name, description)
|
||||||
self.bookcases.add(bookcase)
|
self.sql_session.add(bookcase)
|
||||||
if self.sql_session is not None:
|
|
||||||
self.sql_session.add(bookcase)
|
|
||||||
|
|
||||||
|
def do_add_bookcase_shelf(self, arg: str):
|
||||||
def do_list_bookcase_locations(self, arg: str):
|
"""Usage: add_bookcase_shelf <bookcase_name> <row>-<column> [description]"""
|
||||||
self.columnize([repr(bl) for bl in self.bookcase_locations])
|
|
||||||
|
|
||||||
|
|
||||||
def do_add_bookcase_location(self, arg: str):
|
|
||||||
"""
|
|
||||||
Usage: add_bookcase_location <bookcase_name> <name> [description]
|
|
||||||
"""
|
|
||||||
arg = arg.split(' ')
|
arg = arg.split(' ')
|
||||||
if len(arg) < 2:
|
if len(arg) < 2:
|
||||||
print('Usage: add_bookcase_location <bookcase_name> <name> [description]')
|
print('Usage: add_bookcase_shelf <bookcase_name> <row>-<column> [description]')
|
||||||
return
|
return
|
||||||
|
|
||||||
bookcase_name = arg[0]
|
bookcase_name = arg[0]
|
||||||
name = arg[1]
|
row, column = [int(x) for x in arg[1].split('-')]
|
||||||
description = ' '.join(arg[2:])
|
description = ' '.join(arg[2:])
|
||||||
|
|
||||||
bookcases_with_name = [bc for bc in self.bookcases if bc.name == bookcase_name]
|
if (bookcase := self.sql_session.scalars(
|
||||||
if not len(bookcases_with_name) == 1:
|
select(Bookcase)
|
||||||
|
.where(Bookcase.name == bookcase_name)
|
||||||
|
).one_or_none()) is None:
|
||||||
print(f'Error: Could not find bookcase with name {bookcase_name}')
|
print(f'Error: Could not find bookcase with name {bookcase_name}')
|
||||||
return
|
return
|
||||||
|
|
||||||
if any([bc.name == name for bc in self.bookcase_locations]):
|
if self.sql_session.scalars(
|
||||||
print(f'Error: a bookcase with name {name} already exists')
|
select(BookcaseShelf)
|
||||||
|
.where(
|
||||||
|
BookcaseShelf.bookcase == bookcase,
|
||||||
|
BookcaseShelf.row == row,
|
||||||
|
BookcaseShelf.column == column,
|
||||||
|
)
|
||||||
|
).one_or_none() is not None:
|
||||||
|
print(f'Error: a bookshelf in bookcase {bookcase.name} with position {row}-{column} already exists')
|
||||||
return
|
return
|
||||||
|
|
||||||
location = BookcaseLocation(
|
shelf = BookcaseShelf(
|
||||||
name,
|
row,
|
||||||
bookcases_with_name[0],
|
column,
|
||||||
|
bookcase,
|
||||||
description,
|
description,
|
||||||
)
|
)
|
||||||
self.bookcase_locations.add(location)
|
self.sql_session.add(shelf)
|
||||||
if self.sql_session is not None:
|
|
||||||
self.sql_session.add(location)
|
|
||||||
|
|
||||||
|
|
||||||
def do_list_bookcase_items(self, arg: str):
|
def do_list_bookcase_items(self, _: str):
|
||||||
|
"""Usage: list_bookcase_items"""
|
||||||
self.columnize([repr(bi) for bi in self.bookcase_items])
|
self.columnize([repr(bi) for bi in self.bookcase_items])
|
||||||
|
|
||||||
|
|
||||||
def default(self, arg: str):
|
def default(self, isbn: str):
|
||||||
if not is_valid_isbn(arg):
|
isbn = isbn.strip()
|
||||||
print(f'"{arg}" is not a valid isbn')
|
if not is_valid_isbn(isbn):
|
||||||
|
print(f'"{isbn}" is not a valid isbn')
|
||||||
return
|
return
|
||||||
|
|
||||||
bookcase_item = create_bookcase_item_from_isbn(arg)
|
if (existing_item := self.sql_session.scalars(
|
||||||
if bookcase_item == None:
|
select(BookcaseItem)
|
||||||
print(f'Could not find data about item with isbn {arg} online.')
|
.where(BookcaseItem.isbn == isbn)
|
||||||
print(f'If you think this is not due to a bug, please add the book to openlibrary.org before continuing.')
|
).one_or_none()) is not None:
|
||||||
|
print('Found existing BookcaseItem:', existing_item)
|
||||||
|
print(f'There are currently {existing_item.amount} of these in the system.')
|
||||||
|
if _prompt_yes_no('Would you like to add another?', default=True):
|
||||||
|
existing_item.amount += 1
|
||||||
|
return
|
||||||
|
|
||||||
print('Please select the location where the item is placed:')
|
bookcase_item = create_bookcase_item_from_isbn(isbn, self.sql_session)
|
||||||
bookcase_location_selector = _InteractiveItemSelector(
|
if bookcase_item is None:
|
||||||
cls = BookcaseLocation,
|
print(f'Could not find data about item with isbn {isbn} online.')
|
||||||
|
print(f'If you think this is not due to a bug, please add the book to openlibrary.org before continuing.')
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
print(dedent(f"""
|
||||||
|
Found item:
|
||||||
|
title: {bookcase_item.name}
|
||||||
|
authors: {', '.join(a.name for a in bookcase_item.authors)}
|
||||||
|
language: {bookcase_item.language}
|
||||||
|
"""))
|
||||||
|
|
||||||
|
print('Please select the shelf where the item is placed:')
|
||||||
|
bookcase_shelf_selector = _InteractiveItemSelector(
|
||||||
|
cls = BookcaseShelf,
|
||||||
sql_session = self.sql_session,
|
sql_session = self.sql_session,
|
||||||
items = self.bookcase_locations,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
bookcase_location_selector.cmdloop()
|
bookcase_shelf_selector.cmdloop()
|
||||||
bookcase_item.location = bookcase_location_selector.result
|
bookcase_item.shelf = bookcase_shelf_selector.result
|
||||||
|
|
||||||
print('Please select the items media type:')
|
print('Please select the items media type:')
|
||||||
media_type_selector = _InteractiveItemSelector(
|
media_type_selector = _InteractiveItemSelector(
|
||||||
cls = MediaType,
|
cls = MediaType,
|
||||||
sql_session = self.sql_session,
|
sql_session = self.sql_session,
|
||||||
items = self.media_types,
|
default = self.sql_session.scalars(
|
||||||
default = next(mt for mt in self.media_types if mt.name.lower() == 'book'),
|
select(MediaType)
|
||||||
|
.where(MediaType.name.ilike("book")),
|
||||||
|
).one(),
|
||||||
)
|
)
|
||||||
|
|
||||||
media_type_selector.cmdloop()
|
media_type_selector.cmdloop()
|
||||||
|
@ -227,14 +243,16 @@ Type "help" to see list of commands
|
||||||
if username != '':
|
if username != '':
|
||||||
bookcase_item.owner = username
|
bookcase_item.owner = username
|
||||||
|
|
||||||
self.bookcase_items.add(bookcase_item)
|
self.sql_session.add(bookcase_item)
|
||||||
if self.sql_session is not None:
|
|
||||||
self.sql_session.add(bookcase_item)
|
|
||||||
|
|
||||||
|
|
||||||
def do_exit(self, arg: str):
|
def do_exit(self, _: str):
|
||||||
# TODO: take internally stored data, and save it, either in csv, json or database
|
"""Usage: exit"""
|
||||||
raise NotImplementedError()
|
if _prompt_yes_no('Would you like to save your changes?'):
|
||||||
|
self.sql_session.commit()
|
||||||
|
else:
|
||||||
|
self.sql_session.rollback()
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|
Loading…
Reference in New Issue