From 66f23f5dda942f128bc83adc97a1b93156fe6628 Mon Sep 17 00:00:00 2001 From: s3lph Date: Wed, 27 Nov 2024 23:43:23 +0100 Subject: [PATCH] fix: store notifications in the session so that they won't be served to other clients feat: list all users and products in a table in the settings feat: add back buttons to signup, password login and touchkey login pages feat: if the tabfocus webextension is installed, use it to focus the tab when a barcode is scanned --- CHANGELOG.md | 16 ++ matemat/__init__.py | 2 +- matemat/webserver/pagelets/buy.py | 10 +- matemat/webserver/pagelets/deposit.py | 5 +- matemat/webserver/pagelets/main.py | 6 - matemat/webserver/session/__init__.py | 2 +- matemat/webserver/session/sessions.py | 20 +- matemat/webserver/template/notification.py | 15 +- templates/admin.html | 2 - templates/admin_restricted.html | 202 ++++++++++----------- templates/base.html | 8 +- templates/login.html | 8 + templates/modproduct.html | 2 - templates/productlist.html | 2 - templates/signup.html | 9 + templates/signup_kiosk.html | 8 + templates/touchkey.html | 9 +- templates/userlist.html | 4 +- 18 files changed, 197 insertions(+), 133 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4407fe1..ce611a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Matemat Changelog + +## Version 0.3.16 + +Settings UI rework + +### Changes + + +- fix: store notifications in the session so that they won't be served to other clients +- feat: list all users and products in a table in the settings +- feat: add back buttons to signup, password login and touchkey login pages +- feat: if the tabfocus webextension is installed, use it to focus the tab when a barcode is scanned + + + + ## Version 0.3.15 diff --git a/matemat/__init__.py b/matemat/__init__.py index 67cbcd2..b91143c 100644 --- a/matemat/__init__.py +++ b/matemat/__init__.py @@ -1,2 +1,2 @@ -__version__ = '0.3.15' +__version__ = '0.3.16' diff --git a/matemat/webserver/pagelets/buy.py b/matemat/webserver/pagelets/buy.py index 84b7855..5ce27ba 100644 --- a/matemat/webserver/pagelets/buy.py +++ b/matemat/webserver/pagelets/buy.py @@ -3,6 +3,8 @@ from bottle import get, post, redirect, request from matemat.db import MatematDatabase from matemat.webserver import session from matemat.webserver import config as c +from matemat.webserver.template import Notification +from matemat.util.currency_format import format_chf @get('/buy') @@ -35,9 +37,11 @@ def buy(): stock_provider = c.get_stock_provider() if stock_provider.needs_update(): stock_provider.update_stock(product, -1) + # Show notification on next page load + Notification.success( + f'Purchased {product.name} for {format_chf(price)}', decay=True) # Logout user if configured, logged in via touchkey and no price entry input was shown if user.logout_after_purchase and authlevel < 2 and not product.custom_price: - redirect(f'/logout?lastaction=buy&lastproduct={pid}&lastprice={price}') - # Redirect to the main page (where this request should have come from) - redirect(f'/?lastaction=buy&lastproduct={pid}&lastprice={price}') + redirect('/logout') + # Redirect to the main page (where this request should have come from) redirect('/') diff --git a/matemat/webserver/pagelets/deposit.py b/matemat/webserver/pagelets/deposit.py index 291d0d2..cd19b26 100644 --- a/matemat/webserver/pagelets/deposit.py +++ b/matemat/webserver/pagelets/deposit.py @@ -3,6 +3,8 @@ from bottle import get, post, redirect, request from matemat.db import MatematDatabase from matemat.webserver import session from matemat.webserver.config import get_app_config +from matemat.webserver.template import Notification +from matemat.util.currency_format import format_chf @get('/deposit') @@ -26,6 +28,7 @@ def deposit(): n = int(str(request.params.n)) # Write the deposit to the database db.deposit(user, n) + # Show notification on next page load + Notification.success(f'Deposited {format_chf(n)}', decay=True) # Redirect to the main page (where this request should have come from) - redirect(f'/?lastaction=deposit&lastprice={n}') redirect('/') diff --git a/matemat/webserver/pagelets/main.py b/matemat/webserver/pagelets/main.py index 7f9a255..a3ee069 100644 --- a/matemat/webserver/pagelets/main.py +++ b/matemat/webserver/pagelets/main.py @@ -20,12 +20,6 @@ def main_page(): with MatematDatabase(config['DatabaseFile']) as db: # Fetch the list of products to display products = db.list_products() - lastprice = int(request.params.lastprice) if request.params.lastprice else None - if request.params.lastaction == 'deposit' and lastprice: - Notification.success(f'Deposited {format_chf(lastprice)}', decay=True) - elif request.params.lastaction == 'buy' and lastprice and request.params.lastproduct: - lastproduct = db.get_product(request.params.lastproduct) - Notification.success(f'Purchased {lastproduct.name} for {format_chf(lastprice)}', decay=True) buyproduct = None if request.params.ean: diff --git a/matemat/webserver/session/__init__.py b/matemat/webserver/session/__init__.py index b8aaacf..2428aa3 100644 --- a/matemat/webserver/session/__init__.py +++ b/matemat/webserver/session/__init__.py @@ -1,2 +1,2 @@ -from .sessions import start, end, put, get, has, delete +from .sessions import start, end, put, get, has, delete, setdefault diff --git a/matemat/webserver/session/sessions.py b/matemat/webserver/session/sessions.py index dc9f8a4..0837055 100644 --- a/matemat/webserver/session/sessions.py +++ b/matemat/webserver/session/sessions.py @@ -20,6 +20,9 @@ def start() -> str: :return: The session ID. """ + if hasattr(response, 'session_id'): + # A session has already been created while handling the same request + return response.session_id # Reference date for session timeout now = datetime.now(UTC) # Read the client's session ID, if any @@ -43,6 +46,9 @@ def start() -> str: (now + timedelta(seconds=_SESSION_TIMEOUT), __session_vars[session_id][1]) # Return the session ID and timeout response.set_cookie(_COOKIE_NAME, session_id, secret=__key) + # Piggy-back the session id onto the response object so that we don't create another session + # in subsequent calls to start() while handling the same request. + response.session_id = session_id return session_id @@ -61,10 +67,10 @@ def put(session_id: str, key: str, value: Any) -> None: __session_vars[session_id][1][key] = value -def get(session_id: str, key: str) -> Any: +def get(session_id: str, key: str, default: Any = None) -> Any: if session_id in __session_vars and key in __session_vars[session_id][1]: return __session_vars[session_id][1][key] - return None + return default def delete(session_id: str, key: str) -> None: @@ -74,3 +80,13 @@ def delete(session_id: str, key: str) -> None: def has(session_id: str, key: str) -> bool: return session_id in __session_vars and key in __session_vars[session_id][1] + + +def setdefault(session_id: str, key: str, value: Any) -> Any: + if session_id in __session_vars: + if has(session_id, key): + return get(session_id, key) + else: + put(session_id, key, value) + return value + return None diff --git a/matemat/webserver/template/notification.py b/matemat/webserver/template/notification.py index a82690d..5e4ef33 100644 --- a/matemat/webserver/template/notification.py +++ b/matemat/webserver/template/notification.py @@ -1,7 +1,8 @@ +from matemat.webserver import session + class Notification: - notifications = [] def __init__(self, msg: str, classes=None, decay: bool = False): self.msg = msg @@ -12,14 +13,18 @@ class Notification: @classmethod def render(cls): - n = list(cls.notifications) - cls.notifications.clear() + session_id: str = session.start() + sn = session.get(session_id, 'notifications', []) + n = list(sn) + sn.clear() return n @classmethod def success(cls, msg: str, decay: bool = False): - cls.notifications.append(cls(msg, classes=['success'], decay=decay)) + session_id: str = session.start() + session.setdefault(session_id, 'notifications', []).append(cls(msg, classes=['success'], decay=decay)) @classmethod def error(cls, msg: str, decay: bool = False): - cls.notifications.append(cls(msg, classes=['error'], decay=decay)) + session_id: str = session.start() + session.setdefault(session_id, 'notifications', []).append(cls(msg, classes=['error'], decay=decay)) diff --git a/templates/admin.html b/templates/admin.html index f397c46..31d8d41 100644 --- a/templates/admin.html +++ b/templates/admin.html @@ -25,10 +25,8 @@ {% endblock %} {% block eanwebsocket %} - function (e) { let eaninput = document.getElementById("admin-newproduct-ean"); eaninput.value = e.data; eaninput.select(); eaninput.scrollIntoView(); - } {% endblock %} diff --git a/templates/admin_restricted.html b/templates/admin_restricted.html index dc3056e..aa7656a 100644 --- a/templates/admin_restricted.html +++ b/templates/admin_restricted.html @@ -1,105 +1,85 @@
-

Create New User

+

Users

- -
- - -
- - -
- - -
- - -
- - -
- - -
-
- -
-

Modify User

- -
- -
- - + + + + + + + + + + + + + + + + + + + + {% for user in users %} + + + + + + + + + + {% endfor %} +
UsernameE-Mail (optional)PasswordMemberAdminLogout after purchaseActions
{{ user.name }}{{ '✓' if user.email else '✗' }}••••••••{{ '✓' if user.is_member else '✗' }}{{ '✓' if user.is_admin else '✗' }}{{ '✓' if user.logout_after_purchase else '✗' }} + 🖊 + 🗑 +
-

Create New Product

+

Products

- -
- - -
- - - CHF
- - - CHF
- - -
- - -
- - -
- - -
-
- -
-

Restock Product

- -
- -
- - -
- - -
-
- -
-

Modify Product

- -
- -
- - + + + + + + + + + + + + + + + + + + + + + + {% for product in products %} + + + + + + + + + + + {% endfor %} +
NameEAN codeMember priceNon-member priceCustom priceStockableImageActions
CHF CHF
{{ product.name }}{{ product.ean or '' }}{{ product.price_member | chf }}{{ product.price_non_member | chf }}{{ '✓' if product.custom_price else '✗' }}{{ '✓' if product.stockable else '✗' }}Picture of {{ product.name }} + 🖊 + 🗑 +
@@ -107,16 +87,32 @@

Set default images

-
-
- -
-
- + + + + + + + + + + + + + +
Default user avatarDefault product image
+ + + +
+ + + +
diff --git a/templates/base.html b/templates/base.html index 7609d79..773f1d8 100644 --- a/templates/base.html +++ b/templates/base.html @@ -61,7 +61,13 @@ function connect() { let socket = new WebSocket("{{ eanwebsocket }}"); socket.onclose = () => { setTimeout(connect, 1000); }; - socket.onmessage = {% block eanwebsocket %}() => {}{% endblock %}; + socket.onmessage = function (e) { + // Focus this tab - requires https://git.kabelsalat.ch/ccc-basel/barcode-utils + if (typeof window.extension_tabfocus === "function") { + window.extension_tabfocus(); + } + {% block eanwebsocket %}{% endblock %} + }; } window.addEventListener("load", () => { connect(); }); diff --git a/templates/login.html b/templates/login.html index 09f1fd6..88d61db 100644 --- a/templates/login.html +++ b/templates/login.html @@ -18,6 +18,14 @@ +
+ Cancel +
+ {{ super() }} {% endblock %} + +{% block eanwebsocket %} + document.location = "/?ean=" + e.data; +{% endblock %} diff --git a/templates/modproduct.html b/templates/modproduct.html index fcaaba0..db62540 100644 --- a/templates/modproduct.html +++ b/templates/modproduct.html @@ -54,10 +54,8 @@ {% endblock %} {% block eanwebsocket %} - function (e) { let eaninput = document.getElementById("modproduct-ean"); eaninput.value = e.data; eaninput.select(); eaninput.scrollIntoView(); - } {% endblock %} diff --git a/templates/productlist.html b/templates/productlist.html index 5f355f4..1974d35 100644 --- a/templates/productlist.html +++ b/templates/productlist.html @@ -84,12 +84,10 @@ {% endblock %} {% block eanwebsocket %} - function (e) { let eaninput = document.getElementById("a-buy-ean" + e.data); if (eaninput === null) { document.location = "?ean=" + e.data; } else { eaninput.click(); } - } {% endblock %} diff --git a/templates/signup.html b/templates/signup.html index f3371c7..d894853 100644 --- a/templates/signup.html +++ b/templates/signup.html @@ -32,6 +32,11 @@ + +
+ Cancel +
+