From 48a8588358dc39e9c76bc8a7b060c2754e26c9d0 Mon Sep 17 00:00:00 2001
From: Michele-Alberti <62114934+Michele-Alberti@users.noreply.github.com>
Date: Sun, 14 Apr 2024 14:43:04 +0200
Subject: [PATCH] feat: add button to change lunch time or takeaway option
add change_order_time_takeaway_button to GraphicInterface and add it to buttons_flexbox
add change_order_time_takeaway_button to reload_on_no_more_order_callback to change its enabled state
change toggle_no_more_order_button icon
add change_order_time_takeaway to core.py
add try-except statement to delete_order to handle database errors (as done in send_order)
update reload_menu to handle change_order_time_takeaway_button visibility
move lunch_time column from 'orders' table to 'users' table and add back_populates to both tables
change queries for postgresql and sqlite to join lunch time to orders_query result
remove confirm_message from GraphicInterface
remove unused parameters from functions in core.py and related callbacks
---
dlunch/__init__.py | 1 -
dlunch/conf/db/postgresql.yaml | 6 +-
dlunch/conf/db/sqlite.yaml | 6 +-
dlunch/core.py | 147 +++++++++++++++++++++++++++------
dlunch/gui.py | 32 +++++--
dlunch/models.py | 9 +-
6 files changed, 159 insertions(+), 42 deletions(-)
diff --git a/dlunch/__init__.py b/dlunch/__init__.py
index c2118c4..32d7747 100755
--- a/dlunch/__init__.py
+++ b/dlunch/__init__.py
@@ -102,7 +102,6 @@ def create_app(config: DictConfig) -> pn.Template:
app.main.append(gi.results_divider)
app.main.append(gi.res_col)
app.modal.append(gi.error_message)
- app.modal.append(gi.confirm_message)
# Set components visibility based on no_more_order_button state
# and reload menu
diff --git a/dlunch/conf/db/postgresql.yaml b/dlunch/conf/db/postgresql.yaml
index a2bd9f4..87bdd52 100644
--- a/dlunch/conf/db/postgresql.yaml
+++ b/dlunch/conf/db/postgresql.yaml
@@ -14,10 +14,12 @@ url: ${db.dialect}+${db.driver}://${db.username}:${db.password}@${db.host}:${db.
# QUERIES
# Orders
orders_query: |-
- SELECT o.user, o.lunch_time, m.item, o.note
+ SELECT o.user, u.lunch_time, m.item, o.note
FROM {schema}.orders o
LEFT JOIN {schema}.menu m
- ON m.id = o.menu_item_id;
+ ON m.id = o.menu_item_id
+ LEFT JOIN {schema}.users u
+ ON u.id = o.user;
# Stats
stats_query: |-
SELECT EXTRACT(YEAR FROM date)::varchar(4) AS "Year",
diff --git a/dlunch/conf/db/sqlite.yaml b/dlunch/conf/db/sqlite.yaml
index 606f192..e4a597f 100644
--- a/dlunch/conf/db/sqlite.yaml
+++ b/dlunch/conf/db/sqlite.yaml
@@ -9,10 +9,12 @@ url: ${db.dialect}:///${db.db_path}
# QUERIES
# Orders
orders_query: |-
- SELECT o.user, o.lunch_time, m.item, o.note
+ SELECT o.user, u.lunch_time, m.item, o.note
FROM orders o
LEFT JOIN menu m
- ON m.id = o.menu_item_id;
+ ON m.id = o.menu_item_id
+ LEFT JOIN users u
+ ON u.id = o.user;
# Stats
stats_query: |-
SELECT STRFTIME('%Y', date) AS "Year",
diff --git a/dlunch/core.py b/dlunch/core.py
index be69363..2db08ab 100644
--- a/dlunch/core.py
+++ b/dlunch/core.py
@@ -15,7 +15,7 @@
from pytesseract import pytesseract
import random
import re
-from sqlalchemy import func, select, delete
+from sqlalchemy import func, select, delete, update
from sqlalchemy.sql.expression import true as sql_true
from time import sleep
@@ -166,7 +166,6 @@ def build_menu(
) -> pd.DataFrame:
# Hide messages
gi.error_message.visible = False
- gi.confirm_message.visible = False
# Build image path
menu_filename = str(
@@ -316,17 +315,24 @@ def reload_menu(
value_if_missing=False,
)
- # Set no more orders toggle button visibility and activation
+ # Set no more orders toggle button and the change order time button
+ # visibility and activation
if auth.is_guest(
user=pn_user(config), config=config, allow_override=False
):
- # Deactivate the no_more_orders button for guest users
+ # Deactivate the no_more_orders_button for guest users
gi.toggle_no_more_order_button.disabled = True
gi.toggle_no_more_order_button.visible = False
+ # Deactivate the change_order_time_button for guest users
+ gi.change_order_time_takeaway_button.disabled = True
+ gi.change_order_time_takeaway_button.visible = False
else:
- # Activate the no_more_orders button for privileged users
+ # Activate the no_more_orders_button for privileged users
gi.toggle_no_more_order_button.disabled = False
gi.toggle_no_more_order_button.visible = True
+ # Show the change_order_time_button for privileged users
+ # It is disabled by the no more order button if necessary
+ gi.change_order_time_takeaway_button.visible = True
# Guest graphic configuration
if auth.is_guest(user=pn_user(config), config=config):
@@ -586,7 +592,6 @@ def send_order(
# Hide messages
gi.error_message.visible = False
- gi.confirm_message.visible = False
# Create session
session = models.create_session(config)
@@ -677,11 +682,13 @@ def send_order(
new_user = models.Users(
id=username_key_press,
guest=person.guest,
+ lunch_time=person.lunch_time,
takeaway=person.takeaway,
)
else:
new_user = models.Users(
id=username_key_press,
+ lunch_time=person.lunch_time,
takeaway=person.takeaway,
)
session.add(new_user)
@@ -691,7 +698,6 @@ def send_order(
# Order
new_order = models.Orders(
user=username_key_press,
- lunch_time=person.lunch_time,
menu_item_id=row.Index,
note=getattr(
row, config.panel.gui.note_column_name
@@ -722,7 +728,7 @@ def send_order(
f"DATABASE ERROR
ERROR:
{str(e)}"
)
gi.error_message.visible = True
- log.warning("database error")
+ log.error("database error")
# Open modal window
app.open_modal()
else:
@@ -744,7 +750,6 @@ def delete_order(
event,
config: DictConfig,
app: pn.Template,
- person: gui.Person,
gi: gui.GraphicInterface,
) -> None:
# Get username, updated on every keypress
@@ -752,7 +757,6 @@ def delete_order(
# Hide messages
gi.error_message.visible = False
- gi.confirm_message.visible = False
# Create session
session = models.create_session(config)
@@ -797,21 +801,114 @@ def delete_order(
return
# Delete user
- num_rows_deleted_users = session.execute(
- delete(models.Users).where(
- models.Users.id == username_key_press
+ try:
+ num_rows_deleted_users = session.execute(
+ delete(models.Users).where(
+ models.Users.id == username_key_press
+ )
)
- )
- # Delete also orders (hotfix for Debian)
- num_rows_deleted_orders = session.execute(
- delete(models.Orders).where(
- models.Orders.user == username_key_press
+ # Delete also orders (hotfix for Debian)
+ num_rows_deleted_orders = session.execute(
+ delete(models.Orders).where(
+ models.Orders.user == username_key_press
+ )
)
+ session.commit()
+ if (num_rows_deleted_users.rowcount > 0) or (
+ num_rows_deleted_orders.rowcount > 0
+ ):
+ # Update dataframe widget
+ reload_menu(
+ None,
+ config,
+ gi,
+ )
+
+ pn.state.notifications.success(
+ "Order canceled",
+ duration=config.panel.notifications.duration,
+ )
+ log.info(f"{username_key_press}'s order canceled")
+ else:
+ pn.state.notifications.warning(
+ f'No order for user named
"{username_key_press}"',
+ duration=config.panel.notifications.duration,
+ )
+ log.info(f"no order for user named {username_key_press}")
+ except Exception as e:
+ # Any exception here is a database fault
+ pn.state.notifications.error(
+ "Database error",
+ duration=config.panel.notifications.duration,
+ )
+ gi.error_message.object = (
+ f"DATABASE ERROR
ERROR:
{str(e)}"
+ )
+ gi.error_message.visible = True
+ log.error("database error")
+ # Open modal window
+ app.open_modal()
+ else:
+ pn.state.notifications.warning(
+ "Please insert user name",
+ duration=config.panel.notifications.duration,
+ )
+ log.warning("missing username")
+
+
+def change_order_time_takeaway(
+ event,
+ config: DictConfig,
+ person: gui.Person,
+ gi: gui.GraphicInterface,
+) -> None:
+ # Get username, updated on every keypress
+ username_key_press = gi.person_widget._widgets["username"].value_input
+
+ # Create session
+ session = models.create_session(config)
+
+ with session:
+ # Check if the "no more order" toggle button is pressed
+ if models.get_flag(config=config, id="no_more_orders"):
+ pn.state.notifications.error(
+ "It is not possible to update orders (time)",
+ duration=config.panel.notifications.duration,
)
+
+ # Reload the menu
+ reload_menu(
+ None,
+ config,
+ gi,
+ )
+
+ return
+
+ if username_key_press:
+ # Build and execute the update statement
+ update_statement = (
+ update(models.Users)
+ .where(models.Users.id == username_key_press)
+ .values(lunch_time=person.lunch_time, takeaway=person.takeaway)
+ .returning(models.Users)
+ )
+
+ updated_user = session.scalars(update_statement).one_or_none()
+
session.commit()
- if (num_rows_deleted_users.rowcount > 0) or (
- num_rows_deleted_orders.rowcount > 0
- ):
+
+ if updated_user:
+ # Find updated values
+ updated_time = updated_user.lunch_time
+ updated_takeaway = (
+ (" " + config.panel.gui.takeaway_id)
+ if updated_user.takeaway
+ else ""
+ )
+ updated_items_names = [
+ order.menu_item.item for order in updated_user.orders
+ ]
# Update dataframe widget
reload_menu(
None,
@@ -820,10 +917,10 @@ def delete_order(
)
pn.state.notifications.success(
- "Order canceled",
+ f"{username_key_press}'s
lunch time changed to
{updated_time}{updated_takeaway}
({', '.join(updated_items_names)})",
duration=config.panel.notifications.duration,
)
- log.info(f"{username_key_press}'s order canceled")
+ log.info(f"{username_key_press}'s order updated")
else:
pn.state.notifications.warning(
f'No order for user named
"{username_key_press}"',
@@ -952,12 +1049,8 @@ def clean_up_table(
def download_dataframe(
config: DictConfig,
- app: pn.Template,
gi: gui.GraphicInterface,
) -> None:
- # Hide messages
- gi.error_message.visible = False
- gi.confirm_message.visible = False
# Build a dict of dataframes, one for each lunch time (the key contains
# a lunch time)
diff --git a/dlunch/gui.py b/dlunch/gui.py
index cb111fd..4be808f 100644
--- a/dlunch/gui.py
+++ b/dlunch/gui.py
@@ -383,7 +383,17 @@ def reload_on_guest_override_callback(
button_style="outline",
button_type="warning",
height=generic_button_height,
- icon="alarm",
+ icon="hand-stop",
+ icon_size="2em",
+ sizing_mode="stretch_width",
+ )
+ # Create change time
+ self.change_order_time_takeaway_button = pnw.Button(
+ name="Change Time/Takeaway",
+ button_type="primary",
+ button_style="outline",
+ height=generic_button_height,
+ icon="clock-edit",
icon_size="2em",
sizing_mode="stretch_width",
)
@@ -432,6 +442,7 @@ def reload_on_guest_override_callback(
*[
self.send_order_button,
self.toggle_no_more_order_button,
+ self.change_order_time_takeaway_button,
self.delete_order_button,
],
flex_wrap="nowrap",
@@ -454,9 +465,10 @@ def reload_on_no_more_order_callback(
# Show "no more order" text
self.no_more_order_alert.visible = toggle
- # Deactivate send order and delete order buttons
+ # Deactivate send, delete and change order buttons
self.send_order_button.disabled = toggle
self.delete_order_button.disabled = toggle
+ self.change_order_time_takeaway_button.disabled = toggle
# Simply reload the menu when the toggle button value changes
if reload:
@@ -493,6 +505,14 @@ def reload_on_no_more_order_callback(
e,
config,
app,
+ self,
+ )
+ )
+ # Change order time button callback
+ self.change_order_time_takeaway_button.on_click(
+ lambda e: core.change_order_time_takeaway(
+ e,
+ config,
person,
self,
)
@@ -505,12 +525,6 @@ def reload_on_no_more_order_callback(
sizing_mode="stretch_width",
)
self.error_message.visible = False
- # Confirm message
- self.confirm_message = pn.pane.HTML(
- styles={"color": "green", "font-weight": "bold"},
- sizing_mode="stretch_width",
- )
- self.confirm_message.visible = False
# SIDEBAR -------------------------------------------------------------
# TEXTS
@@ -610,7 +624,7 @@ def reload_on_no_more_order_callback(
)
# Download button and callback
self.download_button = pn.widgets.FileDownload(
- callback=lambda: core.download_dataframe(config, app, self),
+ callback=lambda: core.download_dataframe(config, self),
filename=config.panel.file_name + ".xlsx",
sizing_mode="stretch_width",
icon="download",
diff --git a/dlunch/models.py b/dlunch/models.py
index 9be9be3..8b61979 100755
--- a/dlunch/models.py
+++ b/dlunch/models.py
@@ -212,7 +212,7 @@ class Orders(Data):
index=True,
nullable=False,
)
- lunch_time = Column(String(7), index=True, nullable=False)
+ user_record = relationship("Users", back_populates="orders", uselist=False)
menu_item_id = Column(
Integer,
ForeignKey("menu.id", ondelete="CASCADE"),
@@ -265,9 +265,16 @@ class Users(Data):
default="NotAGuest",
server_default="NotAGuest",
)
+ lunch_time = Column(String(7), index=True, nullable=False)
takeaway = Column(
Boolean, nullable=False, default=False, server_default=sql_false()
)
+ orders = relationship(
+ "Orders",
+ back_populates="user_record",
+ cascade="all, delete-orphan",
+ passive_deletes=True,
+ )
@classmethod
def clear(self, config: DictConfig) -> int: