From ce9f914d4ddc97f2758a3990dbd5cfbbd499b357 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 12 Mar 2024 21:58:35 +0000 Subject: [PATCH 1/8] ci(.pre-commit-config.yaml): pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/commitizen-tools/commitizen: v3.15.0 → v3.18.3](https://github.com/commitizen-tools/commitizen/compare/v3.15.0...v3.18.3) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 687b8a3..8be315f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,7 +9,7 @@ repos: hooks: - id: flake8 - repo: https://github.com/commitizen-tools/commitizen - rev: v3.15.0 + rev: v3.18.3 hooks: - id: commitizen stages: [commit-msg] From d1e8830a58416018e6ac7f983943f67437ca6b98 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Mar 2024 15:28:31 +0000 Subject: [PATCH 2/8] build(deps): bump the gh-minor-deps group with 1 update Bumps the gh-minor-deps group with 1 update: [commitizen-tools/commitizen-action](https://github.com/commitizen-tools/commitizen-action). Updates `commitizen-tools/commitizen-action` from 0.20.0 to 0.21.0 - [Release notes](https://github.com/commitizen-tools/commitizen-action/releases) - [Changelog](https://github.com/commitizen-tools/commitizen-action/blob/master/CHANGELOG.md) - [Commits](https://github.com/commitizen-tools/commitizen-action/compare/0.20.0...0.21.0) --- updated-dependencies: - dependency-name: commitizen-tools/commitizen-action dependency-type: direct:production update-type: version-update:semver-minor dependency-group: gh-minor-deps ... Signed-off-by: dependabot[bot] --- .github/workflows/bump_version.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/bump_version.yaml b/.github/workflows/bump_version.yaml index 33c7d19..a4f8614 100644 --- a/.github/workflows/bump_version.yaml +++ b/.github/workflows/bump_version.yaml @@ -33,7 +33,7 @@ jobs: token: ${{ secrets.CUSTOM_GITHUB_TOKEN }} - name: Bump and changelog id: cz - uses: commitizen-tools/commitizen-action@0.20.0 + uses: commitizen-tools/commitizen-action@0.21.0 with: github_token: ${{ secrets.CUSTOM_GITHUB_TOKEN }} prerelease: "${{ steps.vars.outputs.pre_release }}" From 8e211873b67ee55f3617a378407eebfa6422e371 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Mar 2024 16:02:30 +0000 Subject: [PATCH 3/8] build(deps): bump the pip-minor-deps group with 3 updates Bumps the pip-minor-deps group with 3 updates: [ipykernel](https://github.com/ipython/ipykernel), [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy) and [google-cloud-storage](https://github.com/googleapis/python-storage). Updates `ipykernel` from 6.29.2 to 6.29.3 - [Release notes](https://github.com/ipython/ipykernel/releases) - [Changelog](https://github.com/ipython/ipykernel/blob/main/CHANGELOG.md) - [Commits](https://github.com/ipython/ipykernel/compare/v6.29.2...v6.29.3) Updates `sqlalchemy` from 2.0.27 to 2.0.28 - [Release notes](https://github.com/sqlalchemy/sqlalchemy/releases) - [Changelog](https://github.com/sqlalchemy/sqlalchemy/blob/main/CHANGES.rst) - [Commits](https://github.com/sqlalchemy/sqlalchemy/commits) Updates `google-cloud-storage` from 2.14.0 to 2.15.0 - [Release notes](https://github.com/googleapis/python-storage/releases) - [Changelog](https://github.com/googleapis/python-storage/blob/main/CHANGELOG.md) - [Commits](https://github.com/googleapis/python-storage/compare/v2.14.0...v2.15.0) --- updated-dependencies: - dependency-name: ipykernel dependency-type: direct:production update-type: version-update:semver-patch dependency-group: pip-minor-deps - dependency-name: sqlalchemy dependency-type: direct:production update-type: version-update:semver-patch dependency-group: pip-minor-deps - dependency-name: google-cloud-storage dependency-type: direct:production update-type: version-update:semver-minor dependency-group: pip-minor-deps ... Signed-off-by: dependabot[bot] --- requirements/requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index f32713f..d26c508 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -1,7 +1,7 @@ setuptools==69.1.1 click==8.1.7 cryptography==42.0.5 -ipykernel==6.29.2 +ipykernel==6.29.3 ipywidgets==8.1.2 openpyxl==3.1.2 pandas==2.2.1 @@ -9,8 +9,8 @@ passlib==1.7.4 tenacity==8.2.3 tqdm==4.66.2 panel==1.3.8 -sqlalchemy==2.0.27 +sqlalchemy==2.0.28 psycopg==3.1.18 hydra-core==1.3.2 -google-cloud-storage==2.14.0 +google-cloud-storage==2.15.0 pytesseract==0.3.10 From 9831bb56ab0c85cfbb9f9f932089e9d9034a1af0 Mon Sep 17 00:00:00 2001 From: Michele-Alberti <62114934+Michele-Alberti@users.noreply.github.com> Date: Sat, 16 Mar 2024 12:08:11 +0100 Subject: [PATCH 4/8] build(deps-conda): bump conda env dependencies Bumps the pip-minor-deps group with 3 updates: ipykernel, sqlalchemy restore google-cloud-strage to old version in requirements.txt beacuse latest version is not yet available on anaconda --- requirements/environment.yml | 4 ++-- requirements/requirements.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements/environment.yml b/requirements/environment.yml index fede359..eaaf998 100755 --- a/requirements/environment.yml +++ b/requirements/environment.yml @@ -8,7 +8,7 @@ dependencies: - setuptools=69.1.1 - click=8.1.7 - cryptography=42.0.5 - - ipykernel=6.29.2 + - ipykernel=6.29.3 - ipywidgets=8.1.2 - openpyxl=3.1.2 - pandas=2.2.1 @@ -16,7 +16,7 @@ dependencies: - tenacity=8.2.3 - tqdm=4.66.2 - panel=1.3.8 - - sqlalchemy=2.0.27 + - sqlalchemy=2.0.28 - psycopg=3.1.18 - sqlite=3.41.2 - hydra-core=1.3.2 diff --git a/requirements/requirements.txt b/requirements/requirements.txt index d26c508..a2e97ef 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -12,5 +12,5 @@ panel==1.3.8 sqlalchemy==2.0.28 psycopg==3.1.18 hydra-core==1.3.2 -google-cloud-storage==2.15.0 +google-cloud-storage==2.14.0 pytesseract==0.3.10 From 6c5a8c71686983f251fa16426baf9ebf7e63573f Mon Sep 17 00:00:00 2001 From: Michele-Alberti <62114934+Michele-Alberti@users.noreply.github.com> Date: Sun, 17 Mar 2024 15:10:35 +0100 Subject: [PATCH 5/8] style(__init__.py): improve log level and descriptions --- dlunch/__init__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/dlunch/__init__.py b/dlunch/__init__.py index 1204818..cfa02de 100755 --- a/dlunch/__init__.py +++ b/dlunch/__init__.py @@ -38,14 +38,15 @@ def create_app(config: DictConfig) -> pn.Template: config, add_basic_auth_users=auth.is_basic_auth_active(config=config) ) + log.info("initialize support variables") # Generate a random password only if requested (check on flag) - log.info("set user password") + log.debug("config guest user") guest_password = core.set_guest_user_password(config) - log.info("instantiate Panel app") + log.info("instantiate app") # Panel configurations - log.debug("set toggle initial state") + log.debug("set toggles initial state") # Set the no_more_orders flag if it is None (not found in flags table) if models.get_flag(config=config, id="no_more_orders") is None: models.set_flag(config=config, id="no_more_orders", value=False) From 20f830b580b2c3c9a129ab31c4e7b7c4a7192bf7 Mon Sep 17 00:00:00 2001 From: Michele-Alberti <62114934+Michele-Alberti@users.noreply.github.com> Date: Sun, 17 Mar 2024 15:45:42 +0100 Subject: [PATCH 6/8] feat: move notes from user to menu items and adapt excel file to this change move notes to menu items add note column to menu table and hide id column remove the note column from stats counters remove note from Users table and add it to Orders table remove an unused relationship in Users table adopt Identity columns for ids as best practice for postgresql (ignored by sqlite) change send_order to add notes directly to each row in Orders table change clean_up_table inside df_list_by_lunch_time to handle notes attached to each item of each order, notes with the same text are grouped with pandas's value_counts method improve excel file format and columns width improve widget layout and fix some minor errors in gui.py change postgresql and sqlite queries to correctly handle notes add some options related to menu table layout and notes aggregation to panel/gui/default.yaml BREAKING CHANGE: move notes from user to order --- dlunch/conf/db/postgresql.yaml | 2 +- dlunch/conf/db/sqlite.yaml | 2 +- dlunch/conf/panel/default.yaml | 2 +- dlunch/conf/panel/gui/default.yaml | 14 +++ dlunch/core.py | 168 +++++++++++++++++++++++------ dlunch/gui.py | 32 +++--- dlunch/models.py | 14 +-- 7 files changed, 175 insertions(+), 59 deletions(-) diff --git a/dlunch/conf/db/postgresql.yaml b/dlunch/conf/db/postgresql.yaml index 6494f32..a2bd9f4 100644 --- a/dlunch/conf/db/postgresql.yaml +++ b/dlunch/conf/db/postgresql.yaml @@ -14,7 +14,7 @@ url: ${db.dialect}+${db.driver}://${db.username}:${db.password}@${db.host}:${db. # QUERIES # Orders orders_query: |- - SELECT o.user, o.lunch_time, m.item + SELECT o.user, o.lunch_time, m.item, o.note FROM {schema}.orders o LEFT JOIN {schema}.menu m ON m.id = o.menu_item_id; diff --git a/dlunch/conf/db/sqlite.yaml b/dlunch/conf/db/sqlite.yaml index 22e7fdd..606f192 100644 --- a/dlunch/conf/db/sqlite.yaml +++ b/dlunch/conf/db/sqlite.yaml @@ -9,7 +9,7 @@ url: ${db.dialect}:///${db.db_path} # QUERIES # Orders orders_query: |- - SELECT o.user, o.lunch_time, m.item + SELECT o.user, o.lunch_time, m.item, o.note FROM orders o LEFT JOIN menu m ON m.id = o.menu_item_id; diff --git a/dlunch/conf/panel/default.yaml b/dlunch/conf/panel/default.yaml index 4452315..b4d117f 100755 --- a/dlunch/conf/panel/default.yaml +++ b/dlunch/conf/panel/default.yaml @@ -56,7 +56,7 @@ additional_items_to_concat: - name: Altro short_name: Altro icon: 🃏 - description: richieste speciali (da specificare nelle note). + description: richieste speciali (da specificare nella colonna note). guest_types: # Check also guests icons in panel/gui - Edison Guest - External Guest diff --git a/dlunch/conf/panel/gui/default.yaml b/dlunch/conf/panel/gui/default.yaml index c7a9ab9..7c7823e 100644 --- a/dlunch/conf/panel/gui/default.yaml +++ b/dlunch/conf/panel/gui/default.yaml @@ -100,9 +100,23 @@ takeaway_svg_icon: |- takeaway_alert_text_options: 'class="flashing-animation"' +# MENU TABLE +# Table column alignment +menu_column_align: + id: center + item: left + order: center + note: left + # ORDERS' TABLE # Name of the column with total values for orders total_column_name: totale +# Name of the column with notes for orders +note_column_name: note +# Note separators +note_sep: + count: " " # Between note and counter + element: ", " # Between notes # CUSTOMIZABLE GRAPHIC OBJECTS # Instantiate a panel object to be rendered on app's header diff --git a/dlunch/core.py b/dlunch/core.py index 257d286..f3f1978 100644 --- a/dlunch/core.py +++ b/dlunch/core.py @@ -8,6 +8,7 @@ from . import models from omegaconf import DictConfig, OmegaConf from openpyxl.utils import get_column_interval +from openpyxl.styles import Alignment, Font from bokeh.models.widgets.tables import CheckboxEditor from io import BytesIO from PIL import Image @@ -94,9 +95,11 @@ def set_guest_user_password(config: DictConfig) -> str: if auth.is_basic_auth_active(config=config): # If active basic_auth.guest_user is true if guest user is active is_guest_user_active = config.basic_auth.guest_user + log.debug("guest user flag is {is_guest_user_active}") else: # Otherwise the guest user feature is not applicable is_guest_user_active = False + log.debug("guest user not applicable") # Set the guest password variable if is_guest_user_active: @@ -341,20 +344,29 @@ def reload_menu( config=config, index_col="id", ) + # Add order (for selecting items) and note columns df["order"] = False + df["note"] = "" gi.dataframe.value = df gi.dataframe.formatters = {"order": {"type": "tickCross"}} gi.dataframe.editors = { "id": None, "item": None, "order": CheckboxEditor(), + "note": "input", } + gi.dataframe.header_align = OmegaConf.to_container( + config.panel.gui.menu_column_align, resolve=True + ) + gi.dataframe.text_align = OmegaConf.to_container( + config.panel.gui.menu_column_align, resolve=True + ) if gi.toggle_no_more_order_button.value: - gi.dataframe.hidden_columns = ["order"] + gi.dataframe.hidden_columns = ["id", "order"] gi.dataframe.disabled = True else: - gi.dataframe.hidden_columns = [] + gi.dataframe.hidden_columns = ["id"] gi.dataframe.disabled = False # If menu is empty show banner image, otherwise show menu @@ -404,7 +416,11 @@ def reload_menu( [ c for c in df.columns - if c.lower() != config.panel.gui.total_column_name + if c + not in ( + config.panel.gui.total_column_name, + config.panel.gui.note_column_name, + ) ] ) # Set different graphics for takeaway lunches @@ -468,9 +484,10 @@ def reload_menu( ], } # Add text to result column - gi.res_col.append(pn.Spacer(height=10)) + gi.res_col.append(pn.Spacer(height=15)) gi.res_col.append(gi.build_time_label(**res_col_label_kwargs)) # Add non editable table to result column + gi.res_col.append(pn.Spacer(height=5)) gi.res_col.append( gi.build_order_table( config, df=df, time=time, guests_lists=guests_lists @@ -564,9 +581,8 @@ def send_order( person: gui.Person, gi: gui.GraphicInterface, ) -> None: - # Get username and note, updated at each key press + # Get username updated at each key press username_key_press = gi.person_widget._widgets["username"].value_input - note_key_press = gi.person_widget._widgets["note"].value_input # Hide messages gi.error_message.visible = False @@ -643,7 +659,6 @@ def send_order( # Write order into database table df = gi.dataframe.value.copy() df_order = df[df.order] - # If username is missing or the order is empty return an error message if username_key_press and not df_order.empty: # Check if the user already placed an order @@ -656,29 +671,29 @@ def send_order( else: # Place order try: - # Add User (note is empty by default) + # Add User # Do not pass guest for privileged users (default to NotAGuest) if auth.is_guest(user=pn_user(config), config=config): new_user = models.Users( id=username_key_press, guest=person.guest, takeaway=person.takeaway, - note=note_key_press, ) else: new_user = models.Users( id=username_key_press, takeaway=person.takeaway, - note=note_key_press, ) session.add(new_user) + session.commit() # Add orders as long table (one row for each item selected by a user) - for index, row in df_order.iterrows(): + for row in df_order.itertuples(name="OrderTuple"): # Order new_order = models.Orders( user=username_key_press, lunch_time=person.lunch_time, - menu_item_id=index, + menu_item_id=row.Index, + note=row.note.lower(), ) session.add(new_order) session.commit() @@ -842,20 +857,22 @@ def df_list_by_lunch_time( select(models.Users).where(models.Users.takeaway == sql_true()) ).all() ] - # Read dataframe + # Read dataframe (including notes) df = pd.read_sql_query( config.db.orders_query.format( schema=config.db.get("schema", models.SCHEMA) ), engine, ) + # Build a dict of dataframes, one for each lunch time df_dict = {} for time in df.lunch_time.sort_values().unique(): - # Take only one lunch time + # Take only one lunch time (and remove notes so they do not alter + # numeric counters inside the pivot table) temp_df = ( df[df.lunch_time == time] - .drop(columns="lunch_time") + .drop(columns=["lunch_time", "note"]) .reset_index(drop=True) ) # Users' selections @@ -872,38 +889,58 @@ def df_list_by_lunch_time( :, [c for c in df_users.columns if c in takeaway_list] ] - def clean_up_table(config, df_in): - # Add columns of totals + def clean_up_table( + config: DictConfig, df_in: pd.DataFrame, df_complete: pd.DataFrame + ): df = df_in.copy() - df = df.astype(object) # Avoid mixed types (float and notes str) + # Group notes per menu item by concat users notes + # Use value counts to keep track of how many time a note is repeated + df_notes = ( + df_complete[ + (df_complete.lunch_time == time) + & (df_complete.note != "") + & (df_complete.user.isin(df.columns)) + ] + .drop(columns=["user", "lunch_time"]) + .value_counts() + .reset_index(level="note") + ) + df_notes.note = ( + df_notes["count"] + .astype(str) + .str.cat(df_notes.note, sep=config.panel.gui.note_sep.count) + ) + df_notes = df_notes.drop(columns="count") + df_notes = ( + df_notes.groupby("item")["note"] + .apply(config.panel.gui.note_sep.element.join) + .to_frame() + ) + # Add columns of totals df[config.panel.gui.total_column_name] = df.sum(axis=1) + # Drop unused rows if requested if config.panel.drop_unused_menu_items: df = df[df[config.panel.gui.total_column_name] > 0] - # Find users included in this lunch time - users = df.columns - # Find relevant notes - session = models.create_session(config) - - with session: - user_data = session.scalars( - select(models.Users).where(models.Users.id.in_(users)) - ).all() # Add notes - for user in user_data: - df.loc["NOTE", user.id] = user.note + df = df.join(df_notes) + df = df.rename(columns={"note": config.panel.gui.note_column_name}) # Change NaNs to '-' df = df.fillna("-") + # Avoid mixed types (float and notes str) + df = df.astype(object) return df # Clean and add resulting dataframes to dict # RESTAURANT LUNCH if not df_users_restaurant.empty: - df_users_restaurant = clean_up_table(config, df_users_restaurant) + df_users_restaurant = clean_up_table( + config, df_users_restaurant, df + ) df_dict[time] = df_users_restaurant # TAKEAWAY if not df_users_takeaways.empty: - df_users_takeaways = clean_up_table(config, df_users_takeaways) + df_users_takeaways = clean_up_table(config, df_users_takeaways, df) df_dict[f"{time} {config.panel.gui.takeaway_id}"] = ( df_users_takeaways ) @@ -930,14 +967,20 @@ def download_dataframe( if df_dict: for time, df in df_dict.items(): log.info(f"writing sheet {time}") - # users that placed an order for a given time + + # Find users that placed an order for a given time users_n = len( [ c for c in df.columns - if c != config.panel.gui.total_column_name + if c + not in ( + config.panel.gui.total_column_name, + config.panel.gui.note_column_name, + ) ] ) + # Export dataframe to new sheet worksheet_name = time.replace(":", ".") df.to_excel(writer, sheet_name=worksheet_name, startrow=1) @@ -948,7 +991,64 @@ def download_dataframe( 1, f"Time - {time} | # {users_n}", ) - # Group and hide columns, leave only ID and total + + # HEADER FORMAT + worksheet["A1"].font = Font(size=13, bold=True, color="00FF0000") + + # INDEX ALIGNMENT + for row in worksheet[worksheet.min_row : worksheet.max_row]: + cell = row[0] # column A + cell.alignment = Alignment(horizontal="left") + cell = row[users_n + 2] # column note + cell.alignment = Alignment(horizontal="left") + cells = row[1 : users_n + 2] # from column B to note-1 + for cell in cells: + cell.alignment = Alignment(horizontal="center") + + # AUTO SIZE + # Set auto-size for all columns + # Use end +1 for ID column, and +2 for 'total' and 'note' columns + column_letters = get_column_interval(start=1, end=users_n + 1 + 2) + # Get columns + columns = worksheet[column_letters[0] : column_letters[-1]] + for column_letter, column in zip(column_letters, columns): + # Instantiate max length then loop on cells to find max value + max_length = 0 + # Cell loop + for cell in column: + log.debug( + f"autosize for cell {cell.coordinate} with value '{cell.value}'" + ) + try: # Necessary to avoid error on empty cells + if len(str(cell.value)) > max_length: + max_length = len(cell.value) + log.debug(f"new max length set to {max_length}") + except Exception: + log.debug("empty cell") + log.debug(f"final max length is {max_length}") + adjusted_width = (max_length + 2) * 0.85 + log.debug( + f"adjusted width for column '{column_letter}' is {adjusted_width}" + ) + worksheet.column_dimensions[column_letter].width = ( + adjusted_width + ) + # Since grouping fix width equal to first column width (openpyxl + # bug), set first column of users' order equal to max width of + # all users columns to avoid issues + max_width = 0 + log.debug( + f"find max width for users' columns '{column_letters[1]}:{column_letters[-3]}'" + ) + for column_letter in column_letters[1:-2]: + max_width = max( + max_width, worksheet.column_dimensions[column_letter].width + ) + log.debug(f"max width for first users' columns is {max_width}") + worksheet.column_dimensions[column_letters[1]].width = max_width + + # GROUPING + # Group and hide columns, leave only ID, total and note column_letters = get_column_interval(start=2, end=users_n + 1) worksheet.column_dimensions.group( column_letters[0], column_letters[-1], hidden=True diff --git a/dlunch/gui.py b/dlunch/gui.py index e100990..30381da 100644 --- a/dlunch/gui.py +++ b/dlunch/gui.py @@ -29,6 +29,9 @@ generic_button_height = 45 sidebar_width = 400 sidebar_content_width = sidebar_width - 10 +time_col_width = 90 +time_col_spacer_width = 5 +main_area_min_width = 580 + time_col_spacer_width + time_col_width backend_min_height = 500 @@ -44,7 +47,6 @@ class Person(param.Parameterized): takeaway = param.Boolean( default=False, doc="tick to order a takeaway meal" ) - note = param.String(default="", doc="write your notes here") def __init__(self, config, **params): super().__init__(**params) @@ -349,6 +351,8 @@ def reload_on_guest_override_callback( # Create dataframe instance self.dataframe = pnw.Tabulator( name="Order", + widths={"note": 180}, + selectable=False, stylesheets=[config.panel.gui.css_files.custom_tabulator_path], ) @@ -406,17 +410,23 @@ def reload_on_guest_override_callback( self.no_menu_image, self.no_menu_image_attribution, sizing_mode="stretch_width", - min_width=465, + min_width=main_area_min_width, ) # Create column for lunch time labels - self.time_col = pn.Column(width=85) + self.time_col = pn.Column(width=time_col_width) # Create column for resulting menus - self.res_col = pn.Column(sizing_mode="stretch_width", min_width=465) + self.res_col = pn.Column( + sizing_mode="stretch_width", min_width=main_area_min_width + ) # FLEXBOXES self.menu_flexbox = pn.FlexBox( - *[self.dataframe, pn.Spacer(width=5), self.time_col], - min_width=465, + *[ + self.dataframe, + pn.Spacer(width=time_col_spacer_width), + self.time_col, + ], + min_width=main_area_min_width, ) self.buttons_flexbox = pn.FlexBox( *[ @@ -425,11 +435,11 @@ def reload_on_guest_override_callback( self.delete_order_button, ], flex_wrap="nowrap", - min_width=465, + min_width=main_area_min_width, sizing_mode="stretch_width", ) self.results_divider = pn.layout.Divider( - sizing_mode="stretch_width", min_width=465 + sizing_mode="stretch_width", min_width=main_area_min_width ) # CALLBACKS @@ -697,7 +707,7 @@ def build_order_table( config: DictConfig, df: pd.DataFrame, time: str, - guests_lists: list[str] = [], + guests_lists: dict = {}, ) -> pnw.Tabulator: # Add guest icon to users' id columns_with_guests_icons = df.columns.to_series() @@ -711,13 +721,11 @@ def build_order_table( name=time, value=df, frozen_columns=[0], - sizing_mode="stretch_width", - layout="fit_data_stretch", + layout="fit_data_table", stylesheets=[config.panel.gui.css_files.custom_tabulator_path], ) # Make the table non-editable orders_table_widget.editors = {c: None for c in df.columns} - return orders_table_widget def build_time_label( diff --git a/dlunch/models.py b/dlunch/models.py index 0595f61..9be9be3 100755 --- a/dlunch/models.py +++ b/dlunch/models.py @@ -15,6 +15,7 @@ TypeDecorator, Date, Boolean, + Identity, event, MetaData, delete, @@ -162,7 +163,7 @@ def _convert( class Menu(Data): __tablename__ = "menu" - id = Column(Integer, primary_key=True) + id = Column(Integer, Identity(start=1, cycle=True), primary_key=True) item = Column(String(250), unique=False, nullable=False) orders = relationship( "Orders", @@ -204,7 +205,7 @@ def __repr__(self): class Orders(Data): __tablename__ = "orders" - id = Column(Integer, primary_key=True) + id = Column(Integer, Identity(start=1, cycle=True), primary_key=True) user = Column( String(100), ForeignKey("users.id", ondelete="CASCADE"), @@ -218,7 +219,7 @@ class Orders(Data): nullable=False, ) menu_item = relationship("Menu", back_populates="orders") - note = relationship("Users", back_populates="orders", uselist=False) + note = Column(String(300), unique=False, nullable=True) @classmethod def clear(self, config: DictConfig) -> int: @@ -267,13 +268,6 @@ class Users(Data): takeaway = Column( Boolean, nullable=False, default=False, server_default=sql_false() ) - note = Column(String(500), unique=False, nullable=True) - orders = relationship( - "Orders", - back_populates="note", - cascade="all, delete-orphan", - passive_deletes=True, - ) @classmethod def clear(self, config: DictConfig) -> int: From 16c075311610ece0dc1d5392ea508af27cf94e02 Mon Sep 17 00:00:00 2001 From: Michele-Alberti <62114934+Michele-Alberti@users.noreply.github.com> Date: Sun, 17 Mar 2024 15:49:35 +0100 Subject: [PATCH 7/8] fix(gui-config): fix major_release and women_day config files --- dlunch/conf/panel/gui/major_release.yaml | 2 +- dlunch/conf/panel/gui/women_day.yaml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/dlunch/conf/panel/gui/major_release.yaml b/dlunch/conf/panel/gui/major_release.yaml index 6cc6b56..e28fdf7 100644 --- a/dlunch/conf/panel/gui/major_release.yaml +++ b/dlunch/conf/panel/gui/major_release.yaml @@ -2,7 +2,7 @@ defaults: - default # APP -version: 2 +version: 3 title: Data-Lunch    V${panel.gui.version} # CUSTOMIZABLE GRAPHIC OBJECTS diff --git a/dlunch/conf/panel/gui/women_day.yaml b/dlunch/conf/panel/gui/women_day.yaml index 153d08b..b0d70ee 100644 --- a/dlunch/conf/panel/gui/women_day.yaml +++ b/dlunch/conf/panel/gui/women_day.yaml @@ -19,7 +19,6 @@ header_object: style: background-color: white border-radius: 2rem - padding: 0.3rem height: 50px width: 50px display: inline-block \ No newline at end of file From 33e45a18eff552b747f25f25b2244b7e85809323 Mon Sep 17 00:00:00 2001 From: Michele-Alberti <62114934+Michele-Alberti@users.noreply.github.com> Date: Sun, 17 Mar 2024 17:00:03 +0100 Subject: [PATCH 8/8] fix: use config file to define note column name in every widget and excel table --- dlunch/core.py | 8 +++++--- dlunch/gui.py | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/dlunch/core.py b/dlunch/core.py index f3f1978..be69363 100644 --- a/dlunch/core.py +++ b/dlunch/core.py @@ -346,14 +346,14 @@ def reload_menu( ) # Add order (for selecting items) and note columns df["order"] = False - df["note"] = "" + df[config.panel.gui.note_column_name] = "" gi.dataframe.value = df gi.dataframe.formatters = {"order": {"type": "tickCross"}} gi.dataframe.editors = { "id": None, "item": None, "order": CheckboxEditor(), - "note": "input", + config.panel.gui.note_column_name: "input", } gi.dataframe.header_align = OmegaConf.to_container( config.panel.gui.menu_column_align, resolve=True @@ -693,7 +693,9 @@ def send_order( user=username_key_press, lunch_time=person.lunch_time, menu_item_id=row.Index, - note=row.note.lower(), + note=getattr( + row, config.panel.gui.note_column_name + ).lower(), ) session.add(new_order) session.commit() diff --git a/dlunch/gui.py b/dlunch/gui.py index 30381da..cb111fd 100644 --- a/dlunch/gui.py +++ b/dlunch/gui.py @@ -351,7 +351,7 @@ def reload_on_guest_override_callback( # Create dataframe instance self.dataframe = pnw.Tabulator( name="Order", - widths={"note": 180}, + widths={config.panel.gui.note_column_name: 180}, selectable=False, stylesheets=[config.panel.gui.css_files.custom_tabulator_path], )