cli: add some search functionality
This commit is contained in:
parent
e154989a16
commit
d13a3a0932
|
@ -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 bookcases
|
||||||
- [X] Ability to create and update bookcase shelfs
|
- [X] Ability to create and update bookcase shelfs
|
||||||
- [X] Ability to create and update bookcase items
|
- [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
|
- [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 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 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
|
- [ ] 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
|
- [ ] Ascii art of monkey with fingers in eyes
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ from .subclis import (
|
||||||
AdvancedOptionsCli,
|
AdvancedOptionsCli,
|
||||||
BookcaseItemCli,
|
BookcaseItemCli,
|
||||||
select_bookcase_shelf,
|
select_bookcase_shelf,
|
||||||
|
SearchCli,
|
||||||
)
|
)
|
||||||
|
|
||||||
# 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?
|
||||||
|
@ -161,7 +162,13 @@ class WorblehatCli(NumberedCmd):
|
||||||
|
|
||||||
|
|
||||||
def do_search(self, _: str):
|
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):
|
def do_advanced(self, _: str):
|
||||||
|
|
|
@ -129,16 +129,16 @@ class NumberedCmd(Cmd):
|
||||||
|
|
||||||
|
|
||||||
prompt_header: str | None = None
|
prompt_header: str | None = None
|
||||||
|
funcs: dict[int, dict[str, str | Callable[[Any, str], bool | None]]]
|
||||||
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
def _generate_usage_list(self) -> str:
|
||||||
def _generate_usage_list(cls) -> str:
|
|
||||||
result = ''
|
result = ''
|
||||||
for i, func in cls.funcs.items():
|
for i, func in self.funcs.items():
|
||||||
if i == 0:
|
if i == 0:
|
||||||
i = '*'
|
i = '*'
|
||||||
result += f'{i}) {func["doc"]}\n'
|
result += f'{i}) {func["doc"]}\n'
|
||||||
|
@ -185,4 +185,27 @@ class NumberedCmd(Cmd):
|
||||||
else:
|
else:
|
||||||
result += f'[{self.lastcmd}]> '
|
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
|
|
@ -1,3 +1,4 @@
|
||||||
from .advanced_options import AdvancedOptionsCli
|
from .advanced_options import AdvancedOptionsCli
|
||||||
from .bookcase_item import BookcaseItemCli
|
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
|
|
@ -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',
|
||||||
|
},
|
||||||
|
}
|
Loading…
Reference in New Issue