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:
Oystein Kristoffer Tveit 2023-05-05 21:15:00 +02:00
parent 98eed07417
commit 4e868c3324
Signed by: oysteikt
GPG Key ID: 9F2F7D8250F35146
10 changed files with 283 additions and 153 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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)
if self.sql_session is not None:
self.sql_session.add(bookcase) 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)
if self.sql_session is not None:
self.sql_session.add(bookcase_item) 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():