From 7a8b3928a6371936c518d531660bc586ccfafef9 Mon Sep 17 00:00:00 2001 From: Ignace Date: Mon, 12 Jan 2026 15:03:47 +0100 Subject: [PATCH] prerelease of list from serrver-db --- src/justgetit/app.py | 5 +- src/justgetit/backend.py | 24 +++++++- src/justgetit/basepage.py | 5 +- src/justgetit/configuration.py | 27 +++++---- src/justgetit/images/logo.png | Bin 0 -> 5073 bytes src/justgetit/lists.py | 106 ++++++++++++++++++++++++++++++--- src/justgetit/localdata.py | 66 ++++++++++++++++++++ src/justgetit/localmodel.py | 22 ------- src/justgetit/login.py | 4 +- src/justgetit/test_source.py | 59 ++++++++++++++++++ 10 files changed, 270 insertions(+), 48 deletions(-) create mode 100644 src/justgetit/images/logo.png create mode 100644 src/justgetit/localdata.py delete mode 100644 src/justgetit/localmodel.py create mode 100644 src/justgetit/test_source.py diff --git a/src/justgetit/app.py b/src/justgetit/app.py index 99e1f93..da60c91 100644 --- a/src/justgetit/app.py +++ b/src/justgetit/app.py @@ -8,6 +8,7 @@ from justgetit.configuration import Configuration from justgetit.providekey import ProvideKey from justgetit.lists import Lists from justgetit.backend import BackEnd +from justgetit.localdata import LocalData class JustEatIt(toga.App): login_screen = toga.Box() @@ -34,6 +35,7 @@ class JustEatIt(toga.App): def startup(self): self.configuration = Configuration(self) + self.data = LocalData(self) self.login_screen = Login() self.register_screen = Register() @@ -58,10 +60,11 @@ class JustEatIt(toga.App): # Initial layout clamp self.on_resize(self.main_window) - def set_page(self, page): + def set_page(self, page): if self.last_screen_width > 0: page.set_screen_width(self.last_screen_width) self.main_window.content = page + page.on_show() def on_resize(self, widget, **kwargs): width = self.main_window.size.width diff --git a/src/justgetit/backend.py b/src/justgetit/backend.py index 12ef4ca..e8ba4b1 100644 --- a/src/justgetit/backend.py +++ b/src/justgetit/backend.py @@ -1,9 +1,7 @@ from justgetit.constants import * import httpx -import json import toga - class BackEnd(): """ This is the interface for the backend. @@ -32,7 +30,6 @@ class BackEnd(): response = await client.post(f"{API}/{endpoint}", data=parameters) self.payload = response.json() self.status = int(self.payload['status']) - print(f" payload {self.payload}") except httpx.RequestError as exc: self.status = -2 @@ -50,3 +47,24 @@ class BackEnd(): callback(parameters, self.payload) + def postwait(self, endpoint, parameters): + """ + Call the server, always with a POST, for a specific endpoint and the parameters + in the body + + :param endpoint: the endpoint without /api/ (no slashes) + :param parameters: A dict with the parameters for the call + :param callback: def in the calling function to return to + """ + self.status = 0 + self.payload = None + if len(endpoint)>2: + try: + response = httpx.post(f"{API}/{endpoint}", data=parameters) + self.payload = response.json() + self.status = int(self.payload['status']) + except httpx.RequestError as exc: + self.status = -2 + + return self.payload + \ No newline at end of file diff --git a/src/justgetit/basepage.py b/src/justgetit/basepage.py index 5721041..5487c34 100644 --- a/src/justgetit/basepage.py +++ b/src/justgetit/basepage.py @@ -65,7 +65,7 @@ class BasePage(toga.Box): justify_content=START, margin=24, flex=1, - background_color="yellow" + # background_color="yellow" ) ) @@ -136,6 +136,9 @@ class BasePage(toga.Box): if w>0: self.content_box.style.width = w + def on_show(self): + print("new page shown") + async def display_warning(self, message): self.warningtext.text = message self.warning_box = self.warningline_outer diff --git a/src/justgetit/configuration.py b/src/justgetit/configuration.py index 6e88dd7..f08479d 100644 --- a/src/justgetit/configuration.py +++ b/src/justgetit/configuration.py @@ -8,8 +8,7 @@ class Configuration(): This class holds persistent configuration: - appkey: the app key, alowing traffic with the backend - last_key_validation_date: last date the appkey was validated, should be no more than a week ago otherwise the app will not run - - username: the user name - - userpwd: the users pwd + - user: the user as a dict - userlang: the users language - lastlogin_date: date of last sucessful login """ @@ -39,7 +38,6 @@ class Configuration(): def has_valid_app_key(self): return True if 'appkey' in self.data: - print("found app key {self.data['appkey']}") today = datetime.date.today() last_month = today - datetime.timedelta(days=30) if self.data['last_key_validation_date'] > last_month: @@ -53,9 +51,9 @@ class Configuration(): self.save() def user_is_logged_in(self): - return True - if 'username' in self.data: - print("found user {self.data['username']}") + # return True + if 'user' in self.data: + print(f"found user {self.data['user']['name']}") today = datetime.date.today() last_week = today - datetime.timedelta(days=7) if self.data['lastlogin_date'] > last_week: @@ -63,9 +61,8 @@ class Configuration(): return True return False - def store_login(self, user, pwd): - self.data['username'] = user - self.data['userpwd'] = pwd + def store_login(self, user_dict): + self.data['user'] = user_dict self.data['lastlogin_date'] = datetime.date.today() self.save() @@ -75,7 +72,13 @@ class Configuration(): self.data['lastlogin_date'] = today def logout_user(self): - self.data.pop('username', None) - self.data.pop('userpwd', None) + self.data.pop('user', None) self.data.pop('lastlogin_date', None) - self.save() \ No newline at end of file + self.save() + + def get_userid(self): + result = 0 + if 'user' in self.data: + print() + result = self.data['user']['id'] + return result \ No newline at end of file diff --git a/src/justgetit/images/logo.png b/src/justgetit/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..6d3ca3263f187280408749d62fb61a3e60ebfc93 GIT binary patch literal 5073 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 literal 0 HcmV?d00001 diff --git a/src/justgetit/lists.py b/src/justgetit/lists.py index 9fd1955..4e9e486 100644 --- a/src/justgetit/lists.py +++ b/src/justgetit/lists.py @@ -1,11 +1,90 @@ import toga from toga.style import Pack from toga.style.pack import COLUMN, ROW, END, START, CENTER, HIDDEN, JUSTIFY -# from justgetit.constants import SCREENHEIGHT, SCREENWIDTH -import asyncio +from toga.sources import ListSource, Listener +import datetime from justgetit.constants import * from justgetit.basepage import BasePage +class DatabaseList(toga.Box): + def __init__(self, id=None, style=None, children=None, **kwargs): + style.direction = COLUMN + super().__init__(id, style, children, **kwargs) + self.data = ListSource( + accessors=["id", "owner_user_id", "name", "is_active"] + ) + self.data.add_listener(DatabaseList.DataListener(self)) + + class DataListener(Listener): + # listen to the database + def __init__(self, parent_widget): + print("widget: data-item init") + self.parent_widget = parent_widget + super().__init__() + + def change(self, item): + print("widget: data-item change") + return super().change(item) + + def clear(self): + print("widget: data-item clear") + return super().clear() + + def insert(self, index, item): + print("widget: data-item insert") + self.parent_widget.add(self.create_row(item.name,"3", "20")) + return super().insert(index, item) + + def remove(self, index, item): + print("widget: data-item remove") + return super().remove(index, item) + + def create_row(self, text, openitems, totalitems): + label = toga.Button( + text=text, + margin_bottom=2, + style = Pack( + # margin_bottom=2, # scrollig doesnt work, bug?, replaced by margin_bottom in Button + # width=250, + flex=15, + background_color="white", #white + font_size=15 + ) + ) + spacer = toga.Box( + style = Pack( + flex=1 + ) + ) + indicator = toga.Label( + text = f"{openitems} / {totalitems}", + style = Pack( + color="blue", + margin=8, + flex=2 + ) + ) + listrow_inner = toga.Box( + style = Pack( + margin_bottom=1, + flex=1, + background_color="white", #white + ) + ) + listrow_inner.add(label) + listrow_inner.add(spacer) + listrow_inner.add(indicator) + + listrow_outer = toga.Box( + style = Pack( + # flex=1, + background_color="#ccc", # ccc + ) + ) + listrow_outer.add(listrow_inner) + + return(listrow_outer) + class Lists(BasePage): @@ -19,6 +98,7 @@ class Lists(BasePage): :param args: args for super :param kwargs: kwars for super """ + # set button and image self.first_menu_button_icon = 'edit3' self.first_menu_button_callback = self.edit_button_pressed @@ -28,14 +108,17 @@ class Lists(BasePage): super().__init__(*args, **kwargs) # here the list of lists - listholder = toga.Column( + self.listholder = DatabaseList( style=Pack( margin_bottom=10, - background_color="#aaa", + # background_color="white", ) ) + + + scrollarea = toga.ScrollContainer( - content=listholder, + content=self.listholder, style=Pack( flex=1, margin=10, @@ -45,8 +128,8 @@ class Lists(BasePage): - for i in range(30): - listholder.add(self.create_row("Supermarkt",f"{i}","30")) + # for i in range(30): + # listholder.add(self.create_row("Supermarkt",f"{i}","30")) @@ -75,6 +158,15 @@ class Lists(BasePage): self.content_box.add(scrollarea) # self.content_box.add(save_button_box) + def on_show(self): + super().on_show() + self.listholder.data.clear() + for l in self.app.data.reload_list(): + print(type(l)) + self.listholder.data.append(l) + + # self.listholder.data.data = self.app.data.reload_list() + def edit_button_pressed(self): self.app.goto_next_page_by_name(PAGE_EDITLISTS) diff --git a/src/justgetit/localdata.py b/src/justgetit/localdata.py new file mode 100644 index 0000000..bc544f2 --- /dev/null +++ b/src/justgetit/localdata.py @@ -0,0 +1,66 @@ +import datetime +import asyncio + +class ListOfLists(): + def __init__(self, app): + self.app = app + # self.listento = widget_to_listen_to + self.data = [] + # self.widget_data = list_source_object + # self.reload() + + # self.widget_data.append({"name": "ToDo", "id": 3, "owner_user_id": 3, "is_active": True, "updated_at": datetime.datetime.now() }) + + def reload(self, userid): + parameters = {} + parameters['userid'] = userid + result = self.app.backend.postwait('load_lists', parameters) + return result['data'] + + + def on_reloaded(self, bla): + print("reloaded") + + def clear(self): + self.data = [] + + + +class List(): + id = 0 + updated_at = None + owner_user_id = 0 + name = '' + is_active = True + + +class Item(): + id = 0 + updated_at = None + listofitems_id = 0 + label = '' + quantity = 0 + unit = '' + label_alt1 = '' + label_alt2 = '' + is_checked = False + is_suggestion = False + category = '' + +class LocalData(): + def __init__(self, app): + self.app = app + self.curent_userid = 0 + self.current_listid = 0 + self.lists = ListOfLists(app) + + def set_user(self, userid): + if userid != self.curent_userid: + self.lists.clear() + self.curent_userid = userid + + def reload_list(self): + self.curent_userid = self.app.configuration.get_userid() + if self.curent_userid > 0: + return self.lists.reload(self.curent_userid) + \ No newline at end of file diff --git a/src/justgetit/localmodel.py b/src/justgetit/localmodel.py deleted file mode 100644 index 8294e7f..0000000 --- a/src/justgetit/localmodel.py +++ /dev/null @@ -1,22 +0,0 @@ - - -class ListItems(): - id = 0 - updated_at = None - owner_user_id = 0 - name = '' - is_active = True - - -class Item(): - id = 0 - updated_at = db.Column(db.TIMESTAMP, default=datetime.now(), onupdate=datetime.now(), nullable=False) - listofitems_id = db.Column(db.Integer, nullable=False) - label = db.Column(db.String(40), nullable=False) - quantity = db.Column(db.Integer, default=0) - unit = db.Column(db.String(10)) - label_alt1 = db.Column(db.String(40)) - label_alt2 = db.Column(db.String(40)) - is_checked = db.Column(db.Boolean, default=False) - is_suggestion = db.Column(db.Boolean, default=False) - category = db.Column(db.String(20)) \ No newline at end of file diff --git a/src/justgetit/login.py b/src/justgetit/login.py index 7422c7e..fa6422b 100644 --- a/src/justgetit/login.py +++ b/src/justgetit/login.py @@ -60,7 +60,6 @@ class Login(BasePage): ) ) - # wrapping up the windows elements self.content_box.add(self.name_input) self.content_box.add(self.password_input) @@ -82,7 +81,8 @@ class Login(BasePage): def after_backend_login(self, parameters, return_data): if return_data['status'] > 0: # store login etc in config - self.app.configuration.store_login(parameters['user'], parameters['password']) + self.app.configuration.store_login(return_data['data']) + print(return_data) # goto lists screen self.app.goto_next_page_by_name(PAGE_LISTS) diff --git a/src/justgetit/test_source.py b/src/justgetit/test_source.py new file mode 100644 index 0000000..3119735 --- /dev/null +++ b/src/justgetit/test_source.py @@ -0,0 +1,59 @@ +from toga.sources import ListSource, Listener +import datetime + + +class Ear(Listener): + + def __init__(self): + print("ear init") + super().__init__() + + def change(self, item): + print("ear change") + return super().change(item) + + def clear(self): + print("ear clear") + return super().clear(item) + + def insert(self, index, item): + print("ear insert") + return super().insert(index, item) + + def remove(self, index, item): + print("ear remove") + return super().remove(index, item) + + + + + +source = ListSource( + accessors=["id", "updated_at", "owner_user_id", "name", "is_active"], + data=[ + {"name": "Super", "id": 2, "owner_user_id": 3, "is_active": True, "updated_at": datetime.datetime.now() }, + + ] +) + +e = Ear() +source.add_listener(e) + + +# Get the first item in the source +item = source[0] +print(f"Animal's name is {item.name}") +print(item) +item.name = "more super" +print(item) + +# Find an item with a name of "Thylacine" +# item = source.find({"name": "Super"}) + +# Remove that item from the data +source.remove(item) + +# Insert a new item at the start of the data +# source.insert(0, {"name": "Bettong", "weight": 1.2}) + +source.append({"name": "ToDo", "id": 3, "owner_user_id": 3, "is_active": True, "updated_at": datetime.datetime.now() })