Misc improvements:

- Extract the bookshelf selector into its own file, since it was a
  little bit complicated with completion and everything.
- Print out bookcase item information on every prompt in the bookcase
  item specific subclis
- Add funcs to EditBookcaseCli
- Add shelf editing to EditBookcaseCli
- Ensure shorthand column-row pairs are always written in that order
This commit is contained in:
Oystein Kristoffer Tveit 2023-05-06 03:51:35 +02:00
parent cd666377f8
commit 9b96875346
Signed by: oysteikt
GPG Key ID: 9F2F7D8250F35146
5 changed files with 158 additions and 80 deletions

View File

@ -8,7 +8,6 @@ from sqlalchemy import (
from sqlalchemy.orm import ( from sqlalchemy.orm import (
Session, Session,
) )
from worblehat.cli.subclis.bookcase_item import BookcaseItemCli
from worblehat.services.bookcase_item import ( from worblehat.services.bookcase_item import (
create_bookcase_item_from_isbn, create_bookcase_item_from_isbn,
@ -20,6 +19,9 @@ from .prompt_utils import *
from worblehat.config import Config from worblehat.config import Config
from worblehat.models import * from worblehat.models import *
from .subclis.bookcase_item import BookcaseItemCli
from .subclis.bookcase_shelf_selector import select_bookcase_shelf
# TODO: Category seems to have been forgotten. Maybe relevant interactivity should be added? # TODO: Category seems to have been forgotten. Maybe relevant interactivity should be added?
# However, is there anyone who are going to search by category rather than just look in # However, is there anyone who are going to search by category rather than just look in
# the shelves? # the shelves?
@ -66,14 +68,10 @@ class WorblehatCli(NumberedCmd):
bookcase_uid = None bookcase_uid = None
for shelf in bookcase_shelfs: for shelf in bookcase_shelfs:
if shelf.bookcase.uid != bookcase_uid: if shelf.bookcase.uid != bookcase_uid:
print(shelf.bookcase.name) print(shelf.bookcase.short_str())
bookcase_uid = shelf.bookcase.uid bookcase_uid = shelf.bookcase.uid
name = f"r{shelf.row}-c{shelf.column}" print(f' {shelf.short_str()} - {sum(i.amount for i in shelf.items)} items')
if shelf.description is not None:
name += f" [{shelf.description}]"
print(f' {name} - {sum(i.amount for i in shelf.items)} items')
def do_show_bookcase(self, arg: str): def do_show_bookcase(self, arg: str):
@ -85,10 +83,7 @@ class WorblehatCli(NumberedCmd):
bookcase = bookcase_selector.result bookcase = bookcase_selector.result
for shelf in bookcase.shelfs: for shelf in bookcase.shelfs:
name = f"r{shelf.row}-c{shelf.column}" print(shelf.short_str())
if shelf.description is not None:
name += f" [{shelf.description}]"
print(name)
for item in shelf.items: for item in shelf.items:
print(f' {item.name} - {item.amount} copies') print(f' {item.name} - {item.amount} copies')
@ -126,15 +121,6 @@ class WorblehatCli(NumberedCmd):
bookcase_selector.cmdloop() bookcase_selector.cmdloop()
bookcase = bookcase_selector.result bookcase = bookcase_selector.result
while True:
row = input('Row> ')
try:
row = int(row)
except ValueError:
print('Error: row must be a number')
continue
break
while True: while True:
column = input('Column> ') column = input('Column> ')
try: try:
@ -144,15 +130,24 @@ class WorblehatCli(NumberedCmd):
continue continue
break break
while True:
row = input('Row> ')
try:
row = int(row)
except ValueError:
print('Error: row must be a number')
continue
break
if self.sql_session.scalars( if self.sql_session.scalars(
select(BookcaseShelf) select(BookcaseShelf)
.where( .where(
BookcaseShelf.bookcase == bookcase, BookcaseShelf.bookcase == bookcase,
BookcaseShelf.row == row,
BookcaseShelf.column == column, BookcaseShelf.column == column,
BookcaseShelf.row == row,
) )
).one_or_none() is not None: ).one_or_none() is not None:
print(f'Error: a bookshelf in bookcase {bookcase.name} with position {row}-{column} already exists') print(f'Error: a bookshelf in bookcase {bookcase.name} with position c{column}-r{row} already exists')
return return
description = input('Description> ') description = input('Description> ')
@ -172,7 +167,7 @@ class WorblehatCli(NumberedCmd):
def _create_bookcase_item(self, isbn: str): def _create_bookcase_item(self, isbn: str):
bookcase_item = create_bookcase_item_from_isbn(isbn, self.sql_session) bookcase_item = create_bookcase_item_from_isbn(isbn, self.sql_session)
if bookcase_item is None: if bookcase_item is None:
print(f'Could not find data about item with isbn {isbn} online.') 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.') print(f'If you think this is not due to a bug, please add the book to openlibrary.org before continuing.')
return return
else: else:
@ -191,37 +186,7 @@ class WorblehatCli(NumberedCmd):
bookcase_selector.cmdloop() bookcase_selector.cmdloop()
bookcase = bookcase_selector.result bookcase = bookcase_selector.result
def __complete_bookshelf_selection(session: Session, cls: type, arg: str): bookcase_item.shelf = select_bookcase_shelf(bookcase, self.sql_session)
args = arg.split('-')
query = select(cls.row, cls.column).where(cls.bookcase == bookcase)
try:
if arg != '' and len(args) > 0:
query = query.where(cls.row == int(args[0]))
if len(args) > 1:
query = query.where(cls.column == int(args[1]))
except ValueError:
return []
result = session.execute(query).all()
return [f"{r}-{c}" for r,c in result]
print('Please select the shelf where the item is placed:')
bookcase_shelf_selector = InteractiveItemSelector(
cls = BookcaseShelf,
sql_session = self.sql_session,
execute_selection = lambda session, cls, arg: session.scalars(
select(cls)
.where(
cls.bookcase == bookcase,
cls.column == int(arg.split('-')[1]),
cls.row == int(arg.split('-')[0]),
)
).all(),
complete_selection = __complete_bookshelf_selection,
)
bookcase_shelf_selector.cmdloop()
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(
@ -254,14 +219,14 @@ class WorblehatCli(NumberedCmd):
select(BookcaseItem) select(BookcaseItem)
.where(BookcaseItem.isbn == isbn) .where(BookcaseItem.isbn == isbn)
).one_or_none()) is not None: ).one_or_none()) is not None:
print('Found existing BookcaseItem:', existing_item) print(f'\nFound existing item for isbn "{isbn}"')
BookcaseItemCli( BookcaseItemCli(
sql_session = self.sql_session, sql_session = self.sql_session,
bookcase_item = existing_item, bookcase_item = existing_item,
).cmdloop() ).cmdloop()
return return
if prompt_yes_no(f"Could not find item with isbn '{isbn}'.\nWould you like to create it?", default=True): if prompt_yes_no(f"Could not find item with ISBN '{isbn}'.\nWould you like to create it?", default=True):
self._create_bookcase_item(isbn) self._create_bookcase_item(isbn)

View File

@ -9,6 +9,7 @@ from worblehat.cli.prompt_utils import (
prompt_yes_no, prompt_yes_no,
) )
from worblehat.models import ( from worblehat.models import (
Bookcase,
BookcaseItem, BookcaseItem,
Language, Language,
MediaType, MediaType,
@ -18,21 +19,27 @@ from worblehat.services.bookcase_item import (
is_valid_isbn, is_valid_isbn,
) )
from .bookcase_shelf_selector import select_bookcase_shelf
def _selected_bookcase_item_prompt(bookcase_item: BookcaseItem) -> str:
return dedent(f'''
Item: {bookcase_item.name}
ISBN: {bookcase_item.isbn}
Amount: {bookcase_item.amount}
Authors: {', '.join(a.name for a in bookcase_item.authors)}
Bookcase: {bookcase_item.shelf.bookcase.short_str()}
Shelf: {bookcase_item.shelf.short_str()}
''')
class BookcaseItemCli(NumberedCmd): class BookcaseItemCli(NumberedCmd):
def __init__(self, sql_session: Session, bookcase_item: BookcaseItem): def __init__(self, sql_session: Session, bookcase_item: BookcaseItem):
super().__init__() super().__init__()
self.sql_session = sql_session self.sql_session = sql_session
self.bookcase_item = bookcase_item self.bookcase_item = bookcase_item
def do_show(self, _: str): @property
print(dedent(f""" def prompt_header(self) -> str:
Bookcase Item: return _selected_bookcase_item_prompt(self.bookcase_item)
Name: {self.bookcase_item.name}
ISBN: {self.bookcase_item.isbn}
Amount: {self.bookcase_item.amount}
Shelf: {self.bookcase_item.shelf.column}-{self.bookcase_item.shelf.row}
Description: {self.bookcase_item.shelf.description}
"""))
def do_update_data(self, _: str): def do_update_data(self, _: str):
item = create_bookcase_item_from_isbn(self.sql_session, self.bookcase_item.isbn) item = create_bookcase_item_from_isbn(self.sql_session, self.bookcase_item.isbn)
@ -41,35 +48,35 @@ class BookcaseItemCli(NumberedCmd):
self.bookcase_item.authors = item.authors self.bookcase_item.authors = item.authors
self.bookcase_item.language = item.language self.bookcase_item.language = item.language
def do_edit(self, arg: str): def do_edit(self, arg: str):
EditBookcaseCli(self.sql_session, self.bookcase_item, self).cmdloop() EditBookcaseCli(self.sql_session, self.bookcase_item, self).cmdloop()
def do_loan(self, arg: str): def do_loan(self, arg: str):
print('TODO: implement loan') print('TODO: implement loan')
def do_exit(self, _: str):
def do_done(self, _: str):
return True return True
funcs = { funcs = {
1: { 1: {
'f': do_show, 'f': do_loan,
'doc': 'Show bookcase item', 'doc': 'Loan',
}, },
2: { 2: {
'f': do_update_data,
'doc': 'Pull updated data from online databases',
},
3: {
'f': do_edit, 'f': do_edit,
'doc': 'Edit', 'doc': 'Edit',
}, },
4: { 3: {
'f': do_loan, 'f': do_update_data,
'doc': 'Loan bookcase item', 'doc': 'Pull updated data from online databases',
}, },
5: { 4: {
'f': do_exit, 'f': do_done,
'doc': 'Exit', 'doc': 'Done',
}, },
} }
@ -80,6 +87,9 @@ class EditBookcaseCli(NumberedCmd):
self.bookcase_item = bookcase_item self.bookcase_item = bookcase_item
self.parent = parent self.parent = parent
@property
def prompt_header(self) -> str:
return _selected_bookcase_item_prompt(self.bookcase_item)
def do_name(self, _: str): def do_name(self, _: str):
while True: while True:
@ -159,5 +169,51 @@ class EditBookcaseCli(NumberedCmd):
self.bookcase_item.amount = new_amount self.bookcase_item.amount = new_amount
def do_exit(): def do_shelf(self, _: str):
bookcase_selector = InteractiveItemSelector(
Bookcase,
self.sql_session,
)
bookcase_selector.cmdloop()
bookcase = bookcase_selector.result
shelf = select_bookcase_shelf(bookcase, self.sql_session)
self.bookcase_item.shelf = shelf
def do_done(self, _: str):
return True return True
funcs = {
1: {
'f': do_name,
'doc': 'Change name',
},
2: {
'f': do_isbn,
'doc': 'Change ISBN',
},
3: {
'f': do_language,
'doc': 'Change language',
},
4: {
'f': do_media_type,
'doc': 'Change media type',
},
5: {
'f': do_amount,
'doc': 'Change amount',
},
6: {
'f': do_shelf,
'doc': 'Change shelf',
},
7: {
'f': do_done,
'doc': 'Done',
},
}

View File

@ -0,0 +1,45 @@
from sqlalchemy import select
from sqlalchemy.orm import Session
from worblehat.cli.prompt_utils import InteractiveItemSelector
from worblehat.models import (
Bookcase,
BookcaseShelf,
)
def select_bookcase_shelf(
bookcase: Bookcase,
sql_session: Session,
prompt: str = "Please select the shelf where the item is placed (col-row):"
) -> BookcaseShelf:
def __complete_bookshelf_selection(session: Session, cls: type, arg: str):
args = arg.split('-')
query = select(cls.row, cls.column).where(cls.bookcase == bookcase)
try:
if arg != '' and len(args) > 0:
query = query.where(cls.column == int(args[0]))
if len(args) > 1:
query = query.where(cls.row == int(args[1]))
except ValueError:
return []
result = session.execute(query).all()
return [f"{c}-{r}" for r,c in result]
print(prompt)
bookcase_shelf_selector = InteractiveItemSelector(
cls = BookcaseShelf,
sql_session = sql_session,
execute_selection = lambda session, cls, arg: session.scalars(
select(cls)
.where(
cls.bookcase == bookcase,
cls.column == int(arg.split('-')[0]),
cls.row == int(arg.split('-')[1]),
)
).all(),
complete_selection = __complete_bookshelf_selection,
)
bookcase_shelf_selector.cmdloop()
return bookcase_shelf_selector.result

View File

@ -29,3 +29,9 @@ class Bookcase(Base, UidMixin, UniqueNameMixin):
self.name = name self.name = name
self.description = description self.description = description
def short_str(self) -> str:
result = self.name
if self.description is not None:
result += f' [{self.description}]'
return result

View File

@ -51,3 +51,9 @@ class BookcaseShelf(Base, UidMixin):
self.column = column self.column = column
self.bookcase = bookcase self.bookcase = bookcase
self.description = description self.description = description
def short_str(self) -> str:
result = f'{self.column}-{self.row}'
if self.description is not None:
result += f' [{self.description}]'
return result