From aa53cd3223240783967a71f2d3f71675ec3083e2 Mon Sep 17 00:00:00 2001 From: Kristofers Solo Date: Mon, 6 Nov 2023 22:08:26 +0200 Subject: [PATCH] feat(table): display data as a table --- bookstore.sqlite | 0 pyproject.toml | 2 +- requirements.txt | 2 +- src/bookstore/book.py | 2 +- src/bookstore/isbn.py | 10 +++++-- src/main.py | 10 ++----- src/ui/app.py | 9 ++++++ src/ui/forms.py | 24 +++++++++++++++ src/ui/handlers.py | 7 ----- src/ui/tui.py | 23 --------------- src/ui/utils.py | 65 ++--------------------------------------- src/ui/widget.py | 13 +++++++++ src/ui/widgets/table.py | 6 ---- 13 files changed, 62 insertions(+), 111 deletions(-) delete mode 100644 bookstore.sqlite create mode 100644 src/ui/app.py create mode 100644 src/ui/forms.py delete mode 100644 src/ui/handlers.py delete mode 100644 src/ui/tui.py create mode 100644 src/ui/widget.py delete mode 100644 src/ui/widgets/table.py diff --git a/bookstore.sqlite b/bookstore.sqlite deleted file mode 100644 index e69de29..0000000 diff --git a/pyproject.toml b/pyproject.toml index 749d684..e6b7f02 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ authors = [{ name = "Kristofers Solo", email = "dev@kristofers.xyz" }] readme = "README.md" requires-python = ">=3.11" license = { text = "MIT" } -dependencies = ["attrs==23.1.0", "urwid==2.2.3"] +dependencies = ["attrs==23.1.0", "npyscreen==4.10.5"] [tool.mypy] check_untyped_defs = true diff --git a/requirements.txt b/requirements.txt index 6e7ff89..2fb84cb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ attrs>=23.1.0 -urwid>=2.2.3 +npyscreen>=4.10.5 diff --git a/src/bookstore/book.py b/src/bookstore/book.py index dd34dd0..e12fac9 100644 --- a/src/bookstore/book.py +++ b/src/bookstore/book.py @@ -29,5 +29,5 @@ class Book: def fields(cls) -> tuple[str, str, str, str, str]: return "ISBN", "Title", "Author", "Price", "Stock" - def field_values(self) -> tuple[ISBN, str, str, float, int]: + def values(self) -> tuple[ISBN, str, str, float, int]: return self.isbn, self.title, self.author, self.price, self.stock diff --git a/src/bookstore/isbn.py b/src/bookstore/isbn.py index 0c8e1ce..082ec7d 100644 --- a/src/bookstore/isbn.py +++ b/src/bookstore/isbn.py @@ -6,7 +6,11 @@ from attrs import define, field, validators class ISBN(str): number: str = field(converter=str, validator=validators.matches_re(r"^\d{10}$|^\d{13}$")) + def __repr__(self): + return str(self) + def __str__(self): - if len(self.number) == 10: - return f"{self.number[:9]}-{self.number[9:]}" - return f"{self.number[:3]}-{self.number[3:4]}-{self.number[4:6]}-{self.number[6:12]}-{self.number[12:]}" + return self.number + # if len(self.number) == 10: + # return f"{self.number[:9]}-{self.number[9:]}" + # return f"{self.number[:3]}-{self.number[3:4]}-{self.number[4:6]}-{self.number[6:12]}-{self.number[12:]}" diff --git a/src/main.py b/src/main.py index 38dcd8c..1a9d456 100755 --- a/src/main.py +++ b/src/main.py @@ -1,15 +1,11 @@ #!/usr/bin/env python3 -from pathlib import Path - -from bookstore.inventory import Inventory -from ui import tui +from ui.app import App def main() -> None: - db_path = Path("db.sqlite3") - inventory = Inventory(db_path) - tui.render(inventory) + app = App() + app.run() if __name__ == "__main__": diff --git a/src/ui/app.py b/src/ui/app.py new file mode 100644 index 0000000..acb3b1d --- /dev/null +++ b/src/ui/app.py @@ -0,0 +1,9 @@ +import npyscreen as nps + +from .forms import AddBookForm, BookForm + + +class App(nps.NPSAppManaged): + def onStart(self): + self.addForm("MAIN", BookForm, name="Bookstore") + self.addForm("ADD_BOOK", AddBookForm, name="Add Book") diff --git a/src/ui/forms.py b/src/ui/forms.py new file mode 100644 index 0000000..66f684e --- /dev/null +++ b/src/ui/forms.py @@ -0,0 +1,24 @@ +import npyscreen as nps +from bookstore.book import Book + +from .utils import INVENTORY +from .widget import BookGrid + + +class BookForm(nps.FormBaseNew): + def create(self): + self.grid = self.add_widget(BookGrid, columns=5, select_whole_line=True, col_titles=Book.fields()) + self.grid.values = [book.values() for book in INVENTORY.list_all()] + + +class AddBookForm(nps.ActionForm): + def create(self): + self.isbn = self.add(nps.TitleText, name="ISBN:") + self.title = self.add(nps.TitleText, name="Title:") + self.author = self.add(nps.TitleText, name="Author:") + self.price = self.add(nps.TitleText, name="Price:") + self.stock = self.add(nps.TitleText, name="Stock:") + + def on_ok(self): + book = Book(self.isbn.value, self.title.value, self.author.value, self.price.value, self.stock.value) + INVENTORY.add(book) diff --git a/src/ui/handlers.py b/src/ui/handlers.py deleted file mode 100644 index 060888c..0000000 --- a/src/ui/handlers.py +++ /dev/null @@ -1,7 +0,0 @@ -import urwid - - -def exit_on_q(key): - """Define a function to handle exit when the `q` key is pressed""" - if key in ("q", "Q"): - raise urwid.ExitMainLoop() diff --git a/src/ui/tui.py b/src/ui/tui.py deleted file mode 100644 index 780d1fe..0000000 --- a/src/ui/tui.py +++ /dev/null @@ -1,23 +0,0 @@ -import urwid -from bookstore.book import Book -from bookstore.inventory import Inventory - -from .handlers import exit_on_q - -from .widgets.table import Table - - -def create_row(cells) -> urwid.Columns: - return urwid.Columns([urwid.Text(str(cell)) for cell in cells]) - - -def create_books_table(books: list[Book]) -> Table: - header = create_row(Book.fields()) - rows = [create_row(book.field_values()) for book in books] - return Table(urwid.SimpleListWalker([header] + rows)) - - -def render(inventory: Inventory): - books_table = create_books_table(inventory.list_all()) - loop = urwid.MainLoop(books_table, unhandled_input=exit_on_q) - loop.run() diff --git a/src/ui/utils.py b/src/ui/utils.py index 58f0803..8fc6ae6 100644 --- a/src/ui/utils.py +++ b/src/ui/utils.py @@ -1,64 +1,5 @@ -import customtkinter as ctk -from bookstore.book import Book +from pathlib import Path + from bookstore.inventory import Inventory - -class UI: - def __init__(self, inventory: Inventory) -> None: - self.inventory = inventory - self.theme() - self.root = ctk.CTk() - self.root.geometry("650x400") - self.frame = ctk.CTkFrame(self.root) - - def render(self) -> None: - self._create_gui() - self.root.mainloop() - - def theme(self, mode: str = "dark", color: str = "dark-blue") -> None: - ctk.set_appearance_mode(mode) - ctk.set_default_color_theme(color) - - def _create_gui(self) -> None: - self._show() - - label = ctk.CTkLabel(self.frame, text="LU Bookstore") - label.pack(pady=12, padx=10) - - button = ctk.CTkButton(self.frame, text="New book", command=self._add_book) - button.pack(pady=12, padx=10) - - def _list_books(self) -> None: - pass - - def _add_book(self) -> None: - self._hide() - self._show() - - placeholders = ("ISBN", "Title", "Author", "Price", "Stock") - self.entries: list[ctk.CTkEntry] = [] - - for placeholder in placeholders: - entry = ctk.CTkEntry(self.frame, placeholder_text=placeholder) - entry.pack(pady=12, padx=10) - self.entries.append(entry) - - button = ctk.CTkButton(self.frame, text="Save", command=self._save_book) - button.pack(pady=12, padx=10) - - def _save_book(self) -> None: - entry_values = [entry.get() for entry in self.entries] - if entry_values[0]: # ISBN must be a value - new_book = Book(*entry_values) - self.inventory.add(new_book) - - for entry in self.entries: - entry.destroy() - self.entries = [] - self._show() - - def _hide(self) -> None: - self.frame.pack_forget() - - def _show(self) -> None: - self.frame.pack(pady=20, padx=60, fill="both", expand=True) +INVENTORY = Inventory(Path("db.sqlite3")) diff --git a/src/ui/widget.py b/src/ui/widget.py new file mode 100644 index 0000000..4f54768 --- /dev/null +++ b/src/ui/widget.py @@ -0,0 +1,13 @@ +import npyscreen as nps + + +class BookGrid(nps.GridColTitles): + def custom_print_cell(self, actual_cell, cell_display_value): + if actual_cell == 4: + if int(cell_display_value) == 0: + actual_cell.color = "DANGER" + elif int(cell_display_value) < 5: + actual_cell.color = "WARNING" + else: + actual_cell.color = "DEFAULT" + return super().custom_print_cell(actual_cell, cell_display_value) diff --git a/src/ui/widgets/table.py b/src/ui/widgets/table.py deleted file mode 100644 index bf5df2d..0000000 --- a/src/ui/widgets/table.py +++ /dev/null @@ -1,6 +0,0 @@ -import urwid - - -class Table(urwid.ListBox): - def __init__(self, body): - super().__init__(body)