cli: add some search functionality

This commit is contained in:
Oystein Kristoffer Tveit 2023-05-12 16:18:30 +02:00
parent e154989a16
commit d13a3a0932
Signed by: oysteikt
GPG Key ID: 9F2F7D8250F35146
5 changed files with 185 additions and 10 deletions

View File

@ -58,13 +58,12 @@ Run `poetry run worblehat --help` for more info
- [X] Ability to create and update bookcases
- [X] Ability to create and update bookcase shelfs
- [X] Ability to create and update bookcase items
- [X] Ability to borrow an item
- [X] Ability to borrow and deliver items
- [ ] Ability to borrow and deliver multiple items at a time
- [X] Ability to enter the queue for borrowing an item
- [ ] Ability to extend a borrowing, only if no one is behind you in the queue
- [ ] Ability to borrow multiple items at a time
- [ ] Ability to deliver multiple items at a time
- [ ] Ability to list borrowed items which are overdue
- [ ] Ability to search for items
- [~] Ability to search for items
- [ ] Ability to print PVV-specific labels for items missing a label, or which for any other reason needs a custom one
- [ ] Ascii art of monkey with fingers in eyes

View File

@ -18,6 +18,7 @@ from .subclis import (
AdvancedOptionsCli,
BookcaseItemCli,
select_bookcase_shelf,
SearchCli,
)
# TODO: Category seems to have been forgotten. Maybe relevant interactivity should be added?
@ -161,7 +162,13 @@ class WorblehatCli(NumberedCmd):
def do_search(self, _: str):
print('TODO: implement search')
search_cli = SearchCli(self.sql_session)
search_cli.cmdloop()
if search_cli.result is not None:
BookcaseItemCli(
sql_session = self.sql_session,
bookcase_item = search_cli.result,
).cmdloop()
def do_advanced(self, _: str):

View File

@ -129,16 +129,16 @@ class NumberedCmd(Cmd):
prompt_header: str | None = None
funcs: dict[int, dict[str, str | Callable[[Any, str], bool | None]]]
def __init__(self):
super().__init__()
@classmethod
def _generate_usage_list(cls) -> str:
def _generate_usage_list(self) -> str:
result = ''
for i, func in cls.funcs.items():
for i, func in self.funcs.items():
if i == 0:
i = '*'
result += f'{i}) {func["doc"]}\n'
@ -185,4 +185,27 @@ class NumberedCmd(Cmd):
else:
result += f'[{self.lastcmd}]> '
return result
return result
class NumberedItemSelector(NumberedCmd):
def __init__(
self,
items: list[Any],
stringify: Callable[[Any], str] = lambda x: str(x),
):
super().__init__()
self.items = items
self.stringify = stringify
self.funcs = {
i: {
'f': self._select_item,
'doc': self.stringify(item),
}
for i, item in enumerate(items, start=1)
}
def _select_item(self, *a):
self.result = self.items[int(self.lastcmd)-1]
return True

View File

@ -1,3 +1,4 @@
from .advanced_options import AdvancedOptionsCli
from .bookcase_item import BookcaseItemCli
from .bookcase_shelf_selector import select_bookcase_shelf
from .bookcase_shelf_selector import select_bookcase_shelf
from .search import SearchCli

View File

@ -0,0 +1,145 @@
from sqlalchemy import select
from sqlalchemy.orm import Session
from worblehat.cli.prompt_utils import (
NumberedCmd,
NumberedItemSelector,
)
from worblehat.models import Author, BookcaseItem
class SearchCli(NumberedCmd):
def __init__(self, sql_session: Session):
super().__init__()
self.sql_session = sql_session
def do_search_all(self, _: str):
print('TODO: Implement search all')
def do_search_title(self, _: str):
while (input_text := input('Enter title: ')) == '':
pass
items = self.sql_session.scalars(
select(BookcaseItem)
.where(BookcaseItem.name.ilike(f'%{input_text}%')),
).all()
if len(items) == 0:
print('No items found.')
return
selector = NumberedItemSelector(
items = items,
stringify = lambda item: f"{item.name} ({item.isbn})",
)
selector.cmdloop()
if selector.result is not None:
self.result = selector.result
return True
def do_search_author(self, _: str):
while (input_text := input('Enter author name: ')) == '':
pass
author = self.sql_session.scalars(
select(Author)
.where(Author.name.ilike(f'%{input_text}%')),
).all()
if len(author) == 0:
print('No authors found.')
return
elif len(author) == 1:
selected_author = author[0]
print('Found author:')
print(f" {selected_author.name} ({sum(item.amount for item in selected_author.books)} items)")
else:
selector = NumberedItemSelector(
items = author,
stringify = lambda author: f"{author.name} ({sum(item.amount for item in author.books)} items)",
)
selector.cmdloop()
if selector.result is None:
return
selected_author = selector.result
selector = NumberedItemSelector(
items = selected_author.books,
stringify = lambda item: f"{item.name} ({item.isbn})",
)
selector.cmdloop()
if selector.result is not None:
self.result = selector.result
return True
def do_search_owner(self, _: str):
while (input_text := input('Enter username: ')) == '':
pass
users = self.sql_session.scalars(
select(BookcaseItem.owner)
.where(BookcaseItem.owner.ilike(f'%{input_text}%'))
.distinct(),
).all()
if len(users) == 0:
print('No users found.')
return
elif len(users) == 1:
selected_user = users[0]
print('Found user:')
print(f" {selected_user}")
else:
selector = NumberedItemSelector(items = users)
selector.cmdloop()
if selector.result is None:
return
selected_user = selector.result
items = self.sql_session.scalars(
select(BookcaseItem)
.where(BookcaseItem.owner == selected_user),
).all()
selector = NumberedItemSelector(
items = items,
stringify = lambda item: f"{item.name} ({item.isbn})",
)
selector.cmdloop()
if selector.result is not None:
self.result = selector.result
return True
def do_done(self, _: str):
return True
funcs = {
1: {
'f': do_search_all,
'doc': 'Search everything',
},
2: {
'f': do_search_title,
'doc': 'Search by title',
},
3: {
'f': do_search_author,
'doc': 'Search by author',
},
4: {
'f': do_search_owner,
'doc': 'Search by owner',
},
9: {
'f': do_done,
'doc': 'Done',
},
}