From 8830cee1c006b45f0e0cc157dfe1d2955a1a8ee9 Mon Sep 17 00:00:00 2001 From: Michele-Alberti Date: Thu, 2 Jan 2025 11:00:05 +0000 Subject: [PATCH] Deployed bc6330f to latest with MkDocs 1.6.1 and mike 2.1.2 --- latest/reference/dlunch/core/index.html | 220 +++++++++++------------- latest/search/search_index.json | 2 +- 2 files changed, 103 insertions(+), 119 deletions(-) diff --git a/latest/reference/dlunch/core/index.html b/latest/reference/dlunch/core/index.html index cdd4815..3ba11ea 100644 --- a/latest/reference/dlunch/core/index.html +++ b/latest/reference/dlunch/core/index.html @@ -2709,11 +2709,7 @@

1285 1286 1287 -1288 -1289 -1290 -1291 -1292
class Waiter:
+1288
class Waiter:
     """Class that defines main functions used to manage Data-Lunch operations.
 
     Args:
@@ -3711,7 +3707,7 @@ 

) ).all() ] - # Read dataframe (including notes) + # Read orders dataframe (including notes) df = pd.read_sql_query( self.config.db.orders_query.format( schema=self.config.db.get("schema", models.SCHEMA) @@ -3719,6 +3715,52 @@

engine, ) + # The following function prepare the dataframe before saving it into + # the dictionary that will be returned + def _clean_up_table( + config: DictConfig, + df_in: pd.DataFrame, + df_complete: pd.DataFrame, + ): + df = df_in.copy() + # 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] + # Add notes + 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 + # Build a dict of dataframes, one for each lunch time df_dict = {} for time in df.lunch_time.sort_values().unique(): @@ -3743,56 +3785,6 @@

:, [c for c in df_users.columns if c in takeaway_list] ] - # The following function prepare the dataframe before saving it into - # the dictionary that will be returned - def _clean_up_table( - config: DictConfig, - df_in: pd.DataFrame, - df_complete: pd.DataFrame, - ): - df = df_in.copy() - # 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] - # Add notes - 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: @@ -5293,11 +5285,7 @@

1134 1135 1136 -1137 -1138 -1139 -1140 -1141

def df_list_by_lunch_time(
+1137
def df_list_by_lunch_time(
     self,
 ) -> dict:
     """Build a dictionary of dataframes for each lunch-time, with takeaways included in a dedicated dataframe.
@@ -5330,7 +5318,7 @@ 

) ).all() ] - # Read dataframe (including notes) + # Read orders dataframe (including notes) df = pd.read_sql_query( self.config.db.orders_query.format( schema=self.config.db.get("schema", models.SCHEMA) @@ -5338,6 +5326,52 @@

engine, ) + # The following function prepare the dataframe before saving it into + # the dictionary that will be returned + def _clean_up_table( + config: DictConfig, + df_in: pd.DataFrame, + df_complete: pd.DataFrame, + ): + df = df_in.copy() + # 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] + # Add notes + 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 + # Build a dict of dataframes, one for each lunch time df_dict = {} for time in df.lunch_time.sort_values().unique(): @@ -5362,56 +5396,6 @@

:, [c for c in df_users.columns if c in takeaway_list] ] - # The following function prepare the dataframe before saving it into - # the dictionary that will be returned - def _clean_up_table( - config: DictConfig, - df_in: pd.DataFrame, - df_complete: pd.DataFrame, - ): - df = df_in.copy() - # 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] - # Add notes - 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: @@ -5509,7 +5493,11 @@

Source code in dlunch/core.py -
1143
+              
1139
+1140
+1141
+1142
+1143
 1144
 1145
 1146
@@ -5654,11 +5642,7 @@ 

1285 1286 1287 -1288 -1289 -1290 -1291 -1292

def download_dataframe(
+1288
def download_dataframe(
     self,
     gi: gui.GraphicInterface,
 ) -> BytesIO:
diff --git a/latest/search/search_index.json b/latest/search/search_index.json
index 1169da4..bbea630 100644
--- a/latest/search/search_index.json
+++ b/latest/search/search_index.json
@@ -1 +1 @@
-{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Welcome to Data-Lunch documentation!","text":"

Data-Lunch is your best friends when it's vital to collect and administer lunch orders for a group of people (wether they are friends or teammates).

It started as a small personal project and it quickly became an unrepleaceable companion for me and my colleagues.

"},{"location":"#more-info","title":"More info","text":"

Work in Progress

This section is a work in progress, more info will be available in the future!

"},{"location":"developers/","title":"Developers Instructions","text":""},{"location":"developers/#development-environment-setup","title":"Development environment setup","text":"

The following steps will guide you through the installation procedure.

"},{"location":"developers/#miniconda","title":"Miniconda","text":"

Conda is required for creating the development environment (it is suggested to install Miniconda).

"},{"location":"developers/#setup-the-development-environment","title":"Setup the development environment","text":"

Use the setup script (setup_dev_env.sh) to install all the required development tools.

Use source to properly launch the script.

source setup_dev_env.sh\n

Important

The setup script will take care of setting up the development environment for you. The script installs:

  • 3 environments (data-lunch for development, ci-cd for pre-commit and other utilities, gc-sdk for interacting with Google Cloud Platform)
  • pre-commit hooks
  • data-lunch command line
"},{"location":"developers/#environment-variables","title":"Environment variables","text":"

The following environment variables are required for running the web app, the makefile or utility scripts.

"},{"location":"developers/#general","title":"General","text":"Variable Type Required Description PANEL_APP str \u2714\ufe0f app name, data-lunch-app by default (used by makefile) PANEL_ENV str \u2714\ufe0f environment, e.g. development, quality, production, affects app configuration (Hydra) and build processes (makefile) PANEL_ARGS str \u274c additional arguments passed to Hydra (e.g. panel/gui=major_release) in makefile and docker-compose commands PORT int \u274c port used by the web app (or the container), default to 5000; affects app configuration and build process (it is used by makefile, Hydra and Docker)"},{"location":"developers/#docker-and-google-cloud-platform","title":"Docker and Google Cloud Platform","text":"

Note

The following variables are mainly used during the building process or by external scripts.

Variable Type Required Description DOCKER_USERNAME str \u274c your Docker Hub username, used by makefile and stats panel to extract container name IMAGE_VERSION str \u274c Docker image version, typically stable or latest (used by makefile and docker-compose commands) GCLOUD_PROJECT str \u274c Google Cloud Platform project_id, used by makefile for GCP's CLI authentication and for uploading the database to gcp storage, if active in web app configuration files (see panel.scheduled_tasks) GCLOUD_BUCKET str \u274c Google Cloud Platform bucket, used for uploading database to gcp storage, if active in web app configuration files (see panel.scheduled_tasks) MAIL_USER str \u274c email client user, used for sending emails containing the instance IP, e.g.mywebappemail@email.com (used only for Google Cloud Platform) MAIL_APP_PASSWORD str \u274c email client password used for sending emails containing the instance IP (used only for Google Cloud Platform) MAIL_RECIPIENTS str \u274c email recipients as string, separated by , (used for sending emails containing the instance IP when hosted on Google Cloud Platform) DUCKDNS_URL str \u274c URL used in compose_init.sh to update dynamic address (see Duck DNS's instructions for details, used when hosted on Google Cloud Platform)"},{"location":"developers/#tlsssl-certificate","title":"TLS/SSL Certificate","text":"

Tip

Use the command make ssl-gen-certificate to generate local SSL certificates.

Variable Type Required Description CERT_EMAIL str \u274c email for registering SSL certificates, shared with the authority Let's Encrypt (via certbot); used by docker-compose commands DOMAIN str \u274c domain name, e.g. mywebapp.com, used by docker-compose commands (certbot), in email generation (scripts folder) and to auto-generate SSL certificates"},{"location":"developers/#encryption-and-authorization","title":"Encryption and Authorization","text":"

Note

All variables used by Postgresql are not required if db=sqlite (default value). All variables used by OAuth are not required if server=no_auth (default value).

Important

DATA_LUNCH_COOKIE_SECRET and DATA_LUNCH_OAUTH_ENC_KEY are required even if server=no_auth is set.

Variable Type Required Description DATA_LUNCH_COOKIE_SECRET str \u2714\ufe0f Secret used for securing the authentication cookie (use make generate-secrets to generate a valid secret) DATA_LUNCH_OAUTH_ENC_KEY str \u2714\ufe0f Encription key used by the OAuth algorithm for encryption (use make generate-secrets to generate a valid secret) DATA_LUNCH_OAUTH_KEY str \u274c OAuth key used for configuring the OAuth provider (GitHub, Azure) DATA_LUNCH_OAUTH_SECRET str \u274c OAuth secret used for configuring the OAuth provider (GitHub, Azure) DATA_LUNCH_OAUTH_REDIRECT_URI str \u274c OAuth redirect uri used for configuring the OAuth provider (GitHub, Azure), do not set to use default value DATA_LUNCH_OAUTH_TENANT_ID str \u274c OAuth tenant id used for configuring the OAuth provider (Azure), do not set to use default value DATA_LUNCH_DB_USER str \u274c Postgresql user, do not set to use default value DATA_LUNCH_DB_PASSWORD str \u274c Postgresql password DATA_LUNCH_DB_HOST str \u274c Postgresql host, do not set to use default value DATA_LUNCH_DB_PORT str \u274c Postgresql port, do not set to use default value DATA_LUNCH_DB_DATABASE str \u274c Postgresql database, do not set to use default value DATA_LUNCH_DB_SCHEMA str \u274c Postgresql schema, do not set to use default value"},{"location":"developers/#manually-install-the-development-environment","title":"Manually install the development environment","text":"

Warning

This step is not required if the setup script (setup_dev_env.sh) is used.

Use the terminal for navigating to the repository base directory. Use the following command in your terminal to create an environment named data-lunch manually. Otherwise use the setup script to activate the guided installing procedure.

conda env create -f environment.yml\n

Activate the new Conda environment with the following command.

conda activate data-lunch\n
"},{"location":"developers/#manually-install-data-lunch-cli","title":"Manually install data-lunch CLI","text":"

Warning

This step is not required if the setup script (setup_dev_env.sh) is used.

The CLI is distributed with setuptools instead of using Unix shebangs. It is a very simple utility to initialize and delete the app database. There are different use cases:

  • Create/delete the sqlite database used by the app
  • Initialize/drop tables inside the sqlite database

Use the following command for generating the CLI executable from the setup.py file, it will install your package locally.

pip install .\n

If you want to make some changes to the source code it is suggested to use the following option.

pip install --editable .\n

It will just link the package to the original location, basically meaning any changes to the original package would reflect directly in your environment.

Now you can activate the Conda environment and access the CLI commands directly from the terminal (without using annoying shebangs or prepending python to run your CLI calls).

Test that everything is working correctly with the following commands.

data-lunch --version\ndata-lunch --help\n
"},{"location":"developers/#running-the-docker-compose-system","title":"Running the docker-compose system","text":"

Since this app will be deployed with an hosting service a Dockerfile to build a container image is available. The docker compose file (see docker-compose.yaml) deploys the web app container along with a load balancer (the nginx container) to improve the system scalability.

Look inside the makefile to see the docker and docker-compose options.

To build and run the dockerized system you have to install Docker. Call the following make command to start the build process.

make docker-up-init docker-up-build\n

up-init initialize the ssl certificate based on the selected environment (as set in the environment variable PANEL_ENV, i.e. development or production). Call only make (without arguments) to trigger the same command. A missing or incomplete ssl certificate folder will result in an nginx container failure on start-up.

Images are built and the two containers are started.

You can then access your web app at http://localhost:PORT (where PORT will match the value set through the environment variable).

Note

You can also use make docker-up to spin up the containers if you do not need to re-build any image or initialize ssl certificate folders.

Important

An additional container named db is started if db=postgresql is set

"},{"location":"developers/#running-a-single-container","title":"Running a single container","text":"

It is possible to launch a single server by calling the following command.

make docker-build\n\nmake docker-run\n

You can then access your web app at http://localhost:5000 (if the deafult PORT is selected).

"},{"location":"developers/#running-locally","title":"Running locally","text":"

Launch a local server with default options by calling the following command.

python -m dlunch\n

Use Hydra arguments to alter the app behaviour.

python -m dlunch server=basic_auth\n

See Hydra's documentation for additional details.

"},{"location":"developers/#additional-installations-before-contributing","title":"Additional installations before contributing","text":"

Warning

This step is not required if the setup script (setup_dev_env.sh) is used.

Before contributing please create the pre-commit and commitizen environments.

cd requirements\nconda env create -f pre-commit.yml\nconda env create -f commitizen.yml\n
"},{"location":"developers/#pre-commit-hooks","title":"Pre-commit hooks","text":"

Warning

This step is not required if the setup script (setup_dev_env.sh) is used.

Then install the precommit hooks.

conda activate pre-commit\npre-commit install\npre-commit autoupdate\n

Optionally run hooks on all files.

pre-commit run --all-files\n
"},{"location":"developers/#commitizen","title":"Commitizen","text":"

Warning

This step is not required if the setup script (setup_dev_env.sh) is used.

The Commitizen hook checks that rules for conventional commits are respected in commits messages. Use the following command to enjoy Commitizen's interactive prompt.

conda activate commitizen\ncz commit\n

cz c is a shorther alias for cz commit.

"},{"location":"developers/#release-strategy-from-development-to-main-branch","title":"Release strategy from development to main branch","text":"

Caution

This step is required only if the CI-CD pipeline on GitHub does not work.

In order to take advantage of Commitizen bump command follow this guideline.

First check that you are on the correct branch.

git checkout main\n

Then start the merge process forcing it to stop before commit (--no-commit) and without using the fast forward strategy (--no-ff).

git merge development --no-commit --no-ff\n

Check that results match your expectations then commit (you can leave the default message).

git commit\n

Now Commitizen bump command will add an additional commit with updated versions to every file listed inside pyproject.toml.

cz bump --no-verify\n

You can now merge results of the release process back to the development branch.

git checkout development\ngit merge main --no-ff\n

Use \"update files from last release\" or the default text as commit message.

"},{"location":"developers/#google-cloud-platform-utilities","title":"Google Cloud Platform utilities","text":"

Warning

This step is not required if the setup script (setup_dev_env.sh) is used.

The makefile has two rules for conviniently setting up and removing authentication credentials for Google Cloud Platform command line interface: gcp-config and gcp-revoke.

"},{"location":"getting_started/","title":"Getting Started","text":""},{"location":"getting_started/#introduction","title":"Introduction","text":"

Data-Lunch is a web app to help people managing lunch orders. The interface make possible to upload a menu as an Excel file or as an image. The menu is copied inside the app and people are then able to place orders at preset lunch times. Users can flag an order as a takeway one, and, if something change, they can move an order to another lunch time (or change its takeaway status).

The systems handle guests users by giving them a limited ability to interact with the app (they can in fact just place an order).

Once all order are placed it is possible to stop new orders from being placed and download an Excel files with every order, grouped by lunch time and takeaway status.

The idea behind this app is to simplify data collection at lunch time, so that a single person can book a restaurant and place orders for a group of people (friends, colleagues, etc.).

Important

This section is a work in progress, the app has a lot of configurations, not yet described in this documentation. Authentication and guest management are just examples of what is missing from this documentation.

"},{"location":"getting_started/#installation","title":"Installation","text":"

Install commitizen using pip:

 pip install dlunch\n

For the program to work properly you need the following system dipendencies:

  • SQLite: used if you set the database to sqlite.
  • Tesseract: used to convert images with menu to text.

If you need help on how to install those system preferences on linux (Debian) you can check the file docker/web/Dockerfile.web. It shows how to install them with apt.

"},{"location":"getting_started/#usage","title":"Usage","text":"

Before starting you need the following environment variables to avoid errors on Data-Lunch startup.

"},{"location":"getting_started/#environment-variables","title":"Environment variables","text":"

Important

DATA_LUNCH_COOKIE_SECRET and DATA_LUNCH_OAUTH_ENC_KEY are required even if server=no_auth is set.

Tip

Use the command data-lunch utils generate-secrets to generate a valid secret.

Variable Type Required Description PANEL_ENV str \u2714\ufe0f Environment, e.g. development, quality, production, affects app configuration (Hydra) PORT int \u274c Port used by the web app (or the container), default to 5000; affects app configuration (it is used by Hydra) DATA_LUNCH_COOKIE_SECRET str \u2714\ufe0f Secret used for securing the authentication cookie (use data-lunch utils generate-secrets to generate a valid secret); leave it as an empty variable if no authentication is set DATA_LUNCH_OAUTH_ENC_KEY str \u2714\ufe0f Encription key used by the OAuth algorithm for encryption (use data-lunch utils generate-secrets to generate a valid secret); leave it as an empty variable if no authentication is set

Note

The following variables are just a small part of the total. See here for more details.

"},{"location":"getting_started/#launch-command","title":"Launch command","text":"

Use the following command to start the server with default configuration (SQLite database, no authentication):

python -m dlunch\n

Connect to localhost:5000 (if the default port is used) to see the web app.

"},{"location":"getting_started/#customization","title":"Customization","text":"

Data-Lunch configurations explout Hydra versatility. It is possible to alter default configurations by using Hydra's overrides, for example

python -m dlunch panel/gui=major_release\n

will alter some graphic elements to show a release banner.

"},{"location":"getting_started/#docker","title":"Docker","text":"

A Docker image with Data-Lunch is available here.

"},{"location":"reference/SUMMARY/","title":"SUMMARY","text":"
  • dlunch
    • __main__
    • auth
    • cli
    • cloud
    • conf
    • core
    • gui
    • models
    • scheduled_tasks
"},{"location":"reference/dlunch/","title":"dlunch","text":"

Main Data-Lunch package.

Modules:

Name Description __main__

Data-Lunch package entrypoint.

auth

Module with classes and functions used for authentication and password handling.

cli

Module with Data-Lunch's command line.

cloud

Module with functions to interact with GCP storage service.

conf

Package with Hydra configuration yaml files.

core

Module that defines main functions used to manage Data-Lunch operations.

gui

Module that defines main graphic interface and backend graphic interface.

models

Module with database tables definitions.

scheduled_tasks

Module with functions used to execute scheduled tasks.

Functions:

Name Description create_app

Panel main app factory function

create_backend

Panel backend app factory function

Attributes:

Name Type Description log Logger

Module logger.

waiter"},{"location":"reference/dlunch/#dlunch.log","title":"log module-attribute","text":"
log: Logger = getLogger(__name__)\n

Module logger.

"},{"location":"reference/dlunch/#dlunch.waiter","title":"waiter module-attribute","text":"
waiter = Waiter()\n
"},{"location":"reference/dlunch/#dlunch.create_app","title":"create_app","text":"
create_app(config: DictConfig) -> Template\n

Panel main app factory function

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description Template

Panel main app template.

Source code in dlunch/__init__.py
def create_app(config: DictConfig) -> pn.Template:\n    \"\"\"Panel main app factory function\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        pn.Template: Panel main app template.\n    \"\"\"\n    log.info(\"starting initialization process\")\n\n    log.info(\"initialize database\")\n    # Create tables\n    models.create_database(\n        config, add_basic_auth_users=auth.is_basic_auth_active(config=config)\n    )\n\n    log.info(\"initialize support variables\")\n    # Generate a random password only if requested (check on flag)\n    log.debug(\"config guest user\")\n    guest_password = auth.set_guest_user_password(config)\n\n    log.info(\"instantiate app\")\n\n    # Panel configurations\n    log.debug(\"set toggles initial state\")\n    # Set the no_more_orders flag if it is None (not found in flags table)\n    if models.get_flag(config=config, id=\"no_more_orders\") is None:\n        models.set_flag(config=config, id=\"no_more_orders\", value=False)\n    # Set guest override flag if it is None (not found in flags table)\n    # Guest override flag is per-user and is not set for guests\n    if (\n        models.get_flag(config=config, id=f\"{pn_user(config)}_guest_override\")\n        is None\n    ) and not auth.is_guest(\n        user=pn_user(config), config=config, allow_override=False\n    ):\n        models.set_flag(\n            config=config, id=f\"{pn_user(config)}_guest_override\", value=False\n        )\n\n    # DASHBOARD BASE TEMPLATE\n    log.debug(\"instantiate base template\")\n    # Create web app template\n    app = pn.template.VanillaTemplate(\n        title=config.panel.gui.title,\n        sidebar_width=gui.sidebar_width,\n        favicon=config.panel.gui.favicon_path,\n        logo=config.panel.gui.logo_path,\n        css_files=OmegaConf.to_container(\n            config.panel.gui.template_css_files, resolve=True\n        ),\n        raw_css=OmegaConf.to_container(\n            config.panel.gui.template_raw_css, resolve=True\n        ),\n    )\n\n    # CONFIGURABLE OBJECTS\n    # Since Person class need the config variable for initialization, every\n    # graphic element that require the Person class has to be instantiated\n    # by a dedicated function\n    # Create person instance, widget and column\n    log.debug(\"instantiate person class and graphic graphic interface\")\n    person = gui.Person(config, name=\"User\")\n    gi = gui.GraphicInterface(\n        config=config,\n        waiter=waiter,\n        app=app,\n        person=person,\n        guest_password=guest_password,\n    )\n\n    # DASHBOARD\n    # Build dashboard (the header object is used if defined)\n    app.header.append(gi.header_row)\n    app.sidebar.append(gi.sidebar_tabs)\n    app.main.append(gi.no_menu_col)\n    app.main.append(gi.guest_override_alert)\n    app.main.append(gi.no_more_order_alert)\n    app.main.append(gi.main_header_row)\n    app.main.append(gi.quote)\n    app.main.append(pn.Spacer(height=15))\n    app.main.append(gi.menu_flexbox)\n    app.main.append(gi.buttons_flexbox)\n    app.main.append(gi.results_divider)\n    app.main.append(gi.res_col)\n    app.modal.append(gi.error_message)\n\n    # Set components visibility based on no_more_order_button state\n    # and reload menu\n    gi.reload_on_no_more_order(\n        toggle=models.get_flag(config=config, id=\"no_more_orders\"),\n        reload=False,\n    )\n    gi.reload_on_guest_override(\n        toggle=models.get_flag(\n            config=config,\n            id=f\"{pn_user(config)}_guest_override\",\n            value_if_missing=False,\n        ),\n        reload=False,\n    )\n    waiter.reload_menu(\n        None,\n        gi,\n    )\n\n    app.servable()\n\n    log.info(\"initialization process completed\")\n\n    return app\n
"},{"location":"reference/dlunch/#dlunch.create_backend","title":"create_backend","text":"
create_backend(config: DictConfig) -> Template\n

Panel backend app factory function

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description Template

Panel backend app template.

Source code in dlunch/__init__.py
def create_backend(config: DictConfig) -> pn.Template:\n    \"\"\"Panel backend app factory function\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        pn.Template: Panel backend app template.\n    \"\"\"\n\n    log.info(\"starting initialization process\")\n\n    log.info(\"initialize database\")\n    # Create tables\n    models.create_database(\n        config, add_basic_auth_users=auth.is_basic_auth_active(config=config)\n    )\n\n    log.info(\"instantiate backend\")\n\n    # DASHBOARD\n    log.debug(\"instantiate base template\")\n    # Create web app template\n    backend = pn.template.VanillaTemplate(\n        title=f\"{config.panel.gui.title} Backend\",\n        favicon=config.panel.gui.favicon_path,\n        logo=config.panel.gui.logo_path,\n        css_files=OmegaConf.to_container(\n            config.panel.gui.template_css_files, resolve=True\n        ),\n        raw_css=OmegaConf.to_container(\n            config.panel.gui.template_raw_css, resolve=True\n        ),\n    )\n\n    # CONFIGURABLE OBJECTS\n    backend_gi = gui.BackendInterface(config)\n\n    # DASHBOARD\n    # Build dashboard\n    backend.header.append(backend_gi.header_row)\n    backend.main.append(backend_gi.backend_controls)\n\n    backend.servable()\n\n    log.info(\"initialization process completed\")\n\n    return backend\n
"},{"location":"reference/dlunch/__main__/","title":"__main__","text":"

Data-Lunch package entrypoint.

Modules:

Name Description auth

Module with classes and functions used for authentication and password handling.

models

Module with database tables definitions.

Functions:

Name Description run_app

Main entrypoint, start the app loop.

schedule_task

Schedule a task execution using Panel.

Attributes:

Name Type Description log Logger

Module logger.

"},{"location":"reference/dlunch/__main__/#dlunch.__main__.log","title":"log module-attribute","text":"
log: Logger = getLogger(__name__)\n

Module logger.

"},{"location":"reference/dlunch/__main__/#dlunch.__main__.run_app","title":"run_app","text":"
run_app(config: DictConfig) -> None\n

Main entrypoint, start the app loop.

Initialize database, authentication and encription. Setup panel and create objects for frontend and backend.

Parameters:

Name Type Description Default config DictConfig

hydra configuration object.

required Source code in dlunch/__main__.py
@hydra.main(config_path=\"conf\", config_name=\"config\", version_base=\"1.3\")\ndef run_app(config: DictConfig) -> None:\n    \"\"\"Main entrypoint, start the app loop.\n\n    Initialize database, authentication and encription.\n    Setup panel and create objects for frontend and backend.\n\n    Args:\n        config (DictConfig): hydra configuration object.\n    \"\"\"\n\n    # Configure waiter\n    log.info(\"set waiter config\")\n    waiter.set_config(config)\n\n    # Set auth configurations\n    log.info(\"set auth config and encryption\")\n    # Auth encryption\n    auth.set_app_auth_and_encryption(config)\n    log.debug(\n        f'authentication {\"\" if auth.is_auth_active(config) else \"not \"}active'\n    )\n\n    log.info(\"set panel config\")\n    # Set notifications options\n    pn.extension(\n        disconnect_notification=config.panel.notifications.disconnect_notification,\n        ready_notification=config.panel.notifications.ready_notification,\n    )\n    # Configurations\n    pn.config.nthreads = config.panel.nthreads\n    pn.config.notifications = True\n    authorize_callback_factory = hydra.utils.call(\n        config.auth.authorization_callback, config\n    )\n    pn.config.authorize_callback = lambda ui, tp: authorize_callback_factory(\n        user_info=ui, target_path=tp\n    )\n    pn.config.auth_template = config.auth.auth_error_template\n\n    # If basic auth is used the database and users credentials shall be created here\n    if auth.is_basic_auth_active:\n        log.info(\"initialize database and users credentials for basic auth\")\n        # Create tables\n        models.create_database(\n            config,\n            add_basic_auth_users=auth.is_basic_auth_active(config=config),\n        )\n\n    # Starting scheduled tasks\n    if config.panel.scheduled_tasks:\n        log.info(\"starting scheduled tasks\")\n        for task in config.panel.scheduled_tasks:\n            schedule_task(\n                **task.kwargs,\n                callable=hydra.utils.instantiate(task.callable, config),\n            )\n\n    # Call the app factory function\n    log.info(\"set config for app factory function\")\n    # Pass the create_app and create_backend function as a lambda function to\n    # ensure that each invocation has a dedicated state variable (users'\n    # selections are not shared between instances)\n    # Backend exist only if auth is active\n    # Health is an endpoint for app health assessments\n    # Pass a dictionary for a multipage app\n    pages = {\"\": lambda: create_app(config=config)}\n    if auth.is_auth_active(config=config):\n        pages[\"backend\"] = lambda: create_backend(config=config)\n\n    # If basic authentication is active, instantiate ta special auth object\n    # otherwise leave an empty dict\n    # This step is done before panel.serve because auth_provider requires that\n    # the whole config is passed as an input\n    if auth.is_basic_auth_active(config=config):\n        auth_object = {\n            \"auth_provider\": hydra.utils.instantiate(\n                config.basic_auth.auth_provider, config\n            )\n        }\n        log.debug(\n            \"auth_object dict set to instantiated object from config.server.auth_provider\"\n        )\n    else:\n        auth_object = {}\n        log.debug(\n            \"missing config.server.auth_provider, auth_object dict left empty\"\n        )\n\n    # Set session begin/end logs\n    pn.state.on_session_created(lambda ctx: log.debug(\"session created\"))\n    pn.state.on_session_destroyed(lambda ctx: log.debug(\"session closed\"))\n\n    pn.serve(\n        panels=pages, **hydra.utils.instantiate(config.server), **auth_object\n    )\n
"},{"location":"reference/dlunch/__main__/#dlunch.__main__.schedule_task","title":"schedule_task","text":"
schedule_task(\n    name: str,\n    enabled: bool,\n    hour: int | None,\n    minute: int | None,\n    period: str,\n    callable: Callable,\n) -> None\n

Schedule a task execution using Panel.

Parameters:

Name Type Description Default name str

task name (used for logs).

required enabled bool

flag that marks a task as enabled. tasks are not executed if disabled.

required hour int | None

start hour (used only if also minute is not None).

required minute int | None

start minute (used only if also hour is not None).

required period str

the period between executions. May be expressed as a timedelta or a string.

  • Week: '1w'
  • Day: '1d'
  • Hour: '1h'
  • Minute: '1m'
  • Second: '1s'
required callable Callable

the task to be executed.

required Source code in dlunch/__main__.py
def schedule_task(\n    name: str,\n    enabled: bool,\n    hour: int | None,\n    minute: int | None,\n    period: str,\n    callable: Callable,\n) -> None:\n    \"\"\"Schedule a task execution using Panel.\n\n    Args:\n        name (str): task name (used for logs).\n        enabled (bool): flag that marks a task as enabled.\n            tasks are not executed if disabled.\n        hour (int | None): start hour (used only if also minute is not None).\n        minute (int | None): start minute (used only if also hour is not None).\n        period (str): the period between executions.\n            May be expressed as a timedelta or a string.\n\n            * Week: `'1w'`\n            * Day: `'1d'`\n            * Hour: `'1h'`\n            * Minute: `'1m'`\n            * Second: `'1s'`\n\n        callable (Callable): the task to be executed.\n    \"\"\"\n    # Starting scheduled tasks (if enabled)\n    if enabled:\n        log.info(f\"starting task '{name}'\")\n        if (hour is not None) and (minute is not None):\n            start_time = dt.datetime.today().replace(\n                hour=hour,\n                minute=minute,\n            )\n            # Set start time to tomorrow if the time already passed\n            if start_time < dt.datetime.now():\n                start_time = start_time + dt.timedelta(days=1)\n            log.info(\n                f\"starting time: {start_time.strftime('%Y-%m-%d %H:%M')} - period: {period}\"\n            )\n        else:\n            start_time = None\n            log.info(f\"starting time: now - period: {period}\")\n        pn.state.schedule_task(\n            f\"data_lunch_{name}\",\n            callable,\n            period=period,\n            at=start_time,\n        )\n
"},{"location":"reference/dlunch/auth/","title":"auth","text":"

Module with classes and functions used for authentication and password handling.

Modules:

Name Description gui

Module that defines main graphic interface and backend graphic interface.

models

Module with database tables definitions.

Classes:

Name Description DataLunchLoginHandler

Custom Panel login Handler.

DataLunchProvider

Custom Panel auth provider.

PasswordEncrypt

Class that store the encrypted value of a password.

PasswordHash

Class that store the hashed value of a password.

Functions:

Name Description add_privileged_user

Add user id to privileged_users table.

add_user_hashed_password

Add user credentials to credentials table.

authorize

Authorization callback: read config, user info and the target path of the

backend_submit_password

Submit password to database from backend but used also from frontend as

force_logout

Redirect the browser to the logout endpoint

generate_password

summary

get_hash_from_user

Query the database to retrieve the hashed password for a certain user.

is_admin

Check if a user is an admin by checking the privileged_users table

is_auth_active

Check configuration dictionary and return True if basic authentication or OAuth is active.

is_basic_auth_active

Check config object and return True if basic authentication is active.

is_guest

Check if a user is a guest by checking if it is listed inside the privileged_users table.

list_users

List only privileged users (from privileged_users table).

list_users_guests_and_privileges

Join privileged_users and credentials tables to list normal users,

open_backend

Redirect the browser to the backend endpoint

pn_user

Return the user from Panel state object.

remove_user

Remove user from the database.

set_app_auth_and_encryption

Setup Panel authorization and encryption.

set_guest_user_password

If guest user is active return a password, otherwise return an empty string.

submit_password

Same as backend_submit_password with an additional check on old

Attributes:

Name Type Description log Logger

Module logger.

pwd_context CryptContext

Crypt context with configurations for passlib (selected algorithm, etc.).

"},{"location":"reference/dlunch/auth/#dlunch.auth.log","title":"log module-attribute","text":"
log: Logger = getLogger(__name__)\n

Module logger.

"},{"location":"reference/dlunch/auth/#dlunch.auth.pwd_context","title":"pwd_context module-attribute","text":"
pwd_context: CryptContext = CryptContext(\n    schemes=[\"pbkdf2_sha256\"], deprecated=\"auto\"\n)\n

Crypt context with configurations for passlib (selected algorithm, etc.).

"},{"location":"reference/dlunch/auth/#dlunch.auth.DataLunchLoginHandler","title":"DataLunchLoginHandler","text":"

Bases: RequestHandler

Custom Panel login Handler.

This class run the user authentication process for Data-Lunch when basic authentication is selected in configuration options.

It is responsible of rendering the login page, validate the user (and update its password hash if the hashing protocol is superseeded) and set the current user once validated.

Methods:

Name Description check_permission

Validate user.

get

Render the login template.

post

Validate user and set the current user if valid.

set_current_user

Set secure cookie for the selected user.

Source code in dlunch/auth.py
class DataLunchLoginHandler(RequestHandler):\n    \"\"\"Custom Panel login Handler.\n\n    This class run the user authentication process for Data-Lunch when basic authentication\n    is selected in configuration options.\n\n    It is responsible of rendering the login page, validate the user (and update its\n    password hash if the hashing protocol is superseeded) and set the current user once validated.\n    \"\"\"\n\n    def get(self) -> None:\n        \"\"\"Render the login template.\"\"\"\n        try:\n            errormessage = self.get_argument(\"error\")\n        except Exception:\n            errormessage = \"\"\n        html = self._login_template.render(errormessage=errormessage)\n        self.write(html)\n\n    def check_permission(self, user: str, password: str) -> bool:\n        \"\"\"Validate user.\n\n        Automatically update the password hash if it was generated by an old hashing protocol.\n\n        Args:\n            user (str): username.\n            password (str): password (not hashed).\n\n        Returns:\n            bool: user authentication flag (`True` if authenticated)\n        \"\"\"\n        password_hash = get_hash_from_user(user, self.config)\n        if password_hash == password:\n            # Check if hash needs update\n            valid, new_hash = password_hash.verify_and_update(password)\n            if valid and new_hash:\n                # Update to new hash\n                add_user_hashed_password(user, password, config=self.config)\n            # Return the OK value\n            return True\n        # Return the NOT OK value\n        return False\n\n    def post(self) -> None:\n        \"\"\"Validate user and set the current user if valid.\"\"\"\n        username = self.get_argument(\"username\", \"\")\n        password = self.get_argument(\"password\", \"\")\n        auth = self.check_permission(username, password)\n        if auth:\n            self.set_current_user(username)\n            self.redirect(\"/\")\n        else:\n            error_msg = \"?error=\" + tornado.escape.url_escape(\n                \"Login incorrect\"\n            )\n            self.redirect(\"/login\" + error_msg)\n\n    def set_current_user(self, user: str):\n        \"\"\"Set secure cookie for the selected user.\n\n        Args:\n            user (str): username.\n        \"\"\"\n        if not user:\n            self.clear_cookie(\"user\")\n            return\n        self.set_secure_cookie(\n            \"user\",\n            user,\n            expires_days=pn.config.oauth_expiry,\n            **self.config.auth.cookie_kwargs,\n        )\n        id_token = base64url_encode(json.dumps({\"user\": user}))\n        if pn.state.encryption:\n            id_token = pn.state.encryption.encrypt(id_token.encode(\"utf-8\"))\n        self.set_secure_cookie(\n            \"id_token\",\n            id_token,\n            expires_days=pn.config.oauth_expiry,\n            **self.config.auth.cookie_kwargs,\n        )\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.DataLunchLoginHandler.check_permission","title":"check_permission","text":"
check_permission(user: str, password: str) -> bool\n

Validate user.

Automatically update the password hash if it was generated by an old hashing protocol.

Parameters:

Name Type Description Default user str

username.

required password str

password (not hashed).

required

Returns:

Type Description bool

user authentication flag (True if authenticated)

Source code in dlunch/auth.py
def check_permission(self, user: str, password: str) -> bool:\n    \"\"\"Validate user.\n\n    Automatically update the password hash if it was generated by an old hashing protocol.\n\n    Args:\n        user (str): username.\n        password (str): password (not hashed).\n\n    Returns:\n        bool: user authentication flag (`True` if authenticated)\n    \"\"\"\n    password_hash = get_hash_from_user(user, self.config)\n    if password_hash == password:\n        # Check if hash needs update\n        valid, new_hash = password_hash.verify_and_update(password)\n        if valid and new_hash:\n            # Update to new hash\n            add_user_hashed_password(user, password, config=self.config)\n        # Return the OK value\n        return True\n    # Return the NOT OK value\n    return False\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.DataLunchLoginHandler.get","title":"get","text":"
get() -> None\n

Render the login template.

Source code in dlunch/auth.py
def get(self) -> None:\n    \"\"\"Render the login template.\"\"\"\n    try:\n        errormessage = self.get_argument(\"error\")\n    except Exception:\n        errormessage = \"\"\n    html = self._login_template.render(errormessage=errormessage)\n    self.write(html)\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.DataLunchLoginHandler.post","title":"post","text":"
post() -> None\n

Validate user and set the current user if valid.

Source code in dlunch/auth.py
def post(self) -> None:\n    \"\"\"Validate user and set the current user if valid.\"\"\"\n    username = self.get_argument(\"username\", \"\")\n    password = self.get_argument(\"password\", \"\")\n    auth = self.check_permission(username, password)\n    if auth:\n        self.set_current_user(username)\n        self.redirect(\"/\")\n    else:\n        error_msg = \"?error=\" + tornado.escape.url_escape(\n            \"Login incorrect\"\n        )\n        self.redirect(\"/login\" + error_msg)\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.DataLunchLoginHandler.set_current_user","title":"set_current_user","text":"
set_current_user(user: str)\n

Set secure cookie for the selected user.

Parameters:

Name Type Description Default user str

username.

required Source code in dlunch/auth.py
def set_current_user(self, user: str):\n    \"\"\"Set secure cookie for the selected user.\n\n    Args:\n        user (str): username.\n    \"\"\"\n    if not user:\n        self.clear_cookie(\"user\")\n        return\n    self.set_secure_cookie(\n        \"user\",\n        user,\n        expires_days=pn.config.oauth_expiry,\n        **self.config.auth.cookie_kwargs,\n    )\n    id_token = base64url_encode(json.dumps({\"user\": user}))\n    if pn.state.encryption:\n        id_token = pn.state.encryption.encrypt(id_token.encode(\"utf-8\"))\n    self.set_secure_cookie(\n        \"id_token\",\n        id_token,\n        expires_days=pn.config.oauth_expiry,\n        **self.config.auth.cookie_kwargs,\n    )\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.DataLunchProvider","title":"DataLunchProvider","text":"

Bases: OAuthProvider

Custom Panel auth provider.

It's a simple login page with a form that interacts with authentication tables.

It is used only if basic authentication is selected in Data-Lunch configuration options.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required login_template str | None

path to login template. Defaults to None.

None logout_template str | None

path to logout template. Defaults to None.

None

Methods:

Name Description __init__

Attributes:

Name Type Description config DictConfig

Hydra configuration dictionary.

login_handler DataLunchLoginHandler

Data-Lunch custom login handler.

login_url str

Login url (/login).

Source code in dlunch/auth.py
class DataLunchProvider(OAuthProvider):\n    \"\"\"Custom Panel auth provider.\n\n    It's a simple login page with a form that interacts with authentication tables.\n\n    It is used only if basic authentication is selected in Data-Lunch configuration options.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n        login_template (str | None, optional): path to login template. Defaults to None.\n        logout_template (str | None, optional): path to logout template. Defaults to None.\n    \"\"\"\n\n    def __init__(\n        self,\n        config: DictConfig,\n        login_template: str | None = None,\n        logout_template: str | None = None,\n    ) -> None:\n        # Set Hydra config info\n        self.config: DictConfig = config\n        \"\"\"Hydra configuration dictionary.\"\"\"\n\n        super().__init__(\n            login_template=login_template, logout_template=logout_template\n        )\n\n    @property\n    def login_url(self) -> str:\n        \"\"\"Login url (`/login`).\"\"\"\n        return \"/login\"\n\n    @property\n    def login_handler(self) -> DataLunchLoginHandler:\n        \"\"\"Data-Lunch custom login handler.\"\"\"\n        # Set basic template\n        DataLunchLoginHandler._login_template = self._login_template\n        # Set Hydra config info\n        DataLunchLoginHandler.config = self.config\n\n        return DataLunchLoginHandler\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.DataLunchProvider.config","title":"config instance-attribute","text":"
config: DictConfig = config\n

Hydra configuration dictionary.

"},{"location":"reference/dlunch/auth/#dlunch.auth.DataLunchProvider.login_handler","title":"login_handler property","text":"
login_handler: DataLunchLoginHandler\n

Data-Lunch custom login handler.

"},{"location":"reference/dlunch/auth/#dlunch.auth.DataLunchProvider.login_url","title":"login_url property","text":"
login_url: str\n

Login url (/login).

"},{"location":"reference/dlunch/auth/#dlunch.auth.DataLunchProvider.__init__","title":"__init__","text":"
__init__(\n    config: DictConfig,\n    login_template: str | None = None,\n    logout_template: str | None = None,\n) -> None\n
Source code in dlunch/auth.py
def __init__(\n    self,\n    config: DictConfig,\n    login_template: str | None = None,\n    logout_template: str | None = None,\n) -> None:\n    # Set Hydra config info\n    self.config: DictConfig = config\n    \"\"\"Hydra configuration dictionary.\"\"\"\n\n    super().__init__(\n        login_template=login_template, logout_template=logout_template\n    )\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.PasswordEncrypt","title":"PasswordEncrypt","text":"

Class that store the encrypted value of a password.

The encryption is based on Panel encryption system.

The class has methods to encrypt and decrypt a string.

The encrypted password may be passed to instantiate the new object. If the encrypted password is not aviailable use the class method PasswordEncrypt.from_str to create an istance with the string properly encrypted.

Parameters:

Name Type Description Default encrypted_password str

encrypted password.

required

Methods:

Name Description __eq__

Decrypt the candidate string and compares it to the stored encrypted value.

__init__ __repr__

Simple object representation.

decrypt

Return decrypted password.

encrypt

Return encrypted password.

from_str

Creates a PasswordEncrypt from the given string.

Attributes:

Name Type Description encrypted_password str

Encrypted password.

Source code in dlunch/auth.py
class PasswordEncrypt:\n    \"\"\"Class that store the encrypted value of a password.\n\n    The encryption is based on Panel encryption system.\n\n    The class has methods to encrypt and decrypt a string.\n\n    The encrypted password may be passed to instantiate the new object.\n    If the encrypted password is not aviailable use the class method\n    `PasswordEncrypt.from_str` to create an istance with the string properly\n    encrypted.\n\n    Args:\n        encrypted_password (str): encrypted password.\n    \"\"\"\n\n    def __init__(self, encrypted_password: str) -> None:\n        # Consistency checks\n        assert (\n            len(encrypted_password) <= 150\n        ), \"encrypted string should have less than 150 chars.\"\n        # Attributes\n        self.encrypted_password: str = encrypted_password\n        \"\"\"Encrypted password.\"\"\"\n\n    def __eq__(self, candidate: str) -> bool:\n        \"\"\"Decrypt the candidate string and compares it to the stored encrypted value.\n\n        Args:\n            candidate (str): candidate string.\n\n        Returns:\n            bool: `True` if equal.\n        \"\"\"\n        # If string check hash, otherwise return False\n        if isinstance(candidate, str):\n            # Replace hashed_password if the algorithm changes\n            valid = self.decrypt() == candidate\n        else:\n            valid = False\n\n        return valid\n\n    def __repr__(self) -> str:\n        \"\"\"Simple object representation.\n\n        Returns:\n            str: string representation.\n        \"\"\"\n        return f\"<{type(self).__name__}>\"\n\n    @staticmethod\n    def encrypt(password: str) -> str:\n        \"\"\"Return encrypted password.\n\n        Args:\n            password (str): plain password (not encrypted).\n\n        Returns:\n            str: encrypted password.\n        \"\"\"\n        if pn.state.encryption:\n            encrypted_password = pn.state.encryption.encrypt(\n                password.encode(\"utf-8\")\n            ).decode(\"utf-8\")\n        else:\n            encrypted_password = password\n        return encrypted_password\n\n    def decrypt(self) -> str:\n        \"\"\"Return decrypted password.\n\n        Returns:\n            str: plain password (not encrypted).\n        \"\"\"\n        if pn.state.encryption:\n            password = pn.state.encryption.decrypt(\n                self.encrypted_password.encode(\"utf-8\")\n            ).decode(\"utf-8\")\n        else:\n            password = self.encrypted_password\n        return password\n\n    @classmethod\n    def from_str(cls, password: str) -> Self:\n        \"\"\"Creates a PasswordEncrypt from the given string.\n\n        Args:\n            password (str): plain password (not encrypted).\n\n        Returns:\n            PasswordEncrypt: new class instance with encrypted value already stored.\n        \"\"\"\n        return cls(cls.encrypt(password))\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.PasswordEncrypt.encrypted_password","title":"encrypted_password instance-attribute","text":"
encrypted_password: str = encrypted_password\n

Encrypted password.

"},{"location":"reference/dlunch/auth/#dlunch.auth.PasswordEncrypt.__eq__","title":"__eq__","text":"
__eq__(candidate: str) -> bool\n

Decrypt the candidate string and compares it to the stored encrypted value.

Parameters:

Name Type Description Default candidate str

candidate string.

required

Returns:

Type Description bool

True if equal.

Source code in dlunch/auth.py
def __eq__(self, candidate: str) -> bool:\n    \"\"\"Decrypt the candidate string and compares it to the stored encrypted value.\n\n    Args:\n        candidate (str): candidate string.\n\n    Returns:\n        bool: `True` if equal.\n    \"\"\"\n    # If string check hash, otherwise return False\n    if isinstance(candidate, str):\n        # Replace hashed_password if the algorithm changes\n        valid = self.decrypt() == candidate\n    else:\n        valid = False\n\n    return valid\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.PasswordEncrypt.__init__","title":"__init__","text":"
__init__(encrypted_password: str) -> None\n
Source code in dlunch/auth.py
def __init__(self, encrypted_password: str) -> None:\n    # Consistency checks\n    assert (\n        len(encrypted_password) <= 150\n    ), \"encrypted string should have less than 150 chars.\"\n    # Attributes\n    self.encrypted_password: str = encrypted_password\n    \"\"\"Encrypted password.\"\"\"\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.PasswordEncrypt.__repr__","title":"__repr__","text":"
__repr__() -> str\n

Simple object representation.

Returns:

Type Description str

string representation.

Source code in dlunch/auth.py
def __repr__(self) -> str:\n    \"\"\"Simple object representation.\n\n    Returns:\n        str: string representation.\n    \"\"\"\n    return f\"<{type(self).__name__}>\"\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.PasswordEncrypt.decrypt","title":"decrypt","text":"
decrypt() -> str\n

Return decrypted password.

Returns:

Type Description str

plain password (not encrypted).

Source code in dlunch/auth.py
def decrypt(self) -> str:\n    \"\"\"Return decrypted password.\n\n    Returns:\n        str: plain password (not encrypted).\n    \"\"\"\n    if pn.state.encryption:\n        password = pn.state.encryption.decrypt(\n            self.encrypted_password.encode(\"utf-8\")\n        ).decode(\"utf-8\")\n    else:\n        password = self.encrypted_password\n    return password\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.PasswordEncrypt.encrypt","title":"encrypt staticmethod","text":"
encrypt(password: str) -> str\n

Return encrypted password.

Parameters:

Name Type Description Default password str

plain password (not encrypted).

required

Returns:

Type Description str

encrypted password.

Source code in dlunch/auth.py
@staticmethod\ndef encrypt(password: str) -> str:\n    \"\"\"Return encrypted password.\n\n    Args:\n        password (str): plain password (not encrypted).\n\n    Returns:\n        str: encrypted password.\n    \"\"\"\n    if pn.state.encryption:\n        encrypted_password = pn.state.encryption.encrypt(\n            password.encode(\"utf-8\")\n        ).decode(\"utf-8\")\n    else:\n        encrypted_password = password\n    return encrypted_password\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.PasswordEncrypt.from_str","title":"from_str classmethod","text":"
from_str(password: str) -> Self\n

Creates a PasswordEncrypt from the given string.

Parameters:

Name Type Description Default password str

plain password (not encrypted).

required

Returns:

Type Description PasswordEncrypt

new class instance with encrypted value already stored.

Source code in dlunch/auth.py
@classmethod\ndef from_str(cls, password: str) -> Self:\n    \"\"\"Creates a PasswordEncrypt from the given string.\n\n    Args:\n        password (str): plain password (not encrypted).\n\n    Returns:\n        PasswordEncrypt: new class instance with encrypted value already stored.\n    \"\"\"\n    return cls(cls.encrypt(password))\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.PasswordHash","title":"PasswordHash","text":"

Class that store the hashed value of a password.

The password hash may be passed to instantiate the new object. If the hash is not aviailable use the class method PasswordHash.from_str to create an istance with the string properly hashed.

Parameters:

Name Type Description Default hashed_password str

password hash.

required

Methods:

Name Description __eq__

Hashes the candidate string and compares it to the stored hash.

__init__ __repr__

Simple object representation.

from_str

Creates a PasswordHash from the given string.

hash

Return hash of the given password.

verify

Check a password against its hash and return True if check passes,

verify_and_update

Check a password against its hash and return True if check passes,

Attributes:

Name Type Description hashed_password str

Password hash.

Source code in dlunch/auth.py
class PasswordHash:\n    \"\"\"Class that store the hashed value of a password.\n\n    The password hash may be passed to instantiate the new object.\n    If the hash is not aviailable use the class method\n    `PasswordHash.from_str` to create an istance with the string properly\n    hashed.\n\n    Args:\n        hashed_password (str): password hash.\n    \"\"\"\n\n    def __init__(self, hashed_password: str) -> None:\n        # Consistency checks\n        assert (\n            len(hashed_password) <= 150\n        ), \"hash should have less than 150 chars.\"\n        # Attributes\n        self.hashed_password: str = hashed_password\n        \"\"\"Password hash.\"\"\"\n\n    def __eq__(self, candidate: str) -> bool:\n        \"\"\"Hashes the candidate string and compares it to the stored hash.\n\n        Args:\n            candidate (str): candidate string.\n\n        Returns:\n            bool: `True` if equal.\n        \"\"\"\n        # If string check hash, otherwise return False\n        if isinstance(candidate, str):\n            # Replace hashed_password if the algorithm changes\n            valid = self.verify(candidate)\n        else:\n            valid = False\n\n        return valid\n\n    def __repr__(self) -> str:\n        \"\"\"Simple object representation.\n\n        Returns:\n            str: string representation.\n        \"\"\"\n        return f\"<{type(self).__name__}>\"\n\n    def verify(self, password: str) -> bool:\n        \"\"\"Check a password against its hash and return `True` if check passes,\n        `False` otherwise.\n\n        Args:\n            password (str): plain password (not hashed).\n\n        Returns:\n            bool: `True` if password and hash match.\n        \"\"\"\n        valid = pwd_context.verify(saslprep(password), self.hashed_password)\n\n        return valid\n\n    def verify_and_update(self, password: str) -> tuple[bool, str | None]:\n        \"\"\"Check a password against its hash and return `True` if check passes,\n        `False` otherwise. Return also a new hash if the original hashing  method\n        is superseeded\n\n        Args:\n            password (str): plain password (not hashed).\n\n        Returns:\n            tuple[bool, str | None]: return a tuple with two elements (valid, new_hash).\n                valid: `True` if password and hash match.\n                new_hash: new hash to replace the one generated with an old algorithm.\n        \"\"\"\n        valid, new_hash = pwd_context.verify_and_update(\n            saslprep(password), self.hashed_password\n        )\n        if valid and new_hash:\n            self.hashed_password = new_hash\n\n        return valid, new_hash\n\n    @staticmethod\n    def hash(password: str) -> str:\n        \"\"\"Return hash of the given password.\n\n        Args:\n            password (str): plain password (not hashed).\n\n        Returns:\n            str: hashed password.\n        \"\"\"\n        return pwd_context.hash(saslprep(password))\n\n    @classmethod\n    def from_str(cls, password: str) -> Self:\n        \"\"\"Creates a PasswordHash from the given string.\n\n        Args:\n            password (str): plain password (not hashed).\n\n        Returns:\n            PasswordHash: new class instance with hashed value already stored.\n        \"\"\"\n        return cls(cls.hash(password))\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.PasswordHash.hashed_password","title":"hashed_password instance-attribute","text":"
hashed_password: str = hashed_password\n

Password hash.

"},{"location":"reference/dlunch/auth/#dlunch.auth.PasswordHash.__eq__","title":"__eq__","text":"
__eq__(candidate: str) -> bool\n

Hashes the candidate string and compares it to the stored hash.

Parameters:

Name Type Description Default candidate str

candidate string.

required

Returns:

Type Description bool

True if equal.

Source code in dlunch/auth.py
def __eq__(self, candidate: str) -> bool:\n    \"\"\"Hashes the candidate string and compares it to the stored hash.\n\n    Args:\n        candidate (str): candidate string.\n\n    Returns:\n        bool: `True` if equal.\n    \"\"\"\n    # If string check hash, otherwise return False\n    if isinstance(candidate, str):\n        # Replace hashed_password if the algorithm changes\n        valid = self.verify(candidate)\n    else:\n        valid = False\n\n    return valid\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.PasswordHash.__init__","title":"__init__","text":"
__init__(hashed_password: str) -> None\n
Source code in dlunch/auth.py
def __init__(self, hashed_password: str) -> None:\n    # Consistency checks\n    assert (\n        len(hashed_password) <= 150\n    ), \"hash should have less than 150 chars.\"\n    # Attributes\n    self.hashed_password: str = hashed_password\n    \"\"\"Password hash.\"\"\"\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.PasswordHash.__repr__","title":"__repr__","text":"
__repr__() -> str\n

Simple object representation.

Returns:

Type Description str

string representation.

Source code in dlunch/auth.py
def __repr__(self) -> str:\n    \"\"\"Simple object representation.\n\n    Returns:\n        str: string representation.\n    \"\"\"\n    return f\"<{type(self).__name__}>\"\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.PasswordHash.from_str","title":"from_str classmethod","text":"
from_str(password: str) -> Self\n

Creates a PasswordHash from the given string.

Parameters:

Name Type Description Default password str

plain password (not hashed).

required

Returns:

Type Description PasswordHash

new class instance with hashed value already stored.

Source code in dlunch/auth.py
@classmethod\ndef from_str(cls, password: str) -> Self:\n    \"\"\"Creates a PasswordHash from the given string.\n\n    Args:\n        password (str): plain password (not hashed).\n\n    Returns:\n        PasswordHash: new class instance with hashed value already stored.\n    \"\"\"\n    return cls(cls.hash(password))\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.PasswordHash.hash","title":"hash staticmethod","text":"
hash(password: str) -> str\n

Return hash of the given password.

Parameters:

Name Type Description Default password str

plain password (not hashed).

required

Returns:

Type Description str

hashed password.

Source code in dlunch/auth.py
@staticmethod\ndef hash(password: str) -> str:\n    \"\"\"Return hash of the given password.\n\n    Args:\n        password (str): plain password (not hashed).\n\n    Returns:\n        str: hashed password.\n    \"\"\"\n    return pwd_context.hash(saslprep(password))\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.PasswordHash.verify","title":"verify","text":"
verify(password: str) -> bool\n

Check a password against its hash and return True if check passes, False otherwise.

Parameters:

Name Type Description Default password str

plain password (not hashed).

required

Returns:

Type Description bool

True if password and hash match.

Source code in dlunch/auth.py
def verify(self, password: str) -> bool:\n    \"\"\"Check a password against its hash and return `True` if check passes,\n    `False` otherwise.\n\n    Args:\n        password (str): plain password (not hashed).\n\n    Returns:\n        bool: `True` if password and hash match.\n    \"\"\"\n    valid = pwd_context.verify(saslprep(password), self.hashed_password)\n\n    return valid\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.PasswordHash.verify_and_update","title":"verify_and_update","text":"
verify_and_update(password: str) -> tuple[bool, str | None]\n

Check a password against its hash and return True if check passes, False otherwise. Return also a new hash if the original hashing method is superseeded

Parameters:

Name Type Description Default password str

plain password (not hashed).

required

Returns:

Type Description tuple[bool, str | None]

return a tuple with two elements (valid, new_hash). valid: True if password and hash match. new_hash: new hash to replace the one generated with an old algorithm.

Source code in dlunch/auth.py
def verify_and_update(self, password: str) -> tuple[bool, str | None]:\n    \"\"\"Check a password against its hash and return `True` if check passes,\n    `False` otherwise. Return also a new hash if the original hashing  method\n    is superseeded\n\n    Args:\n        password (str): plain password (not hashed).\n\n    Returns:\n        tuple[bool, str | None]: return a tuple with two elements (valid, new_hash).\n            valid: `True` if password and hash match.\n            new_hash: new hash to replace the one generated with an old algorithm.\n    \"\"\"\n    valid, new_hash = pwd_context.verify_and_update(\n        saslprep(password), self.hashed_password\n    )\n    if valid and new_hash:\n        self.hashed_password = new_hash\n\n    return valid, new_hash\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.add_privileged_user","title":"add_privileged_user","text":"
add_privileged_user(\n    user: str, is_admin: bool, config: DictConfig\n) -> None\n

Add user id to privileged_users table.

The table is used by every authentication methods to understand which users are privileged and which ones are guests.

Parameters:

Name Type Description Default user str

username.

required is_admin bool

admin flag. Set to True if the new user has admin privileges.

required config DictConfig

Hydra configuration dictionary.

required Source code in dlunch/auth.py
def add_privileged_user(user: str, is_admin: bool, config: DictConfig) -> None:\n    \"\"\"Add user id to `privileged_users` table.\n\n    The table is used by every authentication methods to understand which users are\n    privileged and which ones are guests.\n\n    Args:\n        user (str): username.\n        is_admin (bool): admin flag.\n            Set to `True` if the new user has admin privileges.\n        config (DictConfig): Hydra configuration dictionary.\n    \"\"\"\n    # Create session\n    session = models.create_session(config)\n    # New credentials\n    new_privileged_user = models.PrivilegedUsers(user=user, admin=is_admin)\n\n    # Update credentials\n    # Use an upsert for postgresql, a simple session add otherwise\n    models.session_add_with_upsert(\n        session=session,\n        constraint=\"privileged_users_pkey\",\n        new_record=new_privileged_user,\n    )\n    session.commit()\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.add_user_hashed_password","title":"add_user_hashed_password","text":"
add_user_hashed_password(\n    user: str, password: str, config: DictConfig\n) -> None\n

Add user credentials to credentials table.

Used only by basic authentication.

Parameters:

Name Type Description Default user str

username

required password str

plain password (not hashed).

required config DictConfig

Hydra configuration dictionary.

required Source code in dlunch/auth.py
def add_user_hashed_password(\n    user: str, password: str, config: DictConfig\n) -> None:\n    \"\"\"Add user credentials to `credentials` table.\n\n    Used only by basic authentication.\n\n    Args:\n        user (str): username\n        password (str): plain password (not hashed).\n        config (DictConfig): Hydra configuration dictionary.\n    \"\"\"\n    # Create session\n    session = models.create_session(config)\n    # New credentials\n    # For the user named \"guest\" add also the encrypted password so that panel\n    # can show the decrypted guest password to logged users\n    # Can't use is_guest to determine the user that need encription, because\n    # only the user named guest is shown in the guest user password widget\n    if user == \"guest\":\n        new_user_credential = models.Credentials(\n            user=user, password_hash=password, password_encrypted=password\n        )\n    else:\n        new_user_credential = models.Credentials(\n            user=user, password_hash=password\n        )\n\n    # Update credentials\n    # Use an upsert for postgresql, a simple session add otherwise\n    models.session_add_with_upsert(\n        session=session,\n        constraint=\"credentials_pkey\",\n        new_record=new_user_credential,\n    )\n    session.commit()\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.authorize","title":"authorize","text":"
authorize(\n    config: DictConfig,\n    user_info: dict,\n    target_path: str,\n    authorize_guest_users=False,\n) -> bool\n

Authorization callback: read config, user info and the target path of the requested resource.

Return True (authorized) or False (not authorized) by checking current user and target path.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required user_info dict

dictionary with user info passed by Panel to the authorization handle.

required target_path str

path of the requested resource.

required authorize_guest_users bool

Set to True to enable the main page to guest users. Defaults to False.

False

Returns:

Type Description bool

authorization flag. True if authorized.

Source code in dlunch/auth.py
def authorize(\n    config: DictConfig,\n    user_info: dict,\n    target_path: str,\n    authorize_guest_users=False,\n) -> bool:\n    \"\"\"Authorization callback: read config, user info and the target path of the\n    requested resource.\n\n    Return `True` (authorized) or `False` (not authorized) by checking current user\n    and target path.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n        user_info (dict): dictionary with user info passed by Panel to the authorization handle.\n        target_path (str): path of the requested resource.\n        authorize_guest_users (bool, optional): Set to `True` to enable the main page to guest users.\n            Defaults to `False`.\n\n    Returns:\n        bool: authorization flag. `True` if authorized.\n    \"\"\"\n\n    # If authorization is not active authorize every user\n    if not is_auth_active(config=config):\n        return True\n\n    # Set current user from panel state\n    current_user = pn_user(config)\n    privileged_users = list_users(config=config)\n    log.debug(f\"target path: {target_path}\")\n    # If user is not authenticated block it\n    if not current_user:\n        return False\n    # All privileged users can reach backend (but the backend will have\n    # controls only for admins)\n    if current_user in privileged_users:\n        return True\n    # If the target is the mainpage always authorized (if authenticated)\n    if authorize_guest_users and (target_path == \"/\"):\n        return True\n\n    # In all other cases, don't authorize and logout\n    pn.state.location.pathname.split(\"/\")[0] + \"/logout\"\n    return False\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.backend_submit_password","title":"backend_submit_password","text":"
backend_submit_password(\n    gi: GraphicInterface | BackendInterface,\n    config: DictConfig,\n    user: str = None,\n    user_is_guest: bool | None = None,\n    user_is_admin: bool | None = None,\n    logout_on_success: bool = False,\n) -> bool\n

Submit password to database from backend but used also from frontend as part of submit_password function.

When used for backend is_guest and is_admin are selected from a widget. When called from frontend they are None and the function read them from database using the user input.

Parameters:

Name Type Description Default gi GraphicInterface | BackendInterface

graphic interface object (used to interact with Panel widgets).

required config DictConfig

Hydra configuration dictionary.

required user str

username. Defaults to None.

None user_is_guest bool | None

guest flag (true if guest). Defaults to None.

None user_is_admin bool | None

admin flag (true if admin). Defaults to None.

None logout_on_success bool

set to true to force logout once the new password is set. Defaults to False.

False

Returns:

Type Description bool

true if successful, false otherwise.

Source code in dlunch/auth.py
def backend_submit_password(\n    gi: gui.GraphicInterface | gui.BackendInterface,\n    config: DictConfig,\n    user: str = None,\n    user_is_guest: bool | None = None,\n    user_is_admin: bool | None = None,\n    logout_on_success: bool = False,\n) -> bool:\n    \"\"\"Submit password to database from backend but used also from frontend as\n    part of `submit_password` function.\n\n    When used for backend `is_guest` and `is_admin` are selected from a widget.\n    When called from frontend they are `None` and the function read them from\n    database using the user input.\n\n    Args:\n        gi (gui.GraphicInterface | gui.BackendInterface): graphic interface object (used to interact with Panel widgets).\n        config (DictConfig): Hydra configuration dictionary.\n        user (str, optional): username. Defaults to None.\n        user_is_guest (bool | None, optional): guest flag (true if guest). Defaults to None.\n        user_is_admin (bool | None, optional): admin flag (true if admin). Defaults to None.\n        logout_on_success (bool, optional): set to true to force logout once the new password is set. Defaults to False.\n\n    Returns:\n        bool: true if successful, false otherwise.\n    \"\"\"\n    # Check if user is passed, otherwise check if backend widget\n    # (password_widget.object.user) is available\n    if not user:\n        username = gi.password_widget._widgets[\"user\"].value_input\n    else:\n        username = user\n    # Get all passwords, updated at each key press\n    new_password_key_press = gi.password_widget._widgets[\n        \"new_password\"\n    ].value_input\n    repeat_new_password_key_press = gi.password_widget._widgets[\n        \"repeat_new_password\"\n    ].value_input\n    # Check if new password match repeat password\n    if username:\n        if new_password_key_press == repeat_new_password_key_press:\n            # Check if new password is valid with regex\n            if re.fullmatch(\n                config.basic_auth.psw_regex,\n                new_password_key_press,\n            ):\n                # If is_guest and is_admin are None (not passed) use the ones\n                # already set for the user\n                if user_is_guest is None:\n                    user_is_guest = is_guest(user=user, config=config)\n                if user_is_admin is None:\n                    user_is_admin = is_admin(user=user, config=config)\n                # First remove user from both 'privileged_users' and\n                # 'credentials' tables.\n                deleted_data = remove_user(user=username, config=config)\n                if (deleted_data[\"privileged_users_deleted\"] > 0) or (\n                    deleted_data[\"credentials_deleted\"] > 0\n                ):\n                    pn.state.notifications.success(\n                        f\"Removed old data for<br>'{username}'<br>auth: {deleted_data['privileged_users_deleted']}<br>cred: {deleted_data['credentials_deleted']}\",\n                        duration=config.panel.notifications.duration,\n                    )\n                else:\n                    pn.state.notifications.warning(\n                        f\"Creating new user<br>'{username}' does not exist\",\n                        duration=config.panel.notifications.duration,\n                    )\n                # Add a privileged users only if guest option is not active\n                if not user_is_guest:\n                    add_privileged_user(\n                        user=username,\n                        is_admin=user_is_admin,\n                        config=config,\n                    )\n                # Green light: update the password!\n                add_user_hashed_password(\n                    user=username,\n                    password=new_password_key_press,\n                    config=config,\n                )\n\n                # Logout if requested\n                if logout_on_success:\n                    pn.state.notifications.success(\n                        \"Password updated<br>Logging out\",\n                        duration=config.panel.notifications.duration,\n                    )\n                    sleep(4)\n                    force_logout()\n                else:\n                    pn.state.notifications.success(\n                        \"Password updated\",\n                        duration=config.panel.notifications.duration,\n                    )\n                return True\n\n            else:\n                pn.state.notifications.error(\n                    \"Password requirements not satisfied<br>Check again!\",\n                    duration=config.panel.notifications.duration,\n                )\n\n        else:\n            pn.state.notifications.error(\n                \"Passwords are different!\",\n                duration=config.panel.notifications.duration,\n            )\n    else:\n        pn.state.notifications.error(\n            \"Missing user!\",\n            duration=config.panel.notifications.duration,\n        )\n\n    return False\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.force_logout","title":"force_logout","text":"
force_logout() -> None\n

Redirect the browser to the logout endpoint

Source code in dlunch/auth.py
def force_logout() -> None:\n    \"\"\"Redirect the browser to the logout endpoint\"\"\"\n    # Edit pathname to force logout\n    pn.state.location.pathname = (\n        pn.state.location.pathname.split(\"/\")[0] + \"/logout\"\n    )\n    pn.state.location.reload = True\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.generate_password","title":"generate_password","text":"
generate_password(\n    alphabet: str | None = None,\n    special_chars: str | None = \"\",\n    length: int = 12,\n) -> str\n

summary

Parameters:

Name Type Description Default alphabet str | None

list of charachters to use as alphabet to generate the password. Defaults to None.

None special_chars str | None

special charachters to include inside the password string. Defaults to \"\".

'' length int

length of the random password. Defaults to 12.

12

Returns:

Type Description str

random password.

Source code in dlunch/auth.py
def generate_password(\n    alphabet: str | None = None,\n    special_chars: str | None = \"\",\n    length: int = 12,\n) -> str:\n    \"\"\"_summary_\n\n    Args:\n        alphabet (str | None, optional): list of charachters to use as alphabet to generate the password.\n            Defaults to None.\n        special_chars (str | None, optional): special charachters to include inside the password string.\n            Defaults to \"\".\n        length (int, optional): length of the random password.\n            Defaults to 12.\n\n    Returns:\n        str: random password.\n    \"\"\"\n    # If alphabet is not avilable use a default one\n    if alphabet is None:\n        alphabet = string.ascii_letters + string.digits + special_chars\n    # Infinite loop for finding a valid password\n    while True:\n        password = \"\".join(secrets.choice(alphabet) for i in range(length))\n        # Create special chars condition only if special chars is non-empty\n        if special_chars:\n            special_chars_condition = any(c in special_chars for c in password)\n        else:\n            special_chars_condition = True\n        if (\n            any(c.islower() for c in password)\n            and any(c.isupper() for c in password)\n            and any(c.isdigit() for c in password)\n            and special_chars_condition\n        ):\n            break\n\n    return password\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.get_hash_from_user","title":"get_hash_from_user","text":"
get_hash_from_user(\n    user: str, config: DictConfig\n) -> PasswordHash | None\n

Query the database to retrieve the hashed password for a certain user.

Parameters:

Name Type Description Default user str

username.

required config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description PasswordHash | None

returns password object if the user exist, None otherwise.

Source code in dlunch/auth.py
def get_hash_from_user(user: str, config: DictConfig) -> PasswordHash | None:\n    \"\"\"Query the database to retrieve the hashed password for a certain user.\n\n    Args:\n        user (str): username.\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        PasswordHash | None: returns password object if the user exist, `None` otherwise.\n    \"\"\"\n    # Create session\n    session = models.create_session(config)\n    # Load user from database\n    with session:\n        user_credential = session.get(models.Credentials, user)\n\n    # Get the hashed password\n    if user_credential:\n        hash = user_credential.password_hash or None\n    else:\n        hash = None\n\n    return hash\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.is_admin","title":"is_admin","text":"
is_admin(user: str, config: DictConfig) -> bool\n

Check if a user is an admin by checking the privileged_users table

Parameters:

Name Type Description Default user str

username.

required config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description bool

admin flag. True if the user is an admin.

Source code in dlunch/auth.py
def is_admin(user: str, config: DictConfig) -> bool:\n    \"\"\"Check if a user is an admin by checking the `privileged_users` table\n\n    Args:\n        user (str): username.\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        bool: admin flag. `True` if the user is an admin.\n    \"\"\"\n\n    # If authorization is not active always return false (ther is no admin)\n    if not is_auth_active(config=config):\n        return False\n\n    # Create session\n    session = models.create_session(config)\n\n    with session:\n        admin_users = session.scalars(\n            select(models.PrivilegedUsers).where(\n                models.PrivilegedUsers.admin == sql_true()\n            )\n        ).all()\n    admin_list = [u.user for u in admin_users]\n\n    is_admin = user in admin_list\n\n    return is_admin\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.is_auth_active","title":"is_auth_active","text":"
is_auth_active(config: DictConfig) -> bool\n

Check configuration dictionary and return True if basic authentication or OAuth is active. Return False otherwise.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description bool

True if authentication (basic or OAuth) is active, False otherwise.

Source code in dlunch/auth.py
def is_auth_active(config: DictConfig) -> bool:\n    \"\"\"Check configuration dictionary and return `True` if basic authentication or OAuth is active.\n    Return `False` otherwise.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        bool: `True` if authentication (basic or OAuth) is active, `False` otherwise.\n    \"\"\"\n\n    # Check if a valid auth key exists\n    auth_provider = is_basic_auth_active(config=config)\n    oauth_provider = config.server.get(\"oauth_provider\", None)\n\n    return auth_provider or oauth_provider\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.is_basic_auth_active","title":"is_basic_auth_active","text":"
is_basic_auth_active(config: DictConfig) -> bool\n

Check config object and return True if basic authentication is active. Return False otherwise.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description bool

True if basic authentication is active, False otherwise.

Source code in dlunch/auth.py
def is_basic_auth_active(config: DictConfig) -> bool:\n    \"\"\"Check config object and return `True` if basic authentication is active.\n    Return `False` otherwise.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        bool: `True` if basic authentication is active, `False` otherwise.\n    \"\"\"\n\n    # Check if a valid auth key exists\n    auth_provider = config.get(\"basic_auth\", None)\n\n    return auth_provider is not None\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.is_guest","title":"is_guest","text":"
is_guest(\n    user: str,\n    config: DictConfig,\n    allow_override: bool = True,\n) -> bool\n

Check if a user is a guest by checking if it is listed inside the privileged_users table.

The guest override chached value (stored in flags table, on a per-user basis) can force the function to always return True.

If allow_override is set to False the guest override value is ignored.

Parameters:

Name Type Description Default user str

username.

required config DictConfig

Hydra configuration dictionary.

required allow_override bool

override enablement flag, set to False to ignore guest override value. Defaults to True.

True

Returns:

Type Description bool

guest flag. True if the user is a guest.

Source code in dlunch/auth.py
def is_guest(\n    user: str, config: DictConfig, allow_override: bool = True\n) -> bool:\n    \"\"\"Check if a user is a guest by checking if it is listed inside the `privileged_users` table.\n\n    The guest override chached value (stored in `flags` table, on a per-user basis) can force\n    the function to always return True.\n\n    If `allow_override` is set to `False` the guest override value is ignored.\n\n    Args:\n        user (str): username.\n        config (DictConfig): Hydra configuration dictionary.\n        allow_override (bool, optional): override enablement flag, set to `False` to ignore guest override value.\n            Defaults to True.\n\n    Returns:\n        bool: guest flag. `True` if the user is a guest.\n    \"\"\"\n\n    # If authorization is not active always return false (user is not guest)\n    if not is_auth_active(config=config):\n        return False\n\n    # Load guest override from flag table (if the button is pressed its value\n    # is True). If not available use False.\n    guest_override = models.get_flag(\n        config=config,\n        id=f\"{pn_user(config)}_guest_override\",\n        value_if_missing=False,\n    )\n\n    # If guest override is active always return true (user act like guest)\n    if guest_override and allow_override:\n        return True\n\n    # Otherwise check if user is not included in privileged users\n    privileged_users = list_users(config)\n\n    is_guest = user not in privileged_users\n\n    return is_guest\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.list_users","title":"list_users","text":"
list_users(config: DictConfig) -> list[str]\n

List only privileged users (from privileged_users table).

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description list[str]

list of usernames.

Source code in dlunch/auth.py
def list_users(config: DictConfig) -> list[str]:\n    \"\"\"List only privileged users (from `privileged_users` table).\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        list[str]: list of usernames.\n    \"\"\"\n    # Create session\n    session = models.create_session(config)\n\n    with session:\n        privileged_users = session.scalars(\n            select(models.PrivilegedUsers)\n        ).all()\n\n    # Return users\n    users_list = [u.user for u in privileged_users]\n    users_list.sort()\n\n    return users_list\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.list_users_guests_and_privileges","title":"list_users_guests_and_privileges","text":"
list_users_guests_and_privileges(\n    config: DictConfig,\n) -> DataFrame\n

Join privileged_users and credentials tables to list normal users, admins and guests.

credentials table is populated only if basic authetication is active (in configuration files). A is considered a guest if it is not listed in privileged_users table but it is available in credentials table.

Returns a dataframe.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description DataFrame

dataframe with users and privileges.

Source code in dlunch/auth.py
def list_users_guests_and_privileges(config: DictConfig) -> pd.DataFrame:\n    \"\"\"Join `privileged_users` and `credentials` tables to list normal users,\n    admins and guests.\n\n    `credentials` table is populated only if basic authetication is active (in configuration files).\n    A is considered a guest if it is not listed in `privileged_users` table\n    but it is available in `credentials` table.\n\n    Returns a dataframe.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        pd.DataFrame: dataframe with users and privileges.\n    \"\"\"\n\n    # Query tables required to understand users and guests\n    df_privileged_users = models.PrivilegedUsers.read_as_df(\n        config=config,\n        index_col=\"user\",\n    )\n    df_credentials = models.Credentials.read_as_df(\n        config=config,\n        index_col=\"user\",\n    )\n    # Change admin column to privileges (used after join)\n    df_privileged_users[\"group\"] = df_privileged_users.admin.map(\n        {True: \"admin\", False: \"user\"}\n    )\n    df_user_guests_privileges = df_privileged_users.join(\n        df_credentials, how=\"outer\"\n    )[[\"group\"]]\n    df_user_guests_privileges = df_user_guests_privileges.fillna(\"guest\")\n\n    return df_user_guests_privileges\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.open_backend","title":"open_backend","text":"
open_backend() -> None\n

Redirect the browser to the backend endpoint

Source code in dlunch/auth.py
def open_backend() -> None:\n    \"\"\"Redirect the browser to the backend endpoint\"\"\"\n    # Edit pathname to open backend\n    pn.state.location.pathname = (\n        pn.state.location.pathname.split(\"/\")[0] + \"/backend\"\n    )\n    pn.state.location.reload = True\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.pn_user","title":"pn_user","text":"
pn_user(config: DictConfig) -> str\n

Return the user from Panel state object.

If config.auth.remove_email_domain is True, remove the email domain from username.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description str

username.

Source code in dlunch/auth.py
def pn_user(config: DictConfig) -> str:\n    \"\"\"Return the user from Panel state object.\n\n    If `config.auth.remove_email_domain` is `True`, remove the email domain from username.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        str: username.\n    \"\"\"\n    # Store user\n    user = pn.state.user\n\n    if user:\n        # Check if username is an email\n        if re.fullmatch(r\"[^@]+@[^@]+\\.[^@]+\", user):\n            # Remove domain from username\n            if config.auth.remove_email_domain:\n                user = user.split(\"@\")[0]\n\n    return user\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.remove_user","title":"remove_user","text":"
remove_user(user: str, config: DictConfig) -> dict\n

Remove user from the database.

User is removed from privileged_users and credentials tables.

Parameters:

Name Type Description Default user str

username.

required config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description dict

dictionary with privileged_users_deleted and credentials_deleted with deleted rows from each table.

Source code in dlunch/auth.py
def remove_user(user: str, config: DictConfig) -> dict:\n    \"\"\"Remove user from the database.\n\n    User is removed from `privileged_users` and `credentials` tables.\n\n    Args:\n        user (str): username.\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        dict: dictionary with `privileged_users_deleted` and `credentials_deleted`\n            with deleted rows from each table.\n    \"\"\"\n    # Create session\n    session = models.create_session(config)\n\n    with session:\n        # Delete user from privileged_users table\n        privileged_users_deleted = session.execute(\n            delete(models.PrivilegedUsers).where(\n                models.PrivilegedUsers.user == user\n            )\n        )\n        session.commit()\n\n        # Delete user from credentials table\n        credentials_deleted = session.execute(\n            delete(models.Credentials).where(models.Credentials.user == user)\n        )\n        session.commit()\n\n    return {\n        \"privileged_users_deleted\": privileged_users_deleted.rowcount,\n        \"credentials_deleted\": credentials_deleted.rowcount,\n    }\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.set_app_auth_and_encryption","title":"set_app_auth_and_encryption","text":"
set_app_auth_and_encryption(config: DictConfig) -> None\n

Setup Panel authorization and encryption.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Raises:

Type Description ImportError

missing library (cryptography).

Source code in dlunch/auth.py
def set_app_auth_and_encryption(config: DictConfig) -> None:\n    \"\"\"Setup Panel authorization and encryption.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Raises:\n        ImportError: missing library (cryptography).\n    \"\"\"\n    try:\n        if config.auth.oauth_encryption_key:\n            pn.config.oauth_encryption_key = (\n                config.auth.oauth_encryption_key.encode(\"ascii\")\n            )\n            pn.state.encryption = Fernet(pn.config.oauth_encryption_key)\n    except ConfigAttributeError:\n        log.warning(\n            \"missing authentication encryption key, generate a key with the `panel oauth-secret` CLI command and then provide it to hydra using the DATA_LUNCH_OAUTH_ENC_KEY environment variable\"\n        )\n    # Cookie expiry date\n    try:\n        if config.auth.oauth_expiry:\n            pn.config.oauth_expiry = config.auth.oauth_expiry\n    except ConfigAttributeError:\n        log.warning(\n            \"missing explicit authentication expiry date for cookies, defaults to 1 day\"\n        )\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.set_guest_user_password","title":"set_guest_user_password","text":"
set_guest_user_password(config: DictConfig) -> str\n

If guest user is active return a password, otherwise return an empty string.

This function always returns an empty string if basic authentication is not active.

Guest user and basic authentication are handled through configuration files.

If the flag reset_guest_user_password is set to True the password is created and uploaded to database. Otherwise the existing password is queried from database credentials table.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description str

guest user password or empty string if basic authentication is not active.

Source code in dlunch/auth.py
def set_guest_user_password(config: DictConfig) -> str:\n    \"\"\"If guest user is active return a password, otherwise return an empty string.\n\n    This function always returns an empty string if basic authentication is not active.\n\n    Guest user and basic authentication are handled through configuration files.\n\n    If the flag `reset_guest_user_password` is set to `True` the password is created\n    and uploaded to database. Otherwise the existing password is queried from database\n    `credentials` table.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        str: guest user password or empty string if basic authentication is not active.\n    \"\"\"\n    # Check if basic auth is active\n    if is_basic_auth_active(config=config):\n        # If active basic_auth.guest_user is true if guest user is active\n        is_guest_user_active = config.basic_auth.guest_user\n        log.debug(\"guest user flag is {is_guest_user_active}\")\n    else:\n        # Otherwise the guest user feature is not applicable\n        is_guest_user_active = False\n        log.debug(\"guest user not applicable\")\n\n    # Set the guest password variable\n    if is_guest_user_active:\n        # If flag for resetting the password does not exist use the default\n        # value\n        if (\n            models.get_flag(config=config, id=\"reset_guest_user_password\")\n            is None\n        ):\n            models.set_flag(\n                config=config,\n                id=\"reset_guest_user_password\",\n                value=config.basic_auth.default_reset_guest_user_password_flag,\n            )\n        # Generate a random password only if requested (check on flag)\n        # otherwise load from pickle\n        if models.get_flag(config=config, id=\"reset_guest_user_password\"):\n            # Turn off reset user password (in order to reset it only once)\n            # This statement also acquire a lock on database (so it is\n            # called first)\n            models.set_flag(\n                config=config,\n                id=\"reset_guest_user_password\",\n                value=False,\n            )\n            # Create password\n            guest_password = generate_password(\n                special_chars=config.basic_auth.psw_special_chars,\n                length=config.basic_auth.generated_psw_length,\n            )\n            # Add hashed password to database\n            add_user_hashed_password(\"guest\", guest_password, config=config)\n        else:\n            # Load from database\n            session = models.create_session(config)\n            with session:\n                try:\n                    guest_password = session.get(\n                        models.Credentials, \"guest\"\n                    ).password_encrypted.decrypt()\n                except InvalidToken:\n                    # Notify exception and suggest to reset guest user password\n                    guest_password = \"\"\n                    log.warning(\n                        \"Unable to decrypt 'guest' user password because an invalid token has been detected: reset password from backend\"\n                    )\n                    pn.state.notifications.warning(\n                        \"Unable to decrypt 'guest' user password<br>Invalid token detected: reset password from backend\",\n                        duration=config.panel.notifications.duration,\n                    )\n    else:\n        guest_password = \"\"\n\n    return guest_password\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.submit_password","title":"submit_password","text":"
submit_password(\n    gi: GraphicInterface, config: DictConfig\n) -> bool\n

Same as backend_submit_password with an additional check on old password.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required gi GraphicInterface

graphic interface object (used to interact with Panel widgets).

required

Returns:

Type Description bool

true if successful, false otherwise.

Source code in dlunch/auth.py
def submit_password(gi: gui.GraphicInterface, config: DictConfig) -> bool:\n    \"\"\"Same as backend_submit_password with an additional check on old\n    password.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n        gi (gui.GraphicInterface): graphic interface object (used to interact with Panel widgets).\n\n    Returns:\n        bool: true if successful, false otherwise.\n    \"\"\"\n    # Get user's password hash\n    password_hash = get_hash_from_user(pn_user(config), config=config)\n    # Get username, updated updated at each key press\n    old_password_key_press = gi.password_widget._widgets[\n        \"old_password\"\n    ].value_input\n    # Check if old password is correct\n    if password_hash == old_password_key_press:\n        # Check if new password match repeat password\n        return backend_submit_password(\n            gi=gi, config=config, user=pn_user(config), logout_on_success=True\n        )\n    else:\n        pn.state.notifications.error(\n            \"Incorrect old password!\",\n            duration=config.panel.notifications.duration,\n        )\n\n    return False\n
"},{"location":"reference/dlunch/cli/","title":"cli","text":"

Module with Data-Lunch's command line.

The command line is built with click.

Call data-lunch --help from the terminal inside an environment where the dlunch package is installed.

Modules:

Name Description auth

Module with classes and functions used for authentication and password handling.

Functions:

Name Description add_privileged_user

Add privileged users (with or without admin privileges).

add_user_credential

Add users credentials to credentials table (used by basic authentication)

clean_tables

Clean 'users', 'menu', 'orders' and 'flags' tables.

cli

Command line interface for managing Data-Lunch database and users.

credentials

Manage users credentials for basic authentication.

db

Manage the database.

delete_database

Delete the database.

delete_table

Drop a single table from database.

export_table_to_csv

Export a single table to a csv file.

generate_secrets

Generate secrets for DATA_LUNCH_COOKIE_SECRET and DATA_LUNCH_OAUTH_ENC_KEY env variables.

init_database

Initialize the database.

list_users_name

List users.

load_table

Load a single table from a csv file.

main

Main command line entrypoint.

remove_privileged_user

Remove user from both privileged users and basic login credentials table.

remove_user_credential

Remove user from both privileged users and basic login credentials table.

table

Manage tables in database.

users

Manage privileged users and admin privileges.

utils

Utility commands.

Attributes:

Name Type Description __version__ str

Data-Lunch command line version.

"},{"location":"reference/dlunch/cli/#dlunch.cli.__version__","title":"__version__ module-attribute","text":"
__version__: str = version\n

Data-Lunch command line version.

"},{"location":"reference/dlunch/cli/#dlunch.cli.add_privileged_user","title":"add_privileged_user","text":"
add_privileged_user(obj, user, is_admin)\n

Add privileged users (with or without admin privileges).

Source code in dlunch/cli.py
@users.command(\"add\")\n@click.argument(\"user\")\n@click.option(\"--admin\", \"is_admin\", is_flag=True, help=\"add admin privileges\")\n@click.pass_obj\ndef add_privileged_user(obj, user, is_admin):\n    \"\"\"Add privileged users (with or without admin privileges).\"\"\"\n\n    # Add privileged user to 'privileged_users' table\n    auth.add_privileged_user(\n        user=user,\n        is_admin=is_admin,\n        config=obj[\"config\"],\n    )\n\n    click.secho(f\"User '{user}' added (admin: {is_admin})\", fg=\"green\")\n
"},{"location":"reference/dlunch/cli/#dlunch.cli.add_user_credential","title":"add_user_credential","text":"
add_user_credential(\n    obj, user, password, is_admin, is_guest\n)\n

Add users credentials to credentials table (used by basic authentication) and to privileged users (if not guest).

Source code in dlunch/cli.py
@credentials.command(\"add\")\n@click.argument(\"user\")\n@click.argument(\"password\")\n@click.option(\"--admin\", \"is_admin\", is_flag=True, help=\"add admin privileges\")\n@click.option(\n    \"--guest\",\n    \"is_guest\",\n    is_flag=True,\n    help=\"add user as guest (not added to privileged users)\",\n)\n@click.pass_obj\ndef add_user_credential(obj, user, password, is_admin, is_guest):\n    \"\"\"Add users credentials to credentials table (used by basic authentication)\n    and to privileged users (if not guest).\"\"\"\n\n    # Add a privileged users only if guest option is not active\n    if not is_guest:\n        auth.add_privileged_user(\n            user=user,\n            is_admin=is_admin,\n            config=obj[\"config\"],\n        )\n    # Add hashed password to credentials table\n    auth.add_user_hashed_password(user, password, config=obj[\"config\"])\n\n    click.secho(f\"User '{user}' added\", fg=\"green\")\n
"},{"location":"reference/dlunch/cli/#dlunch.cli.clean_tables","title":"clean_tables","text":"
clean_tables(obj)\n

Clean 'users', 'menu', 'orders' and 'flags' tables.

Source code in dlunch/cli.py
@db.command(\"clean\")\n@click.confirmation_option()\n@click.pass_obj\ndef clean_tables(obj):\n    \"\"\"Clean 'users', 'menu', 'orders' and 'flags' tables.\"\"\"\n\n    # Drop table\n    try:\n        waiter.clean_tables()\n        click.secho(\"done\", fg=\"green\")\n    except Exception as e:\n        # Generic error\n        click.secho(\"Cannot clean database\", fg=\"red\")\n        click.secho(f\"\\n ===== EXCEPTION =====\\n\\n{e}\", fg=\"red\")\n
"},{"location":"reference/dlunch/cli/#dlunch.cli.cli","title":"cli","text":"
cli(ctx, hydra_overrides: tuple | None)\n

Command line interface for managing Data-Lunch database and users.

Source code in dlunch/cli.py
@click.group()\n@click.version_option(__version__)\n@click.option(\n    \"-o\",\n    \"--hydra-overrides\",\n    \"hydra_overrides\",\n    default=None,\n    multiple=True,\n    help=\"pass hydra override, use multiple time to add more than one override\",\n)\n@click.pass_context\ndef cli(ctx, hydra_overrides: tuple | None):\n    \"\"\"Command line interface for managing Data-Lunch database and users.\"\"\"\n    # global initialization\n    initialize(\n        config_path=\"conf\", job_name=\"data_lunch_cli\", version_base=\"1.3\"\n    )\n    config = compose(config_name=\"config\", overrides=hydra_overrides)\n    ctx.obj = {\"config\": config}\n\n    # Set waiter config\n    waiter.set_config(config)\n\n    # Auth encryption\n    auth.set_app_auth_and_encryption(config)\n
"},{"location":"reference/dlunch/cli/#dlunch.cli.credentials","title":"credentials","text":"
credentials(obj)\n

Manage users credentials for basic authentication.

Source code in dlunch/cli.py
@cli.group()\n@click.pass_obj\ndef credentials(obj):\n    \"\"\"Manage users credentials for basic authentication.\"\"\"\n
"},{"location":"reference/dlunch/cli/#dlunch.cli.db","title":"db","text":"
db(obj)\n

Manage the database.

Source code in dlunch/cli.py
@cli.group()\n@click.pass_obj\ndef db(obj):\n    \"\"\"Manage the database.\"\"\"\n
"},{"location":"reference/dlunch/cli/#dlunch.cli.delete_database","title":"delete_database","text":"
delete_database(obj)\n

Delete the database.

Source code in dlunch/cli.py
@db.command(\"delete\")\n@click.confirmation_option()\n@click.pass_obj\ndef delete_database(obj):\n    \"\"\"Delete the database.\"\"\"\n\n    # Create database\n    try:\n        engine = create_engine(obj[\"config\"])\n        Data.metadata.drop_all(engine)\n        click.secho(\"Database deleted\", fg=\"green\")\n    except Exception as e:\n        # Generic error\n        click.secho(\"Cannot delete database\", fg=\"red\")\n        click.secho(f\"\\n ===== EXCEPTION =====\\n\\n{e}\", fg=\"red\")\n
"},{"location":"reference/dlunch/cli/#dlunch.cli.delete_table","title":"delete_table","text":"
delete_table(obj, name)\n

Drop a single table from database.

Source code in dlunch/cli.py
@table.command(\"drop\")\n@click.confirmation_option()\n@click.argument(\"name\")\n@click.pass_obj\ndef delete_table(obj, name):\n    \"\"\"Drop a single table from database.\"\"\"\n\n    # Drop table\n    try:\n        engine = create_engine(obj[\"config\"])\n        metadata_obj.tables[name].drop(engine)\n        click.secho(f\"Table '{name}' deleted\", fg=\"green\")\n    except Exception as e:\n        # Generic error\n        click.secho(\"Cannot drop table\", fg=\"red\")\n        click.secho(f\"\\n ===== EXCEPTION =====\\n\\n{e}\", fg=\"red\")\n
"},{"location":"reference/dlunch/cli/#dlunch.cli.export_table_to_csv","title":"export_table_to_csv","text":"
export_table_to_csv(obj, name, csv_file_path, index)\n

Export a single table to a csv file.

Source code in dlunch/cli.py
@table.command(\"export\")\n@click.argument(\"name\")\n@click.argument(\"csv_file_path\")\n@click.option(\n    \"--index/--no-index\",\n    \"index\",\n    show_default=True,\n    default=False,\n    help=\"select if index is exported to csv\",\n)\n@click.pass_obj\ndef export_table_to_csv(obj, name, csv_file_path, index):\n    \"\"\"Export a single table to a csv file.\"\"\"\n\n    click.secho(f\"Export table '{name}' to CSV {csv_file_path}\", fg=\"yellow\")\n\n    # Create dataframe\n    try:\n        engine = create_engine(obj[\"config\"])\n        df = pd.read_sql_table(\n            name, engine, schema=obj[\"config\"].db.get(\"schema\", SCHEMA)\n        )\n    except Exception as e:\n        # Generic error\n        click.secho(\"Cannot read table\", fg=\"red\")\n        click.secho(f\"\\n ===== EXCEPTION =====\\n\\n{e}\", fg=\"red\")\n\n    # Show head\n    click.echo(\"First three rows of the table\")\n    click.echo(f\"{df.head(3)}\\n\")\n\n    # Export table\n    try:\n        df.to_csv(csv_file_path, index=index)\n    except Exception as e:\n        # Generic error\n        click.secho(\"Cannot write CSV\", fg=\"red\")\n        click.secho(f\"\\n ===== EXCEPTION =====\\n\\n{e}\", fg=\"red\")\n\n    click.secho(\"Done\", fg=\"green\")\n
"},{"location":"reference/dlunch/cli/#dlunch.cli.generate_secrets","title":"generate_secrets","text":"
generate_secrets(obj)\n

Generate secrets for DATA_LUNCH_COOKIE_SECRET and DATA_LUNCH_OAUTH_ENC_KEY env variables.

Source code in dlunch/cli.py
@utils.command(\"generate-secrets\")\n@click.pass_obj\ndef generate_secrets(obj):\n    \"\"\"Generate secrets for DATA_LUNCH_COOKIE_SECRET and DATA_LUNCH_OAUTH_ENC_KEY env variables.\"\"\"\n\n    try:\n        click.secho(\"Print secrets\\n\", fg=\"yellow\")\n        result_secret = subprocess.run(\n            [\"panel\", \"secret\"],\n            stdout=subprocess.PIPE,\n            stderr=subprocess.STDOUT,\n        )\n        click.secho(\"COOKIE SECRET:\", fg=\"yellow\")\n        click.secho(\n            f\"{result_secret.stdout.decode('utf-8')}\",\n            fg=\"cyan\",\n        )\n        result_encription = subprocess.run(\n            [\"panel\", \"oauth-secret\"],\n            stdout=subprocess.PIPE,\n            stderr=subprocess.STDOUT,\n        )\n        click.secho(\"ENCRIPTION KEY:\", fg=\"yellow\")\n        click.secho(\n            f\"{result_encription.stdout.decode('utf-8')}\",\n            fg=\"cyan\",\n        )\n        click.secho(\"Done\", fg=\"green\")\n    except Exception as e:\n        # Generic error\n        click.secho(\"Cannot generate secrets\", fg=\"red\")\n        click.secho(f\"\\n ===== EXCEPTION =====\\n\\n{e}\", fg=\"red\")\n
"},{"location":"reference/dlunch/cli/#dlunch.cli.init_database","title":"init_database","text":"
init_database(obj, add_basic_auth_users)\n

Initialize the database.

Source code in dlunch/cli.py
@db.command(\"init\")\n@click.option(\n    \"--add-basic-auth-users\",\n    \"add_basic_auth_users\",\n    is_flag=True,\n    help=\"automatically create basic auth standard users\",\n)\n@click.pass_obj\ndef init_database(obj, add_basic_auth_users):\n    \"\"\"Initialize the database.\"\"\"\n\n    # Create database\n    create_database(obj[\"config\"], add_basic_auth_users=add_basic_auth_users)\n\n    click.secho(\n        f\"Database initialized (basic auth users: {add_basic_auth_users})\",\n        fg=\"green\",\n    )\n
"},{"location":"reference/dlunch/cli/#dlunch.cli.list_users_name","title":"list_users_name","text":"
list_users_name(obj)\n

List users.

Source code in dlunch/cli.py
@users.command(\"list\")\n@click.pass_obj\ndef list_users_name(obj):\n    \"\"\"List users.\"\"\"\n\n    # Clear action\n    usernames = auth.list_users(config=obj[\"config\"])\n    click.secho(\"USERS:\")\n    click.secho(\"\\n\".join(usernames), fg=\"yellow\")\n    click.secho(\"\\nDone\", fg=\"green\")\n
"},{"location":"reference/dlunch/cli/#dlunch.cli.load_table","title":"load_table","text":"
load_table(\n    obj,\n    name,\n    csv_file_path,\n    index,\n    index_label,\n    index_col,\n    if_exists,\n)\n

Load a single table from a csv file.

Source code in dlunch/cli.py
@table.command(\"load\")\n@click.confirmation_option()\n@click.argument(\"name\")\n@click.argument(\"csv_file_path\")\n@click.option(\n    \"--index/--no-index\",\n    \"index\",\n    show_default=True,\n    default=True,\n    help=\"select if index is loaded to table\",\n)\n@click.option(\n    \"-l\",\n    \"--index-label\",\n    \"index_label\",\n    type=str,\n    default=None,\n    help=\"select index label\",\n)\n@click.option(\n    \"-c\",\n    \"--index-col\",\n    \"index_col\",\n    type=str,\n    default=None,\n    help=\"select the column used as index\",\n)\n@click.option(\n    \"-e\",\n    \"--if-exists\",\n    \"if_exists\",\n    type=click.Choice([\"fail\", \"replace\", \"append\"], case_sensitive=False),\n    show_default=True,\n    default=\"append\",\n    help=\"logict used if the table already exists\",\n)\n@click.pass_obj\ndef load_table(\n    obj, name, csv_file_path, index, index_label, index_col, if_exists\n):\n    \"\"\"Load a single table from a csv file.\"\"\"\n\n    click.secho(f\"Load CSV {csv_file_path} to table '{name}'\", fg=\"yellow\")\n\n    # Create dataframe\n    df = pd.read_csv(csv_file_path, index_col=index_col)\n\n    # Show head\n    click.echo(\"First three rows of the CSV table\")\n    click.echo(f\"{df.head(3)}\\n\")\n\n    # Load table\n    try:\n        engine = create_engine(obj[\"config\"])\n        df.to_sql(\n            name,\n            engine,\n            schema=obj[\"config\"].db.get(\"schema\", SCHEMA),\n            index=index,\n            index_label=index_label,\n            if_exists=if_exists,\n        )\n        click.secho(\"Done\", fg=\"green\")\n    except Exception as e:\n        # Generic error\n        click.secho(\"Cannot load table\", fg=\"red\")\n        click.secho(f\"\\n ===== EXCEPTION =====\\n\\n{e}\", fg=\"red\")\n
"},{"location":"reference/dlunch/cli/#dlunch.cli.main","title":"main","text":"
main() -> None\n

Main command line entrypoint.

Source code in dlunch/cli.py
def main() -> None:\n    \"\"\"Main command line entrypoint.\"\"\"\n    cli(auto_envvar_prefix=\"DATA_LUNCH\")\n
"},{"location":"reference/dlunch/cli/#dlunch.cli.remove_privileged_user","title":"remove_privileged_user","text":"
remove_privileged_user(obj, user)\n

Remove user from both privileged users and basic login credentials table.

Source code in dlunch/cli.py
@users.command(\"remove\")\n@click.confirmation_option()\n@click.argument(\"user\")\n@click.pass_obj\ndef remove_privileged_user(obj, user):\n    \"\"\"Remove user from both privileged users and basic login credentials table.\"\"\"\n\n    # Clear action\n    deleted_data = auth.remove_user(user, config=obj[\"config\"])\n\n    if (deleted_data[\"privileged_users_deleted\"] > 0) or (\n        deleted_data[\"credentials_deleted\"] > 0\n    ):\n        click.secho(\n            f\"User '{user}' removed (auth: {deleted_data['privileged_users_deleted']}, cred: {deleted_data['credentials_deleted']})\",\n            fg=\"green\",\n        )\n    else:\n        click.secho(f\"User '{user}' does not exist\", fg=\"yellow\")\n
"},{"location":"reference/dlunch/cli/#dlunch.cli.remove_user_credential","title":"remove_user_credential","text":"
remove_user_credential(obj, user)\n

Remove user from both privileged users and basic login credentials table.

Source code in dlunch/cli.py
@credentials.command(\"remove\")\n@click.confirmation_option()\n@click.argument(\"user\")\n@click.pass_obj\ndef remove_user_credential(obj, user):\n    \"\"\"Remove user from both privileged users and basic login credentials table.\"\"\"\n\n    # Clear action\n    deleted_data = auth.remove_user(user, config=obj[\"config\"])\n\n    if (deleted_data[\"privileged_users_deleted\"] > 0) or (\n        deleted_data[\"credentials_deleted\"] > 0\n    ):\n        click.secho(\n            f\"User '{user}' removed (auth: {deleted_data['privileged_users_deleted']}, cred: {deleted_data['credentials_deleted']})\",\n            fg=\"green\",\n        )\n    else:\n        click.secho(f\"User '{user}' does not exist\", fg=\"yellow\")\n
"},{"location":"reference/dlunch/cli/#dlunch.cli.table","title":"table","text":"
table(obj)\n

Manage tables in database.

Source code in dlunch/cli.py
@db.group()\n@click.pass_obj\ndef table(obj):\n    \"\"\"Manage tables in database.\"\"\"\n
"},{"location":"reference/dlunch/cli/#dlunch.cli.users","title":"users","text":"
users(obj)\n

Manage privileged users and admin privileges.

Source code in dlunch/cli.py
@cli.group()\n@click.pass_obj\ndef users(obj):\n    \"\"\"Manage privileged users and admin privileges.\"\"\"\n
"},{"location":"reference/dlunch/cli/#dlunch.cli.utils","title":"utils","text":"
utils(obj)\n

Utility commands.

Source code in dlunch/cli.py
@cli.group()\n@click.pass_obj\ndef utils(obj):\n    \"\"\"Utility commands.\"\"\"\n
"},{"location":"reference/dlunch/cloud/","title":"cloud","text":"

Module with functions to interact with GCP storage service.

Functions:

Name Description download_from_gcloud

Download a file from GCP storage.

download_from_gcloud_as_bytes

Download a file from GCP storage as bytes stream.

get_gcloud_bucket_list

List buckets available in GCP storage.

upload_to_gcloud

Upload a local file to GCP storage.

upload_to_gcloud_from_string

Upload the content of a string to GCP storage.

Attributes:

Name Type Description log Logger

Module logger.

"},{"location":"reference/dlunch/cloud/#dlunch.cloud.log","title":"log module-attribute","text":"
log: Logger = getLogger(__name__)\n

Module logger.

"},{"location":"reference/dlunch/cloud/#dlunch.cloud.download_from_gcloud","title":"download_from_gcloud","text":"
download_from_gcloud(\n    source_blob_name: str,\n    destination_file_name: str,\n    bucket_name: str,\n    project: str,\n) -> None\n

Download a file from GCP storage.

Parameters:

Name Type Description Default source_blob_name str

blob name of the source object.

required destination_file_name str

local filepath for the downloaded resource.

required bucket_name str

bucket name.

required project str

GCP project ID.

required Source code in dlunch/cloud.py
def download_from_gcloud(\n    source_blob_name: str,\n    destination_file_name: str,\n    bucket_name: str,\n    project: str,\n) -> None:\n    \"\"\"Download a file from GCP storage.\n\n    Args:\n        source_blob_name (str): blob name of the source object.\n        destination_file_name (str): local filepath for the downloaded resource.\n        bucket_name (str): bucket name.\n        project (str): GCP project ID.\n    \"\"\"\n    # Create storage client\n    storage_client = storage.Client(project=project)\n\n    try:\n        # Get bucket\n        bucket = storage_client.bucket(bucket_name)\n        # Create blob\n        blob = bucket.blob(source_blob_name)\n        # Download\n        blob.download_to_filename(destination_file_name)\n        log.info(\n            f\"file '{source_blob_name}' downloaded to file '{destination_file_name}' successfully\"\n        )\n    except Exception as e:\n        log.warning(\"google storage download exception\\n\\t\" + str(e))\n
"},{"location":"reference/dlunch/cloud/#dlunch.cloud.download_from_gcloud_as_bytes","title":"download_from_gcloud_as_bytes","text":"
download_from_gcloud_as_bytes(\n    source_blob_name: str, bucket_name: str, project: str\n) -> bytes\n

Download a file from GCP storage as bytes stream.

Parameters:

Name Type Description Default source_blob_name str

blob name of the source file.

required bucket_name str

bucket name.

required project str

GCP project ID.

required

Returns:

Type Description bytes

downloaded resource.

Source code in dlunch/cloud.py
def download_from_gcloud_as_bytes(\n    source_blob_name: str,\n    bucket_name: str,\n    project: str,\n) -> bytes:\n    \"\"\"Download a file from GCP storage as bytes stream.\n\n    Args:\n        source_blob_name (str): blob name of the source file.\n        bucket_name (str): bucket name.\n        project (str): GCP project ID.\n\n    Returns:\n        bytes: downloaded resource.\n    \"\"\"\n    # Create storage client\n    storage_client = storage.Client(project=project)\n\n    try:\n        # Get bucket\n        bucket = storage_client.bucket(bucket_name)\n        # Create blob\n        blob = bucket.blob(source_blob_name)\n        # Download\n        bytes_object = blob.download_as_bytes()\n        log.info(\n            f\"file '{source_blob_name}' downloaded to object successfully\"\n        )\n    except Exception as e:\n        log.warning(\"google storage download exception\\n\\t\" + str(e))\n\n    return bytes_object\n
"},{"location":"reference/dlunch/cloud/#dlunch.cloud.get_gcloud_bucket_list","title":"get_gcloud_bucket_list","text":"
get_gcloud_bucket_list(project: str) -> list[str]\n

List buckets available in GCP storage.

Parameters:

Name Type Description Default project str

GCP project ID.

required

Returns:

Type Description list[str]

list with bucket names.

Source code in dlunch/cloud.py
def get_gcloud_bucket_list(project: str) -> list[str]:\n    \"\"\"List buckets available in GCP storage.\n\n    Args:\n        project (str): GCP project ID.\n\n    Returns:\n        list[str]: list with bucket names.\n    \"\"\"\n    # Create storage client\n    storage_client = storage.Client(project=project)\n\n    # Return bucket\n    buckets = list(storage_client.list_buckets())\n\n    return buckets\n
"},{"location":"reference/dlunch/cloud/#dlunch.cloud.upload_to_gcloud","title":"upload_to_gcloud","text":"
upload_to_gcloud(\n    source_file_name: str,\n    destination_blob_name: str,\n    bucket_name: str,\n    project: str,\n) -> None\n

Upload a local file to GCP storage.

Parameters:

Name Type Description Default source_file_name str

filepath.

required destination_blob_name str

blob name to use as destination.

required bucket_name str

bucket name.

required project str

GCP project ID.

required Source code in dlunch/cloud.py
def upload_to_gcloud(\n    source_file_name: str,\n    destination_blob_name: str,\n    bucket_name: str,\n    project: str,\n) -> None:\n    \"\"\"Upload a local file to GCP storage.\n\n    Args:\n        source_file_name (str): filepath.\n        destination_blob_name (str): blob name to use as destination.\n        bucket_name (str): bucket name.\n        project (str): GCP project ID.\n    \"\"\"\n    # Create storage client\n    storage_client = storage.Client(project=project)\n\n    try:\n        # Get bucket\n        bucket = storage_client.bucket(bucket_name)\n        # Create blob\n        blob = bucket.blob(destination_blob_name)\n        # Upload\n        blob.upload_from_filename(source_file_name)\n        log.info(\n            f\"file '{source_file_name}' uploaded to bucket '{bucket_name}' successfully\"\n        )\n    except Exception as e:\n        log.warning(\"google storage upload exception\\n\\t\" + str(e))\n
"},{"location":"reference/dlunch/cloud/#dlunch.cloud.upload_to_gcloud_from_string","title":"upload_to_gcloud_from_string","text":"
upload_to_gcloud_from_string(\n    source_string: str,\n    destination_blob_name: str,\n    bucket_name: str,\n    project: str,\n) -> None\n

Upload the content of a string to GCP storage.

Parameters:

Name Type Description Default source_string str

string to upload.

required destination_blob_name str

blob name to use as destination.

required bucket_name str

bucket name.

required project str

GCP project ID.

required Source code in dlunch/cloud.py
def upload_to_gcloud_from_string(\n    source_string: str,\n    destination_blob_name: str,\n    bucket_name: str,\n    project: str,\n) -> None:\n    \"\"\"Upload the content of a string to GCP storage.\n\n    Args:\n        source_string (str): string to upload.\n        destination_blob_name (str): blob name to use as destination.\n        bucket_name (str): bucket name.\n        project (str): GCP project ID.\n    \"\"\"\n    # Create storage client\n    storage_client = storage.Client(project=project)\n\n    try:\n        # Get bucket\n        bucket = storage_client.bucket(bucket_name)\n        # Create blob\n        blob = bucket.blob(destination_blob_name)\n        # Upload\n        blob.upload_from_string(source_string)\n        log.info(\n            f\"file uploaded from string to bucket '{bucket_name}' at '{destination_blob_name}' successfully\"\n        )\n    except Exception as e:\n        log.warning(\"google storage upload exception\\n\\t\" + str(e))\n
"},{"location":"reference/dlunch/core/","title":"core","text":"

Module that defines main functions used to manage Data-Lunch operations.

This module defines the Waiter class that contains the main functions used to manage Data-Lunch operations. The class is used to interact with the database and the Panel widgets.

Modules:

Name Description auth

Module with classes and functions used for authentication and password handling.

gui

Module that defines main graphic interface and backend graphic interface.

models

Module with database tables definitions.

Classes:

Name Description Waiter

Class that defines main functions used to manage Data-Lunch operations.

Attributes:

Name Type Description __version__ str

Data-Lunch version.

log Logger

Module logger.

"},{"location":"reference/dlunch/core/#dlunch.core.__version__","title":"__version__ module-attribute","text":"
__version__: str = '3.4.0'\n

Data-Lunch version.

"},{"location":"reference/dlunch/core/#dlunch.core.log","title":"log module-attribute","text":"
log: Logger = getLogger(__name__)\n

Module logger.

"},{"location":"reference/dlunch/core/#dlunch.core.Waiter","title":"Waiter","text":"

Class that defines main functions used to manage Data-Lunch operations.

Parameters:

Name Type Description Default config DictConfig | None

Hydra configuration object. Defaults to None.

None Raise

ValueError: when calling (unmangled) methods, if the configuration is not set.

Methods:

Name Description __init__ build_menu

Read menu from file (Excel or image) and upload menu items to database menu table.

change_order_time_takeaway

Change the time and the takeaway flag of an existing order.

clean_tables

Clean tables that should be reset when a new menu is uploaded.

delete_files

Delete local temporary files.

delete_order

Delete an existing order.

df_list_by_lunch_time

Build a dictionary of dataframes for each lunch-time, with takeaways included in a dedicated dataframe.

download_dataframe

Build an Excel file with tables representing orders for every lunch-time/takeaway-time.

get_host_name

Return hostname.

reload_menu

Main core function that sync Panel widget with database tables.

send_order

Upload orders and user to database tables.

set_config

Set the configuration for the Waiter instance.

Attributes:

Name Type Description config DictConfig | None

Hydra configuration object

Source code in dlunch/core.py
class Waiter:\n    \"\"\"Class that defines main functions used to manage Data-Lunch operations.\n\n    Args:\n        config (DictConfig| None): Hydra configuration object. Defaults to None.\n\n    Raise:\n        ValueError: when calling (unmangled) methods, if the configuration is not set.\n    \"\"\"\n\n    def __init__(self, config: DictConfig | None = None):\n        self.config: DictConfig | None = None\n        \"\"\"Hydra configuration object\"\"\"\n\n        # Define decorator to raise an error if the configuration is not set\n        def _common_decorator(func):\n            def wrapper(*args, **kwargs):\n                if self.config is None:\n                    raise ValueError(\"waiter configuration is not set\")\n                return func(*args, **kwargs)\n\n            return wrapper\n\n        # Set decorator to unmangled methods (do not apply to set_config method)\n        for method_name in [\n            item\n            for item in dir(self)\n            if not item.startswith(\"_\") and not (item == \"set_config\")\n        ]:\n            attr = getattr(self, method_name)\n            # Check if the attribute is a method\n            if callable(attr):\n                wrapped = _common_decorator(attr)\n                setattr(self, method_name, wrapped)\n\n    def set_config(self, config: DictConfig):\n        \"\"\"Set the configuration for the Waiter instance.\n\n        Args:\n            config (DictConfig): Hydra configuration object.\n        \"\"\"\n        self.config = config\n\n    def get_host_name(self) -> str:\n        \"\"\"Return hostname.\n\n        This function behavior changes if called from localhost, Docker container or\n        production server.\n\n        Returns:\n            str: hostname.\n        \"\"\"\n        try:\n            ip_address = socket.gethostbyname(socket.gethostname())\n            dig_res = subprocess.run(\n                [\"dig\", \"+short\", \"-x\", ip_address], stdout=subprocess.PIPE\n            ).stdout\n            host_name = (\n                subprocess.run(\n                    [\"cut\", \"-d.\", \"-f1\"],\n                    stdout=subprocess.PIPE,\n                    input=dig_res,\n                )\n                .stdout.decode(\"utf-8\")\n                .strip()\n            )\n            if host_name:\n                host_name = host_name.replace(\n                    f\"{self.config.docker_username}_\", \"\"\n                )\n            else:\n                host_name = \"no info\"\n        except Exception:\n            host_name = \"not available\"\n\n        return host_name\n\n    def delete_files(self) -> None:\n        \"\"\"Delete local temporary files.\"\"\"\n        # Delete menu file if exist (every extension)\n        files = list(\n            pathlib.Path(self.config.db.shared_data_folder).glob(\n                self.config.panel.file_name + \"*\"\n            )\n        )\n        log.info(f\"delete files {', '.join([f.name for f in files])}\")\n        for file in files:\n            file.unlink(missing_ok=True)\n\n    def clean_tables(self) -> None:\n        \"\"\"Clean tables that should be reset when a new menu is uploaded.\"\"\"\n        # Clean tables\n        # Clean orders\n        models.Orders.clear(config=self.config)\n        # Clean menu\n        models.Menu.clear(config=self.config)\n        # Clean users\n        models.Users.clear(config=self.config)\n        # Clean flags\n        models.Flags.clear_guest_override(config=self.config)\n        # Reset flags\n        models.set_flag(config=self.config, id=\"no_more_orders\", value=False)\n        log.info(\"reset values in table 'flags'\")\n        # Clean cache\n        pn.state.clear_caches()\n        log.info(\"cache cleaned\")\n\n    def build_menu(\n        self,\n        event: param.parameterized.Event,\n        app: pn.Template,\n        gi: gui.GraphicInterface,\n    ) -> None:\n        \"\"\"Read menu from file (Excel or image) and upload menu items to database `menu` table.\n\n        Args:\n            event (param.parameterized.Event): Panel button event.\n            app (pn.Template): Panel app template (used to open modal windows in case of database errors).\n            gi (gui.GraphicInterface): graphic interface object (used to interact with Panel widgets).\n        \"\"\"\n        # Hide messages\n        gi.error_message.visible = False\n\n        # Build image path\n        menu_filename = str(\n            pathlib.Path(self.config.db.shared_data_folder)\n            / self.config.panel.file_name\n        )\n\n        # Delete menu file if exist (every extension)\n        self.delete_files()\n\n        # Load file from widget\n        if gi.file_widget.value is not None:\n            # Find file extension\n            file_ext = pathlib.PurePath(gi.file_widget.filename).suffix\n\n            # Save file locally\n            local_menu_filename = menu_filename + file_ext\n            gi.file_widget.save(local_menu_filename)\n\n            # Clean tables\n            self.clean_tables()\n\n            # File can be either an excel file or an image\n            if file_ext == \".png\" or file_ext == \".jpg\" or file_ext == \".jpeg\":\n                # Transform image into a pandas DataFrame\n                # Open image with PIL\n                img = Image.open(local_menu_filename)\n                # Extract text from image\n                text = pytesseract.image_to_string(img, lang=\"ita\")\n                # Process rows (rows that are completely uppercase are section titles)\n                rows = [\n                    row\n                    for row in text.split(\"\\n\")\n                    if row and not row.isupper()\n                ]\n                df = pd.DataFrame({\"item\": rows})\n                # Concat additional items\n                df = pd.concat(\n                    [\n                        df,\n                        pd.DataFrame(\n                            {\n                                \"item\": [\n                                    item[\"name\"]\n                                    for item in self.config.panel.additional_items_to_concat\n                                ]\n                            }\n                        ),\n                    ],\n                    axis=\"index\",\n                )\n\n            elif file_ext == \".xlsx\":\n                log.info(\"excel file uploaded\")\n                df = pd.read_excel(\n                    local_menu_filename, names=[\"item\"], header=None\n                )\n                # Concat additional items\n                df = pd.concat(\n                    [\n                        df,\n                        pd.DataFrame(\n                            {\n                                \"item\": [\n                                    item[\"name\"]\n                                    for item in self.config.panel.additional_items_to_concat\n                                ]\n                            }\n                        ),\n                    ],\n                    axis=\"index\",\n                    ignore_index=True,\n                )\n            else:\n                df = pd.DataFrame()\n                pn.state.notifications.error(\n                    \"Wrong file type\",\n                    duration=self.config.panel.notifications.duration,\n                )\n                log.warning(\"wrong file type\")\n                return\n\n            # Upload to database menu table\n            engine = models.create_engine(self.config)\n            try:\n                df.drop_duplicates(subset=\"item\").to_sql(\n                    models.Menu.__tablename__,\n                    engine,\n                    schema=self.config.db.get(\"schema\", models.SCHEMA),\n                    index=False,\n                    if_exists=\"append\",\n                )\n                # Update dataframe widget\n                self.reload_menu(\n                    None,\n                    gi,\n                )\n\n                pn.state.notifications.success(\n                    \"Menu uploaded\",\n                    duration=self.config.panel.notifications.duration,\n                )\n                log.info(\"menu uploaded\")\n            except Exception as e:\n                # Any exception here is a database fault\n                pn.state.notifications.error(\n                    \"Database error\",\n                    duration=self.config.panel.notifications.duration,\n                )\n                gi.error_message.object = (\n                    f\"DATABASE ERROR<br><br>ERROR:<br>{str(e)}\"\n                )\n                gi.error_message.visible = True\n                log.warning(\"database error\")\n                # Open modal window\n                app.open_modal()\n\n        else:\n            pn.state.notifications.warning(\n                \"No file selected\",\n                duration=self.config.panel.notifications.duration,\n            )\n            log.warning(\"no file selected\")\n\n    def reload_menu(\n        self,\n        event: param.parameterized.Event,\n        gi: gui.GraphicInterface,\n    ) -> None:\n        \"\"\"Main core function that sync Panel widget with database tables.\n\n        Stop orders and guest override checks are carried out by this function.\n        Also the banner image is shown based on a check run by this function.\n\n        `menu`, `orders` and `users` tables are used to build a list of orders for each lunch time.\n        Takeaway orders are evaluated separately.\n\n        At the end stats about lunches are calculated and loaded to database. Finally\n        statistics (values and table) shown inside the app are updated accordingly.\n\n        Args:\n            event (param.parameterized.Event): Panel button event.\n            gi (gui.GraphicInterface): graphic interface object (used to interact with Panel widgets).\n        \"\"\"\n        # Create session\n        session = models.create_session(self.config)\n\n        with session:\n            # Check if someone changed the \"no_more_order\" toggle\n            if gi.toggle_no_more_order_button.value != models.get_flag(\n                config=self.config, id=\"no_more_orders\"\n            ):\n                # The following statement will trigger the toggle callback\n                # which will call reload_menu once again\n                # This is the reason why this if contains a return (without the return\n                # the content will be reloaded twice)\n                gi.toggle_no_more_order_button.value = models.get_flag(\n                    config=self.config, id=\"no_more_orders\"\n                )\n\n                return\n\n            # Check guest override button status (if not in table use False)\n            gi.toggle_guest_override_button.value = models.get_flag(\n                config=self.config,\n                id=f\"{pn_user(self.config)}_guest_override\",\n                value_if_missing=False,\n            )\n\n            # Set no more orders toggle button and the change order time button\n            # visibility and activation\n            if auth.is_guest(\n                user=pn_user(self.config),\n                config=self.config,\n                allow_override=False,\n            ):\n                # Deactivate the no_more_orders_button for guest users\n                gi.toggle_no_more_order_button.disabled = True\n                gi.toggle_no_more_order_button.visible = False\n                # Deactivate the change_order_time_button for guest users\n                gi.change_order_time_takeaway_button.disabled = True\n                gi.change_order_time_takeaway_button.visible = False\n            else:\n                # Activate the no_more_orders_button for privileged users\n                gi.toggle_no_more_order_button.disabled = False\n                gi.toggle_no_more_order_button.visible = True\n                # Show the change_order_time_button for privileged users\n                # It is disabled by the no more order button if necessary\n                gi.change_order_time_takeaway_button.visible = True\n\n            # Guest graphic configuration\n            if auth.is_guest(user=pn_user(self.config), config=self.config):\n                # If guest show guest type selection group\n                gi.person_widget.widgets[\"guest\"].disabled = False\n                gi.person_widget.widgets[\"guest\"].visible = True\n            else:\n                # If user is privileged hide guest type selection group\n                gi.person_widget.widgets[\"guest\"].disabled = True\n                gi.person_widget.widgets[\"guest\"].visible = False\n\n            # Reload menu\n            engine = models.create_engine(self.config)\n            df = models.Menu.read_as_df(\n                config=self.config,\n                index_col=\"id\",\n            )\n            # Add order (for selecting items) and note columns\n            df[\"order\"] = False\n            df[self.config.panel.gui.note_column_name] = \"\"\n            gi.dataframe.value = df\n            gi.dataframe.formatters = {\"order\": {\"type\": \"tickCross\"}}\n            gi.dataframe.editors = {\n                \"id\": None,\n                \"item\": None,\n                \"order\": CheckboxEditor(),\n                self.config.panel.gui.note_column_name: \"input\",\n            }\n            gi.dataframe.header_align = OmegaConf.to_container(\n                self.config.panel.gui.menu_column_align, resolve=True\n            )\n            gi.dataframe.text_align = OmegaConf.to_container(\n                self.config.panel.gui.menu_column_align, resolve=True\n            )\n\n            if gi.toggle_no_more_order_button.value:\n                gi.dataframe.hidden_columns = [\"id\", \"order\"]\n                gi.dataframe.disabled = True\n            else:\n                gi.dataframe.hidden_columns = [\"id\"]\n                gi.dataframe.disabled = False\n\n            # If menu is empty show banner image, otherwise show menu\n            if df.empty:\n                gi.no_menu_col.visible = True\n                gi.main_header_row.visible = False\n                gi.quote.visible = False\n                gi.menu_flexbox.visible = False\n                gi.buttons_flexbox.visible = False\n                gi.results_divider.visible = False\n                gi.res_col.visible = False\n            else:\n                gi.no_menu_col.visible = False\n                gi.main_header_row.visible = True\n                gi.quote.visible = True\n                gi.menu_flexbox.visible = True\n                gi.buttons_flexbox.visible = True\n                gi.results_divider.visible = True\n                gi.res_col.visible = True\n\n            log.debug(\"menu reloaded\")\n\n            # Load results\n            df_dict = self.df_list_by_lunch_time()\n            # Clean columns and load text and dataframes\n            gi.res_col.clear()\n            gi.time_col.clear()\n            if df_dict:\n                # Titles\n                gi.res_col.append(self.config.panel.result_column_text)\n                gi.time_col.append(gi.time_col_title)\n                # Build guests list (one per each guest types)\n                guests_lists = {}\n                for guest_type in self.config.panel.guest_types:\n                    guests_lists[guest_type] = [\n                        user.id\n                        for user in session.scalars(\n                            select(models.Users).where(\n                                models.Users.guest == guest_type\n                            )\n                        ).all()\n                    ]\n                # Loop through lunch times\n                for time, df in df_dict.items():\n                    # Find the number of grumbling stomachs\n                    grumbling_stomachs = len(\n                        [\n                            c\n                            for c in df.columns\n                            if c\n                            not in (\n                                self.config.panel.gui.total_column_name,\n                                self.config.panel.gui.note_column_name,\n                            )\n                        ]\n                    )\n                    # Set different graphics for takeaway lunches\n                    if self.config.panel.gui.takeaway_id in time:\n                        res_col_label_kwargs = {\n                            \"time\": time.replace(\n                                self.config.panel.gui.takeaway_id, \"\"\n                            ),\n                            \"diners_n\": grumbling_stomachs,\n                            \"emoji\": self.config.panel.gui.takeaway_emoji,\n                            \"is_takeaway\": True,\n                            \"takeaway_alert_sign\": f\"&nbsp{gi.takeaway_alert_sign}&nbsp{gi.takeaway_alert_text}\",\n                            \"css_classes\": OmegaConf.to_container(\n                                self.config.panel.gui.takeaway_class_res_col,\n                                resolve=True,\n                            ),\n                            \"stylesheets\": [\n                                self.config.panel.gui.css_files.labels_path\n                            ],\n                        }\n                        time_col_label_kwargs = {\n                            \"time\": time.replace(\n                                self.config.panel.gui.takeaway_id, \"\"\n                            ),\n                            \"diners_n\": str(grumbling_stomachs) + \"&nbsp\",\n                            \"separator\": \"<br>\",\n                            \"emoji\": self.config.panel.gui.takeaway_emoji,\n                            \"align\": (\"center\", \"center\"),\n                            \"sizing_mode\": \"stretch_width\",\n                            \"is_takeaway\": True,\n                            \"takeaway_alert_sign\": gi.takeaway_alert_sign,\n                            \"css_classes\": OmegaConf.to_container(\n                                self.config.panel.gui.takeaway_class_time_col,\n                                resolve=True,\n                            ),\n                            \"stylesheets\": [\n                                self.config.panel.gui.css_files.labels_path\n                            ],\n                        }\n                    else:\n                        res_col_label_kwargs = {\n                            \"time\": time,\n                            \"diners_n\": grumbling_stomachs,\n                            \"emoji\": random.choice(\n                                self.config.panel.gui.food_emoji\n                            ),\n                            \"css_classes\": OmegaConf.to_container(\n                                self.config.panel.gui.time_class_res_col,\n                                resolve=True,\n                            ),\n                            \"stylesheets\": [\n                                self.config.panel.gui.css_files.labels_path\n                            ],\n                        }\n                        time_col_label_kwargs = {\n                            \"time\": time,\n                            \"diners_n\": str(grumbling_stomachs) + \"&nbsp\",\n                            \"separator\": \"<br>\",\n                            \"emoji\": self.config.panel.gui.restaurant_emoji,\n                            \"per_icon\": \"&#10006; \",\n                            \"align\": (\"center\", \"center\"),\n                            \"sizing_mode\": \"stretch_width\",\n                            \"css_classes\": OmegaConf.to_container(\n                                self.config.panel.gui.time_class_time_col,\n                                resolve=True,\n                            ),\n                            \"stylesheets\": [\n                                self.config.panel.gui.css_files.labels_path\n                            ],\n                        }\n                    # Add text to result column\n                    gi.res_col.append(pn.Spacer(height=15))\n                    gi.res_col.append(\n                        gi.build_time_label(**res_col_label_kwargs)\n                    )\n                    # Add non editable table to result column\n                    gi.res_col.append(pn.Spacer(height=5))\n                    gi.res_col.append(\n                        gi.build_order_table(\n                            self.config,\n                            df=df,\n                            time=time,\n                            guests_lists=guests_lists,\n                        )\n                    )\n                    # Add also a label to lunch time column\n                    gi.time_col.append(\n                        gi.build_time_label(**time_col_label_kwargs)\n                    )\n\n            log.debug(\"results reloaded\")\n\n            # Clean stats column\n            gi.sidebar_stats_col.clear()\n            # Update stats\n            # Find how many people eat today (total number) and add value to database\n            # stats table (when adding a stats if guest is not specified None is used\n            # as default)\n            today_locals_count = session.scalar(\n                select(func.count(models.Users.id)).where(\n                    models.Users.guest == \"NotAGuest\"\n                )\n            )\n            new_stat = models.Stats(hungry_people=today_locals_count)\n            # Use an upsert for postgresql, a simple session add otherwise\n            models.session_add_with_upsert(\n                session=session, constraint=\"stats_pkey\", new_record=new_stat\n            )\n            # For each guest type find how many guests eat today\n            for guest_type in self.config.panel.guest_types:\n                today_guests_count = session.scalar(\n                    select(func.count(models.Users.id)).where(\n                        models.Users.guest == guest_type\n                    )\n                )\n                new_stat = models.Stats(\n                    guest=guest_type, hungry_people=today_guests_count\n                )\n                # Use an upsert for postgresql, a simple session add otherwise\n                models.session_add_with_upsert(\n                    session=session,\n                    constraint=\"stats_pkey\",\n                    new_record=new_stat,\n                )\n\n            # Commit stats\n            session.commit()\n\n            # Group stats by month and return how many people had lunch\n            df_stats = pd.read_sql_query(\n                self.config.db.stats_query.format(\n                    schema=self.config.db.get(\"schema\", models.SCHEMA)\n                ),\n                engine,\n            )\n            # Stats top text\n            stats_and_info_text = gi.build_stats_and_info_text(\n                config=self.config,\n                df_stats=df_stats,\n                user=pn_user(self.config),\n                version=__version__,\n                host_name=self.get_host_name(),\n                stylesheets=[self.config.panel.gui.css_files.stats_info_path],\n            )\n            # Remove NotAGuest (non-guest users)\n            df_stats.Guest = df_stats.Guest.replace(\n                \"NotAGuest\", self.config.panel.stats_locals_column_name\n            )\n            # Pivot table on guest type\n            df_stats = df_stats.pivot(\n                columns=\"Guest\",\n                index=self.config.panel.stats_id_cols,\n                values=\"Hungry People\",\n            ).reset_index()\n            df_stats[self.config.panel.gui.total_column_name.title()] = (\n                df_stats.sum(axis=\"columns\", numeric_only=True)\n            )\n            # Add value and non-editable option to stats table\n            gi.stats_widget.editors = {c: None for c in df_stats.columns}\n            gi.stats_widget.value = df_stats\n            gi.sidebar_stats_col.append(stats_and_info_text[\"stats\"])\n            gi.sidebar_stats_col.append(gi.stats_widget)\n            # Add info below person widget (an empty placeholder was left as last\n            # element)\n            gi.sidebar_person_column.objects[-1] = stats_and_info_text[\"info\"]\n            log.debug(\"stats and info updated\")\n\n    def send_order(\n        self,\n        event: param.parameterized.Event,\n        app: pn.Template,\n        person: gui.Person,\n        gi: gui.GraphicInterface,\n    ) -> None:\n        \"\"\"Upload orders and user to database tables.\n\n        The user target of the order is uploaded to `users` table, while the order\n        is uploaded to `orders` table.\n\n        Consistency checks about the user and the order are carried out here (existing user, only one order, etc.).\n        The status of the `stop_orders` flag is checked to avoid that an order is uploaded when it shouldn't.\n\n        Orders for guest users are marked as such before uploading them.\n\n        Args:\n            event (param.parameterized.Event): Panel button event.\n            app (pn.Template): Panel app template (used to open modal windows in case of database errors).\n            person (gui.Person): class that collect order data for the user that is the target of the order.\n            gi (gui.GraphicInterface): graphic interface object (used to interact with Panel widgets).\n        \"\"\"\n        # Get username updated at each key press\n        username_key_press = gi.person_widget._widgets[\"username\"].value_input\n\n        # Hide messages\n        gi.error_message.visible = False\n\n        # Create session\n        session = models.create_session(self.config)\n\n        with session:\n            # Check if the \"no more order\" toggle button is pressed\n            if models.get_flag(config=self.config, id=\"no_more_orders\"):\n                pn.state.notifications.error(\n                    \"It is not possible to place new orders\",\n                    duration=self.config.panel.notifications.duration,\n                )\n\n                # Reload the menu\n                self.reload_menu(\n                    None,\n                    gi,\n                )\n\n                return\n\n            # If auth is active, check if a guests is using a name reserved to a\n            # privileged user\n            if (\n                auth.is_guest(user=pn_user(self.config), config=self.config)\n                and (username_key_press in auth.list_users(config=self.config))\n                and (auth.is_auth_active(config=self.config))\n            ):\n                pn.state.notifications.error(\n                    f\"{username_key_press} is a reserved name<br>Please choose a different one\",\n                    duration=self.config.panel.notifications.duration,\n                )\n\n                # Reload the menu\n                self.reload_menu(\n                    None,\n                    gi,\n                )\n\n                return\n\n            # Check if a privileged user is ordering for an invalid name\n            if (\n                not auth.is_guest(\n                    user=pn_user(self.config), config=self.config\n                )\n                and (\n                    username_key_press\n                    not in (\n                        name\n                        for name in auth.list_users(config=self.config)\n                        if name != \"guest\"\n                    )\n                )\n                and (auth.is_auth_active(config=self.config))\n            ):\n                pn.state.notifications.error(\n                    f\"{username_key_press} is not a valid name<br>for a privileged user<br>Please choose a different one\",\n                    duration=self.config.panel.notifications.duration,\n                )\n\n                # Reload the menu\n                self.reload_menu(\n                    None,\n                    gi,\n                )\n\n                return\n\n            # Write order into database table\n            df = gi.dataframe.value.copy()\n            df_order = df[df.order]\n            # If username is missing or the order is empty return an error message\n            if username_key_press and not df_order.empty:\n                # Check if the user already placed an order\n                if session.get(models.Users, username_key_press):\n                    pn.state.notifications.warning(\n                        f\"Cannot overwrite an order<br>Delete {username_key_press}'s order first and retry\",\n                        duration=self.config.panel.notifications.duration,\n                    )\n                    log.warning(\n                        f\"an order already exist for {username_key_press}\"\n                    )\n                else:\n                    # Place order\n                    try:\n                        # Add User\n                        # Do not pass guest for privileged users (default to NotAGuest)\n                        if auth.is_guest(\n                            user=pn_user(self.config), config=self.config\n                        ):\n                            new_user = models.Users(\n                                id=username_key_press,\n                                guest=person.guest,\n                                lunch_time=person.lunch_time,\n                                takeaway=person.takeaway,\n                            )\n                        else:\n                            new_user = models.Users(\n                                id=username_key_press,\n                                lunch_time=person.lunch_time,\n                                takeaway=person.takeaway,\n                            )\n                        session.add(new_user)\n                        session.commit()\n                        # Add orders as long table (one row for each item selected by a user)\n                        for row in df_order.itertuples(name=\"OrderTuple\"):\n                            # Order\n                            new_order = models.Orders(\n                                user=username_key_press,\n                                menu_item_id=row.Index,\n                                note=getattr(\n                                    row, self.config.panel.gui.note_column_name\n                                ).lower(),\n                            )\n                            session.add(new_order)\n                            session.commit()\n\n                        # Update dataframe widget\n                        self.reload_menu(\n                            None,\n                            gi,\n                        )\n\n                        pn.state.notifications.success(\n                            \"Order sent\",\n                            duration=self.config.panel.notifications.duration,\n                        )\n                        log.info(f\"{username_key_press}'s order saved\")\n                    except Exception as e:\n                        # Any exception here is a database fault\n                        pn.state.notifications.error(\n                            \"Database error\",\n                            duration=self.config.panel.notifications.duration,\n                        )\n                        gi.error_message.object = (\n                            f\"DATABASE ERROR<br><br>ERROR:<br>{str(e)}\"\n                        )\n                        gi.error_message.visible = True\n                        log.error(\"database error\")\n                        # Open modal window\n                        app.open_modal()\n            else:\n                if not username_key_press:\n                    pn.state.notifications.warning(\n                        \"Please insert user name\",\n                        duration=self.config.panel.notifications.duration,\n                    )\n                    log.warning(\"missing username\")\n                else:\n                    pn.state.notifications.warning(\n                        \"Please make a selection\",\n                        duration=self.config.panel.notifications.duration,\n                    )\n                    log.warning(\"no selection made\")\n\n    def delete_order(\n        self,\n        event: param.parameterized.Event,\n        app: pn.Template,\n        gi: gui.GraphicInterface,\n    ) -> None:\n        \"\"\"Delete an existing order.\n\n        Consistency checks about the user and the order are carried out here (existing user, only one order, etc.).\n        The status of the `stop_orders` flag is checked to avoid that an order is uploaded when it shouldn't.\n\n        In addition privileges are taken into account (guest users cannot delete orders that targets a privileged user).\n\n        Args:\n            event (param.parameterized.Event): Panel button event.\n            app (pn.Template): Panel app template (used to open modal windows in case of database errors).\n            gi (gui.GraphicInterface): graphic interface object (used to interact with Panel widgets).\n        \"\"\"\n        # Get username, updated on every keypress\n        username_key_press = gi.person_widget._widgets[\"username\"].value_input\n\n        # Hide messages\n        gi.error_message.visible = False\n\n        # Create session\n        session = models.create_session(self.config)\n\n        with session:\n            # Check if the \"no more order\" toggle button is pressed\n            if models.get_flag(config=self.config, id=\"no_more_orders\"):\n                pn.state.notifications.error(\n                    \"It is not possible to delete orders\",\n                    duration=self.config.panel.notifications.duration,\n                )\n\n                # Reload the menu\n                self.reload_menu(\n                    None,\n                    gi,\n                )\n\n                return\n\n            if username_key_press:\n                # If auth is active, check if a guests is deleting an order of a\n                # privileged user\n                if (\n                    auth.is_guest(\n                        user=pn_user(self.config), config=self.config\n                    )\n                    and (\n                        username_key_press\n                        in auth.list_users(config=self.config)\n                    )\n                    and (auth.is_auth_active(config=self.config))\n                ):\n                    pn.state.notifications.error(\n                        f\"You do not have enough privileges<br>to delete<br>{username_key_press}'s order\",\n                        duration=self.config.panel.notifications.duration,\n                    )\n\n                    # Reload the menu\n                    self.reload_menu(\n                        None,\n                        gi,\n                    )\n\n                    return\n\n                # Delete user\n                try:\n                    num_rows_deleted_users = session.execute(\n                        delete(models.Users).where(\n                            models.Users.id == username_key_press\n                        )\n                    )\n                    # Delete also orders (hotfix for Debian)\n                    num_rows_deleted_orders = session.execute(\n                        delete(models.Orders).where(\n                            models.Orders.user == username_key_press\n                        )\n                    )\n                    session.commit()\n                    if (num_rows_deleted_users.rowcount > 0) or (\n                        num_rows_deleted_orders.rowcount > 0\n                    ):\n                        # Update dataframe widget\n                        self.reload_menu(\n                            None,\n                            gi,\n                        )\n\n                        pn.state.notifications.success(\n                            \"Order canceled\",\n                            duration=self.config.panel.notifications.duration,\n                        )\n                        log.info(f\"{username_key_press}'s order canceled\")\n                    else:\n                        pn.state.notifications.warning(\n                            f'No order for user named<br>\"{username_key_press}\"',\n                            duration=self.config.panel.notifications.duration,\n                        )\n                        log.info(\n                            f\"no order for user named {username_key_press}\"\n                        )\n                except Exception as e:\n                    # Any exception here is a database fault\n                    pn.state.notifications.error(\n                        \"Database error\",\n                        duration=self.config.panel.notifications.duration,\n                    )\n                    gi.error_message.object = (\n                        f\"DATABASE ERROR<br><br>ERROR:<br>{str(e)}\"\n                    )\n                    gi.error_message.visible = True\n                    log.error(\"database error\")\n                    # Open modal window\n                    app.open_modal()\n            else:\n                pn.state.notifications.warning(\n                    \"Please insert user name\",\n                    duration=self.config.panel.notifications.duration,\n                )\n                log.warning(\"missing username\")\n\n    def change_order_time_takeaway(\n        self,\n        event: param.parameterized.Event,\n        person: gui.Person,\n        gi: gui.GraphicInterface,\n    ) -> None:\n        \"\"\"Change the time and the takeaway flag of an existing order.\n\n        Args:\n            event (param.parameterized.Event): Panel button event.\n            person (gui.Person): class that collect order data for the user that is the target of the order.\n            gi (gui.GraphicInterface): graphic interface object (used to interact with Panel widgets).\n        \"\"\"\n        # Get username, updated on every keypress\n        username_key_press = gi.person_widget._widgets[\"username\"].value_input\n\n        # Create session\n        session = models.create_session(self.config)\n\n        with session:\n            # Check if the \"no more order\" toggle button is pressed\n            if models.get_flag(config=self.config, id=\"no_more_orders\"):\n                pn.state.notifications.error(\n                    \"It is not possible to update orders (time)\",\n                    duration=self.config.panel.notifications.duration,\n                )\n\n                # Reload the menu\n                self.reload_menu(\n                    None,\n                    gi,\n                )\n\n                return\n\n            if username_key_press:\n                # Build and execute the update statement\n                update_statement = (\n                    update(models.Users)\n                    .where(models.Users.id == username_key_press)\n                    .values(\n                        lunch_time=person.lunch_time, takeaway=person.takeaway\n                    )\n                    .returning(models.Users)\n                )\n\n                updated_user = session.scalars(update_statement).one_or_none()\n\n                session.commit()\n\n                if updated_user:\n                    # Find updated values\n                    updated_time = updated_user.lunch_time\n                    updated_takeaway = (\n                        (\" \" + self.config.panel.gui.takeaway_id)\n                        if updated_user.takeaway\n                        else \"\"\n                    )\n                    updated_items_names = [\n                        order.menu_item.item for order in updated_user.orders\n                    ]\n                    # Update dataframe widget\n                    self.reload_menu(\n                        None,\n                        gi,\n                    )\n\n                    pn.state.notifications.success(\n                        f\"{username_key_press}'s<br>lunch time changed to<br>{updated_time}{updated_takeaway}<br>({', '.join(updated_items_names)})\",\n                        duration=self.config.panel.notifications.duration,\n                    )\n                    log.info(f\"{username_key_press}'s order updated\")\n                else:\n                    pn.state.notifications.warning(\n                        f'No order for user named<br>\"{username_key_press}\"',\n                        duration=self.config.panel.notifications.duration,\n                    )\n                    log.info(f\"no order for user named {username_key_press}\")\n            else:\n                pn.state.notifications.warning(\n                    \"Please insert user name\",\n                    duration=self.config.panel.notifications.duration,\n                )\n                log.warning(\"missing username\")\n\n    def df_list_by_lunch_time(\n        self,\n    ) -> dict:\n        \"\"\"Build a dictionary of dataframes for each lunch-time, with takeaways included in a dedicated dataframe.\n\n        Each datframe includes orders grouped by users, notes and a total column (with the total value\n        for a specific item).\n\n        The keys of the dataframe are `lunch-times` and `lunch-times + takeaway_id`.\n\n        Returns:\n            dict: dictionary with dataframes summarizing the orders for each lunch-time/takeaway-time.\n        \"\"\"\n        # Create database engine and session\n        engine = models.create_engine(self.config)\n        # Read menu and save how menu items are sorted (first courses, second courses, etc.)\n        original_order = models.Menu.read_as_df(\n            config=self.config,\n            index_col=\"id\",\n        ).item\n        # Create session\n        session = models.create_session(self.config)\n\n        with session:\n            # Build takeaway list\n            takeaway_list = [\n                user.id\n                for user in session.scalars(\n                    select(models.Users).where(\n                        models.Users.takeaway == sql_true()\n                    )\n                ).all()\n            ]\n        # Read dataframe (including notes)\n        df = pd.read_sql_query(\n            self.config.db.orders_query.format(\n                schema=self.config.db.get(\"schema\", models.SCHEMA)\n            ),\n            engine,\n        )\n\n        # Build a dict of dataframes, one for each lunch time\n        df_dict = {}\n        for time in df.lunch_time.sort_values().unique():\n            # Take only one lunch time (and remove notes so they do not alter\n            # numeric counters inside the pivot table)\n            temp_df = (\n                df[df.lunch_time == time]\n                .drop(columns=[\"lunch_time\", \"note\"])\n                .reset_index(drop=True)\n            )\n            # Users' selections\n            df_users = temp_df.pivot_table(\n                index=\"item\", columns=\"user\", aggfunc=len\n            )\n            # Reorder index in accordance with original menu\n            df_users = df_users.reindex(original_order)\n            # Split restaurant lunches from takeaway lunches\n            df_users_restaurant = df_users.loc[\n                :, [c for c in df_users.columns if c not in takeaway_list]\n            ]\n            df_users_takeaways = df_users.loc[\n                :, [c for c in df_users.columns if c in takeaway_list]\n            ]\n\n            # The following function prepare the dataframe before saving it into\n            # the dictionary that will be returned\n            def _clean_up_table(\n                config: DictConfig,\n                df_in: pd.DataFrame,\n                df_complete: pd.DataFrame,\n            ):\n                df = df_in.copy()\n                # Group notes per menu item by concat users notes\n                # Use value counts to keep track of how many time a note is repeated\n                df_notes = (\n                    df_complete[\n                        (df_complete.lunch_time == time)\n                        & (df_complete.note != \"\")\n                        & (df_complete.user.isin(df.columns))\n                    ]\n                    .drop(columns=[\"user\", \"lunch_time\"])\n                    .value_counts()\n                    .reset_index(level=\"note\")\n                )\n                df_notes.note = (\n                    df_notes[\"count\"]\n                    .astype(str)\n                    .str.cat(\n                        df_notes.note, sep=config.panel.gui.note_sep.count\n                    )\n                )\n                df_notes = df_notes.drop(columns=\"count\")\n                df_notes = (\n                    df_notes.groupby(\"item\")[\"note\"]\n                    .apply(config.panel.gui.note_sep.element.join)\n                    .to_frame()\n                )\n                # Add columns of totals\n                df[config.panel.gui.total_column_name] = df.sum(axis=1)\n                # Drop unused rows if requested\n                if config.panel.drop_unused_menu_items:\n                    df = df[df[config.panel.gui.total_column_name] > 0]\n                # Add notes\n                df = df.join(df_notes)\n                df = df.rename(\n                    columns={\"note\": config.panel.gui.note_column_name}\n                )\n                # Change NaNs to '-'\n                df = df.fillna(\"-\")\n                # Avoid mixed types (float and notes str)\n                df = df.astype(object)\n\n                return df\n\n            # Clean and add resulting dataframes to dict\n            # RESTAURANT LUNCH\n            if not df_users_restaurant.empty:\n                df_users_restaurant = _clean_up_table(\n                    self.config, df_users_restaurant, df\n                )\n                df_dict[time] = df_users_restaurant\n            # TAKEAWAY\n            if not df_users_takeaways.empty:\n                df_users_takeaways = _clean_up_table(\n                    self.config, df_users_takeaways, df\n                )\n                df_dict[f\"{time} {self.config.panel.gui.takeaway_id}\"] = (\n                    df_users_takeaways\n                )\n\n        return df_dict\n\n    def download_dataframe(\n        self,\n        gi: gui.GraphicInterface,\n    ) -> BytesIO:\n        \"\"\"Build an Excel file with tables representing orders for every lunch-time/takeaway-time.\n\n        Tables are created by the function `df_list_by_lunch_time` and exported on dedicated Excel worksheets\n        (inside the same workbook).\n\n        The result is returned as bytes stream to satisfy panel.widgets.FileDownload class requirements.\n\n        Args:\n            gi (gui.GraphicInterface): graphic interface object (used to interact with Panel widgets).\n\n        Returns:\n            BytesIO: download stream for the Excel file.\n        \"\"\"\n\n        # Build a dict of dataframes, one for each lunch time (the key contains\n        # a lunch time)\n        df_dict = self.df_list_by_lunch_time()\n        # Export one dataframe for each lunch time\n        bytes_io = BytesIO()\n        writer = pd.ExcelWriter(bytes_io)\n        # If the dataframe dict is non-empty export one dataframe for each sheet\n        if df_dict:\n            for time, df in df_dict.items():\n                log.info(f\"writing sheet {time}\")\n\n                # Find users that placed an order for a given time\n                users_n = len(\n                    [\n                        c\n                        for c in df.columns\n                        if c\n                        not in (\n                            self.config.panel.gui.total_column_name,\n                            self.config.panel.gui.note_column_name,\n                        )\n                    ]\n                )\n\n                # Export dataframe to new sheet\n                worksheet_name = time.replace(\":\", \".\")\n                df.to_excel(writer, sheet_name=worksheet_name, startrow=1)\n                # Add title\n                worksheet = writer.sheets[worksheet_name]\n                worksheet.cell(\n                    1,\n                    1,\n                    f\"Time - {time} | # {users_n}\",\n                )\n\n                # HEADER FORMAT\n                worksheet[\"A1\"].font = Font(\n                    size=13, bold=True, color=\"00FF0000\"\n                )\n\n                # INDEX ALIGNMENT\n                for row in worksheet[worksheet.min_row : worksheet.max_row]:\n                    cell = row[0]  # column A\n                    cell.alignment = Alignment(horizontal=\"left\")\n                    cell = row[users_n + 2]  # column note\n                    cell.alignment = Alignment(horizontal=\"left\")\n                    cells = row[1 : users_n + 2]  # from column B to note-1\n                    for cell in cells:\n                        cell.alignment = Alignment(horizontal=\"center\")\n\n                # AUTO SIZE\n                # Set auto-size for all columns\n                # Use end +1 for ID column, and +2 for 'total' and 'note' columns\n                column_letters = get_column_interval(\n                    start=1, end=users_n + 1 + 2\n                )\n                # Get columns\n                columns = worksheet[column_letters[0] : column_letters[-1]]\n                for column_letter, column in zip(column_letters, columns):\n                    # Instantiate max length then loop on cells to find max value\n                    max_length = 0\n                    # Cell loop\n                    for cell in column:\n                        log.debug(\n                            f\"autosize for cell {cell.coordinate} with value '{cell.value}'\"\n                        )\n                        try:  # Necessary to avoid error on empty cells\n                            if len(str(cell.value)) > max_length:\n                                max_length = len(cell.value)\n                                log.debug(\n                                    f\"new max length set to {max_length}\"\n                                )\n                        except Exception:\n                            log.debug(\"empty cell\")\n                    log.debug(f\"final max length is {max_length}\")\n                    adjusted_width = (max_length + 2) * 0.85\n                    log.debug(\n                        f\"adjusted width for column '{column_letter}' is {adjusted_width}\"\n                    )\n                    worksheet.column_dimensions[column_letter].width = (\n                        adjusted_width\n                    )\n                # Since grouping fix width equal to first column width (openpyxl\n                # bug), set first column of users' order equal to max width of\n                # all users columns to avoid issues\n                max_width = 0\n                log.debug(\n                    f\"find max width for users' columns '{column_letters[1]}:{column_letters[-3]}'\"\n                )\n                for column_letter in column_letters[1:-2]:\n                    max_width = max(\n                        max_width,\n                        worksheet.column_dimensions[column_letter].width,\n                    )\n                log.debug(f\"max width for first users' columns is {max_width}\")\n                worksheet.column_dimensions[column_letters[1]].width = (\n                    max_width\n                )\n\n                # GROUPING\n                # Group and hide columns, leave only ID, total and note\n                column_letters = get_column_interval(start=2, end=users_n + 1)\n                worksheet.column_dimensions.group(\n                    column_letters[0], column_letters[-1], hidden=True\n                )\n\n                # Close and reset bytes_io for the next dataframe\n                writer.close()  # Important!\n                bytes_io.seek(0)  # Important!\n\n            # Message prompt\n            pn.state.notifications.success(\n                \"File with orders downloaded\",\n                duration=self.config.panel.notifications.duration,\n            )\n            log.info(\"xlsx downloaded\")\n        else:\n            gi.dataframe.value.drop(columns=[\"order\"]).to_excel(\n                writer, sheet_name=\"MENU\", index=False\n            )\n            writer.close()  # Important!\n            bytes_io.seek(0)  # Important!\n            # Message prompt\n            pn.state.notifications.warning(\n                \"No order<br>Menu downloaded\",\n                duration=self.config.panel.notifications.duration,\n            )\n            log.warning(\n                \"no order, menu exported to excel in place of orders' list\"\n            )\n\n        return bytes_io\n
"},{"location":"reference/dlunch/core/#dlunch.core.Waiter.config","title":"config instance-attribute","text":"
config: DictConfig | None = None\n

Hydra configuration object

"},{"location":"reference/dlunch/core/#dlunch.core.Waiter.__init__","title":"__init__","text":"
__init__(config: DictConfig | None = None)\n
Source code in dlunch/core.py
def __init__(self, config: DictConfig | None = None):\n    self.config: DictConfig | None = None\n    \"\"\"Hydra configuration object\"\"\"\n\n    # Define decorator to raise an error if the configuration is not set\n    def _common_decorator(func):\n        def wrapper(*args, **kwargs):\n            if self.config is None:\n                raise ValueError(\"waiter configuration is not set\")\n            return func(*args, **kwargs)\n\n        return wrapper\n\n    # Set decorator to unmangled methods (do not apply to set_config method)\n    for method_name in [\n        item\n        for item in dir(self)\n        if not item.startswith(\"_\") and not (item == \"set_config\")\n    ]:\n        attr = getattr(self, method_name)\n        # Check if the attribute is a method\n        if callable(attr):\n            wrapped = _common_decorator(attr)\n            setattr(self, method_name, wrapped)\n
"},{"location":"reference/dlunch/core/#dlunch.core.Waiter.build_menu","title":"build_menu","text":"
build_menu(\n    event: Event, app: Template, gi: GraphicInterface\n) -> None\n

Read menu from file (Excel or image) and upload menu items to database menu table.

Parameters:

Name Type Description Default event Event

Panel button event.

required app Template

Panel app template (used to open modal windows in case of database errors).

required gi GraphicInterface

graphic interface object (used to interact with Panel widgets).

required Source code in dlunch/core.py
def build_menu(\n    self,\n    event: param.parameterized.Event,\n    app: pn.Template,\n    gi: gui.GraphicInterface,\n) -> None:\n    \"\"\"Read menu from file (Excel or image) and upload menu items to database `menu` table.\n\n    Args:\n        event (param.parameterized.Event): Panel button event.\n        app (pn.Template): Panel app template (used to open modal windows in case of database errors).\n        gi (gui.GraphicInterface): graphic interface object (used to interact with Panel widgets).\n    \"\"\"\n    # Hide messages\n    gi.error_message.visible = False\n\n    # Build image path\n    menu_filename = str(\n        pathlib.Path(self.config.db.shared_data_folder)\n        / self.config.panel.file_name\n    )\n\n    # Delete menu file if exist (every extension)\n    self.delete_files()\n\n    # Load file from widget\n    if gi.file_widget.value is not None:\n        # Find file extension\n        file_ext = pathlib.PurePath(gi.file_widget.filename).suffix\n\n        # Save file locally\n        local_menu_filename = menu_filename + file_ext\n        gi.file_widget.save(local_menu_filename)\n\n        # Clean tables\n        self.clean_tables()\n\n        # File can be either an excel file or an image\n        if file_ext == \".png\" or file_ext == \".jpg\" or file_ext == \".jpeg\":\n            # Transform image into a pandas DataFrame\n            # Open image with PIL\n            img = Image.open(local_menu_filename)\n            # Extract text from image\n            text = pytesseract.image_to_string(img, lang=\"ita\")\n            # Process rows (rows that are completely uppercase are section titles)\n            rows = [\n                row\n                for row in text.split(\"\\n\")\n                if row and not row.isupper()\n            ]\n            df = pd.DataFrame({\"item\": rows})\n            # Concat additional items\n            df = pd.concat(\n                [\n                    df,\n                    pd.DataFrame(\n                        {\n                            \"item\": [\n                                item[\"name\"]\n                                for item in self.config.panel.additional_items_to_concat\n                            ]\n                        }\n                    ),\n                ],\n                axis=\"index\",\n            )\n\n        elif file_ext == \".xlsx\":\n            log.info(\"excel file uploaded\")\n            df = pd.read_excel(\n                local_menu_filename, names=[\"item\"], header=None\n            )\n            # Concat additional items\n            df = pd.concat(\n                [\n                    df,\n                    pd.DataFrame(\n                        {\n                            \"item\": [\n                                item[\"name\"]\n                                for item in self.config.panel.additional_items_to_concat\n                            ]\n                        }\n                    ),\n                ],\n                axis=\"index\",\n                ignore_index=True,\n            )\n        else:\n            df = pd.DataFrame()\n            pn.state.notifications.error(\n                \"Wrong file type\",\n                duration=self.config.panel.notifications.duration,\n            )\n            log.warning(\"wrong file type\")\n            return\n\n        # Upload to database menu table\n        engine = models.create_engine(self.config)\n        try:\n            df.drop_duplicates(subset=\"item\").to_sql(\n                models.Menu.__tablename__,\n                engine,\n                schema=self.config.db.get(\"schema\", models.SCHEMA),\n                index=False,\n                if_exists=\"append\",\n            )\n            # Update dataframe widget\n            self.reload_menu(\n                None,\n                gi,\n            )\n\n            pn.state.notifications.success(\n                \"Menu uploaded\",\n                duration=self.config.panel.notifications.duration,\n            )\n            log.info(\"menu uploaded\")\n        except Exception as e:\n            # Any exception here is a database fault\n            pn.state.notifications.error(\n                \"Database error\",\n                duration=self.config.panel.notifications.duration,\n            )\n            gi.error_message.object = (\n                f\"DATABASE ERROR<br><br>ERROR:<br>{str(e)}\"\n            )\n            gi.error_message.visible = True\n            log.warning(\"database error\")\n            # Open modal window\n            app.open_modal()\n\n    else:\n        pn.state.notifications.warning(\n            \"No file selected\",\n            duration=self.config.panel.notifications.duration,\n        )\n        log.warning(\"no file selected\")\n
"},{"location":"reference/dlunch/core/#dlunch.core.Waiter.change_order_time_takeaway","title":"change_order_time_takeaway","text":"
change_order_time_takeaway(\n    event: Event, person: Person, gi: GraphicInterface\n) -> None\n

Change the time and the takeaway flag of an existing order.

Parameters:

Name Type Description Default event Event

Panel button event.

required person Person

class that collect order data for the user that is the target of the order.

required gi GraphicInterface

graphic interface object (used to interact with Panel widgets).

required Source code in dlunch/core.py
def change_order_time_takeaway(\n    self,\n    event: param.parameterized.Event,\n    person: gui.Person,\n    gi: gui.GraphicInterface,\n) -> None:\n    \"\"\"Change the time and the takeaway flag of an existing order.\n\n    Args:\n        event (param.parameterized.Event): Panel button event.\n        person (gui.Person): class that collect order data for the user that is the target of the order.\n        gi (gui.GraphicInterface): graphic interface object (used to interact with Panel widgets).\n    \"\"\"\n    # Get username, updated on every keypress\n    username_key_press = gi.person_widget._widgets[\"username\"].value_input\n\n    # Create session\n    session = models.create_session(self.config)\n\n    with session:\n        # Check if the \"no more order\" toggle button is pressed\n        if models.get_flag(config=self.config, id=\"no_more_orders\"):\n            pn.state.notifications.error(\n                \"It is not possible to update orders (time)\",\n                duration=self.config.panel.notifications.duration,\n            )\n\n            # Reload the menu\n            self.reload_menu(\n                None,\n                gi,\n            )\n\n            return\n\n        if username_key_press:\n            # Build and execute the update statement\n            update_statement = (\n                update(models.Users)\n                .where(models.Users.id == username_key_press)\n                .values(\n                    lunch_time=person.lunch_time, takeaway=person.takeaway\n                )\n                .returning(models.Users)\n            )\n\n            updated_user = session.scalars(update_statement).one_or_none()\n\n            session.commit()\n\n            if updated_user:\n                # Find updated values\n                updated_time = updated_user.lunch_time\n                updated_takeaway = (\n                    (\" \" + self.config.panel.gui.takeaway_id)\n                    if updated_user.takeaway\n                    else \"\"\n                )\n                updated_items_names = [\n                    order.menu_item.item for order in updated_user.orders\n                ]\n                # Update dataframe widget\n                self.reload_menu(\n                    None,\n                    gi,\n                )\n\n                pn.state.notifications.success(\n                    f\"{username_key_press}'s<br>lunch time changed to<br>{updated_time}{updated_takeaway}<br>({', '.join(updated_items_names)})\",\n                    duration=self.config.panel.notifications.duration,\n                )\n                log.info(f\"{username_key_press}'s order updated\")\n            else:\n                pn.state.notifications.warning(\n                    f'No order for user named<br>\"{username_key_press}\"',\n                    duration=self.config.panel.notifications.duration,\n                )\n                log.info(f\"no order for user named {username_key_press}\")\n        else:\n            pn.state.notifications.warning(\n                \"Please insert user name\",\n                duration=self.config.panel.notifications.duration,\n            )\n            log.warning(\"missing username\")\n
"},{"location":"reference/dlunch/core/#dlunch.core.Waiter.clean_tables","title":"clean_tables","text":"
clean_tables() -> None\n

Clean tables that should be reset when a new menu is uploaded.

Source code in dlunch/core.py
def clean_tables(self) -> None:\n    \"\"\"Clean tables that should be reset when a new menu is uploaded.\"\"\"\n    # Clean tables\n    # Clean orders\n    models.Orders.clear(config=self.config)\n    # Clean menu\n    models.Menu.clear(config=self.config)\n    # Clean users\n    models.Users.clear(config=self.config)\n    # Clean flags\n    models.Flags.clear_guest_override(config=self.config)\n    # Reset flags\n    models.set_flag(config=self.config, id=\"no_more_orders\", value=False)\n    log.info(\"reset values in table 'flags'\")\n    # Clean cache\n    pn.state.clear_caches()\n    log.info(\"cache cleaned\")\n
"},{"location":"reference/dlunch/core/#dlunch.core.Waiter.delete_files","title":"delete_files","text":"
delete_files() -> None\n

Delete local temporary files.

Source code in dlunch/core.py
def delete_files(self) -> None:\n    \"\"\"Delete local temporary files.\"\"\"\n    # Delete menu file if exist (every extension)\n    files = list(\n        pathlib.Path(self.config.db.shared_data_folder).glob(\n            self.config.panel.file_name + \"*\"\n        )\n    )\n    log.info(f\"delete files {', '.join([f.name for f in files])}\")\n    for file in files:\n        file.unlink(missing_ok=True)\n
"},{"location":"reference/dlunch/core/#dlunch.core.Waiter.delete_order","title":"delete_order","text":"
delete_order(\n    event: Event, app: Template, gi: GraphicInterface\n) -> None\n

Delete an existing order.

Consistency checks about the user and the order are carried out here (existing user, only one order, etc.). The status of the stop_orders flag is checked to avoid that an order is uploaded when it shouldn't.

In addition privileges are taken into account (guest users cannot delete orders that targets a privileged user).

Parameters:

Name Type Description Default event Event

Panel button event.

required app Template

Panel app template (used to open modal windows in case of database errors).

required gi GraphicInterface

graphic interface object (used to interact with Panel widgets).

required Source code in dlunch/core.py
def delete_order(\n    self,\n    event: param.parameterized.Event,\n    app: pn.Template,\n    gi: gui.GraphicInterface,\n) -> None:\n    \"\"\"Delete an existing order.\n\n    Consistency checks about the user and the order are carried out here (existing user, only one order, etc.).\n    The status of the `stop_orders` flag is checked to avoid that an order is uploaded when it shouldn't.\n\n    In addition privileges are taken into account (guest users cannot delete orders that targets a privileged user).\n\n    Args:\n        event (param.parameterized.Event): Panel button event.\n        app (pn.Template): Panel app template (used to open modal windows in case of database errors).\n        gi (gui.GraphicInterface): graphic interface object (used to interact with Panel widgets).\n    \"\"\"\n    # Get username, updated on every keypress\n    username_key_press = gi.person_widget._widgets[\"username\"].value_input\n\n    # Hide messages\n    gi.error_message.visible = False\n\n    # Create session\n    session = models.create_session(self.config)\n\n    with session:\n        # Check if the \"no more order\" toggle button is pressed\n        if models.get_flag(config=self.config, id=\"no_more_orders\"):\n            pn.state.notifications.error(\n                \"It is not possible to delete orders\",\n                duration=self.config.panel.notifications.duration,\n            )\n\n            # Reload the menu\n            self.reload_menu(\n                None,\n                gi,\n            )\n\n            return\n\n        if username_key_press:\n            # If auth is active, check if a guests is deleting an order of a\n            # privileged user\n            if (\n                auth.is_guest(\n                    user=pn_user(self.config), config=self.config\n                )\n                and (\n                    username_key_press\n                    in auth.list_users(config=self.config)\n                )\n                and (auth.is_auth_active(config=self.config))\n            ):\n                pn.state.notifications.error(\n                    f\"You do not have enough privileges<br>to delete<br>{username_key_press}'s order\",\n                    duration=self.config.panel.notifications.duration,\n                )\n\n                # Reload the menu\n                self.reload_menu(\n                    None,\n                    gi,\n                )\n\n                return\n\n            # Delete user\n            try:\n                num_rows_deleted_users = session.execute(\n                    delete(models.Users).where(\n                        models.Users.id == username_key_press\n                    )\n                )\n                # Delete also orders (hotfix for Debian)\n                num_rows_deleted_orders = session.execute(\n                    delete(models.Orders).where(\n                        models.Orders.user == username_key_press\n                    )\n                )\n                session.commit()\n                if (num_rows_deleted_users.rowcount > 0) or (\n                    num_rows_deleted_orders.rowcount > 0\n                ):\n                    # Update dataframe widget\n                    self.reload_menu(\n                        None,\n                        gi,\n                    )\n\n                    pn.state.notifications.success(\n                        \"Order canceled\",\n                        duration=self.config.panel.notifications.duration,\n                    )\n                    log.info(f\"{username_key_press}'s order canceled\")\n                else:\n                    pn.state.notifications.warning(\n                        f'No order for user named<br>\"{username_key_press}\"',\n                        duration=self.config.panel.notifications.duration,\n                    )\n                    log.info(\n                        f\"no order for user named {username_key_press}\"\n                    )\n            except Exception as e:\n                # Any exception here is a database fault\n                pn.state.notifications.error(\n                    \"Database error\",\n                    duration=self.config.panel.notifications.duration,\n                )\n                gi.error_message.object = (\n                    f\"DATABASE ERROR<br><br>ERROR:<br>{str(e)}\"\n                )\n                gi.error_message.visible = True\n                log.error(\"database error\")\n                # Open modal window\n                app.open_modal()\n        else:\n            pn.state.notifications.warning(\n                \"Please insert user name\",\n                duration=self.config.panel.notifications.duration,\n            )\n            log.warning(\"missing username\")\n
"},{"location":"reference/dlunch/core/#dlunch.core.Waiter.df_list_by_lunch_time","title":"df_list_by_lunch_time","text":"
df_list_by_lunch_time() -> dict\n

Build a dictionary of dataframes for each lunch-time, with takeaways included in a dedicated dataframe.

Each datframe includes orders grouped by users, notes and a total column (with the total value for a specific item).

The keys of the dataframe are lunch-times and lunch-times + takeaway_id.

Returns:

Type Description dict

dictionary with dataframes summarizing the orders for each lunch-time/takeaway-time.

Source code in dlunch/core.py
def df_list_by_lunch_time(\n    self,\n) -> dict:\n    \"\"\"Build a dictionary of dataframes for each lunch-time, with takeaways included in a dedicated dataframe.\n\n    Each datframe includes orders grouped by users, notes and a total column (with the total value\n    for a specific item).\n\n    The keys of the dataframe are `lunch-times` and `lunch-times + takeaway_id`.\n\n    Returns:\n        dict: dictionary with dataframes summarizing the orders for each lunch-time/takeaway-time.\n    \"\"\"\n    # Create database engine and session\n    engine = models.create_engine(self.config)\n    # Read menu and save how menu items are sorted (first courses, second courses, etc.)\n    original_order = models.Menu.read_as_df(\n        config=self.config,\n        index_col=\"id\",\n    ).item\n    # Create session\n    session = models.create_session(self.config)\n\n    with session:\n        # Build takeaway list\n        takeaway_list = [\n            user.id\n            for user in session.scalars(\n                select(models.Users).where(\n                    models.Users.takeaway == sql_true()\n                )\n            ).all()\n        ]\n    # Read dataframe (including notes)\n    df = pd.read_sql_query(\n        self.config.db.orders_query.format(\n            schema=self.config.db.get(\"schema\", models.SCHEMA)\n        ),\n        engine,\n    )\n\n    # Build a dict of dataframes, one for each lunch time\n    df_dict = {}\n    for time in df.lunch_time.sort_values().unique():\n        # Take only one lunch time (and remove notes so they do not alter\n        # numeric counters inside the pivot table)\n        temp_df = (\n            df[df.lunch_time == time]\n            .drop(columns=[\"lunch_time\", \"note\"])\n            .reset_index(drop=True)\n        )\n        # Users' selections\n        df_users = temp_df.pivot_table(\n            index=\"item\", columns=\"user\", aggfunc=len\n        )\n        # Reorder index in accordance with original menu\n        df_users = df_users.reindex(original_order)\n        # Split restaurant lunches from takeaway lunches\n        df_users_restaurant = df_users.loc[\n            :, [c for c in df_users.columns if c not in takeaway_list]\n        ]\n        df_users_takeaways = df_users.loc[\n            :, [c for c in df_users.columns if c in takeaway_list]\n        ]\n\n        # The following function prepare the dataframe before saving it into\n        # the dictionary that will be returned\n        def _clean_up_table(\n            config: DictConfig,\n            df_in: pd.DataFrame,\n            df_complete: pd.DataFrame,\n        ):\n            df = df_in.copy()\n            # Group notes per menu item by concat users notes\n            # Use value counts to keep track of how many time a note is repeated\n            df_notes = (\n                df_complete[\n                    (df_complete.lunch_time == time)\n                    & (df_complete.note != \"\")\n                    & (df_complete.user.isin(df.columns))\n                ]\n                .drop(columns=[\"user\", \"lunch_time\"])\n                .value_counts()\n                .reset_index(level=\"note\")\n            )\n            df_notes.note = (\n                df_notes[\"count\"]\n                .astype(str)\n                .str.cat(\n                    df_notes.note, sep=config.panel.gui.note_sep.count\n                )\n            )\n            df_notes = df_notes.drop(columns=\"count\")\n            df_notes = (\n                df_notes.groupby(\"item\")[\"note\"]\n                .apply(config.panel.gui.note_sep.element.join)\n                .to_frame()\n            )\n            # Add columns of totals\n            df[config.panel.gui.total_column_name] = df.sum(axis=1)\n            # Drop unused rows if requested\n            if config.panel.drop_unused_menu_items:\n                df = df[df[config.panel.gui.total_column_name] > 0]\n            # Add notes\n            df = df.join(df_notes)\n            df = df.rename(\n                columns={\"note\": config.panel.gui.note_column_name}\n            )\n            # Change NaNs to '-'\n            df = df.fillna(\"-\")\n            # Avoid mixed types (float and notes str)\n            df = df.astype(object)\n\n            return df\n\n        # Clean and add resulting dataframes to dict\n        # RESTAURANT LUNCH\n        if not df_users_restaurant.empty:\n            df_users_restaurant = _clean_up_table(\n                self.config, df_users_restaurant, df\n            )\n            df_dict[time] = df_users_restaurant\n        # TAKEAWAY\n        if not df_users_takeaways.empty:\n            df_users_takeaways = _clean_up_table(\n                self.config, df_users_takeaways, df\n            )\n            df_dict[f\"{time} {self.config.panel.gui.takeaway_id}\"] = (\n                df_users_takeaways\n            )\n\n    return df_dict\n
"},{"location":"reference/dlunch/core/#dlunch.core.Waiter.download_dataframe","title":"download_dataframe","text":"
download_dataframe(gi: GraphicInterface) -> BytesIO\n

Build an Excel file with tables representing orders for every lunch-time/takeaway-time.

Tables are created by the function df_list_by_lunch_time and exported on dedicated Excel worksheets (inside the same workbook).

The result is returned as bytes stream to satisfy panel.widgets.FileDownload class requirements.

Parameters:

Name Type Description Default gi GraphicInterface

graphic interface object (used to interact with Panel widgets).

required

Returns:

Type Description BytesIO

download stream for the Excel file.

Source code in dlunch/core.py
def download_dataframe(\n    self,\n    gi: gui.GraphicInterface,\n) -> BytesIO:\n    \"\"\"Build an Excel file with tables representing orders for every lunch-time/takeaway-time.\n\n    Tables are created by the function `df_list_by_lunch_time` and exported on dedicated Excel worksheets\n    (inside the same workbook).\n\n    The result is returned as bytes stream to satisfy panel.widgets.FileDownload class requirements.\n\n    Args:\n        gi (gui.GraphicInterface): graphic interface object (used to interact with Panel widgets).\n\n    Returns:\n        BytesIO: download stream for the Excel file.\n    \"\"\"\n\n    # Build a dict of dataframes, one for each lunch time (the key contains\n    # a lunch time)\n    df_dict = self.df_list_by_lunch_time()\n    # Export one dataframe for each lunch time\n    bytes_io = BytesIO()\n    writer = pd.ExcelWriter(bytes_io)\n    # If the dataframe dict is non-empty export one dataframe for each sheet\n    if df_dict:\n        for time, df in df_dict.items():\n            log.info(f\"writing sheet {time}\")\n\n            # Find users that placed an order for a given time\n            users_n = len(\n                [\n                    c\n                    for c in df.columns\n                    if c\n                    not in (\n                        self.config.panel.gui.total_column_name,\n                        self.config.panel.gui.note_column_name,\n                    )\n                ]\n            )\n\n            # Export dataframe to new sheet\n            worksheet_name = time.replace(\":\", \".\")\n            df.to_excel(writer, sheet_name=worksheet_name, startrow=1)\n            # Add title\n            worksheet = writer.sheets[worksheet_name]\n            worksheet.cell(\n                1,\n                1,\n                f\"Time - {time} | # {users_n}\",\n            )\n\n            # HEADER FORMAT\n            worksheet[\"A1\"].font = Font(\n                size=13, bold=True, color=\"00FF0000\"\n            )\n\n            # INDEX ALIGNMENT\n            for row in worksheet[worksheet.min_row : worksheet.max_row]:\n                cell = row[0]  # column A\n                cell.alignment = Alignment(horizontal=\"left\")\n                cell = row[users_n + 2]  # column note\n                cell.alignment = Alignment(horizontal=\"left\")\n                cells = row[1 : users_n + 2]  # from column B to note-1\n                for cell in cells:\n                    cell.alignment = Alignment(horizontal=\"center\")\n\n            # AUTO SIZE\n            # Set auto-size for all columns\n            # Use end +1 for ID column, and +2 for 'total' and 'note' columns\n            column_letters = get_column_interval(\n                start=1, end=users_n + 1 + 2\n            )\n            # Get columns\n            columns = worksheet[column_letters[0] : column_letters[-1]]\n            for column_letter, column in zip(column_letters, columns):\n                # Instantiate max length then loop on cells to find max value\n                max_length = 0\n                # Cell loop\n                for cell in column:\n                    log.debug(\n                        f\"autosize for cell {cell.coordinate} with value '{cell.value}'\"\n                    )\n                    try:  # Necessary to avoid error on empty cells\n                        if len(str(cell.value)) > max_length:\n                            max_length = len(cell.value)\n                            log.debug(\n                                f\"new max length set to {max_length}\"\n                            )\n                    except Exception:\n                        log.debug(\"empty cell\")\n                log.debug(f\"final max length is {max_length}\")\n                adjusted_width = (max_length + 2) * 0.85\n                log.debug(\n                    f\"adjusted width for column '{column_letter}' is {adjusted_width}\"\n                )\n                worksheet.column_dimensions[column_letter].width = (\n                    adjusted_width\n                )\n            # Since grouping fix width equal to first column width (openpyxl\n            # bug), set first column of users' order equal to max width of\n            # all users columns to avoid issues\n            max_width = 0\n            log.debug(\n                f\"find max width for users' columns '{column_letters[1]}:{column_letters[-3]}'\"\n            )\n            for column_letter in column_letters[1:-2]:\n                max_width = max(\n                    max_width,\n                    worksheet.column_dimensions[column_letter].width,\n                )\n            log.debug(f\"max width for first users' columns is {max_width}\")\n            worksheet.column_dimensions[column_letters[1]].width = (\n                max_width\n            )\n\n            # GROUPING\n            # Group and hide columns, leave only ID, total and note\n            column_letters = get_column_interval(start=2, end=users_n + 1)\n            worksheet.column_dimensions.group(\n                column_letters[0], column_letters[-1], hidden=True\n            )\n\n            # Close and reset bytes_io for the next dataframe\n            writer.close()  # Important!\n            bytes_io.seek(0)  # Important!\n\n        # Message prompt\n        pn.state.notifications.success(\n            \"File with orders downloaded\",\n            duration=self.config.panel.notifications.duration,\n        )\n        log.info(\"xlsx downloaded\")\n    else:\n        gi.dataframe.value.drop(columns=[\"order\"]).to_excel(\n            writer, sheet_name=\"MENU\", index=False\n        )\n        writer.close()  # Important!\n        bytes_io.seek(0)  # Important!\n        # Message prompt\n        pn.state.notifications.warning(\n            \"No order<br>Menu downloaded\",\n            duration=self.config.panel.notifications.duration,\n        )\n        log.warning(\n            \"no order, menu exported to excel in place of orders' list\"\n        )\n\n    return bytes_io\n
"},{"location":"reference/dlunch/core/#dlunch.core.Waiter.get_host_name","title":"get_host_name","text":"
get_host_name() -> str\n

Return hostname.

This function behavior changes if called from localhost, Docker container or production server.

Returns:

Type Description str

hostname.

Source code in dlunch/core.py
def get_host_name(self) -> str:\n    \"\"\"Return hostname.\n\n    This function behavior changes if called from localhost, Docker container or\n    production server.\n\n    Returns:\n        str: hostname.\n    \"\"\"\n    try:\n        ip_address = socket.gethostbyname(socket.gethostname())\n        dig_res = subprocess.run(\n            [\"dig\", \"+short\", \"-x\", ip_address], stdout=subprocess.PIPE\n        ).stdout\n        host_name = (\n            subprocess.run(\n                [\"cut\", \"-d.\", \"-f1\"],\n                stdout=subprocess.PIPE,\n                input=dig_res,\n            )\n            .stdout.decode(\"utf-8\")\n            .strip()\n        )\n        if host_name:\n            host_name = host_name.replace(\n                f\"{self.config.docker_username}_\", \"\"\n            )\n        else:\n            host_name = \"no info\"\n    except Exception:\n        host_name = \"not available\"\n\n    return host_name\n
"},{"location":"reference/dlunch/core/#dlunch.core.Waiter.reload_menu","title":"reload_menu","text":"
reload_menu(event: Event, gi: GraphicInterface) -> None\n

Main core function that sync Panel widget with database tables.

Stop orders and guest override checks are carried out by this function. Also the banner image is shown based on a check run by this function.

menu, orders and users tables are used to build a list of orders for each lunch time. Takeaway orders are evaluated separately.

At the end stats about lunches are calculated and loaded to database. Finally statistics (values and table) shown inside the app are updated accordingly.

Parameters:

Name Type Description Default event Event

Panel button event.

required gi GraphicInterface

graphic interface object (used to interact with Panel widgets).

required Source code in dlunch/core.py
def reload_menu(\n    self,\n    event: param.parameterized.Event,\n    gi: gui.GraphicInterface,\n) -> None:\n    \"\"\"Main core function that sync Panel widget with database tables.\n\n    Stop orders and guest override checks are carried out by this function.\n    Also the banner image is shown based on a check run by this function.\n\n    `menu`, `orders` and `users` tables are used to build a list of orders for each lunch time.\n    Takeaway orders are evaluated separately.\n\n    At the end stats about lunches are calculated and loaded to database. Finally\n    statistics (values and table) shown inside the app are updated accordingly.\n\n    Args:\n        event (param.parameterized.Event): Panel button event.\n        gi (gui.GraphicInterface): graphic interface object (used to interact with Panel widgets).\n    \"\"\"\n    # Create session\n    session = models.create_session(self.config)\n\n    with session:\n        # Check if someone changed the \"no_more_order\" toggle\n        if gi.toggle_no_more_order_button.value != models.get_flag(\n            config=self.config, id=\"no_more_orders\"\n        ):\n            # The following statement will trigger the toggle callback\n            # which will call reload_menu once again\n            # This is the reason why this if contains a return (without the return\n            # the content will be reloaded twice)\n            gi.toggle_no_more_order_button.value = models.get_flag(\n                config=self.config, id=\"no_more_orders\"\n            )\n\n            return\n\n        # Check guest override button status (if not in table use False)\n        gi.toggle_guest_override_button.value = models.get_flag(\n            config=self.config,\n            id=f\"{pn_user(self.config)}_guest_override\",\n            value_if_missing=False,\n        )\n\n        # Set no more orders toggle button and the change order time button\n        # visibility and activation\n        if auth.is_guest(\n            user=pn_user(self.config),\n            config=self.config,\n            allow_override=False,\n        ):\n            # Deactivate the no_more_orders_button for guest users\n            gi.toggle_no_more_order_button.disabled = True\n            gi.toggle_no_more_order_button.visible = False\n            # Deactivate the change_order_time_button for guest users\n            gi.change_order_time_takeaway_button.disabled = True\n            gi.change_order_time_takeaway_button.visible = False\n        else:\n            # Activate the no_more_orders_button for privileged users\n            gi.toggle_no_more_order_button.disabled = False\n            gi.toggle_no_more_order_button.visible = True\n            # Show the change_order_time_button for privileged users\n            # It is disabled by the no more order button if necessary\n            gi.change_order_time_takeaway_button.visible = True\n\n        # Guest graphic configuration\n        if auth.is_guest(user=pn_user(self.config), config=self.config):\n            # If guest show guest type selection group\n            gi.person_widget.widgets[\"guest\"].disabled = False\n            gi.person_widget.widgets[\"guest\"].visible = True\n        else:\n            # If user is privileged hide guest type selection group\n            gi.person_widget.widgets[\"guest\"].disabled = True\n            gi.person_widget.widgets[\"guest\"].visible = False\n\n        # Reload menu\n        engine = models.create_engine(self.config)\n        df = models.Menu.read_as_df(\n            config=self.config,\n            index_col=\"id\",\n        )\n        # Add order (for selecting items) and note columns\n        df[\"order\"] = False\n        df[self.config.panel.gui.note_column_name] = \"\"\n        gi.dataframe.value = df\n        gi.dataframe.formatters = {\"order\": {\"type\": \"tickCross\"}}\n        gi.dataframe.editors = {\n            \"id\": None,\n            \"item\": None,\n            \"order\": CheckboxEditor(),\n            self.config.panel.gui.note_column_name: \"input\",\n        }\n        gi.dataframe.header_align = OmegaConf.to_container(\n            self.config.panel.gui.menu_column_align, resolve=True\n        )\n        gi.dataframe.text_align = OmegaConf.to_container(\n            self.config.panel.gui.menu_column_align, resolve=True\n        )\n\n        if gi.toggle_no_more_order_button.value:\n            gi.dataframe.hidden_columns = [\"id\", \"order\"]\n            gi.dataframe.disabled = True\n        else:\n            gi.dataframe.hidden_columns = [\"id\"]\n            gi.dataframe.disabled = False\n\n        # If menu is empty show banner image, otherwise show menu\n        if df.empty:\n            gi.no_menu_col.visible = True\n            gi.main_header_row.visible = False\n            gi.quote.visible = False\n            gi.menu_flexbox.visible = False\n            gi.buttons_flexbox.visible = False\n            gi.results_divider.visible = False\n            gi.res_col.visible = False\n        else:\n            gi.no_menu_col.visible = False\n            gi.main_header_row.visible = True\n            gi.quote.visible = True\n            gi.menu_flexbox.visible = True\n            gi.buttons_flexbox.visible = True\n            gi.results_divider.visible = True\n            gi.res_col.visible = True\n\n        log.debug(\"menu reloaded\")\n\n        # Load results\n        df_dict = self.df_list_by_lunch_time()\n        # Clean columns and load text and dataframes\n        gi.res_col.clear()\n        gi.time_col.clear()\n        if df_dict:\n            # Titles\n            gi.res_col.append(self.config.panel.result_column_text)\n            gi.time_col.append(gi.time_col_title)\n            # Build guests list (one per each guest types)\n            guests_lists = {}\n            for guest_type in self.config.panel.guest_types:\n                guests_lists[guest_type] = [\n                    user.id\n                    for user in session.scalars(\n                        select(models.Users).where(\n                            models.Users.guest == guest_type\n                        )\n                    ).all()\n                ]\n            # Loop through lunch times\n            for time, df in df_dict.items():\n                # Find the number of grumbling stomachs\n                grumbling_stomachs = len(\n                    [\n                        c\n                        for c in df.columns\n                        if c\n                        not in (\n                            self.config.panel.gui.total_column_name,\n                            self.config.panel.gui.note_column_name,\n                        )\n                    ]\n                )\n                # Set different graphics for takeaway lunches\n                if self.config.panel.gui.takeaway_id in time:\n                    res_col_label_kwargs = {\n                        \"time\": time.replace(\n                            self.config.panel.gui.takeaway_id, \"\"\n                        ),\n                        \"diners_n\": grumbling_stomachs,\n                        \"emoji\": self.config.panel.gui.takeaway_emoji,\n                        \"is_takeaway\": True,\n                        \"takeaway_alert_sign\": f\"&nbsp{gi.takeaway_alert_sign}&nbsp{gi.takeaway_alert_text}\",\n                        \"css_classes\": OmegaConf.to_container(\n                            self.config.panel.gui.takeaway_class_res_col,\n                            resolve=True,\n                        ),\n                        \"stylesheets\": [\n                            self.config.panel.gui.css_files.labels_path\n                        ],\n                    }\n                    time_col_label_kwargs = {\n                        \"time\": time.replace(\n                            self.config.panel.gui.takeaway_id, \"\"\n                        ),\n                        \"diners_n\": str(grumbling_stomachs) + \"&nbsp\",\n                        \"separator\": \"<br>\",\n                        \"emoji\": self.config.panel.gui.takeaway_emoji,\n                        \"align\": (\"center\", \"center\"),\n                        \"sizing_mode\": \"stretch_width\",\n                        \"is_takeaway\": True,\n                        \"takeaway_alert_sign\": gi.takeaway_alert_sign,\n                        \"css_classes\": OmegaConf.to_container(\n                            self.config.panel.gui.takeaway_class_time_col,\n                            resolve=True,\n                        ),\n                        \"stylesheets\": [\n                            self.config.panel.gui.css_files.labels_path\n                        ],\n                    }\n                else:\n                    res_col_label_kwargs = {\n                        \"time\": time,\n                        \"diners_n\": grumbling_stomachs,\n                        \"emoji\": random.choice(\n                            self.config.panel.gui.food_emoji\n                        ),\n                        \"css_classes\": OmegaConf.to_container(\n                            self.config.panel.gui.time_class_res_col,\n                            resolve=True,\n                        ),\n                        \"stylesheets\": [\n                            self.config.panel.gui.css_files.labels_path\n                        ],\n                    }\n                    time_col_label_kwargs = {\n                        \"time\": time,\n                        \"diners_n\": str(grumbling_stomachs) + \"&nbsp\",\n                        \"separator\": \"<br>\",\n                        \"emoji\": self.config.panel.gui.restaurant_emoji,\n                        \"per_icon\": \"&#10006; \",\n                        \"align\": (\"center\", \"center\"),\n                        \"sizing_mode\": \"stretch_width\",\n                        \"css_classes\": OmegaConf.to_container(\n                            self.config.panel.gui.time_class_time_col,\n                            resolve=True,\n                        ),\n                        \"stylesheets\": [\n                            self.config.panel.gui.css_files.labels_path\n                        ],\n                    }\n                # Add text to result column\n                gi.res_col.append(pn.Spacer(height=15))\n                gi.res_col.append(\n                    gi.build_time_label(**res_col_label_kwargs)\n                )\n                # Add non editable table to result column\n                gi.res_col.append(pn.Spacer(height=5))\n                gi.res_col.append(\n                    gi.build_order_table(\n                        self.config,\n                        df=df,\n                        time=time,\n                        guests_lists=guests_lists,\n                    )\n                )\n                # Add also a label to lunch time column\n                gi.time_col.append(\n                    gi.build_time_label(**time_col_label_kwargs)\n                )\n\n        log.debug(\"results reloaded\")\n\n        # Clean stats column\n        gi.sidebar_stats_col.clear()\n        # Update stats\n        # Find how many people eat today (total number) and add value to database\n        # stats table (when adding a stats if guest is not specified None is used\n        # as default)\n        today_locals_count = session.scalar(\n            select(func.count(models.Users.id)).where(\n                models.Users.guest == \"NotAGuest\"\n            )\n        )\n        new_stat = models.Stats(hungry_people=today_locals_count)\n        # Use an upsert for postgresql, a simple session add otherwise\n        models.session_add_with_upsert(\n            session=session, constraint=\"stats_pkey\", new_record=new_stat\n        )\n        # For each guest type find how many guests eat today\n        for guest_type in self.config.panel.guest_types:\n            today_guests_count = session.scalar(\n                select(func.count(models.Users.id)).where(\n                    models.Users.guest == guest_type\n                )\n            )\n            new_stat = models.Stats(\n                guest=guest_type, hungry_people=today_guests_count\n            )\n            # Use an upsert for postgresql, a simple session add otherwise\n            models.session_add_with_upsert(\n                session=session,\n                constraint=\"stats_pkey\",\n                new_record=new_stat,\n            )\n\n        # Commit stats\n        session.commit()\n\n        # Group stats by month and return how many people had lunch\n        df_stats = pd.read_sql_query(\n            self.config.db.stats_query.format(\n                schema=self.config.db.get(\"schema\", models.SCHEMA)\n            ),\n            engine,\n        )\n        # Stats top text\n        stats_and_info_text = gi.build_stats_and_info_text(\n            config=self.config,\n            df_stats=df_stats,\n            user=pn_user(self.config),\n            version=__version__,\n            host_name=self.get_host_name(),\n            stylesheets=[self.config.panel.gui.css_files.stats_info_path],\n        )\n        # Remove NotAGuest (non-guest users)\n        df_stats.Guest = df_stats.Guest.replace(\n            \"NotAGuest\", self.config.panel.stats_locals_column_name\n        )\n        # Pivot table on guest type\n        df_stats = df_stats.pivot(\n            columns=\"Guest\",\n            index=self.config.panel.stats_id_cols,\n            values=\"Hungry People\",\n        ).reset_index()\n        df_stats[self.config.panel.gui.total_column_name.title()] = (\n            df_stats.sum(axis=\"columns\", numeric_only=True)\n        )\n        # Add value and non-editable option to stats table\n        gi.stats_widget.editors = {c: None for c in df_stats.columns}\n        gi.stats_widget.value = df_stats\n        gi.sidebar_stats_col.append(stats_and_info_text[\"stats\"])\n        gi.sidebar_stats_col.append(gi.stats_widget)\n        # Add info below person widget (an empty placeholder was left as last\n        # element)\n        gi.sidebar_person_column.objects[-1] = stats_and_info_text[\"info\"]\n        log.debug(\"stats and info updated\")\n
"},{"location":"reference/dlunch/core/#dlunch.core.Waiter.send_order","title":"send_order","text":"
send_order(\n    event: Event,\n    app: Template,\n    person: Person,\n    gi: GraphicInterface,\n) -> None\n

Upload orders and user to database tables.

The user target of the order is uploaded to users table, while the order is uploaded to orders table.

Consistency checks about the user and the order are carried out here (existing user, only one order, etc.). The status of the stop_orders flag is checked to avoid that an order is uploaded when it shouldn't.

Orders for guest users are marked as such before uploading them.

Parameters:

Name Type Description Default event Event

Panel button event.

required app Template

Panel app template (used to open modal windows in case of database errors).

required person Person

class that collect order data for the user that is the target of the order.

required gi GraphicInterface

graphic interface object (used to interact with Panel widgets).

required Source code in dlunch/core.py
def send_order(\n    self,\n    event: param.parameterized.Event,\n    app: pn.Template,\n    person: gui.Person,\n    gi: gui.GraphicInterface,\n) -> None:\n    \"\"\"Upload orders and user to database tables.\n\n    The user target of the order is uploaded to `users` table, while the order\n    is uploaded to `orders` table.\n\n    Consistency checks about the user and the order are carried out here (existing user, only one order, etc.).\n    The status of the `stop_orders` flag is checked to avoid that an order is uploaded when it shouldn't.\n\n    Orders for guest users are marked as such before uploading them.\n\n    Args:\n        event (param.parameterized.Event): Panel button event.\n        app (pn.Template): Panel app template (used to open modal windows in case of database errors).\n        person (gui.Person): class that collect order data for the user that is the target of the order.\n        gi (gui.GraphicInterface): graphic interface object (used to interact with Panel widgets).\n    \"\"\"\n    # Get username updated at each key press\n    username_key_press = gi.person_widget._widgets[\"username\"].value_input\n\n    # Hide messages\n    gi.error_message.visible = False\n\n    # Create session\n    session = models.create_session(self.config)\n\n    with session:\n        # Check if the \"no more order\" toggle button is pressed\n        if models.get_flag(config=self.config, id=\"no_more_orders\"):\n            pn.state.notifications.error(\n                \"It is not possible to place new orders\",\n                duration=self.config.panel.notifications.duration,\n            )\n\n            # Reload the menu\n            self.reload_menu(\n                None,\n                gi,\n            )\n\n            return\n\n        # If auth is active, check if a guests is using a name reserved to a\n        # privileged user\n        if (\n            auth.is_guest(user=pn_user(self.config), config=self.config)\n            and (username_key_press in auth.list_users(config=self.config))\n            and (auth.is_auth_active(config=self.config))\n        ):\n            pn.state.notifications.error(\n                f\"{username_key_press} is a reserved name<br>Please choose a different one\",\n                duration=self.config.panel.notifications.duration,\n            )\n\n            # Reload the menu\n            self.reload_menu(\n                None,\n                gi,\n            )\n\n            return\n\n        # Check if a privileged user is ordering for an invalid name\n        if (\n            not auth.is_guest(\n                user=pn_user(self.config), config=self.config\n            )\n            and (\n                username_key_press\n                not in (\n                    name\n                    for name in auth.list_users(config=self.config)\n                    if name != \"guest\"\n                )\n            )\n            and (auth.is_auth_active(config=self.config))\n        ):\n            pn.state.notifications.error(\n                f\"{username_key_press} is not a valid name<br>for a privileged user<br>Please choose a different one\",\n                duration=self.config.panel.notifications.duration,\n            )\n\n            # Reload the menu\n            self.reload_menu(\n                None,\n                gi,\n            )\n\n            return\n\n        # Write order into database table\n        df = gi.dataframe.value.copy()\n        df_order = df[df.order]\n        # If username is missing or the order is empty return an error message\n        if username_key_press and not df_order.empty:\n            # Check if the user already placed an order\n            if session.get(models.Users, username_key_press):\n                pn.state.notifications.warning(\n                    f\"Cannot overwrite an order<br>Delete {username_key_press}'s order first and retry\",\n                    duration=self.config.panel.notifications.duration,\n                )\n                log.warning(\n                    f\"an order already exist for {username_key_press}\"\n                )\n            else:\n                # Place order\n                try:\n                    # Add User\n                    # Do not pass guest for privileged users (default to NotAGuest)\n                    if auth.is_guest(\n                        user=pn_user(self.config), config=self.config\n                    ):\n                        new_user = models.Users(\n                            id=username_key_press,\n                            guest=person.guest,\n                            lunch_time=person.lunch_time,\n                            takeaway=person.takeaway,\n                        )\n                    else:\n                        new_user = models.Users(\n                            id=username_key_press,\n                            lunch_time=person.lunch_time,\n                            takeaway=person.takeaway,\n                        )\n                    session.add(new_user)\n                    session.commit()\n                    # Add orders as long table (one row for each item selected by a user)\n                    for row in df_order.itertuples(name=\"OrderTuple\"):\n                        # Order\n                        new_order = models.Orders(\n                            user=username_key_press,\n                            menu_item_id=row.Index,\n                            note=getattr(\n                                row, self.config.panel.gui.note_column_name\n                            ).lower(),\n                        )\n                        session.add(new_order)\n                        session.commit()\n\n                    # Update dataframe widget\n                    self.reload_menu(\n                        None,\n                        gi,\n                    )\n\n                    pn.state.notifications.success(\n                        \"Order sent\",\n                        duration=self.config.panel.notifications.duration,\n                    )\n                    log.info(f\"{username_key_press}'s order saved\")\n                except Exception as e:\n                    # Any exception here is a database fault\n                    pn.state.notifications.error(\n                        \"Database error\",\n                        duration=self.config.panel.notifications.duration,\n                    )\n                    gi.error_message.object = (\n                        f\"DATABASE ERROR<br><br>ERROR:<br>{str(e)}\"\n                    )\n                    gi.error_message.visible = True\n                    log.error(\"database error\")\n                    # Open modal window\n                    app.open_modal()\n        else:\n            if not username_key_press:\n                pn.state.notifications.warning(\n                    \"Please insert user name\",\n                    duration=self.config.panel.notifications.duration,\n                )\n                log.warning(\"missing username\")\n            else:\n                pn.state.notifications.warning(\n                    \"Please make a selection\",\n                    duration=self.config.panel.notifications.duration,\n                )\n                log.warning(\"no selection made\")\n
"},{"location":"reference/dlunch/core/#dlunch.core.Waiter.set_config","title":"set_config","text":"
set_config(config: DictConfig)\n

Set the configuration for the Waiter instance.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration object.

required Source code in dlunch/core.py
def set_config(self, config: DictConfig):\n    \"\"\"Set the configuration for the Waiter instance.\n\n    Args:\n        config (DictConfig): Hydra configuration object.\n    \"\"\"\n    self.config = config\n
"},{"location":"reference/dlunch/gui/","title":"gui","text":"

Module that defines main graphic interface and backend graphic interface.

Classes that uses param are then used to create Panel widget directly (see Panel docs <https://panel.holoviz.org/how_to/param/uis.html>__).

Modules:

Name Description auth

Module with classes and functions used for authentication and password handling.

core

Module that defines main functions used to manage Data-Lunch operations.

models

Module with database tables definitions.

Classes:

Name Description BackendAddPrivilegedUser

Param class used inside the backend to create the widget add new users to the privileged_user table.

BackendInterface

Class with widgets for the backend graphic interface.

BackendPasswordRenewer

Param class used inside the backend to create the widget that collect info to renew users password.

BackendUserEraser

Param class used inside the backend to create the widget that delete users.

GraphicInterface

Class with widgets for the main graphic interface.

PasswordRenewer

Param class used to create the widget that collect info to renew users password.

Person

Param class that define user data and lunch preferences for its order.

Attributes:

Name Type Description backend_min_height int

Backend minimum height.

df_quote DataFrame

Dataframe with the quote of the day.

df_quotes DataFrame

Dataframe with quotes.

download_text str

info Text used in Download Orders tab.

generic_button_height int

Button height.

guest_user_text str

info Text used in guest Password widget.

header_button_width int

Width for buttons used in top header.

header_row_height int

Top header height.

log Logger

Module logger.

main_area_min_width int

Main area width. It's the area with menu and order summary.

person_text str

info Text used in User tab.

quotes_filename Path

Excel file with quotes.

seed_day int

seed to Select the quote of the day.

sidebar_content_width int

Sidebar content width. Should be smaller than sidebar width.

sidebar_width int

Sidebar width.

time_col_spacer_width int

Time column spacer width.

time_col_width int

Time column width (the time column is on the side of the menu table).

upload_text str

info Text used in Menu Upload tab.

"},{"location":"reference/dlunch/gui/#dlunch.gui.backend_min_height","title":"backend_min_height module-attribute","text":"
backend_min_height: int = 500\n

Backend minimum height.

"},{"location":"reference/dlunch/gui/#dlunch.gui.df_quote","title":"df_quote module-attribute","text":"
df_quote: DataFrame = sample(n=1, random_state=seed_day)\n

Dataframe with the quote of the day.

"},{"location":"reference/dlunch/gui/#dlunch.gui.df_quotes","title":"df_quotes module-attribute","text":"
df_quotes: DataFrame = read_excel(quotes_filename)\n

Dataframe with quotes.

"},{"location":"reference/dlunch/gui/#dlunch.gui.download_text","title":"download_text module-attribute","text":"
download_text: str = (\n    \"\\n### Download Orders\\nDownload the order list.\\n\"\n)\n

info Text used in Download Orders tab.

"},{"location":"reference/dlunch/gui/#dlunch.gui.generic_button_height","title":"generic_button_height module-attribute","text":"
generic_button_height: int = 45\n

Button height.

"},{"location":"reference/dlunch/gui/#dlunch.gui.guest_user_text","title":"guest_user_text module-attribute","text":"
guest_user_text: str = '\\n### Guest user\\n'\n

info Text used in guest Password widget.

"},{"location":"reference/dlunch/gui/#dlunch.gui.header_button_width","title":"header_button_width module-attribute","text":"
header_button_width: int = 50\n

Width for buttons used in top header.

"},{"location":"reference/dlunch/gui/#dlunch.gui.header_row_height","title":"header_row_height module-attribute","text":"
header_row_height: int = 55\n

Top header height.

"},{"location":"reference/dlunch/gui/#dlunch.gui.log","title":"log module-attribute","text":"
log: Logger = getLogger(__name__)\n

Module logger.

"},{"location":"reference/dlunch/gui/#dlunch.gui.main_area_min_width","title":"main_area_min_width module-attribute","text":"
main_area_min_width: int = (\n    580 + time_col_spacer_width + time_col_width\n)\n

Main area width. It's the area with menu and order summary.

"},{"location":"reference/dlunch/gui/#dlunch.gui.person_text","title":"person_text module-attribute","text":"
person_text: str = (\n    \"\\n### User Data\\n\\n_Privileged users_ do not need to fill the username.<br>\\n_Guest users_ shall use a valid _unique_ name and select a guest type.\\n\"\n)\n

info Text used in User tab.

"},{"location":"reference/dlunch/gui/#dlunch.gui.quotes_filename","title":"quotes_filename module-attribute","text":"
quotes_filename: Path = parent / 'quotes.xlsx'\n

Excel file with quotes.

"},{"location":"reference/dlunch/gui/#dlunch.gui.seed_day","title":"seed_day module-attribute","text":"
seed_day: int = int(strftime('%Y%m%d'))\n

seed to Select the quote of the day.

"},{"location":"reference/dlunch/gui/#dlunch.gui.sidebar_content_width","title":"sidebar_content_width module-attribute","text":"
sidebar_content_width: int = sidebar_width - 10\n

Sidebar content width. Should be smaller than sidebar width.

"},{"location":"reference/dlunch/gui/#dlunch.gui.sidebar_width","title":"sidebar_width module-attribute","text":"
sidebar_width: int = 400\n

Sidebar width.

"},{"location":"reference/dlunch/gui/#dlunch.gui.time_col_spacer_width","title":"time_col_spacer_width module-attribute","text":"
time_col_spacer_width: int = 5\n

Time column spacer width.

"},{"location":"reference/dlunch/gui/#dlunch.gui.time_col_width","title":"time_col_width module-attribute","text":"
time_col_width: int = 90\n

Time column width (the time column is on the side of the menu table).

"},{"location":"reference/dlunch/gui/#dlunch.gui.upload_text","title":"upload_text module-attribute","text":"
upload_text: str = (\n    \"\\n### Menu Upload\\nSelect a .png, .jpg or .xlsx file with the menu.<br>\\nThe app may add some default items to the menu.\\n\\n**For .xlsx:** list menu items starting from cell A1, one per each row.\\n\"\n)\n

info Text used in Menu Upload tab.

"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendAddPrivilegedUser","title":"BackendAddPrivilegedUser","text":"

Bases: Parameterized

Param class used inside the backend to create the widget add new users to the privileged_user table.

Methods:

Name Description __str__

String representation of this object.

Attributes:

Name Type Description admin Boolean

Admin flag (true if admin).

user String

Username of the new user.

Source code in dlunch/gui.py
class BackendAddPrivilegedUser(param.Parameterized):\n    \"\"\"Param class used inside the backend to create the widget add new users to the `privileged_user` table.\"\"\"\n\n    user: param.String = param.String(default=\"\", doc=\"user to add\")\n    \"\"\"Username of the new user.\"\"\"\n    admin: param.Boolean = param.Boolean(\n        default=False, doc=\"add admin privileges\"\n    )\n    \"\"\"Admin flag (true if admin).\"\"\"\n\n    def __str__(self):\n        \"\"\"String representation of this object.\n\n        Returns:\n            (str): string representation.\n        \"\"\"\n        return \"BackendAddUser\"\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendAddPrivilegedUser.admin","title":"admin class-attribute instance-attribute","text":"
admin: Boolean = Boolean(\n    default=False, doc=\"add admin privileges\"\n)\n

Admin flag (true if admin).

"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendAddPrivilegedUser.user","title":"user class-attribute instance-attribute","text":"
user: String = String(default='', doc='user to add')\n

Username of the new user.

"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendAddPrivilegedUser.__str__","title":"__str__","text":"
__str__()\n

String representation of this object.

Returns:

Type Description str

string representation.

Source code in dlunch/gui.py
def __str__(self):\n    \"\"\"String representation of this object.\n\n    Returns:\n        (str): string representation.\n    \"\"\"\n    return \"BackendAddUser\"\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendInterface","title":"BackendInterface","text":"

Class with widgets for the backend graphic interface.

All widgets are instantiated at class initialization.

Class methods handle specific operations that may be repeated multiple time after class instantiation.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Methods:

Name Description __init__ exit_backend

Return to main homepage.

reload_backend

Reload backend by updating user lists and privileges.

Attributes:

Name Type Description access_denied_text add_privileged_user_button add_privileged_user_column add_privileged_user_widget add_update_user_column backend_controls clear_flags_button clear_flags_column delete_user_button delete_user_column exit_button flags_content header_row list_user_column password_widget submit_password_button user_eraser users_tabulator Source code in dlunch/gui.py
class BackendInterface:\n    \"\"\"Class with widgets for the backend graphic interface.\n\n    All widgets are instantiated at class initialization.\n\n    Class methods handle specific operations that may be repeated multiple time after class instantiation.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n    \"\"\"\n\n    def __init__(\n        self,\n        config: DictConfig,\n    ):\n        # HEADER SECTION ------------------------------------------------------\n        # WIDGET\n\n        # BUTTONS\n        self.exit_button = pnw.Button(\n            name=\"\",\n            button_type=\"primary\",\n            button_style=\"solid\",\n            width=header_button_width,\n            height=generic_button_height,\n            icon=\"home-move\",\n            icon_size=\"2em\",\n        )\n\n        # ROW\n        # Create column for person data (add logout button only if auth is active)\n        self.header_row = pn.Row(\n            height=header_row_height,\n            sizing_mode=\"stretch_width\",\n        )\n        # Append a controls to the right side of header\n        self.header_row.append(pn.HSpacer())\n        self.header_row.append(self.exit_button)\n        self.header_row.append(\n            pn.pane.HTML(styles=dict(background=\"white\"), width=2, height=45)\n        )\n\n        # CALLBACKS\n        # Exit callback\n        self.exit_button.on_click(lambda e: self.exit_backend())\n\n        # MAIN SECTION --------------------------------------------------------\n        # Backend main section\n\n        # TEXTS\n        # \"no more order\" message\n        self.access_denied_text = pn.pane.HTML(\n            \"\"\"\n            <div class=\"no-more-order-flag\">\n                <div class=\"icon-container\">\n                    <svg class=\"flashing-animation\" xmlns=\"http://www.w3.org/2000/svg\" class=\"icon icon-tabler icon-tabler-shield-lock-filled\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n                        <path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\"></path>\n                        <path d=\"M11.998 2l.118 .007l.059 .008l.061 .013l.111 .034a.993 .993 0 0 1 .217 .112l.104 .082l.255 .218a11 11 0 0 0 7.189 2.537l.342 -.01a1 1 0 0 1 1.005 .717a13 13 0 0 1 -9.208 16.25a1 1 0 0 1 -.502 0a13 13 0 0 1 -9.209 -16.25a1 1 0 0 1 1.005 -.717a11 11 0 0 0 7.531 -2.527l.263 -.225l.096 -.075a.993 .993 0 0 1 .217 -.112l.112 -.034a.97 .97 0 0 1 .119 -.021l.115 -.007zm.002 7a2 2 0 0 0 -1.995 1.85l-.005 .15l.005 .15a2 2 0 0 0 .995 1.581v1.769l.007 .117a1 1 0 0 0 1.993 -.117l.001 -1.768a2 2 0 0 0 -1.001 -3.732z\" stroke-width=\"0\" fill=\"currentColor\"></path>\n                    </svg>\n                    <span><strong>Insufficient privileges!</strong></span>\n                </div>\n            </div>\n            \"\"\",\n            margin=5,\n            sizing_mode=\"stretch_width\",\n            stylesheets=[config.panel.gui.css_files.no_more_orders_path],\n        )\n\n        # WIDGET\n        # Password renewer (only basic auth)\n        self.password_widget = pn.Param(\n            BackendPasswordRenewer().param,\n            widgets={\n                \"new_password\": pnw.PasswordInput(\n                    name=\"New password\", placeholder=\"New Password\"\n                ),\n                \"repeat_new_password\": pnw.PasswordInput(\n                    name=\"Repeat new password\",\n                    placeholder=\"Repeat New Password\",\n                ),\n            },\n            name=\"Add/Update User Credentials\",\n            width=sidebar_content_width,\n        )\n        # Add user (only oauth)\n        self.add_privileged_user_widget = pn.Param(\n            BackendAddPrivilegedUser().param,\n            name=\"Add Privileged User\",\n            width=sidebar_content_width,\n        )\n        # User eraser\n        self.user_eraser = pn.Param(\n            BackendUserEraser().param,\n            name=\"Delete User\",\n            width=sidebar_content_width,\n        )\n        # User list\n        self.users_tabulator = pn.widgets.Tabulator(\n            value=auth.list_users_guests_and_privileges(config),\n            sizing_mode=\"stretch_height\",\n        )\n        # Flags content (use empty dataframe to instantiate)\n        df_flags = models.Flags.read_as_df(\n            config=config,\n            index_col=\"id\",\n        )\n        self.flags_content = pn.widgets.Tabulator(\n            value=df_flags,\n            sizing_mode=\"stretch_height\",\n        )\n\n        # BUTTONS\n        # Exit button\n        # Password button\n        self.submit_password_button = pnw.Button(\n            name=\"Submit\",\n            button_type=\"success\",\n            height=generic_button_height,\n            icon=\"key\",\n            icon_size=\"2em\",\n            sizing_mode=\"stretch_width\",\n        )\n        # Delete User button\n        self.add_privileged_user_button = pnw.Button(\n            name=\"Add\",\n            button_type=\"success\",\n            height=generic_button_height,\n            icon=\"user-plus\",\n            icon_size=\"2em\",\n            sizing_mode=\"stretch_width\",\n        )\n        # Delete User button\n        self.delete_user_button = pnw.Button(\n            name=\"Delete\",\n            button_type=\"danger\",\n            height=generic_button_height,\n            icon=\"user-minus\",\n            icon_size=\"2em\",\n            sizing_mode=\"stretch_width\",\n        )\n        # Clear flags table button\n        self.clear_flags_button = pnw.Button(\n            name=\"Clear Guest Override Flags\",\n            button_type=\"danger\",\n            height=generic_button_height,\n            icon=\"file-shredder\",\n            icon_size=\"2em\",\n            sizing_mode=\"stretch_width\",\n        )\n\n        # COLUMN\n        # Create column with user credentials controls (basic auth)\n        self.add_update_user_column = pn.Column(\n            config.panel.gui.psw_text,\n            self.password_widget,\n            pn.VSpacer(),\n            self.submit_password_button,\n            width=sidebar_width,\n            sizing_mode=\"stretch_height\",\n            min_height=backend_min_height,\n        )\n        # Create column with user authenthication controls (oauth)\n        self.add_privileged_user_column = pn.Column(\n            self.add_privileged_user_widget,\n            pn.VSpacer(),\n            self.add_privileged_user_button,\n            width=sidebar_width,\n            sizing_mode=\"stretch_height\",\n            min_height=backend_min_height,\n        )\n        # Create column for deleting users\n        self.delete_user_column = pn.Column(\n            self.user_eraser,\n            pn.VSpacer(),\n            self.delete_user_button,\n            width=sidebar_width,\n            sizing_mode=\"stretch_height\",\n            min_height=backend_min_height,\n        )\n        # Create column with flags' list\n        self.clear_flags_column = pn.Column(\n            pn.pane.HTML(\"<b>Flags Table Content</b>\"),\n            self.flags_content,\n            self.clear_flags_button,\n            width=sidebar_width,\n            sizing_mode=\"stretch_height\",\n            min_height=backend_min_height,\n        )\n        # Create column for users' list\n        self.list_user_column = pn.Column(\n            pn.pane.HTML(\"<b>Users and Privileges</b>\"),\n            self.users_tabulator,\n            width=sidebar_width,\n            sizing_mode=\"stretch_height\",\n            min_height=backend_min_height,\n        )\n\n        # ROWS\n        self.backend_controls = pn.Row(\n            name=\"Actions\",\n            sizing_mode=\"stretch_both\",\n            min_height=backend_min_height,\n        )\n        # Add controls only for admin users\n        if not auth.is_admin(user=pn_user(config), config=config):\n            self.backend_controls.append(self.access_denied_text)\n            self.backend_controls.append(pn.Spacer(height=15))\n        else:\n            # For basic auth use a password renewer, for oauth a widget for\n            # adding privileged users\n            if auth.is_basic_auth_active(config=config):\n                self.backend_controls.append(self.add_update_user_column)\n            else:\n                self.backend_controls.append(self.add_privileged_user_column)\n            self.backend_controls.append(\n                pn.pane.HTML(\n                    styles=dict(background=\"lightgray\"),\n                    width=2,\n                    sizing_mode=\"stretch_height\",\n                )\n            )\n            self.backend_controls.append(self.delete_user_column)\n            self.backend_controls.append(\n                pn.pane.HTML(\n                    styles=dict(background=\"lightgray\"),\n                    width=2,\n                    sizing_mode=\"stretch_height\",\n                )\n            )\n            self.backend_controls.append(self.clear_flags_column)\n            self.backend_controls.append(\n                pn.pane.HTML(\n                    styles=dict(background=\"lightgray\"),\n                    width=2,\n                    sizing_mode=\"stretch_height\",\n                )\n            )\n            self.backend_controls.append(self.list_user_column)\n\n        # CALLBACKS\n        # Submit password button callback\n        def submit_password_button_callback(self, config):\n            success = auth.backend_submit_password(\n                gi=self,\n                user_is_admin=self.password_widget.object.admin,\n                user_is_guest=self.password_widget.object.guest,\n                config=config,\n            )\n            if success:\n                self.reload_backend(config)\n\n        self.submit_password_button.on_click(\n            lambda e: submit_password_button_callback(self, config)\n        )\n\n        # Add privileged user callback\n        def add_privileged_user_button_callback(self):\n            # Get username, updated at each key press\n            username_key_press = self.add_privileged_user_widget._widgets[\n                \"user\"\n            ].value_input\n            # Add user\n            auth.add_privileged_user(\n                username_key_press,\n                is_admin=self.add_privileged_user_widget.object.admin,\n                config=config,\n            )\n\n            self.reload_backend(config)\n            pn.state.notifications.success(\n                f\"User '{username_key_press}' added\",\n                duration=config.panel.notifications.duration,\n            )\n\n        self.add_privileged_user_button.on_click(\n            lambda e: add_privileged_user_button_callback(self)\n        )\n\n        # Delete user callback\n        def delete_user_button_callback(self):\n            # Get username, updated at each key press\n            username_key_press = self.user_eraser._widgets[\"user\"].value_input\n            # Delete user\n            deleted_data = auth.remove_user(\n                user=username_key_press, config=config\n            )\n            if (deleted_data[\"privileged_users_deleted\"] > 0) or (\n                deleted_data[\"credentials_deleted\"] > 0\n            ):\n                self.reload_backend(config)\n                pn.state.notifications.success(\n                    f\"User '{self.user_eraser.object.user}' deleted<br>auth: {deleted_data['privileged_users_deleted']}<br>cred: {deleted_data['credentials_deleted']}\",\n                    duration=config.panel.notifications.duration,\n                )\n            else:\n                pn.state.notifications.error(\n                    f\"User '{username_key_press}' does not exist\",\n                    duration=config.panel.notifications.duration,\n                )\n\n        self.delete_user_button.on_click(\n            lambda e: delete_user_button_callback(self)\n        )\n\n        # Clear flags callback\n        def clear_flags_button_callback(self):\n            # Clear flags\n            num_rows_deleted = models.Flags.clear_guest_override(config=config)\n            # Reload and notify user\n            self.reload_backend(config)\n            pn.state.notifications.success(\n                f\"Guest override flags cleared<br>{num_rows_deleted} rows deleted\",\n                duration=config.panel.notifications.duration,\n            )\n\n        self.clear_flags_button.on_click(\n            lambda e: clear_flags_button_callback(self)\n        )\n\n    # UTILITY FUNCTIONS\n    # MAIN SECTION\n    def reload_backend(self, config: DictConfig) -> None:\n        \"\"\"Reload backend by updating user lists and privileges.\n        Read also flags from `flags` table.\n\n        Args:\n            config (DictConfig): Hydra configuration dictionary.\n        \"\"\"\n        # Users and guests lists\n        self.users_tabulator.value = auth.list_users_guests_and_privileges(\n            config\n        )\n        # Flags table content\n        df_flags = models.Flags.read_as_df(\n            config=config,\n            index_col=\"id\",\n        )\n        self.flags_content.value = df_flags\n\n    def exit_backend(self) -> None:\n        \"\"\"Return to main homepage.\"\"\"\n        # Edit pathname to force exit\n        pn.state.location.pathname = (\n            pn.state.location.pathname.split(\"/\")[0] + \"/\"\n        )\n        pn.state.location.reload = True\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendInterface.access_denied_text","title":"access_denied_text instance-attribute","text":"
access_denied_text = HTML(\n    '\\n            <div class=\"no-more-order-flag\">\\n                <div class=\"icon-container\">\\n                    <svg class=\"flashing-animation\" xmlns=\"http://www.w3.org/2000/svg\" class=\"icon icon-tabler icon-tabler-shield-lock-filled\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\\n                        <path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\"></path>\\n                        <path d=\"M11.998 2l.118 .007l.059 .008l.061 .013l.111 .034a.993 .993 0 0 1 .217 .112l.104 .082l.255 .218a11 11 0 0 0 7.189 2.537l.342 -.01a1 1 0 0 1 1.005 .717a13 13 0 0 1 -9.208 16.25a1 1 0 0 1 -.502 0a13 13 0 0 1 -9.209 -16.25a1 1 0 0 1 1.005 -.717a11 11 0 0 0 7.531 -2.527l.263 -.225l.096 -.075a.993 .993 0 0 1 .217 -.112l.112 -.034a.97 .97 0 0 1 .119 -.021l.115 -.007zm.002 7a2 2 0 0 0 -1.995 1.85l-.005 .15l.005 .15a2 2 0 0 0 .995 1.581v1.769l.007 .117a1 1 0 0 0 1.993 -.117l.001 -1.768a2 2 0 0 0 -1.001 -3.732z\" stroke-width=\"0\" fill=\"currentColor\"></path>\\n                    </svg>\\n                    <span><strong>Insufficient privileges!</strong></span>\\n                </div>\\n            </div>\\n            ',\n    margin=5,\n    sizing_mode=\"stretch_width\",\n    stylesheets=[no_more_orders_path],\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendInterface.add_privileged_user_button","title":"add_privileged_user_button instance-attribute","text":"
add_privileged_user_button = Button(\n    name=\"Add\",\n    button_type=\"success\",\n    height=generic_button_height,\n    icon=\"user-plus\",\n    icon_size=\"2em\",\n    sizing_mode=\"stretch_width\",\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendInterface.add_privileged_user_column","title":"add_privileged_user_column instance-attribute","text":"
add_privileged_user_column = Column(\n    add_privileged_user_widget,\n    VSpacer(),\n    add_privileged_user_button,\n    width=sidebar_width,\n    sizing_mode=\"stretch_height\",\n    min_height=backend_min_height,\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendInterface.add_privileged_user_widget","title":"add_privileged_user_widget instance-attribute","text":"
add_privileged_user_widget = Param(\n    param,\n    name=\"Add Privileged User\",\n    width=sidebar_content_width,\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendInterface.add_update_user_column","title":"add_update_user_column instance-attribute","text":"
add_update_user_column = Column(\n    psw_text,\n    password_widget,\n    VSpacer(),\n    submit_password_button,\n    width=sidebar_width,\n    sizing_mode=\"stretch_height\",\n    min_height=backend_min_height,\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendInterface.backend_controls","title":"backend_controls instance-attribute","text":"
backend_controls = Row(\n    name=\"Actions\",\n    sizing_mode=\"stretch_both\",\n    min_height=backend_min_height,\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendInterface.clear_flags_button","title":"clear_flags_button instance-attribute","text":"
clear_flags_button = Button(\n    name=\"Clear Guest Override Flags\",\n    button_type=\"danger\",\n    height=generic_button_height,\n    icon=\"file-shredder\",\n    icon_size=\"2em\",\n    sizing_mode=\"stretch_width\",\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendInterface.clear_flags_column","title":"clear_flags_column instance-attribute","text":"
clear_flags_column = Column(\n    HTML(\"<b>Flags Table Content</b>\"),\n    flags_content,\n    clear_flags_button,\n    width=sidebar_width,\n    sizing_mode=\"stretch_height\",\n    min_height=backend_min_height,\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendInterface.delete_user_button","title":"delete_user_button instance-attribute","text":"
delete_user_button = Button(\n    name=\"Delete\",\n    button_type=\"danger\",\n    height=generic_button_height,\n    icon=\"user-minus\",\n    icon_size=\"2em\",\n    sizing_mode=\"stretch_width\",\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendInterface.delete_user_column","title":"delete_user_column instance-attribute","text":"
delete_user_column = Column(\n    user_eraser,\n    VSpacer(),\n    delete_user_button,\n    width=sidebar_width,\n    sizing_mode=\"stretch_height\",\n    min_height=backend_min_height,\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendInterface.exit_button","title":"exit_button instance-attribute","text":"
exit_button = Button(\n    name=\"\",\n    button_type=\"primary\",\n    button_style=\"solid\",\n    width=header_button_width,\n    height=generic_button_height,\n    icon=\"home-move\",\n    icon_size=\"2em\",\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendInterface.flags_content","title":"flags_content instance-attribute","text":"
flags_content = Tabulator(\n    value=df_flags, sizing_mode=\"stretch_height\"\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendInterface.header_row","title":"header_row instance-attribute","text":"
header_row = Row(\n    height=header_row_height, sizing_mode=\"stretch_width\"\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendInterface.list_user_column","title":"list_user_column instance-attribute","text":"
list_user_column = Column(\n    HTML(\"<b>Users and Privileges</b>\"),\n    users_tabulator,\n    width=sidebar_width,\n    sizing_mode=\"stretch_height\",\n    min_height=backend_min_height,\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendInterface.password_widget","title":"password_widget instance-attribute","text":"
password_widget = Param(\n    param,\n    widgets={\n        \"new_password\": PasswordInput(\n            name=\"New password\", placeholder=\"New Password\"\n        ),\n        \"repeat_new_password\": PasswordInput(\n            name=\"Repeat new password\",\n            placeholder=\"Repeat New Password\",\n        ),\n    },\n    name=\"Add/Update User Credentials\",\n    width=sidebar_content_width,\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendInterface.submit_password_button","title":"submit_password_button instance-attribute","text":"
submit_password_button = Button(\n    name=\"Submit\",\n    button_type=\"success\",\n    height=generic_button_height,\n    icon=\"key\",\n    icon_size=\"2em\",\n    sizing_mode=\"stretch_width\",\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendInterface.user_eraser","title":"user_eraser instance-attribute","text":"
user_eraser = Param(\n    param, name=\"Delete User\", width=sidebar_content_width\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendInterface.users_tabulator","title":"users_tabulator instance-attribute","text":"
users_tabulator = Tabulator(\n    value=list_users_guests_and_privileges(config),\n    sizing_mode=\"stretch_height\",\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendInterface.__init__","title":"__init__","text":"
__init__(config: DictConfig)\n
Source code in dlunch/gui.py
def __init__(\n    self,\n    config: DictConfig,\n):\n    # HEADER SECTION ------------------------------------------------------\n    # WIDGET\n\n    # BUTTONS\n    self.exit_button = pnw.Button(\n        name=\"\",\n        button_type=\"primary\",\n        button_style=\"solid\",\n        width=header_button_width,\n        height=generic_button_height,\n        icon=\"home-move\",\n        icon_size=\"2em\",\n    )\n\n    # ROW\n    # Create column for person data (add logout button only if auth is active)\n    self.header_row = pn.Row(\n        height=header_row_height,\n        sizing_mode=\"stretch_width\",\n    )\n    # Append a controls to the right side of header\n    self.header_row.append(pn.HSpacer())\n    self.header_row.append(self.exit_button)\n    self.header_row.append(\n        pn.pane.HTML(styles=dict(background=\"white\"), width=2, height=45)\n    )\n\n    # CALLBACKS\n    # Exit callback\n    self.exit_button.on_click(lambda e: self.exit_backend())\n\n    # MAIN SECTION --------------------------------------------------------\n    # Backend main section\n\n    # TEXTS\n    # \"no more order\" message\n    self.access_denied_text = pn.pane.HTML(\n        \"\"\"\n        <div class=\"no-more-order-flag\">\n            <div class=\"icon-container\">\n                <svg class=\"flashing-animation\" xmlns=\"http://www.w3.org/2000/svg\" class=\"icon icon-tabler icon-tabler-shield-lock-filled\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n                    <path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\"></path>\n                    <path d=\"M11.998 2l.118 .007l.059 .008l.061 .013l.111 .034a.993 .993 0 0 1 .217 .112l.104 .082l.255 .218a11 11 0 0 0 7.189 2.537l.342 -.01a1 1 0 0 1 1.005 .717a13 13 0 0 1 -9.208 16.25a1 1 0 0 1 -.502 0a13 13 0 0 1 -9.209 -16.25a1 1 0 0 1 1.005 -.717a11 11 0 0 0 7.531 -2.527l.263 -.225l.096 -.075a.993 .993 0 0 1 .217 -.112l.112 -.034a.97 .97 0 0 1 .119 -.021l.115 -.007zm.002 7a2 2 0 0 0 -1.995 1.85l-.005 .15l.005 .15a2 2 0 0 0 .995 1.581v1.769l.007 .117a1 1 0 0 0 1.993 -.117l.001 -1.768a2 2 0 0 0 -1.001 -3.732z\" stroke-width=\"0\" fill=\"currentColor\"></path>\n                </svg>\n                <span><strong>Insufficient privileges!</strong></span>\n            </div>\n        </div>\n        \"\"\",\n        margin=5,\n        sizing_mode=\"stretch_width\",\n        stylesheets=[config.panel.gui.css_files.no_more_orders_path],\n    )\n\n    # WIDGET\n    # Password renewer (only basic auth)\n    self.password_widget = pn.Param(\n        BackendPasswordRenewer().param,\n        widgets={\n            \"new_password\": pnw.PasswordInput(\n                name=\"New password\", placeholder=\"New Password\"\n            ),\n            \"repeat_new_password\": pnw.PasswordInput(\n                name=\"Repeat new password\",\n                placeholder=\"Repeat New Password\",\n            ),\n        },\n        name=\"Add/Update User Credentials\",\n        width=sidebar_content_width,\n    )\n    # Add user (only oauth)\n    self.add_privileged_user_widget = pn.Param(\n        BackendAddPrivilegedUser().param,\n        name=\"Add Privileged User\",\n        width=sidebar_content_width,\n    )\n    # User eraser\n    self.user_eraser = pn.Param(\n        BackendUserEraser().param,\n        name=\"Delete User\",\n        width=sidebar_content_width,\n    )\n    # User list\n    self.users_tabulator = pn.widgets.Tabulator(\n        value=auth.list_users_guests_and_privileges(config),\n        sizing_mode=\"stretch_height\",\n    )\n    # Flags content (use empty dataframe to instantiate)\n    df_flags = models.Flags.read_as_df(\n        config=config,\n        index_col=\"id\",\n    )\n    self.flags_content = pn.widgets.Tabulator(\n        value=df_flags,\n        sizing_mode=\"stretch_height\",\n    )\n\n    # BUTTONS\n    # Exit button\n    # Password button\n    self.submit_password_button = pnw.Button(\n        name=\"Submit\",\n        button_type=\"success\",\n        height=generic_button_height,\n        icon=\"key\",\n        icon_size=\"2em\",\n        sizing_mode=\"stretch_width\",\n    )\n    # Delete User button\n    self.add_privileged_user_button = pnw.Button(\n        name=\"Add\",\n        button_type=\"success\",\n        height=generic_button_height,\n        icon=\"user-plus\",\n        icon_size=\"2em\",\n        sizing_mode=\"stretch_width\",\n    )\n    # Delete User button\n    self.delete_user_button = pnw.Button(\n        name=\"Delete\",\n        button_type=\"danger\",\n        height=generic_button_height,\n        icon=\"user-minus\",\n        icon_size=\"2em\",\n        sizing_mode=\"stretch_width\",\n    )\n    # Clear flags table button\n    self.clear_flags_button = pnw.Button(\n        name=\"Clear Guest Override Flags\",\n        button_type=\"danger\",\n        height=generic_button_height,\n        icon=\"file-shredder\",\n        icon_size=\"2em\",\n        sizing_mode=\"stretch_width\",\n    )\n\n    # COLUMN\n    # Create column with user credentials controls (basic auth)\n    self.add_update_user_column = pn.Column(\n        config.panel.gui.psw_text,\n        self.password_widget,\n        pn.VSpacer(),\n        self.submit_password_button,\n        width=sidebar_width,\n        sizing_mode=\"stretch_height\",\n        min_height=backend_min_height,\n    )\n    # Create column with user authenthication controls (oauth)\n    self.add_privileged_user_column = pn.Column(\n        self.add_privileged_user_widget,\n        pn.VSpacer(),\n        self.add_privileged_user_button,\n        width=sidebar_width,\n        sizing_mode=\"stretch_height\",\n        min_height=backend_min_height,\n    )\n    # Create column for deleting users\n    self.delete_user_column = pn.Column(\n        self.user_eraser,\n        pn.VSpacer(),\n        self.delete_user_button,\n        width=sidebar_width,\n        sizing_mode=\"stretch_height\",\n        min_height=backend_min_height,\n    )\n    # Create column with flags' list\n    self.clear_flags_column = pn.Column(\n        pn.pane.HTML(\"<b>Flags Table Content</b>\"),\n        self.flags_content,\n        self.clear_flags_button,\n        width=sidebar_width,\n        sizing_mode=\"stretch_height\",\n        min_height=backend_min_height,\n    )\n    # Create column for users' list\n    self.list_user_column = pn.Column(\n        pn.pane.HTML(\"<b>Users and Privileges</b>\"),\n        self.users_tabulator,\n        width=sidebar_width,\n        sizing_mode=\"stretch_height\",\n        min_height=backend_min_height,\n    )\n\n    # ROWS\n    self.backend_controls = pn.Row(\n        name=\"Actions\",\n        sizing_mode=\"stretch_both\",\n        min_height=backend_min_height,\n    )\n    # Add controls only for admin users\n    if not auth.is_admin(user=pn_user(config), config=config):\n        self.backend_controls.append(self.access_denied_text)\n        self.backend_controls.append(pn.Spacer(height=15))\n    else:\n        # For basic auth use a password renewer, for oauth a widget for\n        # adding privileged users\n        if auth.is_basic_auth_active(config=config):\n            self.backend_controls.append(self.add_update_user_column)\n        else:\n            self.backend_controls.append(self.add_privileged_user_column)\n        self.backend_controls.append(\n            pn.pane.HTML(\n                styles=dict(background=\"lightgray\"),\n                width=2,\n                sizing_mode=\"stretch_height\",\n            )\n        )\n        self.backend_controls.append(self.delete_user_column)\n        self.backend_controls.append(\n            pn.pane.HTML(\n                styles=dict(background=\"lightgray\"),\n                width=2,\n                sizing_mode=\"stretch_height\",\n            )\n        )\n        self.backend_controls.append(self.clear_flags_column)\n        self.backend_controls.append(\n            pn.pane.HTML(\n                styles=dict(background=\"lightgray\"),\n                width=2,\n                sizing_mode=\"stretch_height\",\n            )\n        )\n        self.backend_controls.append(self.list_user_column)\n\n    # CALLBACKS\n    # Submit password button callback\n    def submit_password_button_callback(self, config):\n        success = auth.backend_submit_password(\n            gi=self,\n            user_is_admin=self.password_widget.object.admin,\n            user_is_guest=self.password_widget.object.guest,\n            config=config,\n        )\n        if success:\n            self.reload_backend(config)\n\n    self.submit_password_button.on_click(\n        lambda e: submit_password_button_callback(self, config)\n    )\n\n    # Add privileged user callback\n    def add_privileged_user_button_callback(self):\n        # Get username, updated at each key press\n        username_key_press = self.add_privileged_user_widget._widgets[\n            \"user\"\n        ].value_input\n        # Add user\n        auth.add_privileged_user(\n            username_key_press,\n            is_admin=self.add_privileged_user_widget.object.admin,\n            config=config,\n        )\n\n        self.reload_backend(config)\n        pn.state.notifications.success(\n            f\"User '{username_key_press}' added\",\n            duration=config.panel.notifications.duration,\n        )\n\n    self.add_privileged_user_button.on_click(\n        lambda e: add_privileged_user_button_callback(self)\n    )\n\n    # Delete user callback\n    def delete_user_button_callback(self):\n        # Get username, updated at each key press\n        username_key_press = self.user_eraser._widgets[\"user\"].value_input\n        # Delete user\n        deleted_data = auth.remove_user(\n            user=username_key_press, config=config\n        )\n        if (deleted_data[\"privileged_users_deleted\"] > 0) or (\n            deleted_data[\"credentials_deleted\"] > 0\n        ):\n            self.reload_backend(config)\n            pn.state.notifications.success(\n                f\"User '{self.user_eraser.object.user}' deleted<br>auth: {deleted_data['privileged_users_deleted']}<br>cred: {deleted_data['credentials_deleted']}\",\n                duration=config.panel.notifications.duration,\n            )\n        else:\n            pn.state.notifications.error(\n                f\"User '{username_key_press}' does not exist\",\n                duration=config.panel.notifications.duration,\n            )\n\n    self.delete_user_button.on_click(\n        lambda e: delete_user_button_callback(self)\n    )\n\n    # Clear flags callback\n    def clear_flags_button_callback(self):\n        # Clear flags\n        num_rows_deleted = models.Flags.clear_guest_override(config=config)\n        # Reload and notify user\n        self.reload_backend(config)\n        pn.state.notifications.success(\n            f\"Guest override flags cleared<br>{num_rows_deleted} rows deleted\",\n            duration=config.panel.notifications.duration,\n        )\n\n    self.clear_flags_button.on_click(\n        lambda e: clear_flags_button_callback(self)\n    )\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendInterface.exit_backend","title":"exit_backend","text":"
exit_backend() -> None\n

Return to main homepage.

Source code in dlunch/gui.py
def exit_backend(self) -> None:\n    \"\"\"Return to main homepage.\"\"\"\n    # Edit pathname to force exit\n    pn.state.location.pathname = (\n        pn.state.location.pathname.split(\"/\")[0] + \"/\"\n    )\n    pn.state.location.reload = True\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendInterface.reload_backend","title":"reload_backend","text":"
reload_backend(config: DictConfig) -> None\n

Reload backend by updating user lists and privileges. Read also flags from flags table.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required Source code in dlunch/gui.py
def reload_backend(self, config: DictConfig) -> None:\n    \"\"\"Reload backend by updating user lists and privileges.\n    Read also flags from `flags` table.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n    \"\"\"\n    # Users and guests lists\n    self.users_tabulator.value = auth.list_users_guests_and_privileges(\n        config\n    )\n    # Flags table content\n    df_flags = models.Flags.read_as_df(\n        config=config,\n        index_col=\"id\",\n    )\n    self.flags_content.value = df_flags\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendPasswordRenewer","title":"BackendPasswordRenewer","text":"

Bases: Parameterized

Param class used inside the backend to create the widget that collect info to renew users password.

It has more options compared to the standard PasswordRenewer.

This widget is used only if basic authentication is active.

Methods:

Name Description __str__

String representation of this object.

Attributes:

Name Type Description admin Boolean

Admin flag (true if admin).

guest Boolean

Guest flag (true if guest).

new_password String

New password.

repeat_new_password String

Repeat the new password. This field tests if the new password is as intended.

user String

Username.

Source code in dlunch/gui.py
class BackendPasswordRenewer(param.Parameterized):\n    \"\"\"Param class used inside the backend to create the widget that collect info to renew users password.\n\n    It has more options compared to the standard `PasswordRenewer`.\n\n    This widget is used only if basic authentication is active.\"\"\"\n\n    user: param.String = param.String(\n        default=\"\",\n        doc=\"username for password update (use 'guest' for guest user)\",\n    )\n    \"\"\"Username.\"\"\"\n    new_password: param.String = param.String(default=\"\")\n    \"\"\"New password.\"\"\"\n    repeat_new_password: param.String = param.String(default=\"\")\n    \"\"\"Repeat the new password. This field tests if the new password is as intended.\"\"\"\n    admin: param.Boolean = param.Boolean(\n        default=False, doc=\"add admin privileges\"\n    )\n    \"\"\"Admin flag (true if admin).\"\"\"\n    guest: param.Boolean = param.Boolean(\n        default=False,\n        doc=\"guest account (don't add user to privileged users' table)\",\n    )\n    \"\"\"Guest flag (true if guest).\n\n    User credentials are added to `credentials` table, but the user is not listed in `privileged_users` table.\"\"\"\n\n    def __str__(self):\n        \"\"\"String representation of this object.\n\n        Returns:\n            (str): string representation.\n        \"\"\"\n        return \"BackendPasswordRenewer\"\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendPasswordRenewer.admin","title":"admin class-attribute instance-attribute","text":"
admin: Boolean = Boolean(\n    default=False, doc=\"add admin privileges\"\n)\n

Admin flag (true if admin).

"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendPasswordRenewer.guest","title":"guest class-attribute instance-attribute","text":"
guest: Boolean = Boolean(\n    default=False,\n    doc=\"guest account (don't add user to privileged users' table)\",\n)\n

Guest flag (true if guest).

User credentials are added to credentials table, but the user is not listed in privileged_users table.

"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendPasswordRenewer.new_password","title":"new_password class-attribute instance-attribute","text":"
new_password: String = String(default='')\n

New password.

"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendPasswordRenewer.repeat_new_password","title":"repeat_new_password class-attribute instance-attribute","text":"
repeat_new_password: String = String(default='')\n

Repeat the new password. This field tests if the new password is as intended.

"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendPasswordRenewer.user","title":"user class-attribute instance-attribute","text":"
user: String = String(\n    default=\"\",\n    doc=\"username for password update (use 'guest' for guest user)\",\n)\n

Username.

"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendPasswordRenewer.__str__","title":"__str__","text":"
__str__()\n

String representation of this object.

Returns:

Type Description str

string representation.

Source code in dlunch/gui.py
def __str__(self):\n    \"\"\"String representation of this object.\n\n    Returns:\n        (str): string representation.\n    \"\"\"\n    return \"BackendPasswordRenewer\"\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendUserEraser","title":"BackendUserEraser","text":"

Bases: Parameterized

Param class used inside the backend to create the widget that delete users.

Users are deleted from both credentials and privileged_user tables.

Methods:

Name Description __str__

String representation of this object.

Attributes:

Name Type Description user String

User to be deleted.

Source code in dlunch/gui.py
class BackendUserEraser(param.Parameterized):\n    \"\"\"Param class used inside the backend to create the widget that delete users.\n\n    Users are deleted from both `credentials` and `privileged_user` tables.\"\"\"\n\n    user: param.String = param.String(default=\"\", doc=\"user to be deleted\")\n    \"\"\"User to be deleted.\"\"\"\n\n    def __str__(self):\n        \"\"\"String representation of this object.\n\n        Returns:\n            (str): string representation.\n        \"\"\"\n        return \"BackendUserEraser\"\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendUserEraser.user","title":"user class-attribute instance-attribute","text":"
user: String = String(default='', doc='user to be deleted')\n

User to be deleted.

"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendUserEraser.__str__","title":"__str__","text":"
__str__()\n

String representation of this object.

Returns:

Type Description str

string representation.

Source code in dlunch/gui.py
def __str__(self):\n    \"\"\"String representation of this object.\n\n    Returns:\n        (str): string representation.\n    \"\"\"\n    return \"BackendUserEraser\"\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface","title":"GraphicInterface","text":"

Class with widgets for the main graphic interface.

All widgets are instantiated at class initialization.

Class methods handle specific operations that may be repeated multiple time after class instantiation.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required waiter Waiter

Waiter object with methods to handle user requests.

required app Template

App panel template (see Panel docs <https://panel.holoviz.org/how_to/templates/index.html>__).

required person Person

Object with user data and preferences for the lunch order.

required guest_password str

guest password to show in password tab. Used only if basic authentication is active. Defaults to empty string (\"\").

''

Methods:

Name Description __init__ build_order_table

Build Tabulator object to display placed orders.

build_stats_and_info_text

Build text used for statistics under the stats tab, and info under the user tab.

build_time_label

Build HTML field to display the time label.

load_sidebar_tabs

Append tabs to the app template sidebar.

Attributes:

Name Type Description additional_items_details backend_button build_menu_button buttons_flexbox change_order_time_takeaway_button dataframe delete_order_button download_button error_message file_widget guest_override_alert guest_password_widget guest_username_widget header_object header_row logout_button main_header_row menu_flexbox no_menu_col no_menu_image no_menu_image_attribution no_more_order_alert password_widget person_widget quote refresh_button reload_on_guest_override reload_on_no_more_order res_col results_divider send_order_button sidebar_download_orders_col sidebar_menu_upload_col sidebar_password sidebar_person_column sidebar_stats_col sidebar_tabs stats_widget submit_password_button takeaway_alert_sign takeaway_alert_text time_col time_col_title toggle_guest_override_button toggle_no_more_order_button Source code in dlunch/gui.py
class GraphicInterface:\n    \"\"\"Class with widgets for the main graphic interface.\n\n    All widgets are instantiated at class initialization.\n\n    Class methods handle specific operations that may be repeated multiple time after class instantiation.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n        waiter (core.Waiter): Waiter object with methods to handle user requests.\n        app (pn.Template): App panel template (see `Panel docs <https://panel.holoviz.org/how_to/templates/index.html>`__).\n        person (Person): Object with user data and preferences for the lunch order.\n        guest_password (str, optional): guest password to show in password tab. Used only if basic authentication is active.\n            Defaults to empty string (`\"\"`).\n\n    \"\"\"\n\n    def __init__(\n        self,\n        config: DictConfig,\n        waiter: core.Waiter,\n        app: pn.Template,\n        person: Person,\n        guest_password: str = \"\",\n    ):\n        # HEADER SECTION ------------------------------------------------------\n        # WIDGET\n        # Create PNG pane with app icon\n        self.header_object = instantiate(config.panel.gui.header_object)\n\n        # BUTTONS\n        # Backend button\n        self.backend_button = pnw.Button(\n            name=\"\",\n            button_type=\"primary\",\n            button_style=\"solid\",\n            width=header_button_width,\n            height=generic_button_height,\n            icon=\"adjustments\",\n            icon_size=\"2em\",\n        )\n        # Guest override toggle button (if pressed the user act as a guest)\n        self.toggle_guest_override_button = pnw.Toggle(\n            button_type=\"primary\",\n            button_style=\"solid\",\n            width=header_button_width,\n            height=generic_button_height,\n            icon=\"user-bolt\",\n            icon_size=\"2em\",\n            stylesheets=[config.panel.gui.css_files.guest_override_path],\n        )\n        # Logout button\n        self.logout_button = pnw.Button(\n            name=\"\",\n            button_type=\"primary\",\n            button_style=\"solid\",\n            width=header_button_width,\n            height=generic_button_height,\n            icon=\"door-exit\",\n            icon_size=\"2em\",\n        )\n\n        # ROW\n        # Create column for person data (add logout button only if auth is active)\n        self.header_row = pn.Row(\n            height=header_row_height,\n            sizing_mode=\"stretch_width\",\n        )\n        # Append a graphic element to the left side of header\n        if config.panel.gui.header_object:\n            self.header_row.append(self.header_object)\n        # Append a controls to the right side of header\n        if auth.is_auth_active(config=config):\n            self.header_row.append(pn.HSpacer())\n            # Backend only for admin\n            if auth.is_admin(user=pn_user(config), config=config):\n                self.header_row.append(self.backend_button)\n            # Guest override only for non guests\n            if not auth.is_guest(\n                user=pn_user(config), config=config, allow_override=False\n            ):\n                self.header_row.append(self.toggle_guest_override_button)\n            self.header_row.append(self.logout_button)\n            self.header_row.append(\n                pn.pane.HTML(\n                    styles=dict(background=\"white\"), width=2, height=45\n                )\n            )\n\n        # CALLBACKS\n        # Backend callback\n        self.backend_button.on_click(lambda e: auth.open_backend())\n\n        # Guest override callback\n        @pn.depends(self.toggle_guest_override_button, watch=True)\n        def reload_on_guest_override_callback(\n            toggle: pnw.ToggleIcon, reload: bool = True\n        ):\n            # Update global variable that control guest override\n            # Only non guest can store this value in 'flags' table (guest users\n            # are always guests, there is no use in sotring a flag for them)\n            if not auth.is_guest(\n                user=pn_user(config), config=config, allow_override=False\n            ):\n                models.set_flag(\n                    config=config,\n                    id=f\"{pn_user(config)}_guest_override\",\n                    value=toggle,\n                )\n            # Show banner if override is active\n            self.guest_override_alert.visible = toggle\n            # Simply reload the menu when the toggle button value changes\n            if reload:\n                waiter.reload_menu(\n                    None,\n                    self,\n                )\n\n        # Add callback to attribute\n        self.reload_on_guest_override = reload_on_guest_override_callback\n\n        # Logout callback\n        self.logout_button.on_click(lambda e: auth.force_logout())\n\n        # MAIN SECTION --------------------------------------------------------\n        # Elements required for build the main section of the web app\n\n        # TEXTS\n        # Quote of the day\n        self.quote = pn.pane.Markdown(\n            f\"\"\"\n            _{df_quote.quote.iloc[0]}_\n\n            **{df_quote.author.iloc[0]}**\n            \"\"\"\n        )\n        # Time column title\n        self.time_col_title = pn.pane.Markdown(\n            config.panel.time_column_text,\n            sizing_mode=\"stretch_width\",\n            styles={\"text-align\": \"center\"},\n        )\n        # \"no more order\" message\n        self.no_more_order_alert = pn.pane.HTML(\n            \"\"\"\n            <div class=\"no-more-order-flag\">\n                <div class=\"icon-container\">\n                    <svg class=\"flashing-animation\" xmlns=\"http://www.w3.org/2000/svg\" class=\"icon icon-tabler icon-tabler-alert-circle-filled\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n                        <path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\"></path>\n                        <path d=\"M12 2c5.523 0 10 4.477 10 10a10 10 0 0 1 -19.995 .324l-.005 -.324l.004 -.28c.148 -5.393 4.566 -9.72 9.996 -9.72zm.01 13l-.127 .007a1 1 0 0 0 0 1.986l.117 .007l.127 -.007a1 1 0 0 0 0 -1.986l-.117 -.007zm-.01 -8a1 1 0 0 0 -.993 .883l-.007 .117v4l.007 .117a1 1 0 0 0 1.986 0l.007 -.117v-4l-.007 -.117a1 1 0 0 0 -.993 -.883z\" stroke-width=\"0\" fill=\"currentColor\"></path>\n                    </svg>\n                    <span><strong>Oh no! You missed this train...</strong></span>\n                </div>\n                <div>\n                    Orders are closed, better luck next time.\n                </div>\n            </div>\n            \"\"\",\n            margin=5,\n            sizing_mode=\"stretch_width\",\n            stylesheets=[config.panel.gui.css_files.no_more_orders_path],\n        )\n        # Alert for guest override\n        self.guest_override_alert = pn.pane.HTML(\n            \"\"\"\n            <div class=\"guest-override-flag\">\n                <div class=\"icon-container\">\n                    <svg class=\"flashing-animation\" xmlns=\"http://www.w3.org/2000/svg\" class=\"icon icon-tabler icon-tabler-radioactive-filled\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\"/>\n                        <path d=\"M21 11a1 1 0 0 1 1 1a10 10 0 0 1 -5 8.656a1 1 0 0 1 -1.302 -.268l-.064 -.098l-3 -5.19a.995 .995 0 0 1 -.133 -.542l.01 -.11l.023 -.106l.034 -.106l.046 -.1l.056 -.094l.067 -.089a.994 .994 0 0 1 .165 -.155l.098 -.064a2 2 0 0 0 .993 -1.57l.007 -.163a1 1 0 0 1 .883 -.994l.117 -.007h6z\" stroke-width=\"0\" fill=\"currentColor\" />\n                        <path d=\"M7 3.344a10 10 0 0 1 10 0a1 1 0 0 1 .418 1.262l-.052 .104l-3 5.19l-.064 .098a.994 .994 0 0 1 -.155 .165l-.089 .067a1 1 0 0 1 -.195 .102l-.105 .034l-.107 .022a1.003 1.003 0 0 1 -.547 -.07l-.104 -.052a2 2 0 0 0 -1.842 -.082l-.158 .082a1 1 0 0 1 -1.302 -.268l-.064 -.098l-3 -5.19a1 1 0 0 1 .366 -1.366z\" stroke-width=\"0\" fill=\"currentColor\" />\n                        <path d=\"M9 11a1 1 0 0 1 .993 .884l.007 .117a2 2 0 0 0 .861 1.645l.237 .152a.994 .994 0 0 1 .165 .155l.067 .089l.056 .095l.045 .099c.014 .036 .026 .07 .035 .106l.022 .107l.011 .11a.994 .994 0 0 1 -.08 .437l-.053 .104l-3 5.19a1 1 0 0 1 -1.366 .366a10 10 0 0 1 -5 -8.656a1 1 0 0 1 .883 -.993l.117 -.007h6z\" stroke-width=\"0\" fill=\"currentColor\" />\n                    </svg>\n                    <span><strong>Watch out! You are a guest now...</strong></span>\n                </div>\n                <div>\n                    Guest override is active.\n                </div>\n            </div>\n            \"\"\",\n            margin=5,\n            sizing_mode=\"stretch_width\",\n            stylesheets=[config.panel.gui.css_files.guest_override_path],\n        )\n        # Takeaway alert\n        self.takeaway_alert_sign = f\"<span {config.panel.gui.takeaway_alert_icon_options}>{config.panel.gui.takeaway_svg_icon}</span>\"\n        self.takeaway_alert_text = f\"<span {config.panel.gui.takeaway_alert_text_options}>{config.panel.gui.takeaway_id}</span> \"\n        # No menu image attribution\n        self.no_menu_image_attribution = pn.pane.HTML(\n            \"\"\"\n            <i>\n                Image by\n                <a\n                    href=\"https://www.freepik.com/free-vector/tiny-cooks-making-spaghetti-dinner-isolated-flat-illustration_11235909.htm\"\n                    referrerpolicy=\"no-referrer\"\n                    rel=\"external\"\n                    target=\"_blank\"\n                >\n                    pch.vector\n                </a>\n                on Freepik\n            </i>\n            \"\"\",\n            align=\"end\",\n            styles={\n                \"color\": \"darkgray\",\n                \"font-size\": \"10px\",\n                \"font-weight\": \"light\",\n            },\n        )\n\n        # WIDGETS\n        # JPG shown when no menu is available\n        self.no_menu_image = pn.pane.JPG(\n            config.panel.gui.no_menu_image_path, alt_text=\"no menu\"\n        )\n        # Create dataframe instance\n        self.dataframe = pnw.Tabulator(\n            name=\"Order\",\n            widths={config.panel.gui.note_column_name: 180},\n            selectable=False,\n            stylesheets=[config.panel.gui.css_files.custom_tabulator_path],\n        )\n\n        # BUTTONS\n        # Create refresh button\n        self.refresh_button = pnw.Button(\n            name=\"\",\n            button_style=\"outline\",\n            button_type=\"light\",\n            width=45,\n            height=generic_button_height,\n            icon=\"reload\",\n            icon_size=\"2em\",\n        )\n        # Create send button\n        self.send_order_button = pnw.Button(\n            name=\"Send Order\",\n            button_type=\"success\",\n            height=generic_button_height,\n            icon=\"circle-check-filled\",\n            icon_size=\"2em\",\n            sizing_mode=\"stretch_width\",\n        )\n        # Create toggle button that stop orders (used in time column)\n        # Initialized to False, but checked on app creation\n        self.toggle_no_more_order_button = pnw.Toggle(\n            name=\"Stop Orders\",\n            button_style=\"outline\",\n            button_type=\"warning\",\n            height=generic_button_height,\n            icon=\"hand-stop\",\n            icon_size=\"2em\",\n            sizing_mode=\"stretch_width\",\n        )\n        # Create change time\n        self.change_order_time_takeaway_button = pnw.Button(\n            name=\"Change Time/Takeaway\",\n            button_type=\"primary\",\n            button_style=\"outline\",\n            height=generic_button_height,\n            icon=\"clock-edit\",\n            icon_size=\"2em\",\n            sizing_mode=\"stretch_width\",\n        )\n        # Create delete order\n        self.delete_order_button = pnw.Button(\n            name=\"Delete Order\",\n            button_type=\"danger\",\n            height=generic_button_height,\n            icon=\"trash-filled\",\n            icon_size=\"2em\",\n            sizing_mode=\"stretch_width\",\n        )\n\n        # ROWS\n        self.main_header_row = pn.Row(\n            \"# Menu\",\n            pn.layout.HSpacer(),\n            self.refresh_button,\n        )\n\n        # COLUMNS\n        # Create column shown when no menu is available\n        self.no_menu_col = pn.Column(\n            self.no_menu_image,\n            self.no_menu_image_attribution,\n            sizing_mode=\"stretch_width\",\n            min_width=main_area_min_width,\n        )\n        # Create column for lunch time labels\n        self.time_col = pn.Column(width=time_col_width)\n        # Create column for resulting menus\n        self.res_col = pn.Column(\n            sizing_mode=\"stretch_width\", min_width=main_area_min_width\n        )\n\n        # FLEXBOXES\n        self.menu_flexbox = pn.FlexBox(\n            *[\n                self.dataframe,\n                pn.Spacer(width=time_col_spacer_width),\n                self.time_col,\n            ],\n            min_width=main_area_min_width,\n        )\n        self.buttons_flexbox = pn.FlexBox(\n            *[\n                self.send_order_button,\n                self.toggle_no_more_order_button,\n                self.change_order_time_takeaway_button,\n                self.delete_order_button,\n            ],\n            flex_wrap=\"nowrap\",\n            min_width=main_area_min_width,\n            sizing_mode=\"stretch_width\",\n        )\n        self.results_divider = pn.layout.Divider(\n            sizing_mode=\"stretch_width\", min_width=main_area_min_width\n        )\n\n        # CALLBACKS\n        # Callback on every \"toggle\" action\n        @pn.depends(self.toggle_no_more_order_button, watch=True)\n        def reload_on_no_more_order_callback(\n            toggle: pnw.Toggle, reload: bool = True\n        ):\n            # Update global variable\n            models.set_flag(config=config, id=\"no_more_orders\", value=toggle)\n\n            # Show \"no more order\" text\n            self.no_more_order_alert.visible = toggle\n\n            # Deactivate send, delete and change order buttons\n            self.send_order_button.disabled = toggle\n            self.delete_order_button.disabled = toggle\n            self.change_order_time_takeaway_button.disabled = toggle\n\n            # Simply reload the menu when the toggle button value changes\n            if reload:\n                waiter.reload_menu(\n                    None,\n                    self,\n                )\n\n        # Add callback to attribute\n        self.reload_on_no_more_order = reload_on_no_more_order_callback\n\n        # Refresh button callback\n        self.refresh_button.on_click(\n            lambda e: waiter.reload_menu(\n                e,\n                self,\n            )\n        )\n        # Send order button callback\n        self.send_order_button.on_click(\n            lambda e: waiter.send_order(\n                e,\n                app,\n                person,\n                self,\n            )\n        )\n        # Delete order button callback\n        self.delete_order_button.on_click(\n            lambda e: waiter.delete_order(\n                e,\n                app,\n                self,\n            )\n        )\n        # Change order time button callback\n        self.change_order_time_takeaway_button.on_click(\n            lambda e: waiter.change_order_time_takeaway(\n                e,\n                person,\n                self,\n            )\n        )\n\n        # MODAL WINDOW --------------------------------------------------------\n        # Error message\n        self.error_message = pn.pane.HTML(\n            styles={\"color\": \"red\", \"font-weight\": \"bold\"},\n            sizing_mode=\"stretch_width\",\n        )\n        self.error_message.visible = False\n\n        # SIDEBAR -------------------------------------------------------------\n        # TEXTS\n        # Foldable additional item details dropdown menu\n        jinja_template = jinja2.Environment(\n            loader=jinja2.BaseLoader\n        ).from_string(config.panel.gui.additional_item_details_template)\n        self.additional_items_details = pn.pane.HTML(\n            jinja_template.render(\n                items=config.panel.additional_items_to_concat\n            ),\n            width=sidebar_content_width,\n        )\n\n        # WIDGET\n        # Person data\n        self.person_widget = pn.Param(\n            person.param,\n            widgets={\n                \"guest\": pnw.RadioButtonGroup(\n                    options=OmegaConf.to_container(\n                        config.panel.guest_types, resolve=True\n                    ),\n                    button_type=\"primary\",\n                    button_style=\"outline\",\n                ),\n                \"username\": pnw.TextInput(\n                    value=person.username,\n                    value_input=person.username,\n                    description=person.param.username.doc,\n                ),\n            },\n            width=sidebar_content_width,\n        )\n        # File upload\n        self.file_widget = pnw.FileInput(\n            accept=\".png,.jpg,.jpeg,.xlsx\", sizing_mode=\"stretch_width\"\n        )\n        # Stats table\n        # Create stats table (non-editable)\n        self.stats_widget = pnw.Tabulator(\n            name=\"Statistics\",\n            hidden_columns=[\"index\"],\n            width=sidebar_content_width - 20,\n            layout=\"fit_columns\",\n            stylesheets=[\n                config.panel.gui.css_files.custom_tabulator_path,\n                config.panel.gui.css_files.stats_tabulator_path,\n            ],\n        )\n        # Password renewer\n        self.password_widget = pn.Param(\n            PasswordRenewer().param,\n            widgets={\n                \"old_password\": pnw.PasswordInput(\n                    name=\"Old password\", placeholder=\"Old Password\"\n                ),\n                \"new_password\": pnw.PasswordInput(\n                    name=\"New password\", placeholder=\"New Password\"\n                ),\n                \"repeat_new_password\": pnw.PasswordInput(\n                    name=\"Repeat new password\",\n                    placeholder=\"Repeat New Password\",\n                ),\n            },\n            name=\"Change password\",\n            width=sidebar_content_width,\n        )\n        # Guest password text\n        self.guest_username_widget = pnw.TextInput(\n            name=\"Username\",\n            placeholder=\"If empty reload this page.\",\n            value=\"guest\",\n        )\n        self.guest_password_widget = pnw.PasswordInput(\n            name=\"Password\",\n            placeholder=\"If empty reload this page.\",\n            value=guest_password,\n        )\n        # Turn off guest user if no password is set (empty string)\n        if not guest_password:\n            self.guest_username_widget.value = \"\"\n            self.guest_username_widget.disabled = True\n            self.guest_username_widget.placeholder = \"NOT ACTIVE\"\n            self.guest_password_widget.value = \"\"\n            self.guest_password_widget.disabled = True\n            self.guest_password_widget.placeholder = \"NOT ACTIVE\"\n\n        # BUTTONS\n        # Create menu button\n        self.build_menu_button = pnw.Button(\n            name=\"Build Menu\",\n            button_type=\"primary\",\n            sizing_mode=\"stretch_width\",\n            icon=\"tools-kitchen-2\",\n            icon_size=\"2em\",\n        )\n        # Download button and callback\n        self.download_button = pn.widgets.FileDownload(\n            callback=lambda: waiter.download_dataframe(self),\n            filename=config.panel.file_name + \".xlsx\",\n            sizing_mode=\"stretch_width\",\n            icon=\"download\",\n            icon_size=\"2em\",\n        )\n        # Password button\n        self.submit_password_button = pnw.Button(\n            name=\"Submit\",\n            button_type=\"success\",\n            button_style=\"outline\",\n            height=generic_button_height,\n            icon=\"key\",\n            icon_size=\"2em\",\n            sizing_mode=\"stretch_width\",\n        )\n\n        # COLUMNS\n        # Create column for person data\n        self.sidebar_person_column = pn.Column(\n            person_text,\n            self.person_widget,\n            pn.Spacer(height=5),\n            self.additional_items_details,\n            name=\"User\",\n            width=sidebar_content_width,\n        )\n        # Leave an empty widget for the 'other info' section\n        self.sidebar_person_column.append(\n            pn.pane.HTML(),\n        )\n\n        # Create column for uploading image/Excel with the menu\n        self.sidebar_menu_upload_col = pn.Column(\n            upload_text,\n            self.file_widget,\n            self.build_menu_button,\n            name=\"Menu Upload\",\n            width=sidebar_content_width,\n        )\n        # Create column for downloading Excel with orders\n        self.sidebar_download_orders_col = pn.Column(\n            download_text,\n            self.download_button,\n            name=\"Download Orders\",\n            width=sidebar_content_width,\n        )\n        # Create column for statistics\n        self.sidebar_stats_col = pn.Column(\n            name=\"Stats\", width=sidebar_content_width\n        )\n\n        self.sidebar_password = pn.Column(\n            config.panel.gui.psw_text,\n            self.password_widget,\n            self.submit_password_button,\n            pn.Spacer(height=5),\n            pn.layout.Divider(),\n            guest_user_text,\n            self.guest_username_widget,\n            self.guest_password_widget,\n            name=\"Password\",\n            width=sidebar_content_width,\n        )\n\n        # TABS\n        # The person widget is defined in the app factory function because\n        # lunch times are configurable\n        self.sidebar_tabs = pn.Tabs(\n            width=sidebar_content_width,\n        )\n        # Reload tabs according to auth.is_guest results and guest_override\n        # flag (no need to cleans, tabs are already empty)\n        self.load_sidebar_tabs(config=config, clear_before_loading=False)\n\n        # CALLBACKS\n        # Build menu button callback\n        self.build_menu_button.on_click(\n            lambda e: waiter.build_menu(\n                e,\n                app,\n                self,\n            )\n        )\n        # Submit password button callback\n        self.submit_password_button.on_click(\n            lambda e: auth.submit_password(gi=self, config=config)\n        )\n\n    # UTILITY FUNCTIONS\n    # MAIN SECTION\n    def build_order_table(\n        self,\n        config: DictConfig,\n        df: pd.DataFrame,\n        time: str,\n        guests_lists: dict = {},\n    ) -> pnw.Tabulator:\n        \"\"\"Build `Tabulator` object to display placed orders.\n\n        Args:\n            config (DictConfig): Hydra configuration dictionary.\n            df (pd.DataFrame): Table with orders. It has columns for each user that placed an order, total and a note columns.\n            time (str): Lunch time.\n            guests_lists (dict, optional): Dictionary with lists of users dived by guest type.\n                Keys of the dictionary are the type of guest listed.\n                Defaults to empty dictionary (`{}`).\n\n        Returns:\n            pnw.Tabulator: Panel `Tabulator` object representing placed orders.\n        \"\"\"\n        # Add guest icon to users' id\n        columns_with_guests_icons = df.columns.to_series()\n        for guest_type, guests_list in guests_lists.items():\n            columns_with_guests_icons[\n                columns_with_guests_icons.isin(guests_list)\n            ] += f\" {config.panel.gui.guest_icons[guest_type]}\"\n        df.columns = columns_with_guests_icons.to_list()\n        # Create table widget\n        orders_table_widget = pnw.Tabulator(\n            name=time,\n            value=df,\n            frozen_columns=[0],\n            layout=\"fit_data_table\",\n            stylesheets=[config.panel.gui.css_files.custom_tabulator_path],\n        )\n        # Make the table non-editable\n        orders_table_widget.editors = {c: None for c in df.columns}\n        return orders_table_widget\n\n    def build_time_label(\n        self,\n        time: str,\n        diners_n: str,\n        separator: str = \" &#10072; \",\n        emoji: str = \"&#127829;\",\n        per_icon: str = \" &#10006; \",\n        is_takeaway: bool = False,\n        takeaway_alert_sign: str = \"TAKEAWAY\",\n        css_classes: list = [],\n        stylesheets: list = [],\n        **kwargs,\n    ) -> pn.pane.HTML:\n        \"\"\"Build HTML field to display the time label.\n\n        This function is used to display labels that summarize an order.\n\n        Those are shown on the side of the menu table as well as labels above each order table.\n\n        Args:\n            time (str): Lunch time.\n            diners_n (str): Number of people that placed an order.\n            separator (str, optional): Separator between lunch time and order data. Defaults to \" &#10072; \".\n            emoji (str, optional): Emoji used as number lunch symbol. Defaults to \"&#127829;\".\n            per_icon (str, optional): icon used between the lunch emoji and the number of people that placed an order.\n                Usually a multiply operator.\n                Defaults to \" &#10006; \".\n            is_takeaway (bool, optional): takeaway flag (true if the order is to takeaway). Defaults to False.\n            takeaway_alert_sign (str, optional): warning text to highlight that the order is to takeaway. Defaults to \"TAKEAWAY\".\n            css_classes (list, optional): CSS classes to assign to the resulting HTML pane. Defaults to [].\n            stylesheets (list, optional): Stylesheets to assign to the resulting HTML pane\n                (see `Panel docs <https://panel.holoviz.org/how_to/styling/apply_css.html>`__). Defaults to [].\n\n        Returns:\n            pn.pane.HTML: HTML pane representing a label with order summary.\n        \"\"\"\n        # If takeaway add alert sign\n        if is_takeaway:\n            takeaway = f\"{separator}{takeaway_alert_sign}\"\n        else:\n            takeaway = \"\"\n        # Time label pane\n        classes_str = \" \".join(css_classes)\n        time_label = pn.pane.HTML(\n            f'<span class=\"{classes_str}\">{time}{separator}{emoji}{per_icon}{diners_n}{takeaway}</span>',\n            stylesheets=stylesheets,\n            **kwargs,\n        )\n\n        return time_label\n\n    # SIDEBAR SECTION\n    def load_sidebar_tabs(\n        self, config: DictConfig, clear_before_loading: bool = True\n    ) -> None:\n        \"\"\"Append tabs to the app template sidebar.\n\n        The flag `clear_before_loading` is set to true only during first instantiation, because the sidebar is empty at first.\n        Use the default value during normal operation to avoid tabs duplication.\n\n        Args:\n            config (DictConfig): Hydra configuration dictionary.\n            clear_before_loading (bool, optional): Set to true to remove all tabs before appending the new ones. Defaults to True.\n        \"\"\"\n        # Clean tabs\n        if clear_before_loading:\n            self.sidebar_tabs.clear()\n        # Append User tab\n        self.sidebar_tabs.append(self.sidebar_person_column)\n        # Append upload, download and stats only for non-guest\n        # Append password only for non-guest users if auth is active\n        if not auth.is_guest(\n            user=pn_user(config), config=config, allow_override=False\n        ):\n            self.sidebar_tabs.append(self.sidebar_menu_upload_col)\n            self.sidebar_tabs.append(self.sidebar_download_orders_col)\n            self.sidebar_tabs.append(self.sidebar_stats_col)\n            if auth.is_basic_auth_active(config=config):\n                self.sidebar_tabs.append(self.sidebar_password)\n\n    def build_stats_and_info_text(\n        self,\n        config: DictConfig,\n        df_stats: pd.DataFrame,\n        user: str,\n        version: str,\n        host_name: str,\n        stylesheets: list = [],\n    ) -> dict:\n        \"\"\"Build text used for statistics under the `stats` tab, and info under the `user` tab.\n\n        This functions needs Data-Lunch version and the name of the hosting machine to populate the info section.\n\n        Args:\n            config (DictConfig): Hydra configuration dictionary.\n            df_stats (pd.DataFrame): dataframe with statistics.\n            user (str): username.\n            version (str): Data-Lunch version.\n            host_name (str): host name.\n            stylesheets (list, optional): Stylesheets to assign to the resulting HTML pane\n                (see `Panel docs <https://panel.holoviz.org/how_to/styling/apply_css.html>`__). Defaults to [].\n\n        Returns:\n            dict: _description_\n        \"\"\"\n        # Stats top text\n        stats = pn.pane.HTML(\n            f\"\"\"\n            <h3>Statistics</h3>\n            <div>\n                Grumbling stomachs fed:<br>\n                <span id=\"stats-locals\">Locals&nbsp;&nbsp;{df_stats[df_stats[\"Guest\"] == \"NotAGuest\"]['Hungry People'].sum()}</span><br>\n                <span id=\"stats-guests\">Guests&nbsp;&nbsp;{df_stats[df_stats[\"Guest\"] != \"NotAGuest\"]['Hungry People'].sum()}</span><br>\n                =================<br>\n                <strong>TOTAL&nbsp;&nbsp;{df_stats['Hungry People'].sum()}</strong><br>\n                <br>\n            </div>\n            <div>\n                <i>See the table for details</i>\n            </div>\n            \"\"\",\n            stylesheets=stylesheets,\n        )\n        # Define user group\n        if auth.is_guest(user=user, config=config, allow_override=False):\n            user_group = \"guest\"\n        elif auth.is_admin(user=user, config=config):\n            user_group = \"admin\"\n        else:\n            user_group = \"user\"\n        # Other info\n        other_info = pn.pane.HTML(\n            f\"\"\"\n            <details>\n                <summary><strong>Other Info</strong></summary>\n                <div class=\"icon-container\">\n                    <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"icon icon-tabler icon-tabler-user-square\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\"/>\n                        <path d=\"M9 10a3 3 0 1 0 6 0a3 3 0 0 0 -6 0\" />\n                        <path d=\"M6 21v-1a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v1\" />\n                        <path d=\"M3 5a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v14a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2v-14z\" />\n                    </svg>\n                    <span>\n                        <strong>User:</strong> <i>{user}</i>\n                    </span>\n                </div>\n                <div class=\"icon-container\">\n                    <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"icon icon-tabler icon-tabler-users-group\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\"/>\n                        <path d=\"M10 13a2 2 0 1 0 4 0a2 2 0 0 0 -4 0\" />\n                        <path d=\"M8 21v-1a2 2 0 0 1 2 -2h4a2 2 0 0 1 2 2v1\" />\n                        <path d=\"M15 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0\" />\n                        <path d=\"M17 10h2a2 2 0 0 1 2 2v1\" />\n                        <path d=\"M5 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0\" />\n                        <path d=\"M3 13v-1a2 2 0 0 1 2 -2h2\" />\n                    </svg>\n                    <span>\n                        <strong>Group:</strong> <i>{user_group}</i>\n                    </span>\n                </div>\n                <div class=\"icon-container\">\n                    <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"icon icon-tabler icon-tabler-pizza\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\"/>\n                        <path d=\"M12 21.5c-3.04 0 -5.952 -.714 -8.5 -1.983l8.5 -16.517l8.5 16.517a19.09 19.09 0 0 1 -8.5 1.983z\" />\n                        <path d=\"M5.38 15.866a14.94 14.94 0 0 0 6.815 1.634a14.944 14.944 0 0 0 6.502 -1.479\" />\n                        <path d=\"M13 11.01v-.01\" />\n                        <path d=\"M11 14v-.01\" />\n                    </svg>\n                    <span>\n                        <strong>Data-Lunch:</strong> <i>v{version}</i>\n                    </span>\n                </div>\n                <div class=\"icon-container\">\n                    <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"icon icon-tabler icon-tabler-cpu\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n                        <path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\"></path>\n                        <path d=\"M5 5m0 1a1 1 0 0 1 1 -1h12a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-12a1 1 0 0 1 -1 -1z\"></path>\n                        <path d=\"M9 9h6v6h-6z\"></path>\n                        <path d=\"M3 10h2\"></path>\n                        <path d=\"M3 14h2\"></path>\n                        <path d=\"M10 3v2\"></path>\n                        <path d=\"M14 3v2\"></path>\n                        <path d=\"M21 10h-2\"></path>\n                        <path d=\"M21 14h-2\"></path>\n                        <path d=\"M14 21v-2\"></path>\n                        <path d=\"M10 21v-2\"></path>\n                    </svg>\n                    <span>\n                        <strong>Host:</strong> <i>{host_name}</i>\n                    </span>\n                </div>\n            </details>\n            \"\"\",\n            sizing_mode=\"stretch_width\",\n            stylesheets=stylesheets,\n        )\n\n        return {\"stats\": stats, \"info\": other_info}\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.additional_items_details","title":"additional_items_details instance-attribute","text":"
additional_items_details = HTML(\n    render(items=additional_items_to_concat),\n    width=sidebar_content_width,\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.backend_button","title":"backend_button instance-attribute","text":"
backend_button = Button(\n    name=\"\",\n    button_type=\"primary\",\n    button_style=\"solid\",\n    width=header_button_width,\n    height=generic_button_height,\n    icon=\"adjustments\",\n    icon_size=\"2em\",\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.build_menu_button","title":"build_menu_button instance-attribute","text":"
build_menu_button = Button(\n    name=\"Build Menu\",\n    button_type=\"primary\",\n    sizing_mode=\"stretch_width\",\n    icon=\"tools-kitchen-2\",\n    icon_size=\"2em\",\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.buttons_flexbox","title":"buttons_flexbox instance-attribute","text":"
buttons_flexbox = FlexBox(\n    *[\n        send_order_button,\n        toggle_no_more_order_button,\n        change_order_time_takeaway_button,\n        delete_order_button,\n    ],\n    flex_wrap=\"nowrap\",\n    min_width=main_area_min_width,\n    sizing_mode=\"stretch_width\"\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.change_order_time_takeaway_button","title":"change_order_time_takeaway_button instance-attribute","text":"
change_order_time_takeaway_button = Button(\n    name=\"Change Time/Takeaway\",\n    button_type=\"primary\",\n    button_style=\"outline\",\n    height=generic_button_height,\n    icon=\"clock-edit\",\n    icon_size=\"2em\",\n    sizing_mode=\"stretch_width\",\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.dataframe","title":"dataframe instance-attribute","text":"
dataframe = Tabulator(\n    name=\"Order\",\n    widths={note_column_name: 180},\n    selectable=False,\n    stylesheets=[custom_tabulator_path],\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.delete_order_button","title":"delete_order_button instance-attribute","text":"
delete_order_button = Button(\n    name=\"Delete Order\",\n    button_type=\"danger\",\n    height=generic_button_height,\n    icon=\"trash-filled\",\n    icon_size=\"2em\",\n    sizing_mode=\"stretch_width\",\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.download_button","title":"download_button instance-attribute","text":"
download_button = FileDownload(\n    callback=lambda: download_dataframe(self),\n    filename=file_name + \".xlsx\",\n    sizing_mode=\"stretch_width\",\n    icon=\"download\",\n    icon_size=\"2em\",\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.error_message","title":"error_message instance-attribute","text":"
error_message = HTML(\n    styles={\"color\": \"red\", \"font-weight\": \"bold\"},\n    sizing_mode=\"stretch_width\",\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.file_widget","title":"file_widget instance-attribute","text":"
file_widget = FileInput(\n    accept=\".png,.jpg,.jpeg,.xlsx\",\n    sizing_mode=\"stretch_width\",\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.guest_override_alert","title":"guest_override_alert instance-attribute","text":"
guest_override_alert = HTML(\n    '\\n            <div class=\"guest-override-flag\">\\n                <div class=\"icon-container\">\\n                    <svg class=\"flashing-animation\" xmlns=\"http://www.w3.org/2000/svg\" class=\"icon icon-tabler icon-tabler-radioactive-filled\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\"/>\\n                        <path d=\"M21 11a1 1 0 0 1 1 1a10 10 0 0 1 -5 8.656a1 1 0 0 1 -1.302 -.268l-.064 -.098l-3 -5.19a.995 .995 0 0 1 -.133 -.542l.01 -.11l.023 -.106l.034 -.106l.046 -.1l.056 -.094l.067 -.089a.994 .994 0 0 1 .165 -.155l.098 -.064a2 2 0 0 0 .993 -1.57l.007 -.163a1 1 0 0 1 .883 -.994l.117 -.007h6z\" stroke-width=\"0\" fill=\"currentColor\" />\\n                        <path d=\"M7 3.344a10 10 0 0 1 10 0a1 1 0 0 1 .418 1.262l-.052 .104l-3 5.19l-.064 .098a.994 .994 0 0 1 -.155 .165l-.089 .067a1 1 0 0 1 -.195 .102l-.105 .034l-.107 .022a1.003 1.003 0 0 1 -.547 -.07l-.104 -.052a2 2 0 0 0 -1.842 -.082l-.158 .082a1 1 0 0 1 -1.302 -.268l-.064 -.098l-3 -5.19a1 1 0 0 1 .366 -1.366z\" stroke-width=\"0\" fill=\"currentColor\" />\\n                        <path d=\"M9 11a1 1 0 0 1 .993 .884l.007 .117a2 2 0 0 0 .861 1.645l.237 .152a.994 .994 0 0 1 .165 .155l.067 .089l.056 .095l.045 .099c.014 .036 .026 .07 .035 .106l.022 .107l.011 .11a.994 .994 0 0 1 -.08 .437l-.053 .104l-3 5.19a1 1 0 0 1 -1.366 .366a10 10 0 0 1 -5 -8.656a1 1 0 0 1 .883 -.993l.117 -.007h6z\" stroke-width=\"0\" fill=\"currentColor\" />\\n                    </svg>\\n                    <span><strong>Watch out! You are a guest now...</strong></span>\\n                </div>\\n                <div>\\n                    Guest override is active.\\n                </div>\\n            </div>\\n            ',\n    margin=5,\n    sizing_mode=\"stretch_width\",\n    stylesheets=[guest_override_path],\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.guest_password_widget","title":"guest_password_widget instance-attribute","text":"
guest_password_widget = PasswordInput(\n    name=\"Password\",\n    placeholder=\"If empty reload this page.\",\n    value=guest_password,\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.guest_username_widget","title":"guest_username_widget instance-attribute","text":"
guest_username_widget = TextInput(\n    name=\"Username\",\n    placeholder=\"If empty reload this page.\",\n    value=\"guest\",\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.header_object","title":"header_object instance-attribute","text":"
header_object = instantiate(header_object)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.header_row","title":"header_row instance-attribute","text":"
header_row = Row(\n    height=header_row_height, sizing_mode=\"stretch_width\"\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.logout_button","title":"logout_button instance-attribute","text":"
logout_button = Button(\n    name=\"\",\n    button_type=\"primary\",\n    button_style=\"solid\",\n    width=header_button_width,\n    height=generic_button_height,\n    icon=\"door-exit\",\n    icon_size=\"2em\",\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.main_header_row","title":"main_header_row instance-attribute","text":"
main_header_row = Row('# Menu', HSpacer(), refresh_button)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.menu_flexbox","title":"menu_flexbox instance-attribute","text":"
menu_flexbox = FlexBox(\n    *[\n        dataframe,\n        Spacer(width=time_col_spacer_width),\n        time_col,\n    ],\n    min_width=main_area_min_width\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.no_menu_col","title":"no_menu_col instance-attribute","text":"
no_menu_col = Column(\n    no_menu_image,\n    no_menu_image_attribution,\n    sizing_mode=\"stretch_width\",\n    min_width=main_area_min_width,\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.no_menu_image","title":"no_menu_image instance-attribute","text":"
no_menu_image = JPG(no_menu_image_path, alt_text='no menu')\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.no_menu_image_attribution","title":"no_menu_image_attribution instance-attribute","text":"
no_menu_image_attribution = HTML(\n    '\\n            <i>\\n                Image by\\n                <a\\n                    href=\"https://www.freepik.com/free-vector/tiny-cooks-making-spaghetti-dinner-isolated-flat-illustration_11235909.htm\"\\n                    referrerpolicy=\"no-referrer\"\\n                    rel=\"external\"\\n                    target=\"_blank\"\\n                >\\n                    pch.vector\\n                </a>\\n                on Freepik\\n            </i>\\n            ',\n    align=\"end\",\n    styles={\n        \"color\": \"darkgray\",\n        \"font-size\": \"10px\",\n        \"font-weight\": \"light\",\n    },\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.no_more_order_alert","title":"no_more_order_alert instance-attribute","text":"
no_more_order_alert = HTML(\n    '\\n            <div class=\"no-more-order-flag\">\\n                <div class=\"icon-container\">\\n                    <svg class=\"flashing-animation\" xmlns=\"http://www.w3.org/2000/svg\" class=\"icon icon-tabler icon-tabler-alert-circle-filled\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\\n                        <path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\"></path>\\n                        <path d=\"M12 2c5.523 0 10 4.477 10 10a10 10 0 0 1 -19.995 .324l-.005 -.324l.004 -.28c.148 -5.393 4.566 -9.72 9.996 -9.72zm.01 13l-.127 .007a1 1 0 0 0 0 1.986l.117 .007l.127 -.007a1 1 0 0 0 0 -1.986l-.117 -.007zm-.01 -8a1 1 0 0 0 -.993 .883l-.007 .117v4l.007 .117a1 1 0 0 0 1.986 0l.007 -.117v-4l-.007 -.117a1 1 0 0 0 -.993 -.883z\" stroke-width=\"0\" fill=\"currentColor\"></path>\\n                    </svg>\\n                    <span><strong>Oh no! You missed this train...</strong></span>\\n                </div>\\n                <div>\\n                    Orders are closed, better luck next time.\\n                </div>\\n            </div>\\n            ',\n    margin=5,\n    sizing_mode=\"stretch_width\",\n    stylesheets=[no_more_orders_path],\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.password_widget","title":"password_widget instance-attribute","text":"
password_widget = Param(\n    param,\n    widgets={\n        \"old_password\": PasswordInput(\n            name=\"Old password\", placeholder=\"Old Password\"\n        ),\n        \"new_password\": PasswordInput(\n            name=\"New password\", placeholder=\"New Password\"\n        ),\n        \"repeat_new_password\": PasswordInput(\n            name=\"Repeat new password\",\n            placeholder=\"Repeat New Password\",\n        ),\n    },\n    name=\"Change password\",\n    width=sidebar_content_width,\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.person_widget","title":"person_widget instance-attribute","text":"
person_widget = Param(\n    param,\n    widgets={\n        \"guest\": RadioButtonGroup(\n            options=to_container(guest_types, resolve=True),\n            button_type=\"primary\",\n            button_style=\"outline\",\n        ),\n        \"username\": TextInput(\n            value=username,\n            value_input=username,\n            description=doc,\n        ),\n    },\n    width=sidebar_content_width,\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.quote","title":"quote instance-attribute","text":"
quote = Markdown(f'\n            _{iloc[0]}_\n\n            **{iloc[0]}**\n            ')\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.refresh_button","title":"refresh_button instance-attribute","text":"
refresh_button = Button(\n    name=\"\",\n    button_style=\"outline\",\n    button_type=\"light\",\n    width=45,\n    height=generic_button_height,\n    icon=\"reload\",\n    icon_size=\"2em\",\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.reload_on_guest_override","title":"reload_on_guest_override instance-attribute","text":"
reload_on_guest_override = reload_on_guest_override_callback\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.reload_on_no_more_order","title":"reload_on_no_more_order instance-attribute","text":"
reload_on_no_more_order = reload_on_no_more_order_callback\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.res_col","title":"res_col instance-attribute","text":"
res_col = Column(\n    sizing_mode=\"stretch_width\",\n    min_width=main_area_min_width,\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.results_divider","title":"results_divider instance-attribute","text":"
results_divider = Divider(\n    sizing_mode=\"stretch_width\",\n    min_width=main_area_min_width,\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.send_order_button","title":"send_order_button instance-attribute","text":"
send_order_button = Button(\n    name=\"Send Order\",\n    button_type=\"success\",\n    height=generic_button_height,\n    icon=\"circle-check-filled\",\n    icon_size=\"2em\",\n    sizing_mode=\"stretch_width\",\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.sidebar_download_orders_col","title":"sidebar_download_orders_col instance-attribute","text":"
sidebar_download_orders_col = Column(\n    download_text,\n    download_button,\n    name=\"Download Orders\",\n    width=sidebar_content_width,\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.sidebar_menu_upload_col","title":"sidebar_menu_upload_col instance-attribute","text":"
sidebar_menu_upload_col = Column(\n    upload_text,\n    file_widget,\n    build_menu_button,\n    name=\"Menu Upload\",\n    width=sidebar_content_width,\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.sidebar_password","title":"sidebar_password instance-attribute","text":"
sidebar_password = Column(\n    psw_text,\n    password_widget,\n    submit_password_button,\n    Spacer(height=5),\n    Divider(),\n    guest_user_text,\n    guest_username_widget,\n    guest_password_widget,\n    name=\"Password\",\n    width=sidebar_content_width,\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.sidebar_person_column","title":"sidebar_person_column instance-attribute","text":"
sidebar_person_column = Column(\n    person_text,\n    person_widget,\n    Spacer(height=5),\n    additional_items_details,\n    name=\"User\",\n    width=sidebar_content_width,\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.sidebar_stats_col","title":"sidebar_stats_col instance-attribute","text":"
sidebar_stats_col = Column(\n    name=\"Stats\", width=sidebar_content_width\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.sidebar_tabs","title":"sidebar_tabs instance-attribute","text":"
sidebar_tabs = Tabs(width=sidebar_content_width)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.stats_widget","title":"stats_widget instance-attribute","text":"
stats_widget = Tabulator(\n    name=\"Statistics\",\n    hidden_columns=[\"index\"],\n    width=sidebar_content_width - 20,\n    layout=\"fit_columns\",\n    stylesheets=[\n        custom_tabulator_path,\n        stats_tabulator_path,\n    ],\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.submit_password_button","title":"submit_password_button instance-attribute","text":"
submit_password_button = Button(\n    name=\"Submit\",\n    button_type=\"success\",\n    button_style=\"outline\",\n    height=generic_button_height,\n    icon=\"key\",\n    icon_size=\"2em\",\n    sizing_mode=\"stretch_width\",\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.takeaway_alert_sign","title":"takeaway_alert_sign instance-attribute","text":"
takeaway_alert_sign = f\"<span {takeaway_alert_icon_options}>{takeaway_svg_icon}</span>\"\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.takeaway_alert_text","title":"takeaway_alert_text instance-attribute","text":"
takeaway_alert_text = f\"<span {takeaway_alert_text_options}>{takeaway_id}</span> \"\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.time_col","title":"time_col instance-attribute","text":"
time_col = Column(width=time_col_width)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.time_col_title","title":"time_col_title instance-attribute","text":"
time_col_title = Markdown(\n    time_column_text,\n    sizing_mode=\"stretch_width\",\n    styles={\"text-align\": \"center\"},\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.toggle_guest_override_button","title":"toggle_guest_override_button instance-attribute","text":"
toggle_guest_override_button = Toggle(\n    button_type=\"primary\",\n    button_style=\"solid\",\n    width=header_button_width,\n    height=generic_button_height,\n    icon=\"user-bolt\",\n    icon_size=\"2em\",\n    stylesheets=[guest_override_path],\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.toggle_no_more_order_button","title":"toggle_no_more_order_button instance-attribute","text":"
toggle_no_more_order_button = Toggle(\n    name=\"Stop Orders\",\n    button_style=\"outline\",\n    button_type=\"warning\",\n    height=generic_button_height,\n    icon=\"hand-stop\",\n    icon_size=\"2em\",\n    sizing_mode=\"stretch_width\",\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.__init__","title":"__init__","text":"
__init__(\n    config: DictConfig,\n    waiter: Waiter,\n    app: Template,\n    person: Person,\n    guest_password: str = \"\",\n)\n
Source code in dlunch/gui.py
def __init__(\n    self,\n    config: DictConfig,\n    waiter: core.Waiter,\n    app: pn.Template,\n    person: Person,\n    guest_password: str = \"\",\n):\n    # HEADER SECTION ------------------------------------------------------\n    # WIDGET\n    # Create PNG pane with app icon\n    self.header_object = instantiate(config.panel.gui.header_object)\n\n    # BUTTONS\n    # Backend button\n    self.backend_button = pnw.Button(\n        name=\"\",\n        button_type=\"primary\",\n        button_style=\"solid\",\n        width=header_button_width,\n        height=generic_button_height,\n        icon=\"adjustments\",\n        icon_size=\"2em\",\n    )\n    # Guest override toggle button (if pressed the user act as a guest)\n    self.toggle_guest_override_button = pnw.Toggle(\n        button_type=\"primary\",\n        button_style=\"solid\",\n        width=header_button_width,\n        height=generic_button_height,\n        icon=\"user-bolt\",\n        icon_size=\"2em\",\n        stylesheets=[config.panel.gui.css_files.guest_override_path],\n    )\n    # Logout button\n    self.logout_button = pnw.Button(\n        name=\"\",\n        button_type=\"primary\",\n        button_style=\"solid\",\n        width=header_button_width,\n        height=generic_button_height,\n        icon=\"door-exit\",\n        icon_size=\"2em\",\n    )\n\n    # ROW\n    # Create column for person data (add logout button only if auth is active)\n    self.header_row = pn.Row(\n        height=header_row_height,\n        sizing_mode=\"stretch_width\",\n    )\n    # Append a graphic element to the left side of header\n    if config.panel.gui.header_object:\n        self.header_row.append(self.header_object)\n    # Append a controls to the right side of header\n    if auth.is_auth_active(config=config):\n        self.header_row.append(pn.HSpacer())\n        # Backend only for admin\n        if auth.is_admin(user=pn_user(config), config=config):\n            self.header_row.append(self.backend_button)\n        # Guest override only for non guests\n        if not auth.is_guest(\n            user=pn_user(config), config=config, allow_override=False\n        ):\n            self.header_row.append(self.toggle_guest_override_button)\n        self.header_row.append(self.logout_button)\n        self.header_row.append(\n            pn.pane.HTML(\n                styles=dict(background=\"white\"), width=2, height=45\n            )\n        )\n\n    # CALLBACKS\n    # Backend callback\n    self.backend_button.on_click(lambda e: auth.open_backend())\n\n    # Guest override callback\n    @pn.depends(self.toggle_guest_override_button, watch=True)\n    def reload_on_guest_override_callback(\n        toggle: pnw.ToggleIcon, reload: bool = True\n    ):\n        # Update global variable that control guest override\n        # Only non guest can store this value in 'flags' table (guest users\n        # are always guests, there is no use in sotring a flag for them)\n        if not auth.is_guest(\n            user=pn_user(config), config=config, allow_override=False\n        ):\n            models.set_flag(\n                config=config,\n                id=f\"{pn_user(config)}_guest_override\",\n                value=toggle,\n            )\n        # Show banner if override is active\n        self.guest_override_alert.visible = toggle\n        # Simply reload the menu when the toggle button value changes\n        if reload:\n            waiter.reload_menu(\n                None,\n                self,\n            )\n\n    # Add callback to attribute\n    self.reload_on_guest_override = reload_on_guest_override_callback\n\n    # Logout callback\n    self.logout_button.on_click(lambda e: auth.force_logout())\n\n    # MAIN SECTION --------------------------------------------------------\n    # Elements required for build the main section of the web app\n\n    # TEXTS\n    # Quote of the day\n    self.quote = pn.pane.Markdown(\n        f\"\"\"\n        _{df_quote.quote.iloc[0]}_\n\n        **{df_quote.author.iloc[0]}**\n        \"\"\"\n    )\n    # Time column title\n    self.time_col_title = pn.pane.Markdown(\n        config.panel.time_column_text,\n        sizing_mode=\"stretch_width\",\n        styles={\"text-align\": \"center\"},\n    )\n    # \"no more order\" message\n    self.no_more_order_alert = pn.pane.HTML(\n        \"\"\"\n        <div class=\"no-more-order-flag\">\n            <div class=\"icon-container\">\n                <svg class=\"flashing-animation\" xmlns=\"http://www.w3.org/2000/svg\" class=\"icon icon-tabler icon-tabler-alert-circle-filled\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n                    <path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\"></path>\n                    <path d=\"M12 2c5.523 0 10 4.477 10 10a10 10 0 0 1 -19.995 .324l-.005 -.324l.004 -.28c.148 -5.393 4.566 -9.72 9.996 -9.72zm.01 13l-.127 .007a1 1 0 0 0 0 1.986l.117 .007l.127 -.007a1 1 0 0 0 0 -1.986l-.117 -.007zm-.01 -8a1 1 0 0 0 -.993 .883l-.007 .117v4l.007 .117a1 1 0 0 0 1.986 0l.007 -.117v-4l-.007 -.117a1 1 0 0 0 -.993 -.883z\" stroke-width=\"0\" fill=\"currentColor\"></path>\n                </svg>\n                <span><strong>Oh no! You missed this train...</strong></span>\n            </div>\n            <div>\n                Orders are closed, better luck next time.\n            </div>\n        </div>\n        \"\"\",\n        margin=5,\n        sizing_mode=\"stretch_width\",\n        stylesheets=[config.panel.gui.css_files.no_more_orders_path],\n    )\n    # Alert for guest override\n    self.guest_override_alert = pn.pane.HTML(\n        \"\"\"\n        <div class=\"guest-override-flag\">\n            <div class=\"icon-container\">\n                <svg class=\"flashing-animation\" xmlns=\"http://www.w3.org/2000/svg\" class=\"icon icon-tabler icon-tabler-radioactive-filled\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\"/>\n                    <path d=\"M21 11a1 1 0 0 1 1 1a10 10 0 0 1 -5 8.656a1 1 0 0 1 -1.302 -.268l-.064 -.098l-3 -5.19a.995 .995 0 0 1 -.133 -.542l.01 -.11l.023 -.106l.034 -.106l.046 -.1l.056 -.094l.067 -.089a.994 .994 0 0 1 .165 -.155l.098 -.064a2 2 0 0 0 .993 -1.57l.007 -.163a1 1 0 0 1 .883 -.994l.117 -.007h6z\" stroke-width=\"0\" fill=\"currentColor\" />\n                    <path d=\"M7 3.344a10 10 0 0 1 10 0a1 1 0 0 1 .418 1.262l-.052 .104l-3 5.19l-.064 .098a.994 .994 0 0 1 -.155 .165l-.089 .067a1 1 0 0 1 -.195 .102l-.105 .034l-.107 .022a1.003 1.003 0 0 1 -.547 -.07l-.104 -.052a2 2 0 0 0 -1.842 -.082l-.158 .082a1 1 0 0 1 -1.302 -.268l-.064 -.098l-3 -5.19a1 1 0 0 1 .366 -1.366z\" stroke-width=\"0\" fill=\"currentColor\" />\n                    <path d=\"M9 11a1 1 0 0 1 .993 .884l.007 .117a2 2 0 0 0 .861 1.645l.237 .152a.994 .994 0 0 1 .165 .155l.067 .089l.056 .095l.045 .099c.014 .036 .026 .07 .035 .106l.022 .107l.011 .11a.994 .994 0 0 1 -.08 .437l-.053 .104l-3 5.19a1 1 0 0 1 -1.366 .366a10 10 0 0 1 -5 -8.656a1 1 0 0 1 .883 -.993l.117 -.007h6z\" stroke-width=\"0\" fill=\"currentColor\" />\n                </svg>\n                <span><strong>Watch out! You are a guest now...</strong></span>\n            </div>\n            <div>\n                Guest override is active.\n            </div>\n        </div>\n        \"\"\",\n        margin=5,\n        sizing_mode=\"stretch_width\",\n        stylesheets=[config.panel.gui.css_files.guest_override_path],\n    )\n    # Takeaway alert\n    self.takeaway_alert_sign = f\"<span {config.panel.gui.takeaway_alert_icon_options}>{config.panel.gui.takeaway_svg_icon}</span>\"\n    self.takeaway_alert_text = f\"<span {config.panel.gui.takeaway_alert_text_options}>{config.panel.gui.takeaway_id}</span> \"\n    # No menu image attribution\n    self.no_menu_image_attribution = pn.pane.HTML(\n        \"\"\"\n        <i>\n            Image by\n            <a\n                href=\"https://www.freepik.com/free-vector/tiny-cooks-making-spaghetti-dinner-isolated-flat-illustration_11235909.htm\"\n                referrerpolicy=\"no-referrer\"\n                rel=\"external\"\n                target=\"_blank\"\n            >\n                pch.vector\n            </a>\n            on Freepik\n        </i>\n        \"\"\",\n        align=\"end\",\n        styles={\n            \"color\": \"darkgray\",\n            \"font-size\": \"10px\",\n            \"font-weight\": \"light\",\n        },\n    )\n\n    # WIDGETS\n    # JPG shown when no menu is available\n    self.no_menu_image = pn.pane.JPG(\n        config.panel.gui.no_menu_image_path, alt_text=\"no menu\"\n    )\n    # Create dataframe instance\n    self.dataframe = pnw.Tabulator(\n        name=\"Order\",\n        widths={config.panel.gui.note_column_name: 180},\n        selectable=False,\n        stylesheets=[config.panel.gui.css_files.custom_tabulator_path],\n    )\n\n    # BUTTONS\n    # Create refresh button\n    self.refresh_button = pnw.Button(\n        name=\"\",\n        button_style=\"outline\",\n        button_type=\"light\",\n        width=45,\n        height=generic_button_height,\n        icon=\"reload\",\n        icon_size=\"2em\",\n    )\n    # Create send button\n    self.send_order_button = pnw.Button(\n        name=\"Send Order\",\n        button_type=\"success\",\n        height=generic_button_height,\n        icon=\"circle-check-filled\",\n        icon_size=\"2em\",\n        sizing_mode=\"stretch_width\",\n    )\n    # Create toggle button that stop orders (used in time column)\n    # Initialized to False, but checked on app creation\n    self.toggle_no_more_order_button = pnw.Toggle(\n        name=\"Stop Orders\",\n        button_style=\"outline\",\n        button_type=\"warning\",\n        height=generic_button_height,\n        icon=\"hand-stop\",\n        icon_size=\"2em\",\n        sizing_mode=\"stretch_width\",\n    )\n    # Create change time\n    self.change_order_time_takeaway_button = pnw.Button(\n        name=\"Change Time/Takeaway\",\n        button_type=\"primary\",\n        button_style=\"outline\",\n        height=generic_button_height,\n        icon=\"clock-edit\",\n        icon_size=\"2em\",\n        sizing_mode=\"stretch_width\",\n    )\n    # Create delete order\n    self.delete_order_button = pnw.Button(\n        name=\"Delete Order\",\n        button_type=\"danger\",\n        height=generic_button_height,\n        icon=\"trash-filled\",\n        icon_size=\"2em\",\n        sizing_mode=\"stretch_width\",\n    )\n\n    # ROWS\n    self.main_header_row = pn.Row(\n        \"# Menu\",\n        pn.layout.HSpacer(),\n        self.refresh_button,\n    )\n\n    # COLUMNS\n    # Create column shown when no menu is available\n    self.no_menu_col = pn.Column(\n        self.no_menu_image,\n        self.no_menu_image_attribution,\n        sizing_mode=\"stretch_width\",\n        min_width=main_area_min_width,\n    )\n    # Create column for lunch time labels\n    self.time_col = pn.Column(width=time_col_width)\n    # Create column for resulting menus\n    self.res_col = pn.Column(\n        sizing_mode=\"stretch_width\", min_width=main_area_min_width\n    )\n\n    # FLEXBOXES\n    self.menu_flexbox = pn.FlexBox(\n        *[\n            self.dataframe,\n            pn.Spacer(width=time_col_spacer_width),\n            self.time_col,\n        ],\n        min_width=main_area_min_width,\n    )\n    self.buttons_flexbox = pn.FlexBox(\n        *[\n            self.send_order_button,\n            self.toggle_no_more_order_button,\n            self.change_order_time_takeaway_button,\n            self.delete_order_button,\n        ],\n        flex_wrap=\"nowrap\",\n        min_width=main_area_min_width,\n        sizing_mode=\"stretch_width\",\n    )\n    self.results_divider = pn.layout.Divider(\n        sizing_mode=\"stretch_width\", min_width=main_area_min_width\n    )\n\n    # CALLBACKS\n    # Callback on every \"toggle\" action\n    @pn.depends(self.toggle_no_more_order_button, watch=True)\n    def reload_on_no_more_order_callback(\n        toggle: pnw.Toggle, reload: bool = True\n    ):\n        # Update global variable\n        models.set_flag(config=config, id=\"no_more_orders\", value=toggle)\n\n        # Show \"no more order\" text\n        self.no_more_order_alert.visible = toggle\n\n        # Deactivate send, delete and change order buttons\n        self.send_order_button.disabled = toggle\n        self.delete_order_button.disabled = toggle\n        self.change_order_time_takeaway_button.disabled = toggle\n\n        # Simply reload the menu when the toggle button value changes\n        if reload:\n            waiter.reload_menu(\n                None,\n                self,\n            )\n\n    # Add callback to attribute\n    self.reload_on_no_more_order = reload_on_no_more_order_callback\n\n    # Refresh button callback\n    self.refresh_button.on_click(\n        lambda e: waiter.reload_menu(\n            e,\n            self,\n        )\n    )\n    # Send order button callback\n    self.send_order_button.on_click(\n        lambda e: waiter.send_order(\n            e,\n            app,\n            person,\n            self,\n        )\n    )\n    # Delete order button callback\n    self.delete_order_button.on_click(\n        lambda e: waiter.delete_order(\n            e,\n            app,\n            self,\n        )\n    )\n    # Change order time button callback\n    self.change_order_time_takeaway_button.on_click(\n        lambda e: waiter.change_order_time_takeaway(\n            e,\n            person,\n            self,\n        )\n    )\n\n    # MODAL WINDOW --------------------------------------------------------\n    # Error message\n    self.error_message = pn.pane.HTML(\n        styles={\"color\": \"red\", \"font-weight\": \"bold\"},\n        sizing_mode=\"stretch_width\",\n    )\n    self.error_message.visible = False\n\n    # SIDEBAR -------------------------------------------------------------\n    # TEXTS\n    # Foldable additional item details dropdown menu\n    jinja_template = jinja2.Environment(\n        loader=jinja2.BaseLoader\n    ).from_string(config.panel.gui.additional_item_details_template)\n    self.additional_items_details = pn.pane.HTML(\n        jinja_template.render(\n            items=config.panel.additional_items_to_concat\n        ),\n        width=sidebar_content_width,\n    )\n\n    # WIDGET\n    # Person data\n    self.person_widget = pn.Param(\n        person.param,\n        widgets={\n            \"guest\": pnw.RadioButtonGroup(\n                options=OmegaConf.to_container(\n                    config.panel.guest_types, resolve=True\n                ),\n                button_type=\"primary\",\n                button_style=\"outline\",\n            ),\n            \"username\": pnw.TextInput(\n                value=person.username,\n                value_input=person.username,\n                description=person.param.username.doc,\n            ),\n        },\n        width=sidebar_content_width,\n    )\n    # File upload\n    self.file_widget = pnw.FileInput(\n        accept=\".png,.jpg,.jpeg,.xlsx\", sizing_mode=\"stretch_width\"\n    )\n    # Stats table\n    # Create stats table (non-editable)\n    self.stats_widget = pnw.Tabulator(\n        name=\"Statistics\",\n        hidden_columns=[\"index\"],\n        width=sidebar_content_width - 20,\n        layout=\"fit_columns\",\n        stylesheets=[\n            config.panel.gui.css_files.custom_tabulator_path,\n            config.panel.gui.css_files.stats_tabulator_path,\n        ],\n    )\n    # Password renewer\n    self.password_widget = pn.Param(\n        PasswordRenewer().param,\n        widgets={\n            \"old_password\": pnw.PasswordInput(\n                name=\"Old password\", placeholder=\"Old Password\"\n            ),\n            \"new_password\": pnw.PasswordInput(\n                name=\"New password\", placeholder=\"New Password\"\n            ),\n            \"repeat_new_password\": pnw.PasswordInput(\n                name=\"Repeat new password\",\n                placeholder=\"Repeat New Password\",\n            ),\n        },\n        name=\"Change password\",\n        width=sidebar_content_width,\n    )\n    # Guest password text\n    self.guest_username_widget = pnw.TextInput(\n        name=\"Username\",\n        placeholder=\"If empty reload this page.\",\n        value=\"guest\",\n    )\n    self.guest_password_widget = pnw.PasswordInput(\n        name=\"Password\",\n        placeholder=\"If empty reload this page.\",\n        value=guest_password,\n    )\n    # Turn off guest user if no password is set (empty string)\n    if not guest_password:\n        self.guest_username_widget.value = \"\"\n        self.guest_username_widget.disabled = True\n        self.guest_username_widget.placeholder = \"NOT ACTIVE\"\n        self.guest_password_widget.value = \"\"\n        self.guest_password_widget.disabled = True\n        self.guest_password_widget.placeholder = \"NOT ACTIVE\"\n\n    # BUTTONS\n    # Create menu button\n    self.build_menu_button = pnw.Button(\n        name=\"Build Menu\",\n        button_type=\"primary\",\n        sizing_mode=\"stretch_width\",\n        icon=\"tools-kitchen-2\",\n        icon_size=\"2em\",\n    )\n    # Download button and callback\n    self.download_button = pn.widgets.FileDownload(\n        callback=lambda: waiter.download_dataframe(self),\n        filename=config.panel.file_name + \".xlsx\",\n        sizing_mode=\"stretch_width\",\n        icon=\"download\",\n        icon_size=\"2em\",\n    )\n    # Password button\n    self.submit_password_button = pnw.Button(\n        name=\"Submit\",\n        button_type=\"success\",\n        button_style=\"outline\",\n        height=generic_button_height,\n        icon=\"key\",\n        icon_size=\"2em\",\n        sizing_mode=\"stretch_width\",\n    )\n\n    # COLUMNS\n    # Create column for person data\n    self.sidebar_person_column = pn.Column(\n        person_text,\n        self.person_widget,\n        pn.Spacer(height=5),\n        self.additional_items_details,\n        name=\"User\",\n        width=sidebar_content_width,\n    )\n    # Leave an empty widget for the 'other info' section\n    self.sidebar_person_column.append(\n        pn.pane.HTML(),\n    )\n\n    # Create column for uploading image/Excel with the menu\n    self.sidebar_menu_upload_col = pn.Column(\n        upload_text,\n        self.file_widget,\n        self.build_menu_button,\n        name=\"Menu Upload\",\n        width=sidebar_content_width,\n    )\n    # Create column for downloading Excel with orders\n    self.sidebar_download_orders_col = pn.Column(\n        download_text,\n        self.download_button,\n        name=\"Download Orders\",\n        width=sidebar_content_width,\n    )\n    # Create column for statistics\n    self.sidebar_stats_col = pn.Column(\n        name=\"Stats\", width=sidebar_content_width\n    )\n\n    self.sidebar_password = pn.Column(\n        config.panel.gui.psw_text,\n        self.password_widget,\n        self.submit_password_button,\n        pn.Spacer(height=5),\n        pn.layout.Divider(),\n        guest_user_text,\n        self.guest_username_widget,\n        self.guest_password_widget,\n        name=\"Password\",\n        width=sidebar_content_width,\n    )\n\n    # TABS\n    # The person widget is defined in the app factory function because\n    # lunch times are configurable\n    self.sidebar_tabs = pn.Tabs(\n        width=sidebar_content_width,\n    )\n    # Reload tabs according to auth.is_guest results and guest_override\n    # flag (no need to cleans, tabs are already empty)\n    self.load_sidebar_tabs(config=config, clear_before_loading=False)\n\n    # CALLBACKS\n    # Build menu button callback\n    self.build_menu_button.on_click(\n        lambda e: waiter.build_menu(\n            e,\n            app,\n            self,\n        )\n    )\n    # Submit password button callback\n    self.submit_password_button.on_click(\n        lambda e: auth.submit_password(gi=self, config=config)\n    )\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.build_order_table","title":"build_order_table","text":"
build_order_table(\n    config: DictConfig,\n    df: DataFrame,\n    time: str,\n    guests_lists: dict = {},\n) -> Tabulator\n

Build Tabulator object to display placed orders.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required df DataFrame

Table with orders. It has columns for each user that placed an order, total and a note columns.

required time str

Lunch time.

required guests_lists dict

Dictionary with lists of users dived by guest type. Keys of the dictionary are the type of guest listed. Defaults to empty dictionary ({}).

{}

Returns:

Type Description Tabulator

Panel Tabulator object representing placed orders.

Source code in dlunch/gui.py
def build_order_table(\n    self,\n    config: DictConfig,\n    df: pd.DataFrame,\n    time: str,\n    guests_lists: dict = {},\n) -> pnw.Tabulator:\n    \"\"\"Build `Tabulator` object to display placed orders.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n        df (pd.DataFrame): Table with orders. It has columns for each user that placed an order, total and a note columns.\n        time (str): Lunch time.\n        guests_lists (dict, optional): Dictionary with lists of users dived by guest type.\n            Keys of the dictionary are the type of guest listed.\n            Defaults to empty dictionary (`{}`).\n\n    Returns:\n        pnw.Tabulator: Panel `Tabulator` object representing placed orders.\n    \"\"\"\n    # Add guest icon to users' id\n    columns_with_guests_icons = df.columns.to_series()\n    for guest_type, guests_list in guests_lists.items():\n        columns_with_guests_icons[\n            columns_with_guests_icons.isin(guests_list)\n        ] += f\" {config.panel.gui.guest_icons[guest_type]}\"\n    df.columns = columns_with_guests_icons.to_list()\n    # Create table widget\n    orders_table_widget = pnw.Tabulator(\n        name=time,\n        value=df,\n        frozen_columns=[0],\n        layout=\"fit_data_table\",\n        stylesheets=[config.panel.gui.css_files.custom_tabulator_path],\n    )\n    # Make the table non-editable\n    orders_table_widget.editors = {c: None for c in df.columns}\n    return orders_table_widget\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.build_stats_and_info_text","title":"build_stats_and_info_text","text":"
build_stats_and_info_text(\n    config: DictConfig,\n    df_stats: DataFrame,\n    user: str,\n    version: str,\n    host_name: str,\n    stylesheets: list = [],\n) -> dict\n

Build text used for statistics under the stats tab, and info under the user tab.

This functions needs Data-Lunch version and the name of the hosting machine to populate the info section.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required df_stats DataFrame

dataframe with statistics.

required user str

username.

required version str

Data-Lunch version.

required host_name str

host name.

required stylesheets list

Stylesheets to assign to the resulting HTML pane (see Panel docs <https://panel.holoviz.org/how_to/styling/apply_css.html>__). Defaults to [].

[]

Returns:

Type Description dict

description

Source code in dlunch/gui.py
def build_stats_and_info_text(\n    self,\n    config: DictConfig,\n    df_stats: pd.DataFrame,\n    user: str,\n    version: str,\n    host_name: str,\n    stylesheets: list = [],\n) -> dict:\n    \"\"\"Build text used for statistics under the `stats` tab, and info under the `user` tab.\n\n    This functions needs Data-Lunch version and the name of the hosting machine to populate the info section.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n        df_stats (pd.DataFrame): dataframe with statistics.\n        user (str): username.\n        version (str): Data-Lunch version.\n        host_name (str): host name.\n        stylesheets (list, optional): Stylesheets to assign to the resulting HTML pane\n            (see `Panel docs <https://panel.holoviz.org/how_to/styling/apply_css.html>`__). Defaults to [].\n\n    Returns:\n        dict: _description_\n    \"\"\"\n    # Stats top text\n    stats = pn.pane.HTML(\n        f\"\"\"\n        <h3>Statistics</h3>\n        <div>\n            Grumbling stomachs fed:<br>\n            <span id=\"stats-locals\">Locals&nbsp;&nbsp;{df_stats[df_stats[\"Guest\"] == \"NotAGuest\"]['Hungry People'].sum()}</span><br>\n            <span id=\"stats-guests\">Guests&nbsp;&nbsp;{df_stats[df_stats[\"Guest\"] != \"NotAGuest\"]['Hungry People'].sum()}</span><br>\n            =================<br>\n            <strong>TOTAL&nbsp;&nbsp;{df_stats['Hungry People'].sum()}</strong><br>\n            <br>\n        </div>\n        <div>\n            <i>See the table for details</i>\n        </div>\n        \"\"\",\n        stylesheets=stylesheets,\n    )\n    # Define user group\n    if auth.is_guest(user=user, config=config, allow_override=False):\n        user_group = \"guest\"\n    elif auth.is_admin(user=user, config=config):\n        user_group = \"admin\"\n    else:\n        user_group = \"user\"\n    # Other info\n    other_info = pn.pane.HTML(\n        f\"\"\"\n        <details>\n            <summary><strong>Other Info</strong></summary>\n            <div class=\"icon-container\">\n                <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"icon icon-tabler icon-tabler-user-square\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\"/>\n                    <path d=\"M9 10a3 3 0 1 0 6 0a3 3 0 0 0 -6 0\" />\n                    <path d=\"M6 21v-1a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v1\" />\n                    <path d=\"M3 5a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v14a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2v-14z\" />\n                </svg>\n                <span>\n                    <strong>User:</strong> <i>{user}</i>\n                </span>\n            </div>\n            <div class=\"icon-container\">\n                <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"icon icon-tabler icon-tabler-users-group\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\"/>\n                    <path d=\"M10 13a2 2 0 1 0 4 0a2 2 0 0 0 -4 0\" />\n                    <path d=\"M8 21v-1a2 2 0 0 1 2 -2h4a2 2 0 0 1 2 2v1\" />\n                    <path d=\"M15 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0\" />\n                    <path d=\"M17 10h2a2 2 0 0 1 2 2v1\" />\n                    <path d=\"M5 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0\" />\n                    <path d=\"M3 13v-1a2 2 0 0 1 2 -2h2\" />\n                </svg>\n                <span>\n                    <strong>Group:</strong> <i>{user_group}</i>\n                </span>\n            </div>\n            <div class=\"icon-container\">\n                <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"icon icon-tabler icon-tabler-pizza\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\"/>\n                    <path d=\"M12 21.5c-3.04 0 -5.952 -.714 -8.5 -1.983l8.5 -16.517l8.5 16.517a19.09 19.09 0 0 1 -8.5 1.983z\" />\n                    <path d=\"M5.38 15.866a14.94 14.94 0 0 0 6.815 1.634a14.944 14.944 0 0 0 6.502 -1.479\" />\n                    <path d=\"M13 11.01v-.01\" />\n                    <path d=\"M11 14v-.01\" />\n                </svg>\n                <span>\n                    <strong>Data-Lunch:</strong> <i>v{version}</i>\n                </span>\n            </div>\n            <div class=\"icon-container\">\n                <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"icon icon-tabler icon-tabler-cpu\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n                    <path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\"></path>\n                    <path d=\"M5 5m0 1a1 1 0 0 1 1 -1h12a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-12a1 1 0 0 1 -1 -1z\"></path>\n                    <path d=\"M9 9h6v6h-6z\"></path>\n                    <path d=\"M3 10h2\"></path>\n                    <path d=\"M3 14h2\"></path>\n                    <path d=\"M10 3v2\"></path>\n                    <path d=\"M14 3v2\"></path>\n                    <path d=\"M21 10h-2\"></path>\n                    <path d=\"M21 14h-2\"></path>\n                    <path d=\"M14 21v-2\"></path>\n                    <path d=\"M10 21v-2\"></path>\n                </svg>\n                <span>\n                    <strong>Host:</strong> <i>{host_name}</i>\n                </span>\n            </div>\n        </details>\n        \"\"\",\n        sizing_mode=\"stretch_width\",\n        stylesheets=stylesheets,\n    )\n\n    return {\"stats\": stats, \"info\": other_info}\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.build_time_label","title":"build_time_label","text":"
build_time_label(\n    time: str,\n    diners_n: str,\n    separator: str = \" &#10072; \",\n    emoji: str = \"&#127829;\",\n    per_icon: str = \" &#10006; \",\n    is_takeaway: bool = False,\n    takeaway_alert_sign: str = \"TAKEAWAY\",\n    css_classes: list = [],\n    stylesheets: list = [],\n    **kwargs\n) -> HTML\n

Build HTML field to display the time label.

This function is used to display labels that summarize an order.

Those are shown on the side of the menu table as well as labels above each order table.

Parameters:

Name Type Description Default time str

Lunch time.

required diners_n str

Number of people that placed an order.

required separator str

Separator between lunch time and order data. Defaults to \" \u2758 \".

' &#10072; ' emoji str

Emoji used as number lunch symbol. Defaults to \"\ud83c\udf55\".

'&#127829;' per_icon str

icon used between the lunch emoji and the number of people that placed an order. Usually a multiply operator. Defaults to \" \u2716 \".

' &#10006; ' is_takeaway bool

takeaway flag (true if the order is to takeaway). Defaults to False.

False takeaway_alert_sign str

warning text to highlight that the order is to takeaway. Defaults to \"TAKEAWAY\".

'TAKEAWAY' css_classes list

CSS classes to assign to the resulting HTML pane. Defaults to [].

[] stylesheets list

Stylesheets to assign to the resulting HTML pane (see Panel docs <https://panel.holoviz.org/how_to/styling/apply_css.html>__). Defaults to [].

[]

Returns:

Type Description HTML

HTML pane representing a label with order summary.

Source code in dlunch/gui.py
def build_time_label(\n    self,\n    time: str,\n    diners_n: str,\n    separator: str = \" &#10072; \",\n    emoji: str = \"&#127829;\",\n    per_icon: str = \" &#10006; \",\n    is_takeaway: bool = False,\n    takeaway_alert_sign: str = \"TAKEAWAY\",\n    css_classes: list = [],\n    stylesheets: list = [],\n    **kwargs,\n) -> pn.pane.HTML:\n    \"\"\"Build HTML field to display the time label.\n\n    This function is used to display labels that summarize an order.\n\n    Those are shown on the side of the menu table as well as labels above each order table.\n\n    Args:\n        time (str): Lunch time.\n        diners_n (str): Number of people that placed an order.\n        separator (str, optional): Separator between lunch time and order data. Defaults to \" &#10072; \".\n        emoji (str, optional): Emoji used as number lunch symbol. Defaults to \"&#127829;\".\n        per_icon (str, optional): icon used between the lunch emoji and the number of people that placed an order.\n            Usually a multiply operator.\n            Defaults to \" &#10006; \".\n        is_takeaway (bool, optional): takeaway flag (true if the order is to takeaway). Defaults to False.\n        takeaway_alert_sign (str, optional): warning text to highlight that the order is to takeaway. Defaults to \"TAKEAWAY\".\n        css_classes (list, optional): CSS classes to assign to the resulting HTML pane. Defaults to [].\n        stylesheets (list, optional): Stylesheets to assign to the resulting HTML pane\n            (see `Panel docs <https://panel.holoviz.org/how_to/styling/apply_css.html>`__). Defaults to [].\n\n    Returns:\n        pn.pane.HTML: HTML pane representing a label with order summary.\n    \"\"\"\n    # If takeaway add alert sign\n    if is_takeaway:\n        takeaway = f\"{separator}{takeaway_alert_sign}\"\n    else:\n        takeaway = \"\"\n    # Time label pane\n    classes_str = \" \".join(css_classes)\n    time_label = pn.pane.HTML(\n        f'<span class=\"{classes_str}\">{time}{separator}{emoji}{per_icon}{diners_n}{takeaway}</span>',\n        stylesheets=stylesheets,\n        **kwargs,\n    )\n\n    return time_label\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.load_sidebar_tabs","title":"load_sidebar_tabs","text":"
load_sidebar_tabs(\n    config: DictConfig, clear_before_loading: bool = True\n) -> None\n

Append tabs to the app template sidebar.

The flag clear_before_loading is set to true only during first instantiation, because the sidebar is empty at first. Use the default value during normal operation to avoid tabs duplication.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required clear_before_loading bool

Set to true to remove all tabs before appending the new ones. Defaults to True.

True Source code in dlunch/gui.py
def load_sidebar_tabs(\n    self, config: DictConfig, clear_before_loading: bool = True\n) -> None:\n    \"\"\"Append tabs to the app template sidebar.\n\n    The flag `clear_before_loading` is set to true only during first instantiation, because the sidebar is empty at first.\n    Use the default value during normal operation to avoid tabs duplication.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n        clear_before_loading (bool, optional): Set to true to remove all tabs before appending the new ones. Defaults to True.\n    \"\"\"\n    # Clean tabs\n    if clear_before_loading:\n        self.sidebar_tabs.clear()\n    # Append User tab\n    self.sidebar_tabs.append(self.sidebar_person_column)\n    # Append upload, download and stats only for non-guest\n    # Append password only for non-guest users if auth is active\n    if not auth.is_guest(\n        user=pn_user(config), config=config, allow_override=False\n    ):\n        self.sidebar_tabs.append(self.sidebar_menu_upload_col)\n        self.sidebar_tabs.append(self.sidebar_download_orders_col)\n        self.sidebar_tabs.append(self.sidebar_stats_col)\n        if auth.is_basic_auth_active(config=config):\n            self.sidebar_tabs.append(self.sidebar_password)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.PasswordRenewer","title":"PasswordRenewer","text":"

Bases: Parameterized

Param class used to create the widget that collect info to renew users password.

This widget is used only if basic authentication is active.

Methods:

Name Description __str__

String representation of this object.

Attributes:

Name Type Description new_password String

New password.

old_password String

Old password.

repeat_new_password String

Repeat the new password. This field tests if the new password is as intended.

Source code in dlunch/gui.py
class PasswordRenewer(param.Parameterized):\n    \"\"\"Param class used to create the widget that collect info to renew users password.\n\n    This widget is used only if basic authentication is active.\"\"\"\n\n    old_password: param.String = param.String(default=\"\")\n    \"\"\"Old password.\"\"\"\n    new_password: param.String = param.String(default=\"\")\n    \"\"\"New password.\"\"\"\n    repeat_new_password: param.String = param.String(default=\"\")\n    \"\"\"Repeat the new password. This field tests if the new password is as intended.\"\"\"\n\n    def __str__(self):\n        \"\"\"String representation of this object.\n\n        Returns:\n            (str): string representation.\n        \"\"\"\n        return \"PasswordRenewer\"\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.PasswordRenewer.new_password","title":"new_password class-attribute instance-attribute","text":"
new_password: String = String(default='')\n

New password.

"},{"location":"reference/dlunch/gui/#dlunch.gui.PasswordRenewer.old_password","title":"old_password class-attribute instance-attribute","text":"
old_password: String = String(default='')\n

Old password.

"},{"location":"reference/dlunch/gui/#dlunch.gui.PasswordRenewer.repeat_new_password","title":"repeat_new_password class-attribute instance-attribute","text":"
repeat_new_password: String = String(default='')\n

Repeat the new password. This field tests if the new password is as intended.

"},{"location":"reference/dlunch/gui/#dlunch.gui.PasswordRenewer.__str__","title":"__str__","text":"
__str__()\n

String representation of this object.

Returns:

Type Description str

string representation.

Source code in dlunch/gui.py
def __str__(self):\n    \"\"\"String representation of this object.\n\n    Returns:\n        (str): string representation.\n    \"\"\"\n    return \"PasswordRenewer\"\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.Person","title":"Person","text":"

Bases: Parameterized

Param class that define user data and lunch preferences for its order.

username is automatically set for privileged users. It's left empty for guest users.

lunch_time and guest available value are set when instantiation happens. Check panel.lunch_times_options and panel.guest_types config keys.

Methods:

Name Description __init__ __str__

String representation of this object.

Attributes:

Name Type Description guest ObjectSelector

List of available guest types.

lunch_time ObjectSelector

List of available lunch times.

takeaway Boolean

Takeaway flag (true if takeaway).

username String

Username

Source code in dlunch/gui.py
class Person(param.Parameterized):\n    \"\"\"Param class that define user data and lunch preferences for its order.\n\n    `username` is automatically set for privileged users. It's left empty for guest users.\n\n    `lunch_time` and `guest` available value are set when instantiation happens.\n    Check `panel.lunch_times_options` and `panel.guest_types` config keys.\n    \"\"\"\n\n    username: param.String = param.String(default=\"\", doc=\"your name\")\n    \"\"\"Username\"\"\"\n    lunch_time: param.ObjectSelector = param.ObjectSelector(\n        default=\"12:30\", doc=\"choose your lunch time\", objects=[\"12:30\"]\n    )\n    \"\"\"List of available lunch times.\"\"\"\n    guest: param.ObjectSelector = param.ObjectSelector(\n        default=\"Guest\", doc=\"select guest type\", objects=[\"Guest\"]\n    )\n    \"\"\"List of available guest types.\"\"\"\n    takeaway: param.Boolean = param.Boolean(\n        default=False, doc=\"tick to order a takeaway meal\"\n    )\n    \"\"\"Takeaway flag (true if takeaway).\"\"\"\n\n    def __init__(self, config: OmegaConf, **params):\n        super().__init__(**params)\n        # Set lunch times from config\n        self.param.lunch_time.objects = config.panel.lunch_times_options\n        # Set guest type from config\n        self.param.guest.objects = config.panel.guest_types\n        self.param.guest.default = config.panel.guest_types[0]\n        self.guest = config.panel.guest_types[0]\n        # Check user (a username is already set for privileged users)\n        username = pn_user(config)\n        if not auth.is_guest(\n            user=username, config=config, allow_override=False\n        ) and (username is not None):\n            self.username = username\n\n    def __str__(self):\n        \"\"\"String representation of this object.\n\n        Returns:\n            (str): string representation.\n        \"\"\"\n        return f\"PERSON:{self.name}\"\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.Person.guest","title":"guest class-attribute instance-attribute","text":"
guest: ObjectSelector = guest_types[0]\n

List of available guest types.

"},{"location":"reference/dlunch/gui/#dlunch.gui.Person.lunch_time","title":"lunch_time class-attribute instance-attribute","text":"
lunch_time: ObjectSelector = ObjectSelector(\n    default=\"12:30\",\n    doc=\"choose your lunch time\",\n    objects=[\"12:30\"],\n)\n

List of available lunch times.

"},{"location":"reference/dlunch/gui/#dlunch.gui.Person.takeaway","title":"takeaway class-attribute instance-attribute","text":"
takeaway: Boolean = Boolean(\n    default=False, doc=\"tick to order a takeaway meal\"\n)\n

Takeaway flag (true if takeaway).

"},{"location":"reference/dlunch/gui/#dlunch.gui.Person.username","title":"username class-attribute instance-attribute","text":"
username: String = String(default='', doc='your name')\n

Username

"},{"location":"reference/dlunch/gui/#dlunch.gui.Person.__init__","title":"__init__","text":"
__init__(config: OmegaConf, **params)\n
Source code in dlunch/gui.py
def __init__(self, config: OmegaConf, **params):\n    super().__init__(**params)\n    # Set lunch times from config\n    self.param.lunch_time.objects = config.panel.lunch_times_options\n    # Set guest type from config\n    self.param.guest.objects = config.panel.guest_types\n    self.param.guest.default = config.panel.guest_types[0]\n    self.guest = config.panel.guest_types[0]\n    # Check user (a username is already set for privileged users)\n    username = pn_user(config)\n    if not auth.is_guest(\n        user=username, config=config, allow_override=False\n    ) and (username is not None):\n        self.username = username\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.Person.__str__","title":"__str__","text":"
__str__()\n

String representation of this object.

Returns:

Type Description str

string representation.

Source code in dlunch/gui.py
def __str__(self):\n    \"\"\"String representation of this object.\n\n    Returns:\n        (str): string representation.\n    \"\"\"\n    return f\"PERSON:{self.name}\"\n
"},{"location":"reference/dlunch/models/","title":"models","text":"

Module with database tables definitions.

Helper classes and utility functions for data management are defined here.

Modules:

Name Description auth

Module with classes and functions used for authentication and password handling.

Classes:

Name Description Credentials

Table with users credentials, used only if basic authentication is active.

Encrypted

Allows storing and retrieving password hashes using PasswordHash.

Flags

Table with global flags used by Data-Lunch.

Menu

Table with menu items.

Orders

Table with items that belongs to an order.

Password

Allows storing and retrieving password hashes using PasswordHash.

PrivilegedUsers

Table with user that have privileges (normal users and admin).

Stats

Table with number of users that ate a lunch, grouped by guest type.

Users

Table with users that placed an order.

Functions:

Name Description create_database

Function to create the database through SQLAlchemy models.

create_engine

Factory function for SQLAlchemy engine.

create_exclusive_session

Factory function for database exclusive session.

create_session

Factory function for database session.

get_db_dialect

Return database type (postgresql, sqlite, etc.) based on the database object passed as input.

get_flag

Get the value of a flag.

session_add_with_upsert

Use an upsert statement for postgresql to add a new record to a table,

set_flag

Set a key,value pair inside flag table.

set_sqlite_pragma

Force foreign key constraints for sqlite connections.

Attributes:

Name Type Description Data DeclarativeMeta

SQLAlchemy declarative base.

SCHEMA str

Schema name from environment (may be overridden by configuration files).

log Logger

Module logger.

metadata_obj MetaData

Database metadata (SQLAlchemy).

"},{"location":"reference/dlunch/models/#dlunch.models.Data","title":"Data module-attribute","text":"
Data: DeclarativeMeta = declarative_base(\n    metadata=metadata_obj\n)\n

SQLAlchemy declarative base.

"},{"location":"reference/dlunch/models/#dlunch.models.SCHEMA","title":"SCHEMA module-attribute","text":"
SCHEMA: str = get('DATA_LUNCH_DB_SCHEMA', None)\n

Schema name from environment (may be overridden by configuration files).

"},{"location":"reference/dlunch/models/#dlunch.models.log","title":"log module-attribute","text":"
log: Logger = getLogger(__name__)\n

Module logger.

"},{"location":"reference/dlunch/models/#dlunch.models.metadata_obj","title":"metadata_obj module-attribute","text":"
metadata_obj: MetaData = MetaData(schema=SCHEMA)\n

Database metadata (SQLAlchemy).

"},{"location":"reference/dlunch/models/#dlunch.models.Credentials","title":"Credentials","text":"

Bases: Data

Table with users credentials, used only if basic authentication is active.

Methods:

Name Description __repr__

Simple object representation.

clear

Clear table and return deleted rows.

read_as_df

Read table as pandas DataFrame.

Attributes:

Name Type Description __tablename__

Name of the table.

password_encrypted

Encryped password.

password_hash

Hashed password.

user

Username.

Source code in dlunch/models.py
class Credentials(Data):\n    \"\"\"Table with users credentials, used only if basic authentication is active.\"\"\"\n\n    __tablename__ = \"credentials\"\n    \"\"\"Name of the table.\"\"\"\n    user = Column(\n        String(100),\n        primary_key=True,\n        sqlite_on_conflict_primary_key=\"REPLACE\",\n    )\n    \"\"\"Username.\"\"\"\n    password_hash = Column(Password(150), unique=False, nullable=False)\n    \"\"\"Hashed password.\"\"\"\n    password_encrypted = Column(\n        Encrypted(150),\n        unique=False,\n        nullable=True,\n        default=None,\n        server_default=None,\n    )\n    \"\"\"Encryped password.\n\n    Used only if basic authentication and guest users are both active.\"\"\"\n\n    @classmethod\n    def clear(self, config: DictConfig) -> int:\n        \"\"\"Clear table and return deleted rows.\n\n        Args:\n            config (DictConfig): Hydra configuration dictionary.\n\n        Returns:\n            int: deleted rows.\n        \"\"\"\n\n        session = create_session(config)\n        with session:\n            # Clean menu\n            num_rows_deleted = session.execute(delete(self))\n            session.commit()\n            log.info(\n                f\"deleted {num_rows_deleted.rowcount} rows from table '{self.__tablename__}'\"\n            )\n\n        return num_rows_deleted.rowcount\n\n    @classmethod\n    def read_as_df(self, config: DictConfig, **kwargs) -> pd.DataFrame:\n        \"\"\"Read table as pandas DataFrame.\n\n        Args:\n            config (DictConfig): Hydra configuration dictionary.\n\n        Returns:\n            pd.DataFrame: dataframe with table content.\n        \"\"\"\n        df = pd.read_sql_table(\n            self.__tablename__,\n            create_engine(config=config),\n            schema=config.db.get(\"schema\", SCHEMA),\n            **kwargs,\n        )\n\n        return df\n\n    def __repr__(self) -> str:\n        \"\"\"Simple object representation.\n\n        Returns:\n            str: string representation.\n        \"\"\"\n        return f\"<CREDENTIAL:{self.user}>\"\n\n    @validates(\"password_hash\")\n    def _validate_password(\n        self, key: str, password: str\n    ) -> auth.PasswordHash | None:\n        \"\"\"Function that validate password input.\n\n        It converts string to auth.PasswordHash if necessary.\n\n        Args:\n            key (str): validated attribute name.\n            password (str): hashed password to be validated.\n\n        Returns:\n            auth.PasswordHash | None: validated hashed password.\n        \"\"\"\n        return getattr(type(self), key).type.validator(password)\n\n    @validates(\"password_encrypted\")\n    def _validate_encrypted(\n        self, key: str, password: str\n    ) -> auth.PasswordEncrypt | None:\n        \"\"\"Function that validate encrypted input.\n\n        It converts string to auth.PasswordEncrypt if necessary.\n\n        Args:\n            key (str): validated attribute name.\n            password (str): encrypted password to be validated.\n\n        Returns:\n            auth.PasswordEncrypt | None: validated encrypted password.\n        \"\"\"\n        return getattr(type(self), key).type.validator(password)\n
"},{"location":"reference/dlunch/models/#dlunch.models.Credentials.__tablename__","title":"__tablename__ class-attribute instance-attribute","text":"
__tablename__ = 'credentials'\n

Name of the table.

"},{"location":"reference/dlunch/models/#dlunch.models.Credentials.password_encrypted","title":"password_encrypted class-attribute instance-attribute","text":"
password_encrypted = Column(\n    Encrypted(150),\n    unique=False,\n    nullable=True,\n    default=None,\n    server_default=None,\n)\n

Encryped password.

Used only if basic authentication and guest users are both active.

"},{"location":"reference/dlunch/models/#dlunch.models.Credentials.password_hash","title":"password_hash class-attribute instance-attribute","text":"
password_hash = Column(\n    Password(150), unique=False, nullable=False\n)\n

Hashed password.

"},{"location":"reference/dlunch/models/#dlunch.models.Credentials.user","title":"user class-attribute instance-attribute","text":"
user = Column(\n    String(100),\n    primary_key=True,\n    sqlite_on_conflict_primary_key=\"REPLACE\",\n)\n

Username.

"},{"location":"reference/dlunch/models/#dlunch.models.Credentials.__repr__","title":"__repr__","text":"
__repr__() -> str\n

Simple object representation.

Returns:

Type Description str

string representation.

Source code in dlunch/models.py
def __repr__(self) -> str:\n    \"\"\"Simple object representation.\n\n    Returns:\n        str: string representation.\n    \"\"\"\n    return f\"<CREDENTIAL:{self.user}>\"\n
"},{"location":"reference/dlunch/models/#dlunch.models.Credentials.clear","title":"clear classmethod","text":"
clear(config: DictConfig) -> int\n

Clear table and return deleted rows.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description int

deleted rows.

Source code in dlunch/models.py
@classmethod\ndef clear(self, config: DictConfig) -> int:\n    \"\"\"Clear table and return deleted rows.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        int: deleted rows.\n    \"\"\"\n\n    session = create_session(config)\n    with session:\n        # Clean menu\n        num_rows_deleted = session.execute(delete(self))\n        session.commit()\n        log.info(\n            f\"deleted {num_rows_deleted.rowcount} rows from table '{self.__tablename__}'\"\n        )\n\n    return num_rows_deleted.rowcount\n
"},{"location":"reference/dlunch/models/#dlunch.models.Credentials.read_as_df","title":"read_as_df classmethod","text":"
read_as_df(config: DictConfig, **kwargs) -> DataFrame\n

Read table as pandas DataFrame.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description DataFrame

dataframe with table content.

Source code in dlunch/models.py
@classmethod\ndef read_as_df(self, config: DictConfig, **kwargs) -> pd.DataFrame:\n    \"\"\"Read table as pandas DataFrame.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        pd.DataFrame: dataframe with table content.\n    \"\"\"\n    df = pd.read_sql_table(\n        self.__tablename__,\n        create_engine(config=config),\n        schema=config.db.get(\"schema\", SCHEMA),\n        **kwargs,\n    )\n\n    return df\n
"},{"location":"reference/dlunch/models/#dlunch.models.Encrypted","title":"Encrypted","text":"

Bases: TypeDecorator

Allows storing and retrieving password hashes using PasswordHash.

Methods:

Name Description process_bind_param

Ensure the value is a PasswordEncrypt and then return the encrypted password.

process_result_value

Convert the hash to a PasswordEncrypt, if it's non-NULL.

validator

Provides a validator/converter used by @validates.

Attributes:

Name Type Description impl String

Base column implementation.

Source code in dlunch/models.py
class Encrypted(TypeDecorator):\n    \"\"\"Allows storing and retrieving password hashes using PasswordHash.\"\"\"\n\n    impl: String = String\n    \"\"\"Base column implementation.\"\"\"\n\n    def process_bind_param(\n        self, value: auth.PasswordEncrypt | str | None, dialect\n    ) -> str | None:\n        \"\"\"Ensure the value is a PasswordEncrypt and then return the encrypted password.\n\n        Args:\n            value (auth.PasswordEncrypt | str | None): input value (plain password or encrypted or `None` if empty)\n            dialect (Any): dialect (not used).\n\n        Returns:\n            str | None: encrypted password or `None` if empty.\n        \"\"\"\n        converted_value = self._convert(value)\n        if converted_value:\n            return converted_value.encrypted_password\n        else:\n            return None\n\n    def process_result_value(\n        self, value: str | None, dialect\n    ) -> auth.PasswordEncrypt | None:\n        \"\"\"Convert the hash to a PasswordEncrypt, if it's non-NULL.\n\n        Args:\n            value (str | None): input value (plain password or encrypted or `None` if empty)\n            dialect (Any): dialect (not used).\n\n        Returns:\n            auth.PasswordEncrypt | None: encrypted password as object or `None` (if nothing is passed as value).\n        \"\"\"\n        if value is not None:\n            return auth.PasswordEncrypt(value)\n\n    def validator(\n        self, password: auth.PasswordEncrypt | str | None\n    ) -> auth.PasswordEncrypt | None:\n        \"\"\"Provides a validator/converter used by @validates.\n\n        Args:\n            password (auth.PasswordEncrypt | str | None): input value (plain password or encrypted or `None` if empty)\n\n        Returns:\n            auth.PasswordEncrypt | None: encrypted password as object or `None` (if nothing is passed as value).\n        \"\"\"\n        return self._convert(password)\n\n    def _convert(\n        self, value: auth.PasswordEncrypt | str | None\n    ) -> auth.PasswordEncrypt | None:\n        \"\"\"Returns a PasswordEncrypt from the given string.\n\n        PasswordEncrypt instances or None values will return unchanged.\n        Strings will be encrypted and the resulting PasswordEncrypt returned.\n        Any other input will result in a TypeError.\n\n        Args:\n            value (auth.PasswordEncrypt | str | None): input value (plain password or encrypted or `None` if empty)\n\n        Raises:\n            TypeError: unknown type.\n\n        Returns:\n            auth.PasswordEncrypt | None: encrypted password as object or `None` (if nothing is passed as value).\n        \"\"\"\n        if isinstance(value, auth.PasswordEncrypt):\n            return value\n        elif isinstance(value, str):\n            return auth.PasswordEncrypt.from_str(value)\n        elif value is not None:\n            raise TypeError(\n                f\"Cannot initialize PasswordEncrypt from type '{type(value)}'\"\n            )\n\n        # Reached only if value is None\n        return None\n
"},{"location":"reference/dlunch/models/#dlunch.models.Encrypted.impl","title":"impl class-attribute instance-attribute","text":"
impl: String = String\n

Base column implementation.

"},{"location":"reference/dlunch/models/#dlunch.models.Encrypted.process_bind_param","title":"process_bind_param","text":"
process_bind_param(\n    value: PasswordEncrypt | str | None, dialect\n) -> str | None\n

Ensure the value is a PasswordEncrypt and then return the encrypted password.

Parameters:

Name Type Description Default value PasswordEncrypt | str | None

input value (plain password or encrypted or None if empty)

required dialect Any

dialect (not used).

required

Returns:

Type Description str | None

encrypted password or None if empty.

Source code in dlunch/models.py
def process_bind_param(\n    self, value: auth.PasswordEncrypt | str | None, dialect\n) -> str | None:\n    \"\"\"Ensure the value is a PasswordEncrypt and then return the encrypted password.\n\n    Args:\n        value (auth.PasswordEncrypt | str | None): input value (plain password or encrypted or `None` if empty)\n        dialect (Any): dialect (not used).\n\n    Returns:\n        str | None: encrypted password or `None` if empty.\n    \"\"\"\n    converted_value = self._convert(value)\n    if converted_value:\n        return converted_value.encrypted_password\n    else:\n        return None\n
"},{"location":"reference/dlunch/models/#dlunch.models.Encrypted.process_result_value","title":"process_result_value","text":"
process_result_value(\n    value: str | None, dialect\n) -> PasswordEncrypt | None\n

Convert the hash to a PasswordEncrypt, if it's non-NULL.

Parameters:

Name Type Description Default value str | None

input value (plain password or encrypted or None if empty)

required dialect Any

dialect (not used).

required

Returns:

Type Description PasswordEncrypt | None

encrypted password as object or None (if nothing is passed as value).

Source code in dlunch/models.py
def process_result_value(\n    self, value: str | None, dialect\n) -> auth.PasswordEncrypt | None:\n    \"\"\"Convert the hash to a PasswordEncrypt, if it's non-NULL.\n\n    Args:\n        value (str | None): input value (plain password or encrypted or `None` if empty)\n        dialect (Any): dialect (not used).\n\n    Returns:\n        auth.PasswordEncrypt | None: encrypted password as object or `None` (if nothing is passed as value).\n    \"\"\"\n    if value is not None:\n        return auth.PasswordEncrypt(value)\n
"},{"location":"reference/dlunch/models/#dlunch.models.Encrypted.validator","title":"validator","text":"
validator(\n    password: PasswordEncrypt | str | None,\n) -> PasswordEncrypt | None\n

Provides a validator/converter used by @validates.

Parameters:

Name Type Description Default password PasswordEncrypt | str | None

input value (plain password or encrypted or None if empty)

required

Returns:

Type Description PasswordEncrypt | None

encrypted password as object or None (if nothing is passed as value).

Source code in dlunch/models.py
def validator(\n    self, password: auth.PasswordEncrypt | str | None\n) -> auth.PasswordEncrypt | None:\n    \"\"\"Provides a validator/converter used by @validates.\n\n    Args:\n        password (auth.PasswordEncrypt | str | None): input value (plain password or encrypted or `None` if empty)\n\n    Returns:\n        auth.PasswordEncrypt | None: encrypted password as object or `None` (if nothing is passed as value).\n    \"\"\"\n    return self._convert(password)\n
"},{"location":"reference/dlunch/models/#dlunch.models.Flags","title":"Flags","text":"

Bases: Data

Table with global flags used by Data-Lunch.

'No more orders' flag and guest override flags are stored here.

Methods:

Name Description __repr__

Simple object representation.

clear

Clear table and return deleted rows.

clear_guest_override

Clear 'guest_override' flags and return deleted rows

read_as_df

Read table as pandas DataFrame.

Attributes:

Name Type Description __tablename__

Name of the table.

id

Flag ID (name).

value

Flag value.

Source code in dlunch/models.py
class Flags(Data):\n    \"\"\"Table with global flags used by Data-Lunch.\n\n    'No more orders' flag and guest override flags are stored here.\n    \"\"\"\n\n    __tablename__ = \"flags\"\n    \"\"\"Name of the table.\"\"\"\n    id = Column(\n        String(50),\n        primary_key=True,\n        nullable=False,\n        sqlite_on_conflict_primary_key=\"REPLACE\",\n    )\n    \"\"\"Flag ID (name).\"\"\"\n    value = Column(Boolean, nullable=False)\n    \"\"\"Flag value.\"\"\"\n\n    @classmethod\n    def clear(self, config: DictConfig) -> int:\n        \"\"\"Clear table and return deleted rows.\n\n        Args:\n            config (DictConfig): Hydra configuration dictionary.\n\n        Returns:\n            int: deleted rows.\n        \"\"\"\n\n        session = create_session(config)\n        with session:\n            # Clean menu\n            num_rows_deleted = session.execute(delete(self))\n            session.commit()\n            log.info(\n                f\"deleted {num_rows_deleted.rowcount} rows from table '{self.__tablename__}'\"\n            )\n\n        return num_rows_deleted.rowcount\n\n    @classmethod\n    def clear_guest_override(self, config: DictConfig) -> int:\n        \"\"\"Clear 'guest_override' flags and return deleted rows\n\n        Args:\n            config (DictConfig): Hydra configuration dictionary.\n\n        Returns:\n            int: deleted rows.\n        \"\"\"\n\n        session = create_session(config)\n        with session:\n            # Clean menu\n            num_rows_deleted = session.execute(\n                delete(self).where(self.id.like(\"%_guest_override\"))\n            )\n            session.commit()\n            log.info(\n                f\"deleted {num_rows_deleted.rowcount} rows (guest override) from table '{self.__tablename__}'\"\n            )\n\n        return num_rows_deleted.rowcount\n\n    @classmethod\n    def read_as_df(self, config: DictConfig, **kwargs) -> pd.DataFrame:\n        \"\"\"Read table as pandas DataFrame.\n\n        Args:\n            config (DictConfig): Hydra configuration dictionary.\n\n        Returns:\n            pd.DataFrame: dataframe with table content.\n        \"\"\"\n        df = pd.read_sql_table(\n            self.__tablename__,\n            create_engine(config=config),\n            schema=config.db.get(\"schema\", SCHEMA),\n            **kwargs,\n        )\n\n        return df\n\n    def __repr__(self) -> str:\n        \"\"\"Simple object representation.\n\n        Returns:\n            str: string representation.\n        \"\"\"\n        return f\"<FLAG:{self.id} - value:{self.value}>\"\n
"},{"location":"reference/dlunch/models/#dlunch.models.Flags.__tablename__","title":"__tablename__ class-attribute instance-attribute","text":"
__tablename__ = 'flags'\n

Name of the table.

"},{"location":"reference/dlunch/models/#dlunch.models.Flags.id","title":"id class-attribute instance-attribute","text":"
id = Column(\n    String(50),\n    primary_key=True,\n    nullable=False,\n    sqlite_on_conflict_primary_key=\"REPLACE\",\n)\n

Flag ID (name).

"},{"location":"reference/dlunch/models/#dlunch.models.Flags.value","title":"value class-attribute instance-attribute","text":"
value = Column(Boolean, nullable=False)\n

Flag value.

"},{"location":"reference/dlunch/models/#dlunch.models.Flags.__repr__","title":"__repr__","text":"
__repr__() -> str\n

Simple object representation.

Returns:

Type Description str

string representation.

Source code in dlunch/models.py
def __repr__(self) -> str:\n    \"\"\"Simple object representation.\n\n    Returns:\n        str: string representation.\n    \"\"\"\n    return f\"<FLAG:{self.id} - value:{self.value}>\"\n
"},{"location":"reference/dlunch/models/#dlunch.models.Flags.clear","title":"clear classmethod","text":"
clear(config: DictConfig) -> int\n

Clear table and return deleted rows.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description int

deleted rows.

Source code in dlunch/models.py
@classmethod\ndef clear(self, config: DictConfig) -> int:\n    \"\"\"Clear table and return deleted rows.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        int: deleted rows.\n    \"\"\"\n\n    session = create_session(config)\n    with session:\n        # Clean menu\n        num_rows_deleted = session.execute(delete(self))\n        session.commit()\n        log.info(\n            f\"deleted {num_rows_deleted.rowcount} rows from table '{self.__tablename__}'\"\n        )\n\n    return num_rows_deleted.rowcount\n
"},{"location":"reference/dlunch/models/#dlunch.models.Flags.clear_guest_override","title":"clear_guest_override classmethod","text":"
clear_guest_override(config: DictConfig) -> int\n

Clear 'guest_override' flags and return deleted rows

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description int

deleted rows.

Source code in dlunch/models.py
@classmethod\ndef clear_guest_override(self, config: DictConfig) -> int:\n    \"\"\"Clear 'guest_override' flags and return deleted rows\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        int: deleted rows.\n    \"\"\"\n\n    session = create_session(config)\n    with session:\n        # Clean menu\n        num_rows_deleted = session.execute(\n            delete(self).where(self.id.like(\"%_guest_override\"))\n        )\n        session.commit()\n        log.info(\n            f\"deleted {num_rows_deleted.rowcount} rows (guest override) from table '{self.__tablename__}'\"\n        )\n\n    return num_rows_deleted.rowcount\n
"},{"location":"reference/dlunch/models/#dlunch.models.Flags.read_as_df","title":"read_as_df classmethod","text":"
read_as_df(config: DictConfig, **kwargs) -> DataFrame\n

Read table as pandas DataFrame.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description DataFrame

dataframe with table content.

Source code in dlunch/models.py
@classmethod\ndef read_as_df(self, config: DictConfig, **kwargs) -> pd.DataFrame:\n    \"\"\"Read table as pandas DataFrame.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        pd.DataFrame: dataframe with table content.\n    \"\"\"\n    df = pd.read_sql_table(\n        self.__tablename__,\n        create_engine(config=config),\n        schema=config.db.get(\"schema\", SCHEMA),\n        **kwargs,\n    )\n\n    return df\n
"},{"location":"reference/dlunch/models/#dlunch.models.Menu","title":"Menu","text":"

Bases: Data

Table with menu items.

Methods:

Name Description __repr__

Simple object representation.

clear

Clear table and return deleted rows.

read_as_df

Read table as pandas DataFrame.

Attributes:

Name Type Description __tablename__

Name of the table.

id

Menu item ID.

item

Item name.

orders

Orders connected to each menu item.

Source code in dlunch/models.py
class Menu(Data):\n    \"\"\"Table with menu items.\"\"\"\n\n    __tablename__ = \"menu\"\n    \"\"\"Name of the table.\"\"\"\n    id = Column(Integer, Identity(start=1, cycle=True), primary_key=True)\n    \"\"\"Menu item ID.\"\"\"\n    item = Column(String(250), unique=False, nullable=False)\n    \"\"\"Item name.\"\"\"\n    orders = relationship(\n        \"Orders\",\n        back_populates=\"menu_item\",\n        cascade=\"all, delete-orphan\",\n        passive_deletes=True,\n    )\n    \"\"\"Orders connected to each menu item.\"\"\"\n\n    @classmethod\n    def clear(self, config: DictConfig) -> int:\n        \"\"\"Clear table and return deleted rows.\n\n        Args:\n            config (DictConfig): Hydra configuration dictionary.\n\n        Returns:\n            int: deleted rows.\n        \"\"\"\n\n        session = create_session(config)\n        with session:\n            # Clean menu\n            num_rows_deleted = session.execute(delete(self))\n            session.commit()\n            log.info(\n                f\"deleted {num_rows_deleted.rowcount} rows from table '{self.__tablename__}'\"\n            )\n\n        return num_rows_deleted.rowcount\n\n    @classmethod\n    def read_as_df(self, config: DictConfig, **kwargs) -> pd.DataFrame:\n        \"\"\"Read table as pandas DataFrame.\n\n        Args:\n            config (DictConfig): Hydra configuration dictionary.\n\n        Returns:\n            pd.DataFrame: dataframe with table content.\n        \"\"\"\n        df = pd.read_sql_table(\n            self.__tablename__,\n            create_engine(config=config),\n            schema=config.db.get(\"schema\", SCHEMA),\n            **kwargs,\n        )\n\n        return df\n\n    def __repr__(self) -> str:\n        \"\"\"Simple object representation.\n\n        Returns:\n            str: string representation.\n        \"\"\"\n        return f\"<MENU_ITEM:{self.id} - {self.item}>\"\n
"},{"location":"reference/dlunch/models/#dlunch.models.Menu.__tablename__","title":"__tablename__ class-attribute instance-attribute","text":"
__tablename__ = 'menu'\n

Name of the table.

"},{"location":"reference/dlunch/models/#dlunch.models.Menu.id","title":"id class-attribute instance-attribute","text":"
id = Column(\n    Integer, Identity(start=1, cycle=True), primary_key=True\n)\n

Menu item ID.

"},{"location":"reference/dlunch/models/#dlunch.models.Menu.item","title":"item class-attribute instance-attribute","text":"
item = Column(String(250), unique=False, nullable=False)\n

Item name.

"},{"location":"reference/dlunch/models/#dlunch.models.Menu.orders","title":"orders class-attribute instance-attribute","text":"
orders = relationship(\n    \"Orders\",\n    back_populates=\"menu_item\",\n    cascade=\"all, delete-orphan\",\n    passive_deletes=True,\n)\n

Orders connected to each menu item.

"},{"location":"reference/dlunch/models/#dlunch.models.Menu.__repr__","title":"__repr__","text":"
__repr__() -> str\n

Simple object representation.

Returns:

Type Description str

string representation.

Source code in dlunch/models.py
def __repr__(self) -> str:\n    \"\"\"Simple object representation.\n\n    Returns:\n        str: string representation.\n    \"\"\"\n    return f\"<MENU_ITEM:{self.id} - {self.item}>\"\n
"},{"location":"reference/dlunch/models/#dlunch.models.Menu.clear","title":"clear classmethod","text":"
clear(config: DictConfig) -> int\n

Clear table and return deleted rows.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description int

deleted rows.

Source code in dlunch/models.py
@classmethod\ndef clear(self, config: DictConfig) -> int:\n    \"\"\"Clear table and return deleted rows.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        int: deleted rows.\n    \"\"\"\n\n    session = create_session(config)\n    with session:\n        # Clean menu\n        num_rows_deleted = session.execute(delete(self))\n        session.commit()\n        log.info(\n            f\"deleted {num_rows_deleted.rowcount} rows from table '{self.__tablename__}'\"\n        )\n\n    return num_rows_deleted.rowcount\n
"},{"location":"reference/dlunch/models/#dlunch.models.Menu.read_as_df","title":"read_as_df classmethod","text":"
read_as_df(config: DictConfig, **kwargs) -> DataFrame\n

Read table as pandas DataFrame.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description DataFrame

dataframe with table content.

Source code in dlunch/models.py
@classmethod\ndef read_as_df(self, config: DictConfig, **kwargs) -> pd.DataFrame:\n    \"\"\"Read table as pandas DataFrame.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        pd.DataFrame: dataframe with table content.\n    \"\"\"\n    df = pd.read_sql_table(\n        self.__tablename__,\n        create_engine(config=config),\n        schema=config.db.get(\"schema\", SCHEMA),\n        **kwargs,\n    )\n\n    return df\n
"},{"location":"reference/dlunch/models/#dlunch.models.Orders","title":"Orders","text":"

Bases: Data

Table with items that belongs to an order.

Methods:

Name Description __repr__

Simple object representation.

clear

Clear table and return deleted rows.

read_as_df

Read table as pandas DataFrame.

Attributes:

Name Type Description __tablename__

Name of the table.

id

Order ID.

menu_item

Menu items connected to each order (see menu table).

menu_item_id

ID of the menu item included in the order.

note

Note field attached to the order.

user

User that placed the order.

user_record

User connected to this order.

Source code in dlunch/models.py
class Orders(Data):\n    \"\"\"Table with items that belongs to an order.\"\"\"\n\n    __tablename__ = \"orders\"\n    \"\"\"Name of the table.\"\"\"\n    id = Column(Integer, Identity(start=1, cycle=True), primary_key=True)\n    \"\"\"Order ID.\"\"\"\n    user = Column(\n        String(100),\n        ForeignKey(\"users.id\", ondelete=\"CASCADE\"),\n        index=True,\n        nullable=False,\n    )\n    \"\"\"User that placed the order.\"\"\"\n    user_record = relationship(\"Users\", back_populates=\"orders\", uselist=False)\n    \"\"\"User connected to this order.\"\"\"\n    menu_item_id = Column(\n        Integer,\n        ForeignKey(\"menu.id\", ondelete=\"CASCADE\"),\n        nullable=False,\n    )\n    \"\"\"ID of the menu item included in the order.\"\"\"\n    menu_item = relationship(\"Menu\", back_populates=\"orders\")\n    \"\"\"Menu items connected to each order (see `menu` table).\"\"\"\n    note = Column(String(300), unique=False, nullable=True)\n    \"\"\"Note field attached to the order.\"\"\"\n\n    @classmethod\n    def clear(self, config: DictConfig) -> int:\n        \"\"\"Clear table and return deleted rows.\n\n        Args:\n            config (DictConfig): Hydra configuration dictionary.\n\n        Returns:\n            int: deleted rows.\n        \"\"\"\n\n        session = create_session(config)\n        with session:\n            # Clean menu\n            num_rows_deleted = session.execute(delete(self))\n            session.commit()\n            log.info(\n                f\"deleted {num_rows_deleted.rowcount} rows from table '{self.__tablename__}'\"\n            )\n\n        return num_rows_deleted.rowcount\n\n    @classmethod\n    def read_as_df(self, config: DictConfig, **kwargs) -> pd.DataFrame:\n        \"\"\"Read table as pandas DataFrame.\n\n        Args:\n            config (DictConfig): Hydra configuration dictionary.\n\n        Returns:\n            pd.DataFrame: dataframe with table content.\n        \"\"\"\n        df = pd.read_sql_table(\n            self.__tablename__,\n            create_engine(config=config),\n            schema=config.db.get(\"schema\", SCHEMA),\n            **kwargs,\n        )\n\n        return df\n\n    def __repr__(self) -> str:\n        \"\"\"Simple object representation.\n\n        Returns:\n            str: string representation.\n        \"\"\"\n        return f\"<ORDER:{self.user}, {self.menu_item.item}>\"\n
"},{"location":"reference/dlunch/models/#dlunch.models.Orders.__tablename__","title":"__tablename__ class-attribute instance-attribute","text":"
__tablename__ = 'orders'\n

Name of the table.

"},{"location":"reference/dlunch/models/#dlunch.models.Orders.id","title":"id class-attribute instance-attribute","text":"
id = Column(\n    Integer, Identity(start=1, cycle=True), primary_key=True\n)\n

Order ID.

"},{"location":"reference/dlunch/models/#dlunch.models.Orders.menu_item","title":"menu_item class-attribute instance-attribute","text":"
menu_item = relationship('Menu', back_populates='orders')\n

Menu items connected to each order (see menu table).

"},{"location":"reference/dlunch/models/#dlunch.models.Orders.menu_item_id","title":"menu_item_id class-attribute instance-attribute","text":"
menu_item_id = Column(\n    Integer,\n    ForeignKey(\"menu.id\", ondelete=\"CASCADE\"),\n    nullable=False,\n)\n

ID of the menu item included in the order.

"},{"location":"reference/dlunch/models/#dlunch.models.Orders.note","title":"note class-attribute instance-attribute","text":"
note = Column(String(300), unique=False, nullable=True)\n

Note field attached to the order.

"},{"location":"reference/dlunch/models/#dlunch.models.Orders.user","title":"user class-attribute instance-attribute","text":"
user = Column(\n    String(100),\n    ForeignKey(\"users.id\", ondelete=\"CASCADE\"),\n    index=True,\n    nullable=False,\n)\n

User that placed the order.

"},{"location":"reference/dlunch/models/#dlunch.models.Orders.user_record","title":"user_record class-attribute instance-attribute","text":"
user_record = relationship(\n    \"Users\", back_populates=\"orders\", uselist=False\n)\n

User connected to this order.

"},{"location":"reference/dlunch/models/#dlunch.models.Orders.__repr__","title":"__repr__","text":"
__repr__() -> str\n

Simple object representation.

Returns:

Type Description str

string representation.

Source code in dlunch/models.py
def __repr__(self) -> str:\n    \"\"\"Simple object representation.\n\n    Returns:\n        str: string representation.\n    \"\"\"\n    return f\"<ORDER:{self.user}, {self.menu_item.item}>\"\n
"},{"location":"reference/dlunch/models/#dlunch.models.Orders.clear","title":"clear classmethod","text":"
clear(config: DictConfig) -> int\n

Clear table and return deleted rows.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description int

deleted rows.

Source code in dlunch/models.py
@classmethod\ndef clear(self, config: DictConfig) -> int:\n    \"\"\"Clear table and return deleted rows.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        int: deleted rows.\n    \"\"\"\n\n    session = create_session(config)\n    with session:\n        # Clean menu\n        num_rows_deleted = session.execute(delete(self))\n        session.commit()\n        log.info(\n            f\"deleted {num_rows_deleted.rowcount} rows from table '{self.__tablename__}'\"\n        )\n\n    return num_rows_deleted.rowcount\n
"},{"location":"reference/dlunch/models/#dlunch.models.Orders.read_as_df","title":"read_as_df classmethod","text":"
read_as_df(config: DictConfig, **kwargs) -> DataFrame\n

Read table as pandas DataFrame.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description DataFrame

dataframe with table content.

Source code in dlunch/models.py
@classmethod\ndef read_as_df(self, config: DictConfig, **kwargs) -> pd.DataFrame:\n    \"\"\"Read table as pandas DataFrame.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        pd.DataFrame: dataframe with table content.\n    \"\"\"\n    df = pd.read_sql_table(\n        self.__tablename__,\n        create_engine(config=config),\n        schema=config.db.get(\"schema\", SCHEMA),\n        **kwargs,\n    )\n\n    return df\n
"},{"location":"reference/dlunch/models/#dlunch.models.Password","title":"Password","text":"

Bases: TypeDecorator

Allows storing and retrieving password hashes using PasswordHash.

Methods:

Name Description process_bind_param

Ensure the value is a PasswordHash and then return its hash.

process_result_value

Convert the hash to a PasswordHash, if it's non-NULL.

validator

Provides a validator/converter used by @validates.

Attributes:

Name Type Description impl String

Base column implementation.

Source code in dlunch/models.py
class Password(TypeDecorator):\n    \"\"\"Allows storing and retrieving password hashes using PasswordHash.\"\"\"\n\n    impl: String = String\n    \"\"\"Base column implementation.\"\"\"\n\n    def process_bind_param(\n        self, value: auth.PasswordHash | str | None, dialect\n    ) -> str:\n        \"\"\"Ensure the value is a PasswordHash and then return its hash.\n\n        Args:\n            value (auth.PasswordHash | str): input value (plain password or hash, or `None` if empty).\n            dialect (Any): dialect (not used).\n\n        Returns:\n            str: password hash.\n        \"\"\"\n        return self._convert(value).hashed_password\n\n    def process_result_value(\n        self, value: str | None, dialect\n    ) -> auth.PasswordHash | None:\n        \"\"\"Convert the hash to a PasswordHash, if it's non-NULL.\n\n        Args:\n            value (str | None): password hash (or `None` if empty).\n            dialect (Any): dialect (not used).\n\n        Returns:\n            auth.PasswordHash | None: hashed password as object or `None` (if nothing is passed as value).\n        \"\"\"\n        if value is not None:\n            return auth.PasswordHash(value)\n\n    def validator(\n        self, password: auth.PasswordHash | str | None\n    ) -> auth.PasswordHash | None:\n        \"\"\"Provides a validator/converter used by @validates.\n\n        Args:\n            password (auth.PasswordHash | str | None): input value (plain password or hash or `None` if empty).\n\n        Returns:\n            auth.PasswordHash | None: hashed password as object or `None` (if nothing is passed as value).\n        \"\"\"\n        return self._convert(password)\n\n    def _convert(\n        self, value: auth.PasswordHash | str | None\n    ) -> auth.PasswordHash | None:\n        \"\"\"Returns a PasswordHash from the given string.\n\n        PasswordHash instances or None values will return unchanged.\n        Strings will be hashed and the resulting PasswordHash returned.\n        Any other input will result in a TypeError.\n\n        Args:\n            value (auth.PasswordHash | str | None): input value (plain password or hash or `None` if empty).\n\n        Raises:\n            TypeError: unknown type.\n\n        Returns:\n            auth.PasswordHash | None: hashed password as object or `None` (if nothing is passed as value).\n        \"\"\"\n        if isinstance(value, auth.PasswordHash):\n            return value\n        elif isinstance(value, str):\n            return auth.PasswordHash.from_str(value)\n        elif value is not None:\n            raise TypeError(\n                f\"Cannot initialize PasswordHash from type '{type(value)}'\"\n            )\n\n        # Reached only if value is None\n        return None\n
"},{"location":"reference/dlunch/models/#dlunch.models.Password.impl","title":"impl class-attribute instance-attribute","text":"
impl: String = String\n

Base column implementation.

"},{"location":"reference/dlunch/models/#dlunch.models.Password.process_bind_param","title":"process_bind_param","text":"
process_bind_param(\n    value: PasswordHash | str | None, dialect\n) -> str\n

Ensure the value is a PasswordHash and then return its hash.

Parameters:

Name Type Description Default value PasswordHash | str

input value (plain password or hash, or None if empty).

required dialect Any

dialect (not used).

required

Returns:

Type Description str

password hash.

Source code in dlunch/models.py
def process_bind_param(\n    self, value: auth.PasswordHash | str | None, dialect\n) -> str:\n    \"\"\"Ensure the value is a PasswordHash and then return its hash.\n\n    Args:\n        value (auth.PasswordHash | str): input value (plain password or hash, or `None` if empty).\n        dialect (Any): dialect (not used).\n\n    Returns:\n        str: password hash.\n    \"\"\"\n    return self._convert(value).hashed_password\n
"},{"location":"reference/dlunch/models/#dlunch.models.Password.process_result_value","title":"process_result_value","text":"
process_result_value(\n    value: str | None, dialect\n) -> PasswordHash | None\n

Convert the hash to a PasswordHash, if it's non-NULL.

Parameters:

Name Type Description Default value str | None

password hash (or None if empty).

required dialect Any

dialect (not used).

required

Returns:

Type Description PasswordHash | None

hashed password as object or None (if nothing is passed as value).

Source code in dlunch/models.py
def process_result_value(\n    self, value: str | None, dialect\n) -> auth.PasswordHash | None:\n    \"\"\"Convert the hash to a PasswordHash, if it's non-NULL.\n\n    Args:\n        value (str | None): password hash (or `None` if empty).\n        dialect (Any): dialect (not used).\n\n    Returns:\n        auth.PasswordHash | None: hashed password as object or `None` (if nothing is passed as value).\n    \"\"\"\n    if value is not None:\n        return auth.PasswordHash(value)\n
"},{"location":"reference/dlunch/models/#dlunch.models.Password.validator","title":"validator","text":"
validator(\n    password: PasswordHash | str | None,\n) -> PasswordHash | None\n

Provides a validator/converter used by @validates.

Parameters:

Name Type Description Default password PasswordHash | str | None

input value (plain password or hash or None if empty).

required

Returns:

Type Description PasswordHash | None

hashed password as object or None (if nothing is passed as value).

Source code in dlunch/models.py
def validator(\n    self, password: auth.PasswordHash | str | None\n) -> auth.PasswordHash | None:\n    \"\"\"Provides a validator/converter used by @validates.\n\n    Args:\n        password (auth.PasswordHash | str | None): input value (plain password or hash or `None` if empty).\n\n    Returns:\n        auth.PasswordHash | None: hashed password as object or `None` (if nothing is passed as value).\n    \"\"\"\n    return self._convert(password)\n
"},{"location":"reference/dlunch/models/#dlunch.models.PrivilegedUsers","title":"PrivilegedUsers","text":"

Bases: Data

Table with user that have privileges (normal users and admin).

If enabled guests are all the authenticated users that do not belong to this table (see config key auth.authorize_guest_users and basic_auth.guest_user)

Methods:

Name Description __repr__

Simple object representation.

clear

Clear table and return deleted rows.

read_as_df

Read table as pandas DataFrame.

Attributes:

Name Type Description __tablename__

Name of the table.

admin

Admin flag (true if admin).

user

User name.

Source code in dlunch/models.py
class PrivilegedUsers(Data):\n    \"\"\"Table with user that have privileges (normal users and admin).\n\n    If enabled guests are all the authenticated users that do not belong to this table\n    (see config key `auth.authorize_guest_users` and `basic_auth.guest_user`)\n    \"\"\"\n\n    __tablename__ = \"privileged_users\"\n    \"\"\"Name of the table.\"\"\"\n    user = Column(\n        String(100),\n        primary_key=True,\n        sqlite_on_conflict_primary_key=\"REPLACE\",\n    )\n    \"\"\"User name.\"\"\"\n    admin = Column(\n        Boolean, nullable=False, default=False, server_default=sql_false()\n    )\n    \"\"\"Admin flag (true if admin).\"\"\"\n\n    @classmethod\n    def clear(self, config: DictConfig) -> int:\n        \"\"\"Clear table and return deleted rows.\n\n        Args:\n            config (DictConfig): Hydra configuration dictionary.\n\n        Returns:\n            int: deleted rows.\n        \"\"\"\n\n        session = create_session(config)\n        with session:\n            # Clean menu\n            num_rows_deleted = session.execute(delete(self))\n            session.commit()\n            log.info(\n                f\"deleted {num_rows_deleted.rowcount} rows from table '{self.__tablename__}'\"\n            )\n\n        return num_rows_deleted.rowcount\n\n    @classmethod\n    def read_as_df(self, config: DictConfig, **kwargs) -> pd.DataFrame:\n        \"\"\"Read table as pandas DataFrame.\n\n        Args:\n            config (DictConfig): Hydra configuration dictionary.\n\n        Returns:\n            pd.DataFrame: dataframe with table content.\n        \"\"\"\n        df = pd.read_sql_table(\n            self.__tablename__,\n            create_engine(config=config),\n            schema=config.db.get(\"schema\", SCHEMA),\n            **kwargs,\n        )\n\n        return df\n\n    def __repr__(self) -> str:\n        \"\"\"Simple object representation.\n\n        Returns:\n            str: string representation.\n        \"\"\"\n        return f\"<PRIVILEGED_USER:{self.id}>\"\n
"},{"location":"reference/dlunch/models/#dlunch.models.PrivilegedUsers.__tablename__","title":"__tablename__ class-attribute instance-attribute","text":"
__tablename__ = 'privileged_users'\n

Name of the table.

"},{"location":"reference/dlunch/models/#dlunch.models.PrivilegedUsers.admin","title":"admin class-attribute instance-attribute","text":"
admin = Column(\n    Boolean,\n    nullable=False,\n    default=False,\n    server_default=false(),\n)\n

Admin flag (true if admin).

"},{"location":"reference/dlunch/models/#dlunch.models.PrivilegedUsers.user","title":"user class-attribute instance-attribute","text":"
user = Column(\n    String(100),\n    primary_key=True,\n    sqlite_on_conflict_primary_key=\"REPLACE\",\n)\n

User name.

"},{"location":"reference/dlunch/models/#dlunch.models.PrivilegedUsers.__repr__","title":"__repr__","text":"
__repr__() -> str\n

Simple object representation.

Returns:

Type Description str

string representation.

Source code in dlunch/models.py
def __repr__(self) -> str:\n    \"\"\"Simple object representation.\n\n    Returns:\n        str: string representation.\n    \"\"\"\n    return f\"<PRIVILEGED_USER:{self.id}>\"\n
"},{"location":"reference/dlunch/models/#dlunch.models.PrivilegedUsers.clear","title":"clear classmethod","text":"
clear(config: DictConfig) -> int\n

Clear table and return deleted rows.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description int

deleted rows.

Source code in dlunch/models.py
@classmethod\ndef clear(self, config: DictConfig) -> int:\n    \"\"\"Clear table and return deleted rows.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        int: deleted rows.\n    \"\"\"\n\n    session = create_session(config)\n    with session:\n        # Clean menu\n        num_rows_deleted = session.execute(delete(self))\n        session.commit()\n        log.info(\n            f\"deleted {num_rows_deleted.rowcount} rows from table '{self.__tablename__}'\"\n        )\n\n    return num_rows_deleted.rowcount\n
"},{"location":"reference/dlunch/models/#dlunch.models.PrivilegedUsers.read_as_df","title":"read_as_df classmethod","text":"
read_as_df(config: DictConfig, **kwargs) -> DataFrame\n

Read table as pandas DataFrame.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description DataFrame

dataframe with table content.

Source code in dlunch/models.py
@classmethod\ndef read_as_df(self, config: DictConfig, **kwargs) -> pd.DataFrame:\n    \"\"\"Read table as pandas DataFrame.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        pd.DataFrame: dataframe with table content.\n    \"\"\"\n    df = pd.read_sql_table(\n        self.__tablename__,\n        create_engine(config=config),\n        schema=config.db.get(\"schema\", SCHEMA),\n        **kwargs,\n    )\n\n    return df\n
"},{"location":"reference/dlunch/models/#dlunch.models.Stats","title":"Stats","text":"

Bases: Data

Table with number of users that ate a lunch, grouped by guest type.

Methods:

Name Description __repr__

Simple object representation.

clear

Clear table and return deleted rows.

read_as_df

Read table as pandas DataFrame.

Attributes:

Name Type Description __table_args__

Table arguments, used to create primary key (ON CONFLICT options for composite

__tablename__

Name of the table.

date

Day to which the statistics refers to.

guest

Different kind of guests are identified by the value defined in config files

hungry_people

Number of people that ate in a certain day.

Source code in dlunch/models.py
class Stats(Data):\n    \"\"\"Table with number of users that ate a lunch, grouped by guest type.\"\"\"\n\n    # Primary key handled with __table_args__ because ON CONFLICT for composite\n    # primary key is available only with __table_args__\n    __tablename__ = \"stats\"\n    \"\"\"Name of the table.\"\"\"\n    __table_args__ = (\n        PrimaryKeyConstraint(\n            \"date\", \"guest\", name=\"stats_pkey\", sqlite_on_conflict=\"REPLACE\"\n        ),\n    )\n    \"\"\"Table arguments, used to create primary key (ON CONFLICT options for composite\n    primary key is available only with __table_args__).\"\"\"\n    date = Column(\n        Date,\n        nullable=False,\n        server_default=func.current_date(),\n    )\n    \"\"\"Day to which the statistics refers to.\"\"\"\n    guest = Column(\n        String(20),\n        nullable=True,\n        default=\"NotAGuest\",\n        server_default=\"NotAGuest\",\n    )\n    \"\"\"Different kind of guests are identified by the value defined in config files\n    (see config key `panel.guest_types`).\n    'NotAGuest' is the value used for locals.\n    \"\"\"\n    hungry_people = Column(\n        Integer, nullable=False, default=0, server_default=\"0\"\n    )\n    \"\"\"Number of people that ate in a certain day.\n    different kind of guests are identified by the value in guest column.\n    \"\"\"\n\n    @classmethod\n    def clear(self, config: DictConfig) -> int:\n        \"\"\"Clear table and return deleted rows.\n\n        Args:\n            config (DictConfig): Hydra configuration dictionary.\n\n        Returns:\n            int: deleted rows.\n        \"\"\"\n\n        session = create_session(config)\n        with session:\n            # Clean menu\n            num_rows_deleted = session.execute(delete(self))\n            session.commit()\n            log.info(\n                f\"deleted {num_rows_deleted.rowcount} rows from table '{self.__tablename__}'\"\n            )\n\n        return num_rows_deleted.rowcount\n\n    @classmethod\n    def read_as_df(self, config: DictConfig, **kwargs) -> pd.DataFrame:\n        \"\"\"Read table as pandas DataFrame.\n\n        Args:\n            config (DictConfig): Hydra configuration dictionary.\n\n        Returns:\n            pd.DataFrame: dataframe with table content.\n        \"\"\"\n        df = pd.read_sql_table(\n            self.__tablename__,\n            create_engine(config=config),\n            schema=config.db.get(\"schema\", SCHEMA),\n            **kwargs,\n        )\n\n        return df\n\n    def __repr__(self) -> str:\n        \"\"\"Simple object representation.\n\n        Returns:\n            str: string representation.\n        \"\"\"\n        return f\"<STAT:{self.id} - HP:{self.hungry_people} - HG:{self.hungry_guests}>\"\n
"},{"location":"reference/dlunch/models/#dlunch.models.Stats.__table_args__","title":"__table_args__ class-attribute instance-attribute","text":"
__table_args__ = (\n    PrimaryKeyConstraint(\n        \"date\",\n        \"guest\",\n        name=\"stats_pkey\",\n        sqlite_on_conflict=\"REPLACE\",\n    ),\n)\n

Table arguments, used to create primary key (ON CONFLICT options for composite primary key is available only with table_args).

"},{"location":"reference/dlunch/models/#dlunch.models.Stats.__tablename__","title":"__tablename__ class-attribute instance-attribute","text":"
__tablename__ = 'stats'\n

Name of the table.

"},{"location":"reference/dlunch/models/#dlunch.models.Stats.date","title":"date class-attribute instance-attribute","text":"
date = Column(\n    Date, nullable=False, server_default=current_date()\n)\n

Day to which the statistics refers to.

"},{"location":"reference/dlunch/models/#dlunch.models.Stats.guest","title":"guest class-attribute instance-attribute","text":"
guest = Column(\n    String(20),\n    nullable=True,\n    default=\"NotAGuest\",\n    server_default=\"NotAGuest\",\n)\n

Different kind of guests are identified by the value defined in config files (see config key panel.guest_types). 'NotAGuest' is the value used for locals.

"},{"location":"reference/dlunch/models/#dlunch.models.Stats.hungry_people","title":"hungry_people class-attribute instance-attribute","text":"
hungry_people = Column(\n    Integer, nullable=False, default=0, server_default=\"0\"\n)\n

Number of people that ate in a certain day. different kind of guests are identified by the value in guest column.

"},{"location":"reference/dlunch/models/#dlunch.models.Stats.__repr__","title":"__repr__","text":"
__repr__() -> str\n

Simple object representation.

Returns:

Type Description str

string representation.

Source code in dlunch/models.py
def __repr__(self) -> str:\n    \"\"\"Simple object representation.\n\n    Returns:\n        str: string representation.\n    \"\"\"\n    return f\"<STAT:{self.id} - HP:{self.hungry_people} - HG:{self.hungry_guests}>\"\n
"},{"location":"reference/dlunch/models/#dlunch.models.Stats.clear","title":"clear classmethod","text":"
clear(config: DictConfig) -> int\n

Clear table and return deleted rows.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description int

deleted rows.

Source code in dlunch/models.py
@classmethod\ndef clear(self, config: DictConfig) -> int:\n    \"\"\"Clear table and return deleted rows.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        int: deleted rows.\n    \"\"\"\n\n    session = create_session(config)\n    with session:\n        # Clean menu\n        num_rows_deleted = session.execute(delete(self))\n        session.commit()\n        log.info(\n            f\"deleted {num_rows_deleted.rowcount} rows from table '{self.__tablename__}'\"\n        )\n\n    return num_rows_deleted.rowcount\n
"},{"location":"reference/dlunch/models/#dlunch.models.Stats.read_as_df","title":"read_as_df classmethod","text":"
read_as_df(config: DictConfig, **kwargs) -> DataFrame\n

Read table as pandas DataFrame.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description DataFrame

dataframe with table content.

Source code in dlunch/models.py
@classmethod\ndef read_as_df(self, config: DictConfig, **kwargs) -> pd.DataFrame:\n    \"\"\"Read table as pandas DataFrame.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        pd.DataFrame: dataframe with table content.\n    \"\"\"\n    df = pd.read_sql_table(\n        self.__tablename__,\n        create_engine(config=config),\n        schema=config.db.get(\"schema\", SCHEMA),\n        **kwargs,\n    )\n\n    return df\n
"},{"location":"reference/dlunch/models/#dlunch.models.Users","title":"Users","text":"

Bases: Data

Table with users that placed an order.

Methods:

Name Description __repr__

Simple object representation.

clear

Clear table and return deleted rows.

read_as_df

Read table as pandas DataFrame.

Attributes:

Name Type Description __tablename__

Name of the table.

guest

Guest flag (true if guest).

id

User ID.

lunch_time

User selected lunch time.

orders

Orders connected to each user.

takeaway

Takeaway flag (true if takeaway).

Source code in dlunch/models.py
class Users(Data):\n    \"\"\"Table with users that placed an order.\"\"\"\n\n    __tablename__ = \"users\"\n    \"\"\"Name of the table.\"\"\"\n    id = Column(\n        String(100),\n        primary_key=True,\n        nullable=False,\n    )\n    \"\"\"User ID.\"\"\"\n    guest = Column(\n        String(20),\n        nullable=False,\n        default=\"NotAGuest\",\n        server_default=\"NotAGuest\",\n    )\n    \"\"\"Guest flag (true if guest).\"\"\"\n    lunch_time = Column(String(7), index=True, nullable=False)\n    \"\"\"User selected lunch time.\"\"\"\n    takeaway = Column(\n        Boolean, nullable=False, default=False, server_default=sql_false()\n    )\n    \"\"\"Takeaway flag (true if takeaway).\"\"\"\n    orders = relationship(\n        \"Orders\",\n        back_populates=\"user_record\",\n        cascade=\"all, delete-orphan\",\n        passive_deletes=True,\n    )\n    \"\"\"Orders connected to each user.\"\"\"\n\n    @classmethod\n    def clear(self, config: DictConfig) -> int:\n        \"\"\"Clear table and return deleted rows.\n\n        Args:\n            config (DictConfig): Hydra configuration dictionary.\n\n        Returns:\n            int: deleted rows.\n        \"\"\"\n\n        session = create_session(config)\n        with session:\n            # Clean menu\n            num_rows_deleted = session.execute(delete(self))\n            session.commit()\n            log.info(\n                f\"deleted {num_rows_deleted.rowcount} rows from table '{self.__tablename__}'\"\n            )\n\n        return num_rows_deleted.rowcount\n\n    @classmethod\n    def read_as_df(self, config: DictConfig, **kwargs) -> pd.DataFrame:\n        \"\"\"Read table as pandas DataFrame.\n\n        Args:\n            config (DictConfig): Hydra configuration dictionary.\n\n        Returns:\n            pd.DataFrame: dataframe with table content.\n        \"\"\"\n        df = pd.read_sql_table(\n            self.__tablename__,\n            create_engine(config=config),\n            schema=config.db.get(\"schema\", SCHEMA),\n            **kwargs,\n        )\n\n        return df\n\n    def __repr__(self) -> str:\n        \"\"\"Simple object representation.\n\n        Returns:\n            str: string representation.\n        \"\"\"\n        return f\"<USER:{self.id}>\"\n
"},{"location":"reference/dlunch/models/#dlunch.models.Users.__tablename__","title":"__tablename__ class-attribute instance-attribute","text":"
__tablename__ = 'users'\n

Name of the table.

"},{"location":"reference/dlunch/models/#dlunch.models.Users.guest","title":"guest class-attribute instance-attribute","text":"
guest = Column(\n    String(20),\n    nullable=False,\n    default=\"NotAGuest\",\n    server_default=\"NotAGuest\",\n)\n

Guest flag (true if guest).

"},{"location":"reference/dlunch/models/#dlunch.models.Users.id","title":"id class-attribute instance-attribute","text":"
id = Column(String(100), primary_key=True, nullable=False)\n

User ID.

"},{"location":"reference/dlunch/models/#dlunch.models.Users.lunch_time","title":"lunch_time class-attribute instance-attribute","text":"
lunch_time = Column(String(7), index=True, nullable=False)\n

User selected lunch time.

"},{"location":"reference/dlunch/models/#dlunch.models.Users.orders","title":"orders class-attribute instance-attribute","text":"
orders = relationship(\n    \"Orders\",\n    back_populates=\"user_record\",\n    cascade=\"all, delete-orphan\",\n    passive_deletes=True,\n)\n

Orders connected to each user.

"},{"location":"reference/dlunch/models/#dlunch.models.Users.takeaway","title":"takeaway class-attribute instance-attribute","text":"
takeaway = Column(\n    Boolean,\n    nullable=False,\n    default=False,\n    server_default=false(),\n)\n

Takeaway flag (true if takeaway).

"},{"location":"reference/dlunch/models/#dlunch.models.Users.__repr__","title":"__repr__","text":"
__repr__() -> str\n

Simple object representation.

Returns:

Type Description str

string representation.

Source code in dlunch/models.py
def __repr__(self) -> str:\n    \"\"\"Simple object representation.\n\n    Returns:\n        str: string representation.\n    \"\"\"\n    return f\"<USER:{self.id}>\"\n
"},{"location":"reference/dlunch/models/#dlunch.models.Users.clear","title":"clear classmethod","text":"
clear(config: DictConfig) -> int\n

Clear table and return deleted rows.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description int

deleted rows.

Source code in dlunch/models.py
@classmethod\ndef clear(self, config: DictConfig) -> int:\n    \"\"\"Clear table and return deleted rows.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        int: deleted rows.\n    \"\"\"\n\n    session = create_session(config)\n    with session:\n        # Clean menu\n        num_rows_deleted = session.execute(delete(self))\n        session.commit()\n        log.info(\n            f\"deleted {num_rows_deleted.rowcount} rows from table '{self.__tablename__}'\"\n        )\n\n    return num_rows_deleted.rowcount\n
"},{"location":"reference/dlunch/models/#dlunch.models.Users.read_as_df","title":"read_as_df classmethod","text":"
read_as_df(config: DictConfig, **kwargs) -> DataFrame\n

Read table as pandas DataFrame.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description DataFrame

dataframe with table content.

Source code in dlunch/models.py
@classmethod\ndef read_as_df(self, config: DictConfig, **kwargs) -> pd.DataFrame:\n    \"\"\"Read table as pandas DataFrame.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        pd.DataFrame: dataframe with table content.\n    \"\"\"\n    df = pd.read_sql_table(\n        self.__tablename__,\n        create_engine(config=config),\n        schema=config.db.get(\"schema\", SCHEMA),\n        **kwargs,\n    )\n\n    return df\n
"},{"location":"reference/dlunch/models/#dlunch.models.create_database","title":"create_database","text":"
create_database(\n    config: DictConfig, add_basic_auth_users=False\n) -> None\n

Function to create the database through SQLAlchemy models.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required add_basic_auth_users bool

set to true to add admin and guest users. These users are of interest only if 'basic authentication' is selected. Defaults to False.

False Source code in dlunch/models.py
def create_database(config: DictConfig, add_basic_auth_users=False) -> None:\n    \"\"\"Function to create the database through SQLAlchemy models.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n        add_basic_auth_users (bool, optional): set to true to add admin and guest users.\n            These users are of interest only if 'basic authentication' is selected.\n            Defaults to False.\n    \"\"\"\n    # Create directory if missing\n    log.debug(\"create 'shared_data' folder\")\n    pathlib.Path(config.db.shared_data_folder).mkdir(exist_ok=True)\n\n    # In case the database is not ready use a retry mechanism\n    @tenacity.retry(\n        retry=tenacity.retry_if_exception_type(OperationalError),\n        wait=tenacity.wait_fixed(config.db.create_retries.wait),\n        stop=(\n            tenacity.stop_after_delay(config.db.create_retries.stop.delay)\n            | tenacity.stop_after_attempt(\n                config.db.create_retries.stop.attempts\n            )\n        ),\n    )\n    def _create_database_with_retries(config: DictConfig) -> None:\n        engine = create_engine(config)\n        Data.metadata.create_all(engine)\n\n    # Create tables\n    log.debug(f\"attempt database creation: {config.db.attempt_creation}\")\n    if config.db.attempt_creation:\n        _create_database_with_retries(config)\n\n        # Retries stats\n        log.debug(\n            f\"create database attempts: {_create_database_with_retries.retry.statistics}\"\n        )\n\n    # If requested add users for basic auth (admin and guest)\n    if add_basic_auth_users:\n        log.debug(\"add basic auth standard users\")\n        # If no user exist create the default admin\n        session = create_session(config)\n\n        with session:\n            # Check if admin exists\n            if session.get(Credentials, \"admin\") is None:\n                # Add authorization and credentials for admin\n                auth.add_privileged_user(\n                    user=\"admin\",\n                    is_admin=True,\n                    config=config,\n                )\n                auth.add_user_hashed_password(\n                    user=\"admin\",\n                    password=\"admin\",\n                    config=config,\n                )\n            # Check if guest exists\n            if (\n                session.get(Credentials, \"guest\") is None\n            ) and config.basic_auth.guest_user:\n                # Add only credentials for guest (guest users are not included\n                # in privileged_users table)\n                auth.add_user_hashed_password(\n                    user=\"guest\",\n                    password=\"guest\",\n                    config=config,\n                )\n
"},{"location":"reference/dlunch/models/#dlunch.models.create_engine","title":"create_engine","text":"
create_engine(config: DictConfig) -> Engine\n

Factory function for SQLAlchemy engine.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description Engine

SQLAlchemy engine.

Source code in dlunch/models.py
def create_engine(config: DictConfig) -> Engine:\n    \"\"\"Factory function for SQLAlchemy engine.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        Engine: SQLAlchemy engine.\n    \"\"\"\n    engine = hydra.utils.instantiate(config.db.engine)\n\n    # Change schema with change_execution_options\n    # If schema exist in config.db it will override the schema selected through\n    # the environment variable\n    if \"schema\" in config.db:\n        engine.update_execution_options(\n            schema_translate_map={SCHEMA: config.db.schema}\n        )\n\n    return engine\n
"},{"location":"reference/dlunch/models/#dlunch.models.create_exclusive_session","title":"create_exclusive_session","text":"
create_exclusive_session(config: DictConfig) -> Session\n

Factory function for database exclusive session. Database is locked until the transaction is completed (to be used to avoid race conditions).

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description Session

SQLAlchemy exclusive session.

Note

This function is not used inside Data-Lunch code.

Source code in dlunch/models.py
def create_exclusive_session(config: DictConfig) -> Session:\n    \"\"\"Factory function for database exclusive session.\n    Database is locked until the transaction is completed (to be used to avoid\n    race conditions).\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        Session: SQLAlchemy exclusive session.\n\n    Note:\n        This function is not used inside Data-Lunch code.\n\n    \"\"\"\n    engine = create_engine(config)\n\n    # Alter begin statement\n    @event.listens_for(engine, \"connect\")\n    def _do_connect(dbapi_connection, connection_record):\n        # disable pysqlite's emitting of the BEGIN statement entirely.\n        # also stops it from emitting COMMIT before any DDL.\n        dbapi_connection.isolation_level = None\n\n    @event.listens_for(engine, \"begin\")\n    def _do_begin(conn):\n        # Emit exclusive BEGIN\n        conn.exec_driver_sql(\"BEGIN EXCLUSIVE\")\n\n    session = Session(engine)\n\n    return session\n
"},{"location":"reference/dlunch/models/#dlunch.models.create_session","title":"create_session","text":"
create_session(config: DictConfig) -> Session\n

Factory function for database session.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description Session

SQLAlchemy session.

Source code in dlunch/models.py
def create_session(config: DictConfig) -> Session:\n    \"\"\"Factory function for database session.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        Session: SQLAlchemy session.\n    \"\"\"\n    engine = create_engine(config)\n    session = Session(engine)\n\n    return session\n
"},{"location":"reference/dlunch/models/#dlunch.models.get_db_dialect","title":"get_db_dialect","text":"
get_db_dialect(\n    db_obj: Session | Connection | Connection,\n) -> str\n

Return database type (postgresql, sqlite, etc.) based on the database object passed as input. If a session is passed, the database type is set based on an internal map (see models._DBTYPE_MAP).

Parameters:

Name Type Description Default db_obj Session | Connection | Connection

session or connection object.

required

Raises:

Type Description TypeError

db_obj shall be a session or a connection object.

Returns:

Type Description str

database dialect.

Source code in dlunch/models.py
def get_db_dialect(\n    db_obj: Session | ConnectionSqlite | ConnectionPostgresql,\n) -> str:\n    \"\"\"Return database type (postgresql, sqlite, etc.) based on the database object passed as input.\n    If a session is passed, the database type is set based on an internal map (see `models._DBTYPE_MAP`).\n\n    Args:\n        db_obj (Session | ConnectionSqlite | ConnectionPostgresql): session or connection object.\n\n    Raises:\n        TypeError: db_obj shall be a session or a connection object.\n\n    Returns:\n        str: database dialect.\n    \"\"\"\n    if isinstance(db_obj, Session):\n        dialect = db_obj.bind.dialect.name\n    elif isinstance(db_obj, ConnectionSqlite) or isinstance(\n        db_obj, ConnectionPostgresql\n    ):\n        module = db_obj.__class__.__module__.split(\".\", 1)[0]\n        dialect = _MODULE_TO_DIALECT_MAP[module]\n    else:\n        raise TypeError(\"db_obj should be a session or connection object\")\n\n    return dialect\n
"},{"location":"reference/dlunch/models/#dlunch.models.get_flag","title":"get_flag","text":"
get_flag(\n    config: DictConfig,\n    id: str,\n    value_if_missing: bool | None = None,\n) -> bool | None\n

Get the value of a flag. Optionally select the values to return if the flag is missing (default to None).

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required id str

flag id (name).

required value_if_missing bool | None

value to return if the flag does not exist. Defaults to None.

None

Returns:

Type Description bool | None

flag value.

Source code in dlunch/models.py
def get_flag(\n    config: DictConfig, id: str, value_if_missing: bool | None = None\n) -> bool | None:\n    \"\"\"Get the value of a flag.\n    Optionally select the values to return if the flag is missing (default to None).\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n        id (str): flag id (name).\n        value_if_missing (bool | None, optional): value to return if the flag does not exist. Defaults to None.\n\n    Returns:\n        bool | None: flag value.\n    \"\"\"\n\n    session = create_session(config)\n    flag = session.get(Flags, id)\n    if flag is None:\n        value = value_if_missing\n    else:\n        value = flag.value\n    return value\n
"},{"location":"reference/dlunch/models/#dlunch.models.session_add_with_upsert","title":"session_add_with_upsert","text":"
session_add_with_upsert(\n    session: Session,\n    constraint: str,\n    new_record: Stats | Flags,\n) -> None\n

Use an upsert statement for postgresql to add a new record to a table, a simple session add otherwise.

Parameters:

Name Type Description Default session Session

SQLAlchemy session object.

required constraint str

constraint used for upsert (usually the primary key)

required new_record Stats | Flags

table resord (valid tables are stats or flags)

required Source code in dlunch/models.py
def session_add_with_upsert(\n    session: Session, constraint: str, new_record: Stats | Flags\n) -> None:\n    \"\"\"Use an upsert statement for postgresql to add a new record to a table,\n    a simple session add otherwise.\n\n    Args:\n        session (Session): SQLAlchemy session object.\n        constraint (str): constraint used for upsert (usually the primary key)\n        new_record (Stats | Flags): table resord (valid tables are `stats` or `flags`)\n    \"\"\"\n    # Use an upsert for postgresql (for sqlite an 'on conflict replace' is set\n    # on table, so session.add is fine)\n    if get_db_dialect(session) == \"postgresql\":\n        insert_statement = postgresql_upsert(new_record.__table__).values(\n            {\n                column.name: getattr(new_record, column.name)\n                for column in new_record.__table__.c\n                if getattr(new_record, column.name, None) is not None\n            }\n        )\n        upsert_statement = insert_statement.on_conflict_do_update(\n            constraint=constraint,\n            set_={\n                column.name: getattr(insert_statement.excluded, column.name)\n                for column in insert_statement.excluded\n            },\n        )\n        session.execute(upsert_statement)\n    else:\n        session.add(new_record)\n
"},{"location":"reference/dlunch/models/#dlunch.models.set_flag","title":"set_flag","text":"
set_flag(config: DictConfig, id: str, value: bool) -> None\n

Set a key,value pair inside flag table.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required id str

flag id (name).

required value bool

flag value.

required Source code in dlunch/models.py
def set_flag(config: DictConfig, id: str, value: bool) -> None:\n    \"\"\"Set a key,value pair inside `flag` table.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n        id (str): flag id (name).\n        value (bool): flag value.\n    \"\"\"\n\n    session = create_session(config)\n\n    with session:\n        # Write the selected flag (it will be overwritten if exists)\n        new_flag = Flags(id=id, value=value)\n\n        # Use an upsert for postgresql, a simple session add otherwise\n        session_add_with_upsert(\n            session=session, constraint=\"flags_pkey\", new_record=new_flag\n        )\n\n        session.commit()\n
"},{"location":"reference/dlunch/models/#dlunch.models.set_sqlite_pragma","title":"set_sqlite_pragma","text":"
set_sqlite_pragma(dbapi_connection, connection_record)\n

Force foreign key constraints for sqlite connections.

Parameters:

Name Type Description Default dbapi_connection Any

connection to database. Shall have a cursor method.

required connection_record Any

connection record (not used).

required Source code in dlunch/models.py
@event.listens_for(Engine, \"connect\")\ndef set_sqlite_pragma(dbapi_connection, connection_record):\n    \"\"\"Force foreign key constraints for sqlite connections.\n\n    Args:\n        dbapi_connection (Any): connection to database. Shall have a `cursor` method.\n        connection_record (Any): connection record (not used).\n    \"\"\"\n    if get_db_dialect(dbapi_connection) == \"sqlite\":\n        cursor = dbapi_connection.cursor()\n        cursor.execute(\"PRAGMA foreign_keys=ON;\")\n        cursor.close()\n
"},{"location":"reference/dlunch/scheduled_tasks/","title":"scheduled_tasks","text":"

Module with functions used to execute scheduled tasks.

See https://panel.holoviz.org/how_to/callbacks/schedule.html for details.

Modules:

Name Description auth

Module with classes and functions used for authentication and password handling.

cloud

Module with functions to interact with GCP storage service.

models

Module with database tables definitions.

Functions:

Name Description clean_files_db

Return a callable object for cleaning temporary tables and files.

reset_guest_user_password

Return a callable object for resetting guest user password.

upload_db_to_gcp_storage

Return a callable object for uploading database to google cloud storage.

Attributes:

Name Type Description log Logger

Module logger.

"},{"location":"reference/dlunch/scheduled_tasks/#dlunch.scheduled_tasks.log","title":"log module-attribute","text":"
log: Logger = getLogger(__name__)\n

Module logger.

"},{"location":"reference/dlunch/scheduled_tasks/#dlunch.scheduled_tasks.clean_files_db","title":"clean_files_db","text":"
clean_files_db(config: DictConfig) -> callable\n

Return a callable object for cleaning temporary tables and files.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description callable

function to be scheduled.

Source code in dlunch/scheduled_tasks.py
def clean_files_db(config: DictConfig) -> callable:\n    \"\"\"Return a callable object for cleaning temporary tables and files.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        callable: function to be scheduled.\n    \"\"\"\n\n    async def scheduled_function() -> None:\n        \"\"\"Clean menu, orders, users and flags tables. Delete also local files.\"\"\"\n        log.info(f\"clean task (files and db) executed at {dt.datetime.now()}\")\n        # Delete files\n        waiter.delete_files()\n        # Clean tables\n        waiter.clean_tables()\n\n    return scheduled_function\n
"},{"location":"reference/dlunch/scheduled_tasks/#dlunch.scheduled_tasks.reset_guest_user_password","title":"reset_guest_user_password","text":"
reset_guest_user_password(config: DictConfig) -> callable\n

Return a callable object for resetting guest user password.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description callable

function to be scheduled.

Source code in dlunch/scheduled_tasks.py
def reset_guest_user_password(config: DictConfig) -> callable:\n    \"\"\"Return a callable object for resetting guest user password.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        callable: function to be scheduled.\n    \"\"\"\n\n    async def scheduled_function() -> None:\n        \"\"\"Reset guest user password.\"\"\"\n        log.info(f\"reset guest user password executed at {dt.datetime.now()}\")\n        # Change reset flag\n        models.set_flag(\n            config=config, id=\"reset_guest_user_password\", value=True\n        )\n        # Reset password\n        auth.set_guest_user_password(config)\n\n    return scheduled_function\n
"},{"location":"reference/dlunch/scheduled_tasks/#dlunch.scheduled_tasks.upload_db_to_gcp_storage","title":"upload_db_to_gcp_storage","text":"
upload_db_to_gcp_storage(\n    config: DictConfig, **kwargs\n) -> callable\n

Return a callable object for uploading database to google cloud storage.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description callable

function to be scheduled.

Source code in dlunch/scheduled_tasks.py
def upload_db_to_gcp_storage(config: DictConfig, **kwargs) -> callable:\n    \"\"\"Return a callable object for uploading database to google cloud storage.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        callable: function to be scheduled.\n    \"\"\"\n\n    async def scheduled_function() -> None:\n        \"\"\"Upload database to GCP storage.\"\"\"\n        log.info(\n            f\"upload database to gcp storage executed at {dt.datetime.now()}\"\n        )\n        # Upload database\n        cloud.upload_to_gcloud(**kwargs)\n\n    return scheduled_function\n
"},{"location":"reference/dlunch/conf/","title":"conf","text":"

Package with Hydra configuration yaml files.

The following Hydra configuration groups ae available:

  • panel: main Panel configurations (text used in menu and tables, scheduled tasks, other graphic user interface options).
  • db: database dialect (sqlite or postgresql) and specific queries, upload of db to external storage (sqlite only), db table creation at start-up.
  • server: Panel server options and server-level authentication options (basic auth or OAuth).
  • auth: main authentication and authorization options.
  • basic_auth: optional configuration group that add configurations required by basic authentication.
  • hydra/job_logging: override Hydra default logging handlers.
"}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Welcome to Data-Lunch documentation!","text":"

Data-Lunch is your best friends when it's vital to collect and administer lunch orders for a group of people (wether they are friends or teammates).

It started as a small personal project and it quickly became an unrepleaceable companion for me and my colleagues.

"},{"location":"#more-info","title":"More info","text":"

Work in Progress

This section is a work in progress, more info will be available in the future!

"},{"location":"developers/","title":"Developers Instructions","text":""},{"location":"developers/#development-environment-setup","title":"Development environment setup","text":"

The following steps will guide you through the installation procedure.

"},{"location":"developers/#miniconda","title":"Miniconda","text":"

Conda is required for creating the development environment (it is suggested to install Miniconda).

"},{"location":"developers/#setup-the-development-environment","title":"Setup the development environment","text":"

Use the setup script (setup_dev_env.sh) to install all the required development tools.

Use source to properly launch the script.

source setup_dev_env.sh\n

Important

The setup script will take care of setting up the development environment for you. The script installs:

  • 3 environments (data-lunch for development, ci-cd for pre-commit and other utilities, gc-sdk for interacting with Google Cloud Platform)
  • pre-commit hooks
  • data-lunch command line
"},{"location":"developers/#environment-variables","title":"Environment variables","text":"

The following environment variables are required for running the web app, the makefile or utility scripts.

"},{"location":"developers/#general","title":"General","text":"Variable Type Required Description PANEL_APP str \u2714\ufe0f app name, data-lunch-app by default (used by makefile) PANEL_ENV str \u2714\ufe0f environment, e.g. development, quality, production, affects app configuration (Hydra) and build processes (makefile) PANEL_ARGS str \u274c additional arguments passed to Hydra (e.g. panel/gui=major_release) in makefile and docker-compose commands PORT int \u274c port used by the web app (or the container), default to 5000; affects app configuration and build process (it is used by makefile, Hydra and Docker)"},{"location":"developers/#docker-and-google-cloud-platform","title":"Docker and Google Cloud Platform","text":"

Note

The following variables are mainly used during the building process or by external scripts.

Variable Type Required Description DOCKER_USERNAME str \u274c your Docker Hub username, used by makefile and stats panel to extract container name IMAGE_VERSION str \u274c Docker image version, typically stable or latest (used by makefile and docker-compose commands) GCLOUD_PROJECT str \u274c Google Cloud Platform project_id, used by makefile for GCP's CLI authentication and for uploading the database to gcp storage, if active in web app configuration files (see panel.scheduled_tasks) GCLOUD_BUCKET str \u274c Google Cloud Platform bucket, used for uploading database to gcp storage, if active in web app configuration files (see panel.scheduled_tasks) MAIL_USER str \u274c email client user, used for sending emails containing the instance IP, e.g.mywebappemail@email.com (used only for Google Cloud Platform) MAIL_APP_PASSWORD str \u274c email client password used for sending emails containing the instance IP (used only for Google Cloud Platform) MAIL_RECIPIENTS str \u274c email recipients as string, separated by , (used for sending emails containing the instance IP when hosted on Google Cloud Platform) DUCKDNS_URL str \u274c URL used in compose_init.sh to update dynamic address (see Duck DNS's instructions for details, used when hosted on Google Cloud Platform)"},{"location":"developers/#tlsssl-certificate","title":"TLS/SSL Certificate","text":"

Tip

Use the command make ssl-gen-certificate to generate local SSL certificates.

Variable Type Required Description CERT_EMAIL str \u274c email for registering SSL certificates, shared with the authority Let's Encrypt (via certbot); used by docker-compose commands DOMAIN str \u274c domain name, e.g. mywebapp.com, used by docker-compose commands (certbot), in email generation (scripts folder) and to auto-generate SSL certificates"},{"location":"developers/#encryption-and-authorization","title":"Encryption and Authorization","text":"

Note

All variables used by Postgresql are not required if db=sqlite (default value). All variables used by OAuth are not required if server=no_auth (default value).

Important

DATA_LUNCH_COOKIE_SECRET and DATA_LUNCH_OAUTH_ENC_KEY are required even if server=no_auth is set.

Variable Type Required Description DATA_LUNCH_COOKIE_SECRET str \u2714\ufe0f Secret used for securing the authentication cookie (use make generate-secrets to generate a valid secret) DATA_LUNCH_OAUTH_ENC_KEY str \u2714\ufe0f Encription key used by the OAuth algorithm for encryption (use make generate-secrets to generate a valid secret) DATA_LUNCH_OAUTH_KEY str \u274c OAuth key used for configuring the OAuth provider (GitHub, Azure) DATA_LUNCH_OAUTH_SECRET str \u274c OAuth secret used for configuring the OAuth provider (GitHub, Azure) DATA_LUNCH_OAUTH_REDIRECT_URI str \u274c OAuth redirect uri used for configuring the OAuth provider (GitHub, Azure), do not set to use default value DATA_LUNCH_OAUTH_TENANT_ID str \u274c OAuth tenant id used for configuring the OAuth provider (Azure), do not set to use default value DATA_LUNCH_DB_USER str \u274c Postgresql user, do not set to use default value DATA_LUNCH_DB_PASSWORD str \u274c Postgresql password DATA_LUNCH_DB_HOST str \u274c Postgresql host, do not set to use default value DATA_LUNCH_DB_PORT str \u274c Postgresql port, do not set to use default value DATA_LUNCH_DB_DATABASE str \u274c Postgresql database, do not set to use default value DATA_LUNCH_DB_SCHEMA str \u274c Postgresql schema, do not set to use default value"},{"location":"developers/#manually-install-the-development-environment","title":"Manually install the development environment","text":"

Warning

This step is not required if the setup script (setup_dev_env.sh) is used.

Use the terminal for navigating to the repository base directory. Use the following command in your terminal to create an environment named data-lunch manually. Otherwise use the setup script to activate the guided installing procedure.

conda env create -f environment.yml\n

Activate the new Conda environment with the following command.

conda activate data-lunch\n
"},{"location":"developers/#manually-install-data-lunch-cli","title":"Manually install data-lunch CLI","text":"

Warning

This step is not required if the setup script (setup_dev_env.sh) is used.

The CLI is distributed with setuptools instead of using Unix shebangs. It is a very simple utility to initialize and delete the app database. There are different use cases:

  • Create/delete the sqlite database used by the app
  • Initialize/drop tables inside the sqlite database

Use the following command for generating the CLI executable from the setup.py file, it will install your package locally.

pip install .\n

If you want to make some changes to the source code it is suggested to use the following option.

pip install --editable .\n

It will just link the package to the original location, basically meaning any changes to the original package would reflect directly in your environment.

Now you can activate the Conda environment and access the CLI commands directly from the terminal (without using annoying shebangs or prepending python to run your CLI calls).

Test that everything is working correctly with the following commands.

data-lunch --version\ndata-lunch --help\n
"},{"location":"developers/#running-the-docker-compose-system","title":"Running the docker-compose system","text":"

Since this app will be deployed with an hosting service a Dockerfile to build a container image is available. The docker compose file (see docker-compose.yaml) deploys the web app container along with a load balancer (the nginx container) to improve the system scalability.

Look inside the makefile to see the docker and docker-compose options.

To build and run the dockerized system you have to install Docker. Call the following make command to start the build process.

make docker-up-init docker-up-build\n

up-init initialize the ssl certificate based on the selected environment (as set in the environment variable PANEL_ENV, i.e. development or production). Call only make (without arguments) to trigger the same command. A missing or incomplete ssl certificate folder will result in an nginx container failure on start-up.

Images are built and the two containers are started.

You can then access your web app at http://localhost:PORT (where PORT will match the value set through the environment variable).

Note

You can also use make docker-up to spin up the containers if you do not need to re-build any image or initialize ssl certificate folders.

Important

An additional container named db is started if db=postgresql is set

"},{"location":"developers/#running-a-single-container","title":"Running a single container","text":"

It is possible to launch a single server by calling the following command.

make docker-build\n\nmake docker-run\n

You can then access your web app at http://localhost:5000 (if the deafult PORT is selected).

"},{"location":"developers/#running-locally","title":"Running locally","text":"

Launch a local server with default options by calling the following command.

python -m dlunch\n

Use Hydra arguments to alter the app behaviour.

python -m dlunch server=basic_auth\n

See Hydra's documentation for additional details.

"},{"location":"developers/#additional-installations-before-contributing","title":"Additional installations before contributing","text":"

Warning

This step is not required if the setup script (setup_dev_env.sh) is used.

Before contributing please create the pre-commit and commitizen environments.

cd requirements\nconda env create -f pre-commit.yml\nconda env create -f commitizen.yml\n
"},{"location":"developers/#pre-commit-hooks","title":"Pre-commit hooks","text":"

Warning

This step is not required if the setup script (setup_dev_env.sh) is used.

Then install the precommit hooks.

conda activate pre-commit\npre-commit install\npre-commit autoupdate\n

Optionally run hooks on all files.

pre-commit run --all-files\n
"},{"location":"developers/#commitizen","title":"Commitizen","text":"

Warning

This step is not required if the setup script (setup_dev_env.sh) is used.

The Commitizen hook checks that rules for conventional commits are respected in commits messages. Use the following command to enjoy Commitizen's interactive prompt.

conda activate commitizen\ncz commit\n

cz c is a shorther alias for cz commit.

"},{"location":"developers/#release-strategy-from-development-to-main-branch","title":"Release strategy from development to main branch","text":"

Caution

This step is required only if the CI-CD pipeline on GitHub does not work.

In order to take advantage of Commitizen bump command follow this guideline.

First check that you are on the correct branch.

git checkout main\n

Then start the merge process forcing it to stop before commit (--no-commit) and without using the fast forward strategy (--no-ff).

git merge development --no-commit --no-ff\n

Check that results match your expectations then commit (you can leave the default message).

git commit\n

Now Commitizen bump command will add an additional commit with updated versions to every file listed inside pyproject.toml.

cz bump --no-verify\n

You can now merge results of the release process back to the development branch.

git checkout development\ngit merge main --no-ff\n

Use \"update files from last release\" or the default text as commit message.

"},{"location":"developers/#google-cloud-platform-utilities","title":"Google Cloud Platform utilities","text":"

Warning

This step is not required if the setup script (setup_dev_env.sh) is used.

The makefile has two rules for conviniently setting up and removing authentication credentials for Google Cloud Platform command line interface: gcp-config and gcp-revoke.

"},{"location":"getting_started/","title":"Getting Started","text":""},{"location":"getting_started/#introduction","title":"Introduction","text":"

Data-Lunch is a web app to help people managing lunch orders. The interface make possible to upload a menu as an Excel file or as an image. The menu is copied inside the app and people are then able to place orders at preset lunch times. Users can flag an order as a takeway one, and, if something change, they can move an order to another lunch time (or change its takeaway status).

The systems handle guests users by giving them a limited ability to interact with the app (they can in fact just place an order).

Once all order are placed it is possible to stop new orders from being placed and download an Excel files with every order, grouped by lunch time and takeaway status.

The idea behind this app is to simplify data collection at lunch time, so that a single person can book a restaurant and place orders for a group of people (friends, colleagues, etc.).

Important

This section is a work in progress, the app has a lot of configurations, not yet described in this documentation. Authentication and guest management are just examples of what is missing from this documentation.

"},{"location":"getting_started/#installation","title":"Installation","text":"

Install commitizen using pip:

 pip install dlunch\n

For the program to work properly you need the following system dipendencies:

  • SQLite: used if you set the database to sqlite.
  • Tesseract: used to convert images with menu to text.

If you need help on how to install those system preferences on linux (Debian) you can check the file docker/web/Dockerfile.web. It shows how to install them with apt.

"},{"location":"getting_started/#usage","title":"Usage","text":"

Before starting you need the following environment variables to avoid errors on Data-Lunch startup.

"},{"location":"getting_started/#environment-variables","title":"Environment variables","text":"

Important

DATA_LUNCH_COOKIE_SECRET and DATA_LUNCH_OAUTH_ENC_KEY are required even if server=no_auth is set.

Tip

Use the command data-lunch utils generate-secrets to generate a valid secret.

Variable Type Required Description PANEL_ENV str \u2714\ufe0f Environment, e.g. development, quality, production, affects app configuration (Hydra) PORT int \u274c Port used by the web app (or the container), default to 5000; affects app configuration (it is used by Hydra) DATA_LUNCH_COOKIE_SECRET str \u2714\ufe0f Secret used for securing the authentication cookie (use data-lunch utils generate-secrets to generate a valid secret); leave it as an empty variable if no authentication is set DATA_LUNCH_OAUTH_ENC_KEY str \u2714\ufe0f Encription key used by the OAuth algorithm for encryption (use data-lunch utils generate-secrets to generate a valid secret); leave it as an empty variable if no authentication is set

Note

The following variables are just a small part of the total. See here for more details.

"},{"location":"getting_started/#launch-command","title":"Launch command","text":"

Use the following command to start the server with default configuration (SQLite database, no authentication):

python -m dlunch\n

Connect to localhost:5000 (if the default port is used) to see the web app.

"},{"location":"getting_started/#customization","title":"Customization","text":"

Data-Lunch configurations explout Hydra versatility. It is possible to alter default configurations by using Hydra's overrides, for example

python -m dlunch panel/gui=major_release\n

will alter some graphic elements to show a release banner.

"},{"location":"getting_started/#docker","title":"Docker","text":"

A Docker image with Data-Lunch is available here.

"},{"location":"reference/SUMMARY/","title":"SUMMARY","text":"
  • dlunch
    • __main__
    • auth
    • cli
    • cloud
    • conf
    • core
    • gui
    • models
    • scheduled_tasks
"},{"location":"reference/dlunch/","title":"dlunch","text":"

Main Data-Lunch package.

Modules:

Name Description __main__

Data-Lunch package entrypoint.

auth

Module with classes and functions used for authentication and password handling.

cli

Module with Data-Lunch's command line.

cloud

Module with functions to interact with GCP storage service.

conf

Package with Hydra configuration yaml files.

core

Module that defines main functions used to manage Data-Lunch operations.

gui

Module that defines main graphic interface and backend graphic interface.

models

Module with database tables definitions.

scheduled_tasks

Module with functions used to execute scheduled tasks.

Functions:

Name Description create_app

Panel main app factory function

create_backend

Panel backend app factory function

Attributes:

Name Type Description log Logger

Module logger.

waiter"},{"location":"reference/dlunch/#dlunch.log","title":"log module-attribute","text":"
log: Logger = getLogger(__name__)\n

Module logger.

"},{"location":"reference/dlunch/#dlunch.waiter","title":"waiter module-attribute","text":"
waiter = Waiter()\n
"},{"location":"reference/dlunch/#dlunch.create_app","title":"create_app","text":"
create_app(config: DictConfig) -> Template\n

Panel main app factory function

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description Template

Panel main app template.

Source code in dlunch/__init__.py
def create_app(config: DictConfig) -> pn.Template:\n    \"\"\"Panel main app factory function\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        pn.Template: Panel main app template.\n    \"\"\"\n    log.info(\"starting initialization process\")\n\n    log.info(\"initialize database\")\n    # Create tables\n    models.create_database(\n        config, add_basic_auth_users=auth.is_basic_auth_active(config=config)\n    )\n\n    log.info(\"initialize support variables\")\n    # Generate a random password only if requested (check on flag)\n    log.debug(\"config guest user\")\n    guest_password = auth.set_guest_user_password(config)\n\n    log.info(\"instantiate app\")\n\n    # Panel configurations\n    log.debug(\"set toggles initial state\")\n    # Set the no_more_orders flag if it is None (not found in flags table)\n    if models.get_flag(config=config, id=\"no_more_orders\") is None:\n        models.set_flag(config=config, id=\"no_more_orders\", value=False)\n    # Set guest override flag if it is None (not found in flags table)\n    # Guest override flag is per-user and is not set for guests\n    if (\n        models.get_flag(config=config, id=f\"{pn_user(config)}_guest_override\")\n        is None\n    ) and not auth.is_guest(\n        user=pn_user(config), config=config, allow_override=False\n    ):\n        models.set_flag(\n            config=config, id=f\"{pn_user(config)}_guest_override\", value=False\n        )\n\n    # DASHBOARD BASE TEMPLATE\n    log.debug(\"instantiate base template\")\n    # Create web app template\n    app = pn.template.VanillaTemplate(\n        title=config.panel.gui.title,\n        sidebar_width=gui.sidebar_width,\n        favicon=config.panel.gui.favicon_path,\n        logo=config.panel.gui.logo_path,\n        css_files=OmegaConf.to_container(\n            config.panel.gui.template_css_files, resolve=True\n        ),\n        raw_css=OmegaConf.to_container(\n            config.panel.gui.template_raw_css, resolve=True\n        ),\n    )\n\n    # CONFIGURABLE OBJECTS\n    # Since Person class need the config variable for initialization, every\n    # graphic element that require the Person class has to be instantiated\n    # by a dedicated function\n    # Create person instance, widget and column\n    log.debug(\"instantiate person class and graphic graphic interface\")\n    person = gui.Person(config, name=\"User\")\n    gi = gui.GraphicInterface(\n        config=config,\n        waiter=waiter,\n        app=app,\n        person=person,\n        guest_password=guest_password,\n    )\n\n    # DASHBOARD\n    # Build dashboard (the header object is used if defined)\n    app.header.append(gi.header_row)\n    app.sidebar.append(gi.sidebar_tabs)\n    app.main.append(gi.no_menu_col)\n    app.main.append(gi.guest_override_alert)\n    app.main.append(gi.no_more_order_alert)\n    app.main.append(gi.main_header_row)\n    app.main.append(gi.quote)\n    app.main.append(pn.Spacer(height=15))\n    app.main.append(gi.menu_flexbox)\n    app.main.append(gi.buttons_flexbox)\n    app.main.append(gi.results_divider)\n    app.main.append(gi.res_col)\n    app.modal.append(gi.error_message)\n\n    # Set components visibility based on no_more_order_button state\n    # and reload menu\n    gi.reload_on_no_more_order(\n        toggle=models.get_flag(config=config, id=\"no_more_orders\"),\n        reload=False,\n    )\n    gi.reload_on_guest_override(\n        toggle=models.get_flag(\n            config=config,\n            id=f\"{pn_user(config)}_guest_override\",\n            value_if_missing=False,\n        ),\n        reload=False,\n    )\n    waiter.reload_menu(\n        None,\n        gi,\n    )\n\n    app.servable()\n\n    log.info(\"initialization process completed\")\n\n    return app\n
"},{"location":"reference/dlunch/#dlunch.create_backend","title":"create_backend","text":"
create_backend(config: DictConfig) -> Template\n

Panel backend app factory function

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description Template

Panel backend app template.

Source code in dlunch/__init__.py
def create_backend(config: DictConfig) -> pn.Template:\n    \"\"\"Panel backend app factory function\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        pn.Template: Panel backend app template.\n    \"\"\"\n\n    log.info(\"starting initialization process\")\n\n    log.info(\"initialize database\")\n    # Create tables\n    models.create_database(\n        config, add_basic_auth_users=auth.is_basic_auth_active(config=config)\n    )\n\n    log.info(\"instantiate backend\")\n\n    # DASHBOARD\n    log.debug(\"instantiate base template\")\n    # Create web app template\n    backend = pn.template.VanillaTemplate(\n        title=f\"{config.panel.gui.title} Backend\",\n        favicon=config.panel.gui.favicon_path,\n        logo=config.panel.gui.logo_path,\n        css_files=OmegaConf.to_container(\n            config.panel.gui.template_css_files, resolve=True\n        ),\n        raw_css=OmegaConf.to_container(\n            config.panel.gui.template_raw_css, resolve=True\n        ),\n    )\n\n    # CONFIGURABLE OBJECTS\n    backend_gi = gui.BackendInterface(config)\n\n    # DASHBOARD\n    # Build dashboard\n    backend.header.append(backend_gi.header_row)\n    backend.main.append(backend_gi.backend_controls)\n\n    backend.servable()\n\n    log.info(\"initialization process completed\")\n\n    return backend\n
"},{"location":"reference/dlunch/__main__/","title":"__main__","text":"

Data-Lunch package entrypoint.

Modules:

Name Description auth

Module with classes and functions used for authentication and password handling.

models

Module with database tables definitions.

Functions:

Name Description run_app

Main entrypoint, start the app loop.

schedule_task

Schedule a task execution using Panel.

Attributes:

Name Type Description log Logger

Module logger.

"},{"location":"reference/dlunch/__main__/#dlunch.__main__.log","title":"log module-attribute","text":"
log: Logger = getLogger(__name__)\n

Module logger.

"},{"location":"reference/dlunch/__main__/#dlunch.__main__.run_app","title":"run_app","text":"
run_app(config: DictConfig) -> None\n

Main entrypoint, start the app loop.

Initialize database, authentication and encription. Setup panel and create objects for frontend and backend.

Parameters:

Name Type Description Default config DictConfig

hydra configuration object.

required Source code in dlunch/__main__.py
@hydra.main(config_path=\"conf\", config_name=\"config\", version_base=\"1.3\")\ndef run_app(config: DictConfig) -> None:\n    \"\"\"Main entrypoint, start the app loop.\n\n    Initialize database, authentication and encription.\n    Setup panel and create objects for frontend and backend.\n\n    Args:\n        config (DictConfig): hydra configuration object.\n    \"\"\"\n\n    # Configure waiter\n    log.info(\"set waiter config\")\n    waiter.set_config(config)\n\n    # Set auth configurations\n    log.info(\"set auth config and encryption\")\n    # Auth encryption\n    auth.set_app_auth_and_encryption(config)\n    log.debug(\n        f'authentication {\"\" if auth.is_auth_active(config) else \"not \"}active'\n    )\n\n    log.info(\"set panel config\")\n    # Set notifications options\n    pn.extension(\n        disconnect_notification=config.panel.notifications.disconnect_notification,\n        ready_notification=config.panel.notifications.ready_notification,\n    )\n    # Configurations\n    pn.config.nthreads = config.panel.nthreads\n    pn.config.notifications = True\n    authorize_callback_factory = hydra.utils.call(\n        config.auth.authorization_callback, config\n    )\n    pn.config.authorize_callback = lambda ui, tp: authorize_callback_factory(\n        user_info=ui, target_path=tp\n    )\n    pn.config.auth_template = config.auth.auth_error_template\n\n    # If basic auth is used the database and users credentials shall be created here\n    if auth.is_basic_auth_active:\n        log.info(\"initialize database and users credentials for basic auth\")\n        # Create tables\n        models.create_database(\n            config,\n            add_basic_auth_users=auth.is_basic_auth_active(config=config),\n        )\n\n    # Starting scheduled tasks\n    if config.panel.scheduled_tasks:\n        log.info(\"starting scheduled tasks\")\n        for task in config.panel.scheduled_tasks:\n            schedule_task(\n                **task.kwargs,\n                callable=hydra.utils.instantiate(task.callable, config),\n            )\n\n    # Call the app factory function\n    log.info(\"set config for app factory function\")\n    # Pass the create_app and create_backend function as a lambda function to\n    # ensure that each invocation has a dedicated state variable (users'\n    # selections are not shared between instances)\n    # Backend exist only if auth is active\n    # Health is an endpoint for app health assessments\n    # Pass a dictionary for a multipage app\n    pages = {\"\": lambda: create_app(config=config)}\n    if auth.is_auth_active(config=config):\n        pages[\"backend\"] = lambda: create_backend(config=config)\n\n    # If basic authentication is active, instantiate ta special auth object\n    # otherwise leave an empty dict\n    # This step is done before panel.serve because auth_provider requires that\n    # the whole config is passed as an input\n    if auth.is_basic_auth_active(config=config):\n        auth_object = {\n            \"auth_provider\": hydra.utils.instantiate(\n                config.basic_auth.auth_provider, config\n            )\n        }\n        log.debug(\n            \"auth_object dict set to instantiated object from config.server.auth_provider\"\n        )\n    else:\n        auth_object = {}\n        log.debug(\n            \"missing config.server.auth_provider, auth_object dict left empty\"\n        )\n\n    # Set session begin/end logs\n    pn.state.on_session_created(lambda ctx: log.debug(\"session created\"))\n    pn.state.on_session_destroyed(lambda ctx: log.debug(\"session closed\"))\n\n    pn.serve(\n        panels=pages, **hydra.utils.instantiate(config.server), **auth_object\n    )\n
"},{"location":"reference/dlunch/__main__/#dlunch.__main__.schedule_task","title":"schedule_task","text":"
schedule_task(\n    name: str,\n    enabled: bool,\n    hour: int | None,\n    minute: int | None,\n    period: str,\n    callable: Callable,\n) -> None\n

Schedule a task execution using Panel.

Parameters:

Name Type Description Default name str

task name (used for logs).

required enabled bool

flag that marks a task as enabled. tasks are not executed if disabled.

required hour int | None

start hour (used only if also minute is not None).

required minute int | None

start minute (used only if also hour is not None).

required period str

the period between executions. May be expressed as a timedelta or a string.

  • Week: '1w'
  • Day: '1d'
  • Hour: '1h'
  • Minute: '1m'
  • Second: '1s'
required callable Callable

the task to be executed.

required Source code in dlunch/__main__.py
def schedule_task(\n    name: str,\n    enabled: bool,\n    hour: int | None,\n    minute: int | None,\n    period: str,\n    callable: Callable,\n) -> None:\n    \"\"\"Schedule a task execution using Panel.\n\n    Args:\n        name (str): task name (used for logs).\n        enabled (bool): flag that marks a task as enabled.\n            tasks are not executed if disabled.\n        hour (int | None): start hour (used only if also minute is not None).\n        minute (int | None): start minute (used only if also hour is not None).\n        period (str): the period between executions.\n            May be expressed as a timedelta or a string.\n\n            * Week: `'1w'`\n            * Day: `'1d'`\n            * Hour: `'1h'`\n            * Minute: `'1m'`\n            * Second: `'1s'`\n\n        callable (Callable): the task to be executed.\n    \"\"\"\n    # Starting scheduled tasks (if enabled)\n    if enabled:\n        log.info(f\"starting task '{name}'\")\n        if (hour is not None) and (minute is not None):\n            start_time = dt.datetime.today().replace(\n                hour=hour,\n                minute=minute,\n            )\n            # Set start time to tomorrow if the time already passed\n            if start_time < dt.datetime.now():\n                start_time = start_time + dt.timedelta(days=1)\n            log.info(\n                f\"starting time: {start_time.strftime('%Y-%m-%d %H:%M')} - period: {period}\"\n            )\n        else:\n            start_time = None\n            log.info(f\"starting time: now - period: {period}\")\n        pn.state.schedule_task(\n            f\"data_lunch_{name}\",\n            callable,\n            period=period,\n            at=start_time,\n        )\n
"},{"location":"reference/dlunch/auth/","title":"auth","text":"

Module with classes and functions used for authentication and password handling.

Modules:

Name Description gui

Module that defines main graphic interface and backend graphic interface.

models

Module with database tables definitions.

Classes:

Name Description DataLunchLoginHandler

Custom Panel login Handler.

DataLunchProvider

Custom Panel auth provider.

PasswordEncrypt

Class that store the encrypted value of a password.

PasswordHash

Class that store the hashed value of a password.

Functions:

Name Description add_privileged_user

Add user id to privileged_users table.

add_user_hashed_password

Add user credentials to credentials table.

authorize

Authorization callback: read config, user info and the target path of the

backend_submit_password

Submit password to database from backend but used also from frontend as

force_logout

Redirect the browser to the logout endpoint

generate_password

summary

get_hash_from_user

Query the database to retrieve the hashed password for a certain user.

is_admin

Check if a user is an admin by checking the privileged_users table

is_auth_active

Check configuration dictionary and return True if basic authentication or OAuth is active.

is_basic_auth_active

Check config object and return True if basic authentication is active.

is_guest

Check if a user is a guest by checking if it is listed inside the privileged_users table.

list_users

List only privileged users (from privileged_users table).

list_users_guests_and_privileges

Join privileged_users and credentials tables to list normal users,

open_backend

Redirect the browser to the backend endpoint

pn_user

Return the user from Panel state object.

remove_user

Remove user from the database.

set_app_auth_and_encryption

Setup Panel authorization and encryption.

set_guest_user_password

If guest user is active return a password, otherwise return an empty string.

submit_password

Same as backend_submit_password with an additional check on old

Attributes:

Name Type Description log Logger

Module logger.

pwd_context CryptContext

Crypt context with configurations for passlib (selected algorithm, etc.).

"},{"location":"reference/dlunch/auth/#dlunch.auth.log","title":"log module-attribute","text":"
log: Logger = getLogger(__name__)\n

Module logger.

"},{"location":"reference/dlunch/auth/#dlunch.auth.pwd_context","title":"pwd_context module-attribute","text":"
pwd_context: CryptContext = CryptContext(\n    schemes=[\"pbkdf2_sha256\"], deprecated=\"auto\"\n)\n

Crypt context with configurations for passlib (selected algorithm, etc.).

"},{"location":"reference/dlunch/auth/#dlunch.auth.DataLunchLoginHandler","title":"DataLunchLoginHandler","text":"

Bases: RequestHandler

Custom Panel login Handler.

This class run the user authentication process for Data-Lunch when basic authentication is selected in configuration options.

It is responsible of rendering the login page, validate the user (and update its password hash if the hashing protocol is superseeded) and set the current user once validated.

Methods:

Name Description check_permission

Validate user.

get

Render the login template.

post

Validate user and set the current user if valid.

set_current_user

Set secure cookie for the selected user.

Source code in dlunch/auth.py
class DataLunchLoginHandler(RequestHandler):\n    \"\"\"Custom Panel login Handler.\n\n    This class run the user authentication process for Data-Lunch when basic authentication\n    is selected in configuration options.\n\n    It is responsible of rendering the login page, validate the user (and update its\n    password hash if the hashing protocol is superseeded) and set the current user once validated.\n    \"\"\"\n\n    def get(self) -> None:\n        \"\"\"Render the login template.\"\"\"\n        try:\n            errormessage = self.get_argument(\"error\")\n        except Exception:\n            errormessage = \"\"\n        html = self._login_template.render(errormessage=errormessage)\n        self.write(html)\n\n    def check_permission(self, user: str, password: str) -> bool:\n        \"\"\"Validate user.\n\n        Automatically update the password hash if it was generated by an old hashing protocol.\n\n        Args:\n            user (str): username.\n            password (str): password (not hashed).\n\n        Returns:\n            bool: user authentication flag (`True` if authenticated)\n        \"\"\"\n        password_hash = get_hash_from_user(user, self.config)\n        if password_hash == password:\n            # Check if hash needs update\n            valid, new_hash = password_hash.verify_and_update(password)\n            if valid and new_hash:\n                # Update to new hash\n                add_user_hashed_password(user, password, config=self.config)\n            # Return the OK value\n            return True\n        # Return the NOT OK value\n        return False\n\n    def post(self) -> None:\n        \"\"\"Validate user and set the current user if valid.\"\"\"\n        username = self.get_argument(\"username\", \"\")\n        password = self.get_argument(\"password\", \"\")\n        auth = self.check_permission(username, password)\n        if auth:\n            self.set_current_user(username)\n            self.redirect(\"/\")\n        else:\n            error_msg = \"?error=\" + tornado.escape.url_escape(\n                \"Login incorrect\"\n            )\n            self.redirect(\"/login\" + error_msg)\n\n    def set_current_user(self, user: str):\n        \"\"\"Set secure cookie for the selected user.\n\n        Args:\n            user (str): username.\n        \"\"\"\n        if not user:\n            self.clear_cookie(\"user\")\n            return\n        self.set_secure_cookie(\n            \"user\",\n            user,\n            expires_days=pn.config.oauth_expiry,\n            **self.config.auth.cookie_kwargs,\n        )\n        id_token = base64url_encode(json.dumps({\"user\": user}))\n        if pn.state.encryption:\n            id_token = pn.state.encryption.encrypt(id_token.encode(\"utf-8\"))\n        self.set_secure_cookie(\n            \"id_token\",\n            id_token,\n            expires_days=pn.config.oauth_expiry,\n            **self.config.auth.cookie_kwargs,\n        )\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.DataLunchLoginHandler.check_permission","title":"check_permission","text":"
check_permission(user: str, password: str) -> bool\n

Validate user.

Automatically update the password hash if it was generated by an old hashing protocol.

Parameters:

Name Type Description Default user str

username.

required password str

password (not hashed).

required

Returns:

Type Description bool

user authentication flag (True if authenticated)

Source code in dlunch/auth.py
def check_permission(self, user: str, password: str) -> bool:\n    \"\"\"Validate user.\n\n    Automatically update the password hash if it was generated by an old hashing protocol.\n\n    Args:\n        user (str): username.\n        password (str): password (not hashed).\n\n    Returns:\n        bool: user authentication flag (`True` if authenticated)\n    \"\"\"\n    password_hash = get_hash_from_user(user, self.config)\n    if password_hash == password:\n        # Check if hash needs update\n        valid, new_hash = password_hash.verify_and_update(password)\n        if valid and new_hash:\n            # Update to new hash\n            add_user_hashed_password(user, password, config=self.config)\n        # Return the OK value\n        return True\n    # Return the NOT OK value\n    return False\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.DataLunchLoginHandler.get","title":"get","text":"
get() -> None\n

Render the login template.

Source code in dlunch/auth.py
def get(self) -> None:\n    \"\"\"Render the login template.\"\"\"\n    try:\n        errormessage = self.get_argument(\"error\")\n    except Exception:\n        errormessage = \"\"\n    html = self._login_template.render(errormessage=errormessage)\n    self.write(html)\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.DataLunchLoginHandler.post","title":"post","text":"
post() -> None\n

Validate user and set the current user if valid.

Source code in dlunch/auth.py
def post(self) -> None:\n    \"\"\"Validate user and set the current user if valid.\"\"\"\n    username = self.get_argument(\"username\", \"\")\n    password = self.get_argument(\"password\", \"\")\n    auth = self.check_permission(username, password)\n    if auth:\n        self.set_current_user(username)\n        self.redirect(\"/\")\n    else:\n        error_msg = \"?error=\" + tornado.escape.url_escape(\n            \"Login incorrect\"\n        )\n        self.redirect(\"/login\" + error_msg)\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.DataLunchLoginHandler.set_current_user","title":"set_current_user","text":"
set_current_user(user: str)\n

Set secure cookie for the selected user.

Parameters:

Name Type Description Default user str

username.

required Source code in dlunch/auth.py
def set_current_user(self, user: str):\n    \"\"\"Set secure cookie for the selected user.\n\n    Args:\n        user (str): username.\n    \"\"\"\n    if not user:\n        self.clear_cookie(\"user\")\n        return\n    self.set_secure_cookie(\n        \"user\",\n        user,\n        expires_days=pn.config.oauth_expiry,\n        **self.config.auth.cookie_kwargs,\n    )\n    id_token = base64url_encode(json.dumps({\"user\": user}))\n    if pn.state.encryption:\n        id_token = pn.state.encryption.encrypt(id_token.encode(\"utf-8\"))\n    self.set_secure_cookie(\n        \"id_token\",\n        id_token,\n        expires_days=pn.config.oauth_expiry,\n        **self.config.auth.cookie_kwargs,\n    )\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.DataLunchProvider","title":"DataLunchProvider","text":"

Bases: OAuthProvider

Custom Panel auth provider.

It's a simple login page with a form that interacts with authentication tables.

It is used only if basic authentication is selected in Data-Lunch configuration options.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required login_template str | None

path to login template. Defaults to None.

None logout_template str | None

path to logout template. Defaults to None.

None

Methods:

Name Description __init__

Attributes:

Name Type Description config DictConfig

Hydra configuration dictionary.

login_handler DataLunchLoginHandler

Data-Lunch custom login handler.

login_url str

Login url (/login).

Source code in dlunch/auth.py
class DataLunchProvider(OAuthProvider):\n    \"\"\"Custom Panel auth provider.\n\n    It's a simple login page with a form that interacts with authentication tables.\n\n    It is used only if basic authentication is selected in Data-Lunch configuration options.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n        login_template (str | None, optional): path to login template. Defaults to None.\n        logout_template (str | None, optional): path to logout template. Defaults to None.\n    \"\"\"\n\n    def __init__(\n        self,\n        config: DictConfig,\n        login_template: str | None = None,\n        logout_template: str | None = None,\n    ) -> None:\n        # Set Hydra config info\n        self.config: DictConfig = config\n        \"\"\"Hydra configuration dictionary.\"\"\"\n\n        super().__init__(\n            login_template=login_template, logout_template=logout_template\n        )\n\n    @property\n    def login_url(self) -> str:\n        \"\"\"Login url (`/login`).\"\"\"\n        return \"/login\"\n\n    @property\n    def login_handler(self) -> DataLunchLoginHandler:\n        \"\"\"Data-Lunch custom login handler.\"\"\"\n        # Set basic template\n        DataLunchLoginHandler._login_template = self._login_template\n        # Set Hydra config info\n        DataLunchLoginHandler.config = self.config\n\n        return DataLunchLoginHandler\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.DataLunchProvider.config","title":"config instance-attribute","text":"
config: DictConfig = config\n

Hydra configuration dictionary.

"},{"location":"reference/dlunch/auth/#dlunch.auth.DataLunchProvider.login_handler","title":"login_handler property","text":"
login_handler: DataLunchLoginHandler\n

Data-Lunch custom login handler.

"},{"location":"reference/dlunch/auth/#dlunch.auth.DataLunchProvider.login_url","title":"login_url property","text":"
login_url: str\n

Login url (/login).

"},{"location":"reference/dlunch/auth/#dlunch.auth.DataLunchProvider.__init__","title":"__init__","text":"
__init__(\n    config: DictConfig,\n    login_template: str | None = None,\n    logout_template: str | None = None,\n) -> None\n
Source code in dlunch/auth.py
def __init__(\n    self,\n    config: DictConfig,\n    login_template: str | None = None,\n    logout_template: str | None = None,\n) -> None:\n    # Set Hydra config info\n    self.config: DictConfig = config\n    \"\"\"Hydra configuration dictionary.\"\"\"\n\n    super().__init__(\n        login_template=login_template, logout_template=logout_template\n    )\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.PasswordEncrypt","title":"PasswordEncrypt","text":"

Class that store the encrypted value of a password.

The encryption is based on Panel encryption system.

The class has methods to encrypt and decrypt a string.

The encrypted password may be passed to instantiate the new object. If the encrypted password is not aviailable use the class method PasswordEncrypt.from_str to create an istance with the string properly encrypted.

Parameters:

Name Type Description Default encrypted_password str

encrypted password.

required

Methods:

Name Description __eq__

Decrypt the candidate string and compares it to the stored encrypted value.

__init__ __repr__

Simple object representation.

decrypt

Return decrypted password.

encrypt

Return encrypted password.

from_str

Creates a PasswordEncrypt from the given string.

Attributes:

Name Type Description encrypted_password str

Encrypted password.

Source code in dlunch/auth.py
class PasswordEncrypt:\n    \"\"\"Class that store the encrypted value of a password.\n\n    The encryption is based on Panel encryption system.\n\n    The class has methods to encrypt and decrypt a string.\n\n    The encrypted password may be passed to instantiate the new object.\n    If the encrypted password is not aviailable use the class method\n    `PasswordEncrypt.from_str` to create an istance with the string properly\n    encrypted.\n\n    Args:\n        encrypted_password (str): encrypted password.\n    \"\"\"\n\n    def __init__(self, encrypted_password: str) -> None:\n        # Consistency checks\n        assert (\n            len(encrypted_password) <= 150\n        ), \"encrypted string should have less than 150 chars.\"\n        # Attributes\n        self.encrypted_password: str = encrypted_password\n        \"\"\"Encrypted password.\"\"\"\n\n    def __eq__(self, candidate: str) -> bool:\n        \"\"\"Decrypt the candidate string and compares it to the stored encrypted value.\n\n        Args:\n            candidate (str): candidate string.\n\n        Returns:\n            bool: `True` if equal.\n        \"\"\"\n        # If string check hash, otherwise return False\n        if isinstance(candidate, str):\n            # Replace hashed_password if the algorithm changes\n            valid = self.decrypt() == candidate\n        else:\n            valid = False\n\n        return valid\n\n    def __repr__(self) -> str:\n        \"\"\"Simple object representation.\n\n        Returns:\n            str: string representation.\n        \"\"\"\n        return f\"<{type(self).__name__}>\"\n\n    @staticmethod\n    def encrypt(password: str) -> str:\n        \"\"\"Return encrypted password.\n\n        Args:\n            password (str): plain password (not encrypted).\n\n        Returns:\n            str: encrypted password.\n        \"\"\"\n        if pn.state.encryption:\n            encrypted_password = pn.state.encryption.encrypt(\n                password.encode(\"utf-8\")\n            ).decode(\"utf-8\")\n        else:\n            encrypted_password = password\n        return encrypted_password\n\n    def decrypt(self) -> str:\n        \"\"\"Return decrypted password.\n\n        Returns:\n            str: plain password (not encrypted).\n        \"\"\"\n        if pn.state.encryption:\n            password = pn.state.encryption.decrypt(\n                self.encrypted_password.encode(\"utf-8\")\n            ).decode(\"utf-8\")\n        else:\n            password = self.encrypted_password\n        return password\n\n    @classmethod\n    def from_str(cls, password: str) -> Self:\n        \"\"\"Creates a PasswordEncrypt from the given string.\n\n        Args:\n            password (str): plain password (not encrypted).\n\n        Returns:\n            PasswordEncrypt: new class instance with encrypted value already stored.\n        \"\"\"\n        return cls(cls.encrypt(password))\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.PasswordEncrypt.encrypted_password","title":"encrypted_password instance-attribute","text":"
encrypted_password: str = encrypted_password\n

Encrypted password.

"},{"location":"reference/dlunch/auth/#dlunch.auth.PasswordEncrypt.__eq__","title":"__eq__","text":"
__eq__(candidate: str) -> bool\n

Decrypt the candidate string and compares it to the stored encrypted value.

Parameters:

Name Type Description Default candidate str

candidate string.

required

Returns:

Type Description bool

True if equal.

Source code in dlunch/auth.py
def __eq__(self, candidate: str) -> bool:\n    \"\"\"Decrypt the candidate string and compares it to the stored encrypted value.\n\n    Args:\n        candidate (str): candidate string.\n\n    Returns:\n        bool: `True` if equal.\n    \"\"\"\n    # If string check hash, otherwise return False\n    if isinstance(candidate, str):\n        # Replace hashed_password if the algorithm changes\n        valid = self.decrypt() == candidate\n    else:\n        valid = False\n\n    return valid\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.PasswordEncrypt.__init__","title":"__init__","text":"
__init__(encrypted_password: str) -> None\n
Source code in dlunch/auth.py
def __init__(self, encrypted_password: str) -> None:\n    # Consistency checks\n    assert (\n        len(encrypted_password) <= 150\n    ), \"encrypted string should have less than 150 chars.\"\n    # Attributes\n    self.encrypted_password: str = encrypted_password\n    \"\"\"Encrypted password.\"\"\"\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.PasswordEncrypt.__repr__","title":"__repr__","text":"
__repr__() -> str\n

Simple object representation.

Returns:

Type Description str

string representation.

Source code in dlunch/auth.py
def __repr__(self) -> str:\n    \"\"\"Simple object representation.\n\n    Returns:\n        str: string representation.\n    \"\"\"\n    return f\"<{type(self).__name__}>\"\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.PasswordEncrypt.decrypt","title":"decrypt","text":"
decrypt() -> str\n

Return decrypted password.

Returns:

Type Description str

plain password (not encrypted).

Source code in dlunch/auth.py
def decrypt(self) -> str:\n    \"\"\"Return decrypted password.\n\n    Returns:\n        str: plain password (not encrypted).\n    \"\"\"\n    if pn.state.encryption:\n        password = pn.state.encryption.decrypt(\n            self.encrypted_password.encode(\"utf-8\")\n        ).decode(\"utf-8\")\n    else:\n        password = self.encrypted_password\n    return password\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.PasswordEncrypt.encrypt","title":"encrypt staticmethod","text":"
encrypt(password: str) -> str\n

Return encrypted password.

Parameters:

Name Type Description Default password str

plain password (not encrypted).

required

Returns:

Type Description str

encrypted password.

Source code in dlunch/auth.py
@staticmethod\ndef encrypt(password: str) -> str:\n    \"\"\"Return encrypted password.\n\n    Args:\n        password (str): plain password (not encrypted).\n\n    Returns:\n        str: encrypted password.\n    \"\"\"\n    if pn.state.encryption:\n        encrypted_password = pn.state.encryption.encrypt(\n            password.encode(\"utf-8\")\n        ).decode(\"utf-8\")\n    else:\n        encrypted_password = password\n    return encrypted_password\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.PasswordEncrypt.from_str","title":"from_str classmethod","text":"
from_str(password: str) -> Self\n

Creates a PasswordEncrypt from the given string.

Parameters:

Name Type Description Default password str

plain password (not encrypted).

required

Returns:

Type Description PasswordEncrypt

new class instance with encrypted value already stored.

Source code in dlunch/auth.py
@classmethod\ndef from_str(cls, password: str) -> Self:\n    \"\"\"Creates a PasswordEncrypt from the given string.\n\n    Args:\n        password (str): plain password (not encrypted).\n\n    Returns:\n        PasswordEncrypt: new class instance with encrypted value already stored.\n    \"\"\"\n    return cls(cls.encrypt(password))\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.PasswordHash","title":"PasswordHash","text":"

Class that store the hashed value of a password.

The password hash may be passed to instantiate the new object. If the hash is not aviailable use the class method PasswordHash.from_str to create an istance with the string properly hashed.

Parameters:

Name Type Description Default hashed_password str

password hash.

required

Methods:

Name Description __eq__

Hashes the candidate string and compares it to the stored hash.

__init__ __repr__

Simple object representation.

from_str

Creates a PasswordHash from the given string.

hash

Return hash of the given password.

verify

Check a password against its hash and return True if check passes,

verify_and_update

Check a password against its hash and return True if check passes,

Attributes:

Name Type Description hashed_password str

Password hash.

Source code in dlunch/auth.py
class PasswordHash:\n    \"\"\"Class that store the hashed value of a password.\n\n    The password hash may be passed to instantiate the new object.\n    If the hash is not aviailable use the class method\n    `PasswordHash.from_str` to create an istance with the string properly\n    hashed.\n\n    Args:\n        hashed_password (str): password hash.\n    \"\"\"\n\n    def __init__(self, hashed_password: str) -> None:\n        # Consistency checks\n        assert (\n            len(hashed_password) <= 150\n        ), \"hash should have less than 150 chars.\"\n        # Attributes\n        self.hashed_password: str = hashed_password\n        \"\"\"Password hash.\"\"\"\n\n    def __eq__(self, candidate: str) -> bool:\n        \"\"\"Hashes the candidate string and compares it to the stored hash.\n\n        Args:\n            candidate (str): candidate string.\n\n        Returns:\n            bool: `True` if equal.\n        \"\"\"\n        # If string check hash, otherwise return False\n        if isinstance(candidate, str):\n            # Replace hashed_password if the algorithm changes\n            valid = self.verify(candidate)\n        else:\n            valid = False\n\n        return valid\n\n    def __repr__(self) -> str:\n        \"\"\"Simple object representation.\n\n        Returns:\n            str: string representation.\n        \"\"\"\n        return f\"<{type(self).__name__}>\"\n\n    def verify(self, password: str) -> bool:\n        \"\"\"Check a password against its hash and return `True` if check passes,\n        `False` otherwise.\n\n        Args:\n            password (str): plain password (not hashed).\n\n        Returns:\n            bool: `True` if password and hash match.\n        \"\"\"\n        valid = pwd_context.verify(saslprep(password), self.hashed_password)\n\n        return valid\n\n    def verify_and_update(self, password: str) -> tuple[bool, str | None]:\n        \"\"\"Check a password against its hash and return `True` if check passes,\n        `False` otherwise. Return also a new hash if the original hashing  method\n        is superseeded\n\n        Args:\n            password (str): plain password (not hashed).\n\n        Returns:\n            tuple[bool, str | None]: return a tuple with two elements (valid, new_hash).\n                valid: `True` if password and hash match.\n                new_hash: new hash to replace the one generated with an old algorithm.\n        \"\"\"\n        valid, new_hash = pwd_context.verify_and_update(\n            saslprep(password), self.hashed_password\n        )\n        if valid and new_hash:\n            self.hashed_password = new_hash\n\n        return valid, new_hash\n\n    @staticmethod\n    def hash(password: str) -> str:\n        \"\"\"Return hash of the given password.\n\n        Args:\n            password (str): plain password (not hashed).\n\n        Returns:\n            str: hashed password.\n        \"\"\"\n        return pwd_context.hash(saslprep(password))\n\n    @classmethod\n    def from_str(cls, password: str) -> Self:\n        \"\"\"Creates a PasswordHash from the given string.\n\n        Args:\n            password (str): plain password (not hashed).\n\n        Returns:\n            PasswordHash: new class instance with hashed value already stored.\n        \"\"\"\n        return cls(cls.hash(password))\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.PasswordHash.hashed_password","title":"hashed_password instance-attribute","text":"
hashed_password: str = hashed_password\n

Password hash.

"},{"location":"reference/dlunch/auth/#dlunch.auth.PasswordHash.__eq__","title":"__eq__","text":"
__eq__(candidate: str) -> bool\n

Hashes the candidate string and compares it to the stored hash.

Parameters:

Name Type Description Default candidate str

candidate string.

required

Returns:

Type Description bool

True if equal.

Source code in dlunch/auth.py
def __eq__(self, candidate: str) -> bool:\n    \"\"\"Hashes the candidate string and compares it to the stored hash.\n\n    Args:\n        candidate (str): candidate string.\n\n    Returns:\n        bool: `True` if equal.\n    \"\"\"\n    # If string check hash, otherwise return False\n    if isinstance(candidate, str):\n        # Replace hashed_password if the algorithm changes\n        valid = self.verify(candidate)\n    else:\n        valid = False\n\n    return valid\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.PasswordHash.__init__","title":"__init__","text":"
__init__(hashed_password: str) -> None\n
Source code in dlunch/auth.py
def __init__(self, hashed_password: str) -> None:\n    # Consistency checks\n    assert (\n        len(hashed_password) <= 150\n    ), \"hash should have less than 150 chars.\"\n    # Attributes\n    self.hashed_password: str = hashed_password\n    \"\"\"Password hash.\"\"\"\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.PasswordHash.__repr__","title":"__repr__","text":"
__repr__() -> str\n

Simple object representation.

Returns:

Type Description str

string representation.

Source code in dlunch/auth.py
def __repr__(self) -> str:\n    \"\"\"Simple object representation.\n\n    Returns:\n        str: string representation.\n    \"\"\"\n    return f\"<{type(self).__name__}>\"\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.PasswordHash.from_str","title":"from_str classmethod","text":"
from_str(password: str) -> Self\n

Creates a PasswordHash from the given string.

Parameters:

Name Type Description Default password str

plain password (not hashed).

required

Returns:

Type Description PasswordHash

new class instance with hashed value already stored.

Source code in dlunch/auth.py
@classmethod\ndef from_str(cls, password: str) -> Self:\n    \"\"\"Creates a PasswordHash from the given string.\n\n    Args:\n        password (str): plain password (not hashed).\n\n    Returns:\n        PasswordHash: new class instance with hashed value already stored.\n    \"\"\"\n    return cls(cls.hash(password))\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.PasswordHash.hash","title":"hash staticmethod","text":"
hash(password: str) -> str\n

Return hash of the given password.

Parameters:

Name Type Description Default password str

plain password (not hashed).

required

Returns:

Type Description str

hashed password.

Source code in dlunch/auth.py
@staticmethod\ndef hash(password: str) -> str:\n    \"\"\"Return hash of the given password.\n\n    Args:\n        password (str): plain password (not hashed).\n\n    Returns:\n        str: hashed password.\n    \"\"\"\n    return pwd_context.hash(saslprep(password))\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.PasswordHash.verify","title":"verify","text":"
verify(password: str) -> bool\n

Check a password against its hash and return True if check passes, False otherwise.

Parameters:

Name Type Description Default password str

plain password (not hashed).

required

Returns:

Type Description bool

True if password and hash match.

Source code in dlunch/auth.py
def verify(self, password: str) -> bool:\n    \"\"\"Check a password against its hash and return `True` if check passes,\n    `False` otherwise.\n\n    Args:\n        password (str): plain password (not hashed).\n\n    Returns:\n        bool: `True` if password and hash match.\n    \"\"\"\n    valid = pwd_context.verify(saslprep(password), self.hashed_password)\n\n    return valid\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.PasswordHash.verify_and_update","title":"verify_and_update","text":"
verify_and_update(password: str) -> tuple[bool, str | None]\n

Check a password against its hash and return True if check passes, False otherwise. Return also a new hash if the original hashing method is superseeded

Parameters:

Name Type Description Default password str

plain password (not hashed).

required

Returns:

Type Description tuple[bool, str | None]

return a tuple with two elements (valid, new_hash). valid: True if password and hash match. new_hash: new hash to replace the one generated with an old algorithm.

Source code in dlunch/auth.py
def verify_and_update(self, password: str) -> tuple[bool, str | None]:\n    \"\"\"Check a password against its hash and return `True` if check passes,\n    `False` otherwise. Return also a new hash if the original hashing  method\n    is superseeded\n\n    Args:\n        password (str): plain password (not hashed).\n\n    Returns:\n        tuple[bool, str | None]: return a tuple with two elements (valid, new_hash).\n            valid: `True` if password and hash match.\n            new_hash: new hash to replace the one generated with an old algorithm.\n    \"\"\"\n    valid, new_hash = pwd_context.verify_and_update(\n        saslprep(password), self.hashed_password\n    )\n    if valid and new_hash:\n        self.hashed_password = new_hash\n\n    return valid, new_hash\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.add_privileged_user","title":"add_privileged_user","text":"
add_privileged_user(\n    user: str, is_admin: bool, config: DictConfig\n) -> None\n

Add user id to privileged_users table.

The table is used by every authentication methods to understand which users are privileged and which ones are guests.

Parameters:

Name Type Description Default user str

username.

required is_admin bool

admin flag. Set to True if the new user has admin privileges.

required config DictConfig

Hydra configuration dictionary.

required Source code in dlunch/auth.py
def add_privileged_user(user: str, is_admin: bool, config: DictConfig) -> None:\n    \"\"\"Add user id to `privileged_users` table.\n\n    The table is used by every authentication methods to understand which users are\n    privileged and which ones are guests.\n\n    Args:\n        user (str): username.\n        is_admin (bool): admin flag.\n            Set to `True` if the new user has admin privileges.\n        config (DictConfig): Hydra configuration dictionary.\n    \"\"\"\n    # Create session\n    session = models.create_session(config)\n    # New credentials\n    new_privileged_user = models.PrivilegedUsers(user=user, admin=is_admin)\n\n    # Update credentials\n    # Use an upsert for postgresql, a simple session add otherwise\n    models.session_add_with_upsert(\n        session=session,\n        constraint=\"privileged_users_pkey\",\n        new_record=new_privileged_user,\n    )\n    session.commit()\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.add_user_hashed_password","title":"add_user_hashed_password","text":"
add_user_hashed_password(\n    user: str, password: str, config: DictConfig\n) -> None\n

Add user credentials to credentials table.

Used only by basic authentication.

Parameters:

Name Type Description Default user str

username

required password str

plain password (not hashed).

required config DictConfig

Hydra configuration dictionary.

required Source code in dlunch/auth.py
def add_user_hashed_password(\n    user: str, password: str, config: DictConfig\n) -> None:\n    \"\"\"Add user credentials to `credentials` table.\n\n    Used only by basic authentication.\n\n    Args:\n        user (str): username\n        password (str): plain password (not hashed).\n        config (DictConfig): Hydra configuration dictionary.\n    \"\"\"\n    # Create session\n    session = models.create_session(config)\n    # New credentials\n    # For the user named \"guest\" add also the encrypted password so that panel\n    # can show the decrypted guest password to logged users\n    # Can't use is_guest to determine the user that need encription, because\n    # only the user named guest is shown in the guest user password widget\n    if user == \"guest\":\n        new_user_credential = models.Credentials(\n            user=user, password_hash=password, password_encrypted=password\n        )\n    else:\n        new_user_credential = models.Credentials(\n            user=user, password_hash=password\n        )\n\n    # Update credentials\n    # Use an upsert for postgresql, a simple session add otherwise\n    models.session_add_with_upsert(\n        session=session,\n        constraint=\"credentials_pkey\",\n        new_record=new_user_credential,\n    )\n    session.commit()\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.authorize","title":"authorize","text":"
authorize(\n    config: DictConfig,\n    user_info: dict,\n    target_path: str,\n    authorize_guest_users=False,\n) -> bool\n

Authorization callback: read config, user info and the target path of the requested resource.

Return True (authorized) or False (not authorized) by checking current user and target path.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required user_info dict

dictionary with user info passed by Panel to the authorization handle.

required target_path str

path of the requested resource.

required authorize_guest_users bool

Set to True to enable the main page to guest users. Defaults to False.

False

Returns:

Type Description bool

authorization flag. True if authorized.

Source code in dlunch/auth.py
def authorize(\n    config: DictConfig,\n    user_info: dict,\n    target_path: str,\n    authorize_guest_users=False,\n) -> bool:\n    \"\"\"Authorization callback: read config, user info and the target path of the\n    requested resource.\n\n    Return `True` (authorized) or `False` (not authorized) by checking current user\n    and target path.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n        user_info (dict): dictionary with user info passed by Panel to the authorization handle.\n        target_path (str): path of the requested resource.\n        authorize_guest_users (bool, optional): Set to `True` to enable the main page to guest users.\n            Defaults to `False`.\n\n    Returns:\n        bool: authorization flag. `True` if authorized.\n    \"\"\"\n\n    # If authorization is not active authorize every user\n    if not is_auth_active(config=config):\n        return True\n\n    # Set current user from panel state\n    current_user = pn_user(config)\n    privileged_users = list_users(config=config)\n    log.debug(f\"target path: {target_path}\")\n    # If user is not authenticated block it\n    if not current_user:\n        return False\n    # All privileged users can reach backend (but the backend will have\n    # controls only for admins)\n    if current_user in privileged_users:\n        return True\n    # If the target is the mainpage always authorized (if authenticated)\n    if authorize_guest_users and (target_path == \"/\"):\n        return True\n\n    # In all other cases, don't authorize and logout\n    pn.state.location.pathname.split(\"/\")[0] + \"/logout\"\n    return False\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.backend_submit_password","title":"backend_submit_password","text":"
backend_submit_password(\n    gi: GraphicInterface | BackendInterface,\n    config: DictConfig,\n    user: str = None,\n    user_is_guest: bool | None = None,\n    user_is_admin: bool | None = None,\n    logout_on_success: bool = False,\n) -> bool\n

Submit password to database from backend but used also from frontend as part of submit_password function.

When used for backend is_guest and is_admin are selected from a widget. When called from frontend they are None and the function read them from database using the user input.

Parameters:

Name Type Description Default gi GraphicInterface | BackendInterface

graphic interface object (used to interact with Panel widgets).

required config DictConfig

Hydra configuration dictionary.

required user str

username. Defaults to None.

None user_is_guest bool | None

guest flag (true if guest). Defaults to None.

None user_is_admin bool | None

admin flag (true if admin). Defaults to None.

None logout_on_success bool

set to true to force logout once the new password is set. Defaults to False.

False

Returns:

Type Description bool

true if successful, false otherwise.

Source code in dlunch/auth.py
def backend_submit_password(\n    gi: gui.GraphicInterface | gui.BackendInterface,\n    config: DictConfig,\n    user: str = None,\n    user_is_guest: bool | None = None,\n    user_is_admin: bool | None = None,\n    logout_on_success: bool = False,\n) -> bool:\n    \"\"\"Submit password to database from backend but used also from frontend as\n    part of `submit_password` function.\n\n    When used for backend `is_guest` and `is_admin` are selected from a widget.\n    When called from frontend they are `None` and the function read them from\n    database using the user input.\n\n    Args:\n        gi (gui.GraphicInterface | gui.BackendInterface): graphic interface object (used to interact with Panel widgets).\n        config (DictConfig): Hydra configuration dictionary.\n        user (str, optional): username. Defaults to None.\n        user_is_guest (bool | None, optional): guest flag (true if guest). Defaults to None.\n        user_is_admin (bool | None, optional): admin flag (true if admin). Defaults to None.\n        logout_on_success (bool, optional): set to true to force logout once the new password is set. Defaults to False.\n\n    Returns:\n        bool: true if successful, false otherwise.\n    \"\"\"\n    # Check if user is passed, otherwise check if backend widget\n    # (password_widget.object.user) is available\n    if not user:\n        username = gi.password_widget._widgets[\"user\"].value_input\n    else:\n        username = user\n    # Get all passwords, updated at each key press\n    new_password_key_press = gi.password_widget._widgets[\n        \"new_password\"\n    ].value_input\n    repeat_new_password_key_press = gi.password_widget._widgets[\n        \"repeat_new_password\"\n    ].value_input\n    # Check if new password match repeat password\n    if username:\n        if new_password_key_press == repeat_new_password_key_press:\n            # Check if new password is valid with regex\n            if re.fullmatch(\n                config.basic_auth.psw_regex,\n                new_password_key_press,\n            ):\n                # If is_guest and is_admin are None (not passed) use the ones\n                # already set for the user\n                if user_is_guest is None:\n                    user_is_guest = is_guest(user=user, config=config)\n                if user_is_admin is None:\n                    user_is_admin = is_admin(user=user, config=config)\n                # First remove user from both 'privileged_users' and\n                # 'credentials' tables.\n                deleted_data = remove_user(user=username, config=config)\n                if (deleted_data[\"privileged_users_deleted\"] > 0) or (\n                    deleted_data[\"credentials_deleted\"] > 0\n                ):\n                    pn.state.notifications.success(\n                        f\"Removed old data for<br>'{username}'<br>auth: {deleted_data['privileged_users_deleted']}<br>cred: {deleted_data['credentials_deleted']}\",\n                        duration=config.panel.notifications.duration,\n                    )\n                else:\n                    pn.state.notifications.warning(\n                        f\"Creating new user<br>'{username}' does not exist\",\n                        duration=config.panel.notifications.duration,\n                    )\n                # Add a privileged users only if guest option is not active\n                if not user_is_guest:\n                    add_privileged_user(\n                        user=username,\n                        is_admin=user_is_admin,\n                        config=config,\n                    )\n                # Green light: update the password!\n                add_user_hashed_password(\n                    user=username,\n                    password=new_password_key_press,\n                    config=config,\n                )\n\n                # Logout if requested\n                if logout_on_success:\n                    pn.state.notifications.success(\n                        \"Password updated<br>Logging out\",\n                        duration=config.panel.notifications.duration,\n                    )\n                    sleep(4)\n                    force_logout()\n                else:\n                    pn.state.notifications.success(\n                        \"Password updated\",\n                        duration=config.panel.notifications.duration,\n                    )\n                return True\n\n            else:\n                pn.state.notifications.error(\n                    \"Password requirements not satisfied<br>Check again!\",\n                    duration=config.panel.notifications.duration,\n                )\n\n        else:\n            pn.state.notifications.error(\n                \"Passwords are different!\",\n                duration=config.panel.notifications.duration,\n            )\n    else:\n        pn.state.notifications.error(\n            \"Missing user!\",\n            duration=config.panel.notifications.duration,\n        )\n\n    return False\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.force_logout","title":"force_logout","text":"
force_logout() -> None\n

Redirect the browser to the logout endpoint

Source code in dlunch/auth.py
def force_logout() -> None:\n    \"\"\"Redirect the browser to the logout endpoint\"\"\"\n    # Edit pathname to force logout\n    pn.state.location.pathname = (\n        pn.state.location.pathname.split(\"/\")[0] + \"/logout\"\n    )\n    pn.state.location.reload = True\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.generate_password","title":"generate_password","text":"
generate_password(\n    alphabet: str | None = None,\n    special_chars: str | None = \"\",\n    length: int = 12,\n) -> str\n

summary

Parameters:

Name Type Description Default alphabet str | None

list of charachters to use as alphabet to generate the password. Defaults to None.

None special_chars str | None

special charachters to include inside the password string. Defaults to \"\".

'' length int

length of the random password. Defaults to 12.

12

Returns:

Type Description str

random password.

Source code in dlunch/auth.py
def generate_password(\n    alphabet: str | None = None,\n    special_chars: str | None = \"\",\n    length: int = 12,\n) -> str:\n    \"\"\"_summary_\n\n    Args:\n        alphabet (str | None, optional): list of charachters to use as alphabet to generate the password.\n            Defaults to None.\n        special_chars (str | None, optional): special charachters to include inside the password string.\n            Defaults to \"\".\n        length (int, optional): length of the random password.\n            Defaults to 12.\n\n    Returns:\n        str: random password.\n    \"\"\"\n    # If alphabet is not avilable use a default one\n    if alphabet is None:\n        alphabet = string.ascii_letters + string.digits + special_chars\n    # Infinite loop for finding a valid password\n    while True:\n        password = \"\".join(secrets.choice(alphabet) for i in range(length))\n        # Create special chars condition only if special chars is non-empty\n        if special_chars:\n            special_chars_condition = any(c in special_chars for c in password)\n        else:\n            special_chars_condition = True\n        if (\n            any(c.islower() for c in password)\n            and any(c.isupper() for c in password)\n            and any(c.isdigit() for c in password)\n            and special_chars_condition\n        ):\n            break\n\n    return password\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.get_hash_from_user","title":"get_hash_from_user","text":"
get_hash_from_user(\n    user: str, config: DictConfig\n) -> PasswordHash | None\n

Query the database to retrieve the hashed password for a certain user.

Parameters:

Name Type Description Default user str

username.

required config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description PasswordHash | None

returns password object if the user exist, None otherwise.

Source code in dlunch/auth.py
def get_hash_from_user(user: str, config: DictConfig) -> PasswordHash | None:\n    \"\"\"Query the database to retrieve the hashed password for a certain user.\n\n    Args:\n        user (str): username.\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        PasswordHash | None: returns password object if the user exist, `None` otherwise.\n    \"\"\"\n    # Create session\n    session = models.create_session(config)\n    # Load user from database\n    with session:\n        user_credential = session.get(models.Credentials, user)\n\n    # Get the hashed password\n    if user_credential:\n        hash = user_credential.password_hash or None\n    else:\n        hash = None\n\n    return hash\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.is_admin","title":"is_admin","text":"
is_admin(user: str, config: DictConfig) -> bool\n

Check if a user is an admin by checking the privileged_users table

Parameters:

Name Type Description Default user str

username.

required config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description bool

admin flag. True if the user is an admin.

Source code in dlunch/auth.py
def is_admin(user: str, config: DictConfig) -> bool:\n    \"\"\"Check if a user is an admin by checking the `privileged_users` table\n\n    Args:\n        user (str): username.\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        bool: admin flag. `True` if the user is an admin.\n    \"\"\"\n\n    # If authorization is not active always return false (ther is no admin)\n    if not is_auth_active(config=config):\n        return False\n\n    # Create session\n    session = models.create_session(config)\n\n    with session:\n        admin_users = session.scalars(\n            select(models.PrivilegedUsers).where(\n                models.PrivilegedUsers.admin == sql_true()\n            )\n        ).all()\n    admin_list = [u.user for u in admin_users]\n\n    is_admin = user in admin_list\n\n    return is_admin\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.is_auth_active","title":"is_auth_active","text":"
is_auth_active(config: DictConfig) -> bool\n

Check configuration dictionary and return True if basic authentication or OAuth is active. Return False otherwise.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description bool

True if authentication (basic or OAuth) is active, False otherwise.

Source code in dlunch/auth.py
def is_auth_active(config: DictConfig) -> bool:\n    \"\"\"Check configuration dictionary and return `True` if basic authentication or OAuth is active.\n    Return `False` otherwise.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        bool: `True` if authentication (basic or OAuth) is active, `False` otherwise.\n    \"\"\"\n\n    # Check if a valid auth key exists\n    auth_provider = is_basic_auth_active(config=config)\n    oauth_provider = config.server.get(\"oauth_provider\", None)\n\n    return auth_provider or oauth_provider\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.is_basic_auth_active","title":"is_basic_auth_active","text":"
is_basic_auth_active(config: DictConfig) -> bool\n

Check config object and return True if basic authentication is active. Return False otherwise.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description bool

True if basic authentication is active, False otherwise.

Source code in dlunch/auth.py
def is_basic_auth_active(config: DictConfig) -> bool:\n    \"\"\"Check config object and return `True` if basic authentication is active.\n    Return `False` otherwise.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        bool: `True` if basic authentication is active, `False` otherwise.\n    \"\"\"\n\n    # Check if a valid auth key exists\n    auth_provider = config.get(\"basic_auth\", None)\n\n    return auth_provider is not None\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.is_guest","title":"is_guest","text":"
is_guest(\n    user: str,\n    config: DictConfig,\n    allow_override: bool = True,\n) -> bool\n

Check if a user is a guest by checking if it is listed inside the privileged_users table.

The guest override chached value (stored in flags table, on a per-user basis) can force the function to always return True.

If allow_override is set to False the guest override value is ignored.

Parameters:

Name Type Description Default user str

username.

required config DictConfig

Hydra configuration dictionary.

required allow_override bool

override enablement flag, set to False to ignore guest override value. Defaults to True.

True

Returns:

Type Description bool

guest flag. True if the user is a guest.

Source code in dlunch/auth.py
def is_guest(\n    user: str, config: DictConfig, allow_override: bool = True\n) -> bool:\n    \"\"\"Check if a user is a guest by checking if it is listed inside the `privileged_users` table.\n\n    The guest override chached value (stored in `flags` table, on a per-user basis) can force\n    the function to always return True.\n\n    If `allow_override` is set to `False` the guest override value is ignored.\n\n    Args:\n        user (str): username.\n        config (DictConfig): Hydra configuration dictionary.\n        allow_override (bool, optional): override enablement flag, set to `False` to ignore guest override value.\n            Defaults to True.\n\n    Returns:\n        bool: guest flag. `True` if the user is a guest.\n    \"\"\"\n\n    # If authorization is not active always return false (user is not guest)\n    if not is_auth_active(config=config):\n        return False\n\n    # Load guest override from flag table (if the button is pressed its value\n    # is True). If not available use False.\n    guest_override = models.get_flag(\n        config=config,\n        id=f\"{pn_user(config)}_guest_override\",\n        value_if_missing=False,\n    )\n\n    # If guest override is active always return true (user act like guest)\n    if guest_override and allow_override:\n        return True\n\n    # Otherwise check if user is not included in privileged users\n    privileged_users = list_users(config)\n\n    is_guest = user not in privileged_users\n\n    return is_guest\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.list_users","title":"list_users","text":"
list_users(config: DictConfig) -> list[str]\n

List only privileged users (from privileged_users table).

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description list[str]

list of usernames.

Source code in dlunch/auth.py
def list_users(config: DictConfig) -> list[str]:\n    \"\"\"List only privileged users (from `privileged_users` table).\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        list[str]: list of usernames.\n    \"\"\"\n    # Create session\n    session = models.create_session(config)\n\n    with session:\n        privileged_users = session.scalars(\n            select(models.PrivilegedUsers)\n        ).all()\n\n    # Return users\n    users_list = [u.user for u in privileged_users]\n    users_list.sort()\n\n    return users_list\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.list_users_guests_and_privileges","title":"list_users_guests_and_privileges","text":"
list_users_guests_and_privileges(\n    config: DictConfig,\n) -> DataFrame\n

Join privileged_users and credentials tables to list normal users, admins and guests.

credentials table is populated only if basic authetication is active (in configuration files). A is considered a guest if it is not listed in privileged_users table but it is available in credentials table.

Returns a dataframe.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description DataFrame

dataframe with users and privileges.

Source code in dlunch/auth.py
def list_users_guests_and_privileges(config: DictConfig) -> pd.DataFrame:\n    \"\"\"Join `privileged_users` and `credentials` tables to list normal users,\n    admins and guests.\n\n    `credentials` table is populated only if basic authetication is active (in configuration files).\n    A is considered a guest if it is not listed in `privileged_users` table\n    but it is available in `credentials` table.\n\n    Returns a dataframe.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        pd.DataFrame: dataframe with users and privileges.\n    \"\"\"\n\n    # Query tables required to understand users and guests\n    df_privileged_users = models.PrivilegedUsers.read_as_df(\n        config=config,\n        index_col=\"user\",\n    )\n    df_credentials = models.Credentials.read_as_df(\n        config=config,\n        index_col=\"user\",\n    )\n    # Change admin column to privileges (used after join)\n    df_privileged_users[\"group\"] = df_privileged_users.admin.map(\n        {True: \"admin\", False: \"user\"}\n    )\n    df_user_guests_privileges = df_privileged_users.join(\n        df_credentials, how=\"outer\"\n    )[[\"group\"]]\n    df_user_guests_privileges = df_user_guests_privileges.fillna(\"guest\")\n\n    return df_user_guests_privileges\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.open_backend","title":"open_backend","text":"
open_backend() -> None\n

Redirect the browser to the backend endpoint

Source code in dlunch/auth.py
def open_backend() -> None:\n    \"\"\"Redirect the browser to the backend endpoint\"\"\"\n    # Edit pathname to open backend\n    pn.state.location.pathname = (\n        pn.state.location.pathname.split(\"/\")[0] + \"/backend\"\n    )\n    pn.state.location.reload = True\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.pn_user","title":"pn_user","text":"
pn_user(config: DictConfig) -> str\n

Return the user from Panel state object.

If config.auth.remove_email_domain is True, remove the email domain from username.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description str

username.

Source code in dlunch/auth.py
def pn_user(config: DictConfig) -> str:\n    \"\"\"Return the user from Panel state object.\n\n    If `config.auth.remove_email_domain` is `True`, remove the email domain from username.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        str: username.\n    \"\"\"\n    # Store user\n    user = pn.state.user\n\n    if user:\n        # Check if username is an email\n        if re.fullmatch(r\"[^@]+@[^@]+\\.[^@]+\", user):\n            # Remove domain from username\n            if config.auth.remove_email_domain:\n                user = user.split(\"@\")[0]\n\n    return user\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.remove_user","title":"remove_user","text":"
remove_user(user: str, config: DictConfig) -> dict\n

Remove user from the database.

User is removed from privileged_users and credentials tables.

Parameters:

Name Type Description Default user str

username.

required config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description dict

dictionary with privileged_users_deleted and credentials_deleted with deleted rows from each table.

Source code in dlunch/auth.py
def remove_user(user: str, config: DictConfig) -> dict:\n    \"\"\"Remove user from the database.\n\n    User is removed from `privileged_users` and `credentials` tables.\n\n    Args:\n        user (str): username.\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        dict: dictionary with `privileged_users_deleted` and `credentials_deleted`\n            with deleted rows from each table.\n    \"\"\"\n    # Create session\n    session = models.create_session(config)\n\n    with session:\n        # Delete user from privileged_users table\n        privileged_users_deleted = session.execute(\n            delete(models.PrivilegedUsers).where(\n                models.PrivilegedUsers.user == user\n            )\n        )\n        session.commit()\n\n        # Delete user from credentials table\n        credentials_deleted = session.execute(\n            delete(models.Credentials).where(models.Credentials.user == user)\n        )\n        session.commit()\n\n    return {\n        \"privileged_users_deleted\": privileged_users_deleted.rowcount,\n        \"credentials_deleted\": credentials_deleted.rowcount,\n    }\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.set_app_auth_and_encryption","title":"set_app_auth_and_encryption","text":"
set_app_auth_and_encryption(config: DictConfig) -> None\n

Setup Panel authorization and encryption.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Raises:

Type Description ImportError

missing library (cryptography).

Source code in dlunch/auth.py
def set_app_auth_and_encryption(config: DictConfig) -> None:\n    \"\"\"Setup Panel authorization and encryption.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Raises:\n        ImportError: missing library (cryptography).\n    \"\"\"\n    try:\n        if config.auth.oauth_encryption_key:\n            pn.config.oauth_encryption_key = (\n                config.auth.oauth_encryption_key.encode(\"ascii\")\n            )\n            pn.state.encryption = Fernet(pn.config.oauth_encryption_key)\n    except ConfigAttributeError:\n        log.warning(\n            \"missing authentication encryption key, generate a key with the `panel oauth-secret` CLI command and then provide it to hydra using the DATA_LUNCH_OAUTH_ENC_KEY environment variable\"\n        )\n    # Cookie expiry date\n    try:\n        if config.auth.oauth_expiry:\n            pn.config.oauth_expiry = config.auth.oauth_expiry\n    except ConfigAttributeError:\n        log.warning(\n            \"missing explicit authentication expiry date for cookies, defaults to 1 day\"\n        )\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.set_guest_user_password","title":"set_guest_user_password","text":"
set_guest_user_password(config: DictConfig) -> str\n

If guest user is active return a password, otherwise return an empty string.

This function always returns an empty string if basic authentication is not active.

Guest user and basic authentication are handled through configuration files.

If the flag reset_guest_user_password is set to True the password is created and uploaded to database. Otherwise the existing password is queried from database credentials table.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description str

guest user password or empty string if basic authentication is not active.

Source code in dlunch/auth.py
def set_guest_user_password(config: DictConfig) -> str:\n    \"\"\"If guest user is active return a password, otherwise return an empty string.\n\n    This function always returns an empty string if basic authentication is not active.\n\n    Guest user and basic authentication are handled through configuration files.\n\n    If the flag `reset_guest_user_password` is set to `True` the password is created\n    and uploaded to database. Otherwise the existing password is queried from database\n    `credentials` table.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        str: guest user password or empty string if basic authentication is not active.\n    \"\"\"\n    # Check if basic auth is active\n    if is_basic_auth_active(config=config):\n        # If active basic_auth.guest_user is true if guest user is active\n        is_guest_user_active = config.basic_auth.guest_user\n        log.debug(\"guest user flag is {is_guest_user_active}\")\n    else:\n        # Otherwise the guest user feature is not applicable\n        is_guest_user_active = False\n        log.debug(\"guest user not applicable\")\n\n    # Set the guest password variable\n    if is_guest_user_active:\n        # If flag for resetting the password does not exist use the default\n        # value\n        if (\n            models.get_flag(config=config, id=\"reset_guest_user_password\")\n            is None\n        ):\n            models.set_flag(\n                config=config,\n                id=\"reset_guest_user_password\",\n                value=config.basic_auth.default_reset_guest_user_password_flag,\n            )\n        # Generate a random password only if requested (check on flag)\n        # otherwise load from pickle\n        if models.get_flag(config=config, id=\"reset_guest_user_password\"):\n            # Turn off reset user password (in order to reset it only once)\n            # This statement also acquire a lock on database (so it is\n            # called first)\n            models.set_flag(\n                config=config,\n                id=\"reset_guest_user_password\",\n                value=False,\n            )\n            # Create password\n            guest_password = generate_password(\n                special_chars=config.basic_auth.psw_special_chars,\n                length=config.basic_auth.generated_psw_length,\n            )\n            # Add hashed password to database\n            add_user_hashed_password(\"guest\", guest_password, config=config)\n        else:\n            # Load from database\n            session = models.create_session(config)\n            with session:\n                try:\n                    guest_password = session.get(\n                        models.Credentials, \"guest\"\n                    ).password_encrypted.decrypt()\n                except InvalidToken:\n                    # Notify exception and suggest to reset guest user password\n                    guest_password = \"\"\n                    log.warning(\n                        \"Unable to decrypt 'guest' user password because an invalid token has been detected: reset password from backend\"\n                    )\n                    pn.state.notifications.warning(\n                        \"Unable to decrypt 'guest' user password<br>Invalid token detected: reset password from backend\",\n                        duration=config.panel.notifications.duration,\n                    )\n    else:\n        guest_password = \"\"\n\n    return guest_password\n
"},{"location":"reference/dlunch/auth/#dlunch.auth.submit_password","title":"submit_password","text":"
submit_password(\n    gi: GraphicInterface, config: DictConfig\n) -> bool\n

Same as backend_submit_password with an additional check on old password.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required gi GraphicInterface

graphic interface object (used to interact with Panel widgets).

required

Returns:

Type Description bool

true if successful, false otherwise.

Source code in dlunch/auth.py
def submit_password(gi: gui.GraphicInterface, config: DictConfig) -> bool:\n    \"\"\"Same as backend_submit_password with an additional check on old\n    password.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n        gi (gui.GraphicInterface): graphic interface object (used to interact with Panel widgets).\n\n    Returns:\n        bool: true if successful, false otherwise.\n    \"\"\"\n    # Get user's password hash\n    password_hash = get_hash_from_user(pn_user(config), config=config)\n    # Get username, updated updated at each key press\n    old_password_key_press = gi.password_widget._widgets[\n        \"old_password\"\n    ].value_input\n    # Check if old password is correct\n    if password_hash == old_password_key_press:\n        # Check if new password match repeat password\n        return backend_submit_password(\n            gi=gi, config=config, user=pn_user(config), logout_on_success=True\n        )\n    else:\n        pn.state.notifications.error(\n            \"Incorrect old password!\",\n            duration=config.panel.notifications.duration,\n        )\n\n    return False\n
"},{"location":"reference/dlunch/cli/","title":"cli","text":"

Module with Data-Lunch's command line.

The command line is built with click.

Call data-lunch --help from the terminal inside an environment where the dlunch package is installed.

Modules:

Name Description auth

Module with classes and functions used for authentication and password handling.

Functions:

Name Description add_privileged_user

Add privileged users (with or without admin privileges).

add_user_credential

Add users credentials to credentials table (used by basic authentication)

clean_tables

Clean 'users', 'menu', 'orders' and 'flags' tables.

cli

Command line interface for managing Data-Lunch database and users.

credentials

Manage users credentials for basic authentication.

db

Manage the database.

delete_database

Delete the database.

delete_table

Drop a single table from database.

export_table_to_csv

Export a single table to a csv file.

generate_secrets

Generate secrets for DATA_LUNCH_COOKIE_SECRET and DATA_LUNCH_OAUTH_ENC_KEY env variables.

init_database

Initialize the database.

list_users_name

List users.

load_table

Load a single table from a csv file.

main

Main command line entrypoint.

remove_privileged_user

Remove user from both privileged users and basic login credentials table.

remove_user_credential

Remove user from both privileged users and basic login credentials table.

table

Manage tables in database.

users

Manage privileged users and admin privileges.

utils

Utility commands.

Attributes:

Name Type Description __version__ str

Data-Lunch command line version.

"},{"location":"reference/dlunch/cli/#dlunch.cli.__version__","title":"__version__ module-attribute","text":"
__version__: str = version\n

Data-Lunch command line version.

"},{"location":"reference/dlunch/cli/#dlunch.cli.add_privileged_user","title":"add_privileged_user","text":"
add_privileged_user(obj, user, is_admin)\n

Add privileged users (with or without admin privileges).

Source code in dlunch/cli.py
@users.command(\"add\")\n@click.argument(\"user\")\n@click.option(\"--admin\", \"is_admin\", is_flag=True, help=\"add admin privileges\")\n@click.pass_obj\ndef add_privileged_user(obj, user, is_admin):\n    \"\"\"Add privileged users (with or without admin privileges).\"\"\"\n\n    # Add privileged user to 'privileged_users' table\n    auth.add_privileged_user(\n        user=user,\n        is_admin=is_admin,\n        config=obj[\"config\"],\n    )\n\n    click.secho(f\"User '{user}' added (admin: {is_admin})\", fg=\"green\")\n
"},{"location":"reference/dlunch/cli/#dlunch.cli.add_user_credential","title":"add_user_credential","text":"
add_user_credential(\n    obj, user, password, is_admin, is_guest\n)\n

Add users credentials to credentials table (used by basic authentication) and to privileged users (if not guest).

Source code in dlunch/cli.py
@credentials.command(\"add\")\n@click.argument(\"user\")\n@click.argument(\"password\")\n@click.option(\"--admin\", \"is_admin\", is_flag=True, help=\"add admin privileges\")\n@click.option(\n    \"--guest\",\n    \"is_guest\",\n    is_flag=True,\n    help=\"add user as guest (not added to privileged users)\",\n)\n@click.pass_obj\ndef add_user_credential(obj, user, password, is_admin, is_guest):\n    \"\"\"Add users credentials to credentials table (used by basic authentication)\n    and to privileged users (if not guest).\"\"\"\n\n    # Add a privileged users only if guest option is not active\n    if not is_guest:\n        auth.add_privileged_user(\n            user=user,\n            is_admin=is_admin,\n            config=obj[\"config\"],\n        )\n    # Add hashed password to credentials table\n    auth.add_user_hashed_password(user, password, config=obj[\"config\"])\n\n    click.secho(f\"User '{user}' added\", fg=\"green\")\n
"},{"location":"reference/dlunch/cli/#dlunch.cli.clean_tables","title":"clean_tables","text":"
clean_tables(obj)\n

Clean 'users', 'menu', 'orders' and 'flags' tables.

Source code in dlunch/cli.py
@db.command(\"clean\")\n@click.confirmation_option()\n@click.pass_obj\ndef clean_tables(obj):\n    \"\"\"Clean 'users', 'menu', 'orders' and 'flags' tables.\"\"\"\n\n    # Drop table\n    try:\n        waiter.clean_tables()\n        click.secho(\"done\", fg=\"green\")\n    except Exception as e:\n        # Generic error\n        click.secho(\"Cannot clean database\", fg=\"red\")\n        click.secho(f\"\\n ===== EXCEPTION =====\\n\\n{e}\", fg=\"red\")\n
"},{"location":"reference/dlunch/cli/#dlunch.cli.cli","title":"cli","text":"
cli(ctx, hydra_overrides: tuple | None)\n

Command line interface for managing Data-Lunch database and users.

Source code in dlunch/cli.py
@click.group()\n@click.version_option(__version__)\n@click.option(\n    \"-o\",\n    \"--hydra-overrides\",\n    \"hydra_overrides\",\n    default=None,\n    multiple=True,\n    help=\"pass hydra override, use multiple time to add more than one override\",\n)\n@click.pass_context\ndef cli(ctx, hydra_overrides: tuple | None):\n    \"\"\"Command line interface for managing Data-Lunch database and users.\"\"\"\n    # global initialization\n    initialize(\n        config_path=\"conf\", job_name=\"data_lunch_cli\", version_base=\"1.3\"\n    )\n    config = compose(config_name=\"config\", overrides=hydra_overrides)\n    ctx.obj = {\"config\": config}\n\n    # Set waiter config\n    waiter.set_config(config)\n\n    # Auth encryption\n    auth.set_app_auth_and_encryption(config)\n
"},{"location":"reference/dlunch/cli/#dlunch.cli.credentials","title":"credentials","text":"
credentials(obj)\n

Manage users credentials for basic authentication.

Source code in dlunch/cli.py
@cli.group()\n@click.pass_obj\ndef credentials(obj):\n    \"\"\"Manage users credentials for basic authentication.\"\"\"\n
"},{"location":"reference/dlunch/cli/#dlunch.cli.db","title":"db","text":"
db(obj)\n

Manage the database.

Source code in dlunch/cli.py
@cli.group()\n@click.pass_obj\ndef db(obj):\n    \"\"\"Manage the database.\"\"\"\n
"},{"location":"reference/dlunch/cli/#dlunch.cli.delete_database","title":"delete_database","text":"
delete_database(obj)\n

Delete the database.

Source code in dlunch/cli.py
@db.command(\"delete\")\n@click.confirmation_option()\n@click.pass_obj\ndef delete_database(obj):\n    \"\"\"Delete the database.\"\"\"\n\n    # Create database\n    try:\n        engine = create_engine(obj[\"config\"])\n        Data.metadata.drop_all(engine)\n        click.secho(\"Database deleted\", fg=\"green\")\n    except Exception as e:\n        # Generic error\n        click.secho(\"Cannot delete database\", fg=\"red\")\n        click.secho(f\"\\n ===== EXCEPTION =====\\n\\n{e}\", fg=\"red\")\n
"},{"location":"reference/dlunch/cli/#dlunch.cli.delete_table","title":"delete_table","text":"
delete_table(obj, name)\n

Drop a single table from database.

Source code in dlunch/cli.py
@table.command(\"drop\")\n@click.confirmation_option()\n@click.argument(\"name\")\n@click.pass_obj\ndef delete_table(obj, name):\n    \"\"\"Drop a single table from database.\"\"\"\n\n    # Drop table\n    try:\n        engine = create_engine(obj[\"config\"])\n        metadata_obj.tables[name].drop(engine)\n        click.secho(f\"Table '{name}' deleted\", fg=\"green\")\n    except Exception as e:\n        # Generic error\n        click.secho(\"Cannot drop table\", fg=\"red\")\n        click.secho(f\"\\n ===== EXCEPTION =====\\n\\n{e}\", fg=\"red\")\n
"},{"location":"reference/dlunch/cli/#dlunch.cli.export_table_to_csv","title":"export_table_to_csv","text":"
export_table_to_csv(obj, name, csv_file_path, index)\n

Export a single table to a csv file.

Source code in dlunch/cli.py
@table.command(\"export\")\n@click.argument(\"name\")\n@click.argument(\"csv_file_path\")\n@click.option(\n    \"--index/--no-index\",\n    \"index\",\n    show_default=True,\n    default=False,\n    help=\"select if index is exported to csv\",\n)\n@click.pass_obj\ndef export_table_to_csv(obj, name, csv_file_path, index):\n    \"\"\"Export a single table to a csv file.\"\"\"\n\n    click.secho(f\"Export table '{name}' to CSV {csv_file_path}\", fg=\"yellow\")\n\n    # Create dataframe\n    try:\n        engine = create_engine(obj[\"config\"])\n        df = pd.read_sql_table(\n            name, engine, schema=obj[\"config\"].db.get(\"schema\", SCHEMA)\n        )\n    except Exception as e:\n        # Generic error\n        click.secho(\"Cannot read table\", fg=\"red\")\n        click.secho(f\"\\n ===== EXCEPTION =====\\n\\n{e}\", fg=\"red\")\n\n    # Show head\n    click.echo(\"First three rows of the table\")\n    click.echo(f\"{df.head(3)}\\n\")\n\n    # Export table\n    try:\n        df.to_csv(csv_file_path, index=index)\n    except Exception as e:\n        # Generic error\n        click.secho(\"Cannot write CSV\", fg=\"red\")\n        click.secho(f\"\\n ===== EXCEPTION =====\\n\\n{e}\", fg=\"red\")\n\n    click.secho(\"Done\", fg=\"green\")\n
"},{"location":"reference/dlunch/cli/#dlunch.cli.generate_secrets","title":"generate_secrets","text":"
generate_secrets(obj)\n

Generate secrets for DATA_LUNCH_COOKIE_SECRET and DATA_LUNCH_OAUTH_ENC_KEY env variables.

Source code in dlunch/cli.py
@utils.command(\"generate-secrets\")\n@click.pass_obj\ndef generate_secrets(obj):\n    \"\"\"Generate secrets for DATA_LUNCH_COOKIE_SECRET and DATA_LUNCH_OAUTH_ENC_KEY env variables.\"\"\"\n\n    try:\n        click.secho(\"Print secrets\\n\", fg=\"yellow\")\n        result_secret = subprocess.run(\n            [\"panel\", \"secret\"],\n            stdout=subprocess.PIPE,\n            stderr=subprocess.STDOUT,\n        )\n        click.secho(\"COOKIE SECRET:\", fg=\"yellow\")\n        click.secho(\n            f\"{result_secret.stdout.decode('utf-8')}\",\n            fg=\"cyan\",\n        )\n        result_encription = subprocess.run(\n            [\"panel\", \"oauth-secret\"],\n            stdout=subprocess.PIPE,\n            stderr=subprocess.STDOUT,\n        )\n        click.secho(\"ENCRIPTION KEY:\", fg=\"yellow\")\n        click.secho(\n            f\"{result_encription.stdout.decode('utf-8')}\",\n            fg=\"cyan\",\n        )\n        click.secho(\"Done\", fg=\"green\")\n    except Exception as e:\n        # Generic error\n        click.secho(\"Cannot generate secrets\", fg=\"red\")\n        click.secho(f\"\\n ===== EXCEPTION =====\\n\\n{e}\", fg=\"red\")\n
"},{"location":"reference/dlunch/cli/#dlunch.cli.init_database","title":"init_database","text":"
init_database(obj, add_basic_auth_users)\n

Initialize the database.

Source code in dlunch/cli.py
@db.command(\"init\")\n@click.option(\n    \"--add-basic-auth-users\",\n    \"add_basic_auth_users\",\n    is_flag=True,\n    help=\"automatically create basic auth standard users\",\n)\n@click.pass_obj\ndef init_database(obj, add_basic_auth_users):\n    \"\"\"Initialize the database.\"\"\"\n\n    # Create database\n    create_database(obj[\"config\"], add_basic_auth_users=add_basic_auth_users)\n\n    click.secho(\n        f\"Database initialized (basic auth users: {add_basic_auth_users})\",\n        fg=\"green\",\n    )\n
"},{"location":"reference/dlunch/cli/#dlunch.cli.list_users_name","title":"list_users_name","text":"
list_users_name(obj)\n

List users.

Source code in dlunch/cli.py
@users.command(\"list\")\n@click.pass_obj\ndef list_users_name(obj):\n    \"\"\"List users.\"\"\"\n\n    # Clear action\n    usernames = auth.list_users(config=obj[\"config\"])\n    click.secho(\"USERS:\")\n    click.secho(\"\\n\".join(usernames), fg=\"yellow\")\n    click.secho(\"\\nDone\", fg=\"green\")\n
"},{"location":"reference/dlunch/cli/#dlunch.cli.load_table","title":"load_table","text":"
load_table(\n    obj,\n    name,\n    csv_file_path,\n    index,\n    index_label,\n    index_col,\n    if_exists,\n)\n

Load a single table from a csv file.

Source code in dlunch/cli.py
@table.command(\"load\")\n@click.confirmation_option()\n@click.argument(\"name\")\n@click.argument(\"csv_file_path\")\n@click.option(\n    \"--index/--no-index\",\n    \"index\",\n    show_default=True,\n    default=True,\n    help=\"select if index is loaded to table\",\n)\n@click.option(\n    \"-l\",\n    \"--index-label\",\n    \"index_label\",\n    type=str,\n    default=None,\n    help=\"select index label\",\n)\n@click.option(\n    \"-c\",\n    \"--index-col\",\n    \"index_col\",\n    type=str,\n    default=None,\n    help=\"select the column used as index\",\n)\n@click.option(\n    \"-e\",\n    \"--if-exists\",\n    \"if_exists\",\n    type=click.Choice([\"fail\", \"replace\", \"append\"], case_sensitive=False),\n    show_default=True,\n    default=\"append\",\n    help=\"logict used if the table already exists\",\n)\n@click.pass_obj\ndef load_table(\n    obj, name, csv_file_path, index, index_label, index_col, if_exists\n):\n    \"\"\"Load a single table from a csv file.\"\"\"\n\n    click.secho(f\"Load CSV {csv_file_path} to table '{name}'\", fg=\"yellow\")\n\n    # Create dataframe\n    df = pd.read_csv(csv_file_path, index_col=index_col)\n\n    # Show head\n    click.echo(\"First three rows of the CSV table\")\n    click.echo(f\"{df.head(3)}\\n\")\n\n    # Load table\n    try:\n        engine = create_engine(obj[\"config\"])\n        df.to_sql(\n            name,\n            engine,\n            schema=obj[\"config\"].db.get(\"schema\", SCHEMA),\n            index=index,\n            index_label=index_label,\n            if_exists=if_exists,\n        )\n        click.secho(\"Done\", fg=\"green\")\n    except Exception as e:\n        # Generic error\n        click.secho(\"Cannot load table\", fg=\"red\")\n        click.secho(f\"\\n ===== EXCEPTION =====\\n\\n{e}\", fg=\"red\")\n
"},{"location":"reference/dlunch/cli/#dlunch.cli.main","title":"main","text":"
main() -> None\n

Main command line entrypoint.

Source code in dlunch/cli.py
def main() -> None:\n    \"\"\"Main command line entrypoint.\"\"\"\n    cli(auto_envvar_prefix=\"DATA_LUNCH\")\n
"},{"location":"reference/dlunch/cli/#dlunch.cli.remove_privileged_user","title":"remove_privileged_user","text":"
remove_privileged_user(obj, user)\n

Remove user from both privileged users and basic login credentials table.

Source code in dlunch/cli.py
@users.command(\"remove\")\n@click.confirmation_option()\n@click.argument(\"user\")\n@click.pass_obj\ndef remove_privileged_user(obj, user):\n    \"\"\"Remove user from both privileged users and basic login credentials table.\"\"\"\n\n    # Clear action\n    deleted_data = auth.remove_user(user, config=obj[\"config\"])\n\n    if (deleted_data[\"privileged_users_deleted\"] > 0) or (\n        deleted_data[\"credentials_deleted\"] > 0\n    ):\n        click.secho(\n            f\"User '{user}' removed (auth: {deleted_data['privileged_users_deleted']}, cred: {deleted_data['credentials_deleted']})\",\n            fg=\"green\",\n        )\n    else:\n        click.secho(f\"User '{user}' does not exist\", fg=\"yellow\")\n
"},{"location":"reference/dlunch/cli/#dlunch.cli.remove_user_credential","title":"remove_user_credential","text":"
remove_user_credential(obj, user)\n

Remove user from both privileged users and basic login credentials table.

Source code in dlunch/cli.py
@credentials.command(\"remove\")\n@click.confirmation_option()\n@click.argument(\"user\")\n@click.pass_obj\ndef remove_user_credential(obj, user):\n    \"\"\"Remove user from both privileged users and basic login credentials table.\"\"\"\n\n    # Clear action\n    deleted_data = auth.remove_user(user, config=obj[\"config\"])\n\n    if (deleted_data[\"privileged_users_deleted\"] > 0) or (\n        deleted_data[\"credentials_deleted\"] > 0\n    ):\n        click.secho(\n            f\"User '{user}' removed (auth: {deleted_data['privileged_users_deleted']}, cred: {deleted_data['credentials_deleted']})\",\n            fg=\"green\",\n        )\n    else:\n        click.secho(f\"User '{user}' does not exist\", fg=\"yellow\")\n
"},{"location":"reference/dlunch/cli/#dlunch.cli.table","title":"table","text":"
table(obj)\n

Manage tables in database.

Source code in dlunch/cli.py
@db.group()\n@click.pass_obj\ndef table(obj):\n    \"\"\"Manage tables in database.\"\"\"\n
"},{"location":"reference/dlunch/cli/#dlunch.cli.users","title":"users","text":"
users(obj)\n

Manage privileged users and admin privileges.

Source code in dlunch/cli.py
@cli.group()\n@click.pass_obj\ndef users(obj):\n    \"\"\"Manage privileged users and admin privileges.\"\"\"\n
"},{"location":"reference/dlunch/cli/#dlunch.cli.utils","title":"utils","text":"
utils(obj)\n

Utility commands.

Source code in dlunch/cli.py
@cli.group()\n@click.pass_obj\ndef utils(obj):\n    \"\"\"Utility commands.\"\"\"\n
"},{"location":"reference/dlunch/cloud/","title":"cloud","text":"

Module with functions to interact with GCP storage service.

Functions:

Name Description download_from_gcloud

Download a file from GCP storage.

download_from_gcloud_as_bytes

Download a file from GCP storage as bytes stream.

get_gcloud_bucket_list

List buckets available in GCP storage.

upload_to_gcloud

Upload a local file to GCP storage.

upload_to_gcloud_from_string

Upload the content of a string to GCP storage.

Attributes:

Name Type Description log Logger

Module logger.

"},{"location":"reference/dlunch/cloud/#dlunch.cloud.log","title":"log module-attribute","text":"
log: Logger = getLogger(__name__)\n

Module logger.

"},{"location":"reference/dlunch/cloud/#dlunch.cloud.download_from_gcloud","title":"download_from_gcloud","text":"
download_from_gcloud(\n    source_blob_name: str,\n    destination_file_name: str,\n    bucket_name: str,\n    project: str,\n) -> None\n

Download a file from GCP storage.

Parameters:

Name Type Description Default source_blob_name str

blob name of the source object.

required destination_file_name str

local filepath for the downloaded resource.

required bucket_name str

bucket name.

required project str

GCP project ID.

required Source code in dlunch/cloud.py
def download_from_gcloud(\n    source_blob_name: str,\n    destination_file_name: str,\n    bucket_name: str,\n    project: str,\n) -> None:\n    \"\"\"Download a file from GCP storage.\n\n    Args:\n        source_blob_name (str): blob name of the source object.\n        destination_file_name (str): local filepath for the downloaded resource.\n        bucket_name (str): bucket name.\n        project (str): GCP project ID.\n    \"\"\"\n    # Create storage client\n    storage_client = storage.Client(project=project)\n\n    try:\n        # Get bucket\n        bucket = storage_client.bucket(bucket_name)\n        # Create blob\n        blob = bucket.blob(source_blob_name)\n        # Download\n        blob.download_to_filename(destination_file_name)\n        log.info(\n            f\"file '{source_blob_name}' downloaded to file '{destination_file_name}' successfully\"\n        )\n    except Exception as e:\n        log.warning(\"google storage download exception\\n\\t\" + str(e))\n
"},{"location":"reference/dlunch/cloud/#dlunch.cloud.download_from_gcloud_as_bytes","title":"download_from_gcloud_as_bytes","text":"
download_from_gcloud_as_bytes(\n    source_blob_name: str, bucket_name: str, project: str\n) -> bytes\n

Download a file from GCP storage as bytes stream.

Parameters:

Name Type Description Default source_blob_name str

blob name of the source file.

required bucket_name str

bucket name.

required project str

GCP project ID.

required

Returns:

Type Description bytes

downloaded resource.

Source code in dlunch/cloud.py
def download_from_gcloud_as_bytes(\n    source_blob_name: str,\n    bucket_name: str,\n    project: str,\n) -> bytes:\n    \"\"\"Download a file from GCP storage as bytes stream.\n\n    Args:\n        source_blob_name (str): blob name of the source file.\n        bucket_name (str): bucket name.\n        project (str): GCP project ID.\n\n    Returns:\n        bytes: downloaded resource.\n    \"\"\"\n    # Create storage client\n    storage_client = storage.Client(project=project)\n\n    try:\n        # Get bucket\n        bucket = storage_client.bucket(bucket_name)\n        # Create blob\n        blob = bucket.blob(source_blob_name)\n        # Download\n        bytes_object = blob.download_as_bytes()\n        log.info(\n            f\"file '{source_blob_name}' downloaded to object successfully\"\n        )\n    except Exception as e:\n        log.warning(\"google storage download exception\\n\\t\" + str(e))\n\n    return bytes_object\n
"},{"location":"reference/dlunch/cloud/#dlunch.cloud.get_gcloud_bucket_list","title":"get_gcloud_bucket_list","text":"
get_gcloud_bucket_list(project: str) -> list[str]\n

List buckets available in GCP storage.

Parameters:

Name Type Description Default project str

GCP project ID.

required

Returns:

Type Description list[str]

list with bucket names.

Source code in dlunch/cloud.py
def get_gcloud_bucket_list(project: str) -> list[str]:\n    \"\"\"List buckets available in GCP storage.\n\n    Args:\n        project (str): GCP project ID.\n\n    Returns:\n        list[str]: list with bucket names.\n    \"\"\"\n    # Create storage client\n    storage_client = storage.Client(project=project)\n\n    # Return bucket\n    buckets = list(storage_client.list_buckets())\n\n    return buckets\n
"},{"location":"reference/dlunch/cloud/#dlunch.cloud.upload_to_gcloud","title":"upload_to_gcloud","text":"
upload_to_gcloud(\n    source_file_name: str,\n    destination_blob_name: str,\n    bucket_name: str,\n    project: str,\n) -> None\n

Upload a local file to GCP storage.

Parameters:

Name Type Description Default source_file_name str

filepath.

required destination_blob_name str

blob name to use as destination.

required bucket_name str

bucket name.

required project str

GCP project ID.

required Source code in dlunch/cloud.py
def upload_to_gcloud(\n    source_file_name: str,\n    destination_blob_name: str,\n    bucket_name: str,\n    project: str,\n) -> None:\n    \"\"\"Upload a local file to GCP storage.\n\n    Args:\n        source_file_name (str): filepath.\n        destination_blob_name (str): blob name to use as destination.\n        bucket_name (str): bucket name.\n        project (str): GCP project ID.\n    \"\"\"\n    # Create storage client\n    storage_client = storage.Client(project=project)\n\n    try:\n        # Get bucket\n        bucket = storage_client.bucket(bucket_name)\n        # Create blob\n        blob = bucket.blob(destination_blob_name)\n        # Upload\n        blob.upload_from_filename(source_file_name)\n        log.info(\n            f\"file '{source_file_name}' uploaded to bucket '{bucket_name}' successfully\"\n        )\n    except Exception as e:\n        log.warning(\"google storage upload exception\\n\\t\" + str(e))\n
"},{"location":"reference/dlunch/cloud/#dlunch.cloud.upload_to_gcloud_from_string","title":"upload_to_gcloud_from_string","text":"
upload_to_gcloud_from_string(\n    source_string: str,\n    destination_blob_name: str,\n    bucket_name: str,\n    project: str,\n) -> None\n

Upload the content of a string to GCP storage.

Parameters:

Name Type Description Default source_string str

string to upload.

required destination_blob_name str

blob name to use as destination.

required bucket_name str

bucket name.

required project str

GCP project ID.

required Source code in dlunch/cloud.py
def upload_to_gcloud_from_string(\n    source_string: str,\n    destination_blob_name: str,\n    bucket_name: str,\n    project: str,\n) -> None:\n    \"\"\"Upload the content of a string to GCP storage.\n\n    Args:\n        source_string (str): string to upload.\n        destination_blob_name (str): blob name to use as destination.\n        bucket_name (str): bucket name.\n        project (str): GCP project ID.\n    \"\"\"\n    # Create storage client\n    storage_client = storage.Client(project=project)\n\n    try:\n        # Get bucket\n        bucket = storage_client.bucket(bucket_name)\n        # Create blob\n        blob = bucket.blob(destination_blob_name)\n        # Upload\n        blob.upload_from_string(source_string)\n        log.info(\n            f\"file uploaded from string to bucket '{bucket_name}' at '{destination_blob_name}' successfully\"\n        )\n    except Exception as e:\n        log.warning(\"google storage upload exception\\n\\t\" + str(e))\n
"},{"location":"reference/dlunch/core/","title":"core","text":"

Module that defines main functions used to manage Data-Lunch operations.

This module defines the Waiter class that contains the main functions used to manage Data-Lunch operations. The class is used to interact with the database and the Panel widgets.

Modules:

Name Description auth

Module with classes and functions used for authentication and password handling.

gui

Module that defines main graphic interface and backend graphic interface.

models

Module with database tables definitions.

Classes:

Name Description Waiter

Class that defines main functions used to manage Data-Lunch operations.

Attributes:

Name Type Description __version__ str

Data-Lunch version.

log Logger

Module logger.

"},{"location":"reference/dlunch/core/#dlunch.core.__version__","title":"__version__ module-attribute","text":"
__version__: str = '3.4.0'\n

Data-Lunch version.

"},{"location":"reference/dlunch/core/#dlunch.core.log","title":"log module-attribute","text":"
log: Logger = getLogger(__name__)\n

Module logger.

"},{"location":"reference/dlunch/core/#dlunch.core.Waiter","title":"Waiter","text":"

Class that defines main functions used to manage Data-Lunch operations.

Parameters:

Name Type Description Default config DictConfig | None

Hydra configuration object. Defaults to None.

None Raise

ValueError: when calling (unmangled) methods, if the configuration is not set.

Methods:

Name Description __init__ build_menu

Read menu from file (Excel or image) and upload menu items to database menu table.

change_order_time_takeaway

Change the time and the takeaway flag of an existing order.

clean_tables

Clean tables that should be reset when a new menu is uploaded.

delete_files

Delete local temporary files.

delete_order

Delete an existing order.

df_list_by_lunch_time

Build a dictionary of dataframes for each lunch-time, with takeaways included in a dedicated dataframe.

download_dataframe

Build an Excel file with tables representing orders for every lunch-time/takeaway-time.

get_host_name

Return hostname.

reload_menu

Main core function that sync Panel widget with database tables.

send_order

Upload orders and user to database tables.

set_config

Set the configuration for the Waiter instance.

Attributes:

Name Type Description config DictConfig | None

Hydra configuration object

Source code in dlunch/core.py
class Waiter:\n    \"\"\"Class that defines main functions used to manage Data-Lunch operations.\n\n    Args:\n        config (DictConfig| None): Hydra configuration object. Defaults to None.\n\n    Raise:\n        ValueError: when calling (unmangled) methods, if the configuration is not set.\n    \"\"\"\n\n    def __init__(self, config: DictConfig | None = None):\n        self.config: DictConfig | None = None\n        \"\"\"Hydra configuration object\"\"\"\n\n        # Define decorator to raise an error if the configuration is not set\n        def _common_decorator(func):\n            def wrapper(*args, **kwargs):\n                if self.config is None:\n                    raise ValueError(\"waiter configuration is not set\")\n                return func(*args, **kwargs)\n\n            return wrapper\n\n        # Set decorator to unmangled methods (do not apply to set_config method)\n        for method_name in [\n            item\n            for item in dir(self)\n            if not item.startswith(\"_\") and not (item == \"set_config\")\n        ]:\n            attr = getattr(self, method_name)\n            # Check if the attribute is a method\n            if callable(attr):\n                wrapped = _common_decorator(attr)\n                setattr(self, method_name, wrapped)\n\n    def set_config(self, config: DictConfig):\n        \"\"\"Set the configuration for the Waiter instance.\n\n        Args:\n            config (DictConfig): Hydra configuration object.\n        \"\"\"\n        self.config = config\n\n    def get_host_name(self) -> str:\n        \"\"\"Return hostname.\n\n        This function behavior changes if called from localhost, Docker container or\n        production server.\n\n        Returns:\n            str: hostname.\n        \"\"\"\n        try:\n            ip_address = socket.gethostbyname(socket.gethostname())\n            dig_res = subprocess.run(\n                [\"dig\", \"+short\", \"-x\", ip_address], stdout=subprocess.PIPE\n            ).stdout\n            host_name = (\n                subprocess.run(\n                    [\"cut\", \"-d.\", \"-f1\"],\n                    stdout=subprocess.PIPE,\n                    input=dig_res,\n                )\n                .stdout.decode(\"utf-8\")\n                .strip()\n            )\n            if host_name:\n                host_name = host_name.replace(\n                    f\"{self.config.docker_username}_\", \"\"\n                )\n            else:\n                host_name = \"no info\"\n        except Exception:\n            host_name = \"not available\"\n\n        return host_name\n\n    def delete_files(self) -> None:\n        \"\"\"Delete local temporary files.\"\"\"\n        # Delete menu file if exist (every extension)\n        files = list(\n            pathlib.Path(self.config.db.shared_data_folder).glob(\n                self.config.panel.file_name + \"*\"\n            )\n        )\n        log.info(f\"delete files {', '.join([f.name for f in files])}\")\n        for file in files:\n            file.unlink(missing_ok=True)\n\n    def clean_tables(self) -> None:\n        \"\"\"Clean tables that should be reset when a new menu is uploaded.\"\"\"\n        # Clean tables\n        # Clean orders\n        models.Orders.clear(config=self.config)\n        # Clean menu\n        models.Menu.clear(config=self.config)\n        # Clean users\n        models.Users.clear(config=self.config)\n        # Clean flags\n        models.Flags.clear_guest_override(config=self.config)\n        # Reset flags\n        models.set_flag(config=self.config, id=\"no_more_orders\", value=False)\n        log.info(\"reset values in table 'flags'\")\n        # Clean cache\n        pn.state.clear_caches()\n        log.info(\"cache cleaned\")\n\n    def build_menu(\n        self,\n        event: param.parameterized.Event,\n        app: pn.Template,\n        gi: gui.GraphicInterface,\n    ) -> None:\n        \"\"\"Read menu from file (Excel or image) and upload menu items to database `menu` table.\n\n        Args:\n            event (param.parameterized.Event): Panel button event.\n            app (pn.Template): Panel app template (used to open modal windows in case of database errors).\n            gi (gui.GraphicInterface): graphic interface object (used to interact with Panel widgets).\n        \"\"\"\n        # Hide messages\n        gi.error_message.visible = False\n\n        # Build image path\n        menu_filename = str(\n            pathlib.Path(self.config.db.shared_data_folder)\n            / self.config.panel.file_name\n        )\n\n        # Delete menu file if exist (every extension)\n        self.delete_files()\n\n        # Load file from widget\n        if gi.file_widget.value is not None:\n            # Find file extension\n            file_ext = pathlib.PurePath(gi.file_widget.filename).suffix\n\n            # Save file locally\n            local_menu_filename = menu_filename + file_ext\n            gi.file_widget.save(local_menu_filename)\n\n            # Clean tables\n            self.clean_tables()\n\n            # File can be either an excel file or an image\n            if file_ext == \".png\" or file_ext == \".jpg\" or file_ext == \".jpeg\":\n                # Transform image into a pandas DataFrame\n                # Open image with PIL\n                img = Image.open(local_menu_filename)\n                # Extract text from image\n                text = pytesseract.image_to_string(img, lang=\"ita\")\n                # Process rows (rows that are completely uppercase are section titles)\n                rows = [\n                    row\n                    for row in text.split(\"\\n\")\n                    if row and not row.isupper()\n                ]\n                df = pd.DataFrame({\"item\": rows})\n                # Concat additional items\n                df = pd.concat(\n                    [\n                        df,\n                        pd.DataFrame(\n                            {\n                                \"item\": [\n                                    item[\"name\"]\n                                    for item in self.config.panel.additional_items_to_concat\n                                ]\n                            }\n                        ),\n                    ],\n                    axis=\"index\",\n                )\n\n            elif file_ext == \".xlsx\":\n                log.info(\"excel file uploaded\")\n                df = pd.read_excel(\n                    local_menu_filename, names=[\"item\"], header=None\n                )\n                # Concat additional items\n                df = pd.concat(\n                    [\n                        df,\n                        pd.DataFrame(\n                            {\n                                \"item\": [\n                                    item[\"name\"]\n                                    for item in self.config.panel.additional_items_to_concat\n                                ]\n                            }\n                        ),\n                    ],\n                    axis=\"index\",\n                    ignore_index=True,\n                )\n            else:\n                df = pd.DataFrame()\n                pn.state.notifications.error(\n                    \"Wrong file type\",\n                    duration=self.config.panel.notifications.duration,\n                )\n                log.warning(\"wrong file type\")\n                return\n\n            # Upload to database menu table\n            engine = models.create_engine(self.config)\n            try:\n                df.drop_duplicates(subset=\"item\").to_sql(\n                    models.Menu.__tablename__,\n                    engine,\n                    schema=self.config.db.get(\"schema\", models.SCHEMA),\n                    index=False,\n                    if_exists=\"append\",\n                )\n                # Update dataframe widget\n                self.reload_menu(\n                    None,\n                    gi,\n                )\n\n                pn.state.notifications.success(\n                    \"Menu uploaded\",\n                    duration=self.config.panel.notifications.duration,\n                )\n                log.info(\"menu uploaded\")\n            except Exception as e:\n                # Any exception here is a database fault\n                pn.state.notifications.error(\n                    \"Database error\",\n                    duration=self.config.panel.notifications.duration,\n                )\n                gi.error_message.object = (\n                    f\"DATABASE ERROR<br><br>ERROR:<br>{str(e)}\"\n                )\n                gi.error_message.visible = True\n                log.warning(\"database error\")\n                # Open modal window\n                app.open_modal()\n\n        else:\n            pn.state.notifications.warning(\n                \"No file selected\",\n                duration=self.config.panel.notifications.duration,\n            )\n            log.warning(\"no file selected\")\n\n    def reload_menu(\n        self,\n        event: param.parameterized.Event,\n        gi: gui.GraphicInterface,\n    ) -> None:\n        \"\"\"Main core function that sync Panel widget with database tables.\n\n        Stop orders and guest override checks are carried out by this function.\n        Also the banner image is shown based on a check run by this function.\n\n        `menu`, `orders` and `users` tables are used to build a list of orders for each lunch time.\n        Takeaway orders are evaluated separately.\n\n        At the end stats about lunches are calculated and loaded to database. Finally\n        statistics (values and table) shown inside the app are updated accordingly.\n\n        Args:\n            event (param.parameterized.Event): Panel button event.\n            gi (gui.GraphicInterface): graphic interface object (used to interact with Panel widgets).\n        \"\"\"\n        # Create session\n        session = models.create_session(self.config)\n\n        with session:\n            # Check if someone changed the \"no_more_order\" toggle\n            if gi.toggle_no_more_order_button.value != models.get_flag(\n                config=self.config, id=\"no_more_orders\"\n            ):\n                # The following statement will trigger the toggle callback\n                # which will call reload_menu once again\n                # This is the reason why this if contains a return (without the return\n                # the content will be reloaded twice)\n                gi.toggle_no_more_order_button.value = models.get_flag(\n                    config=self.config, id=\"no_more_orders\"\n                )\n\n                return\n\n            # Check guest override button status (if not in table use False)\n            gi.toggle_guest_override_button.value = models.get_flag(\n                config=self.config,\n                id=f\"{pn_user(self.config)}_guest_override\",\n                value_if_missing=False,\n            )\n\n            # Set no more orders toggle button and the change order time button\n            # visibility and activation\n            if auth.is_guest(\n                user=pn_user(self.config),\n                config=self.config,\n                allow_override=False,\n            ):\n                # Deactivate the no_more_orders_button for guest users\n                gi.toggle_no_more_order_button.disabled = True\n                gi.toggle_no_more_order_button.visible = False\n                # Deactivate the change_order_time_button for guest users\n                gi.change_order_time_takeaway_button.disabled = True\n                gi.change_order_time_takeaway_button.visible = False\n            else:\n                # Activate the no_more_orders_button for privileged users\n                gi.toggle_no_more_order_button.disabled = False\n                gi.toggle_no_more_order_button.visible = True\n                # Show the change_order_time_button for privileged users\n                # It is disabled by the no more order button if necessary\n                gi.change_order_time_takeaway_button.visible = True\n\n            # Guest graphic configuration\n            if auth.is_guest(user=pn_user(self.config), config=self.config):\n                # If guest show guest type selection group\n                gi.person_widget.widgets[\"guest\"].disabled = False\n                gi.person_widget.widgets[\"guest\"].visible = True\n            else:\n                # If user is privileged hide guest type selection group\n                gi.person_widget.widgets[\"guest\"].disabled = True\n                gi.person_widget.widgets[\"guest\"].visible = False\n\n            # Reload menu\n            engine = models.create_engine(self.config)\n            df = models.Menu.read_as_df(\n                config=self.config,\n                index_col=\"id\",\n            )\n            # Add order (for selecting items) and note columns\n            df[\"order\"] = False\n            df[self.config.panel.gui.note_column_name] = \"\"\n            gi.dataframe.value = df\n            gi.dataframe.formatters = {\"order\": {\"type\": \"tickCross\"}}\n            gi.dataframe.editors = {\n                \"id\": None,\n                \"item\": None,\n                \"order\": CheckboxEditor(),\n                self.config.panel.gui.note_column_name: \"input\",\n            }\n            gi.dataframe.header_align = OmegaConf.to_container(\n                self.config.panel.gui.menu_column_align, resolve=True\n            )\n            gi.dataframe.text_align = OmegaConf.to_container(\n                self.config.panel.gui.menu_column_align, resolve=True\n            )\n\n            if gi.toggle_no_more_order_button.value:\n                gi.dataframe.hidden_columns = [\"id\", \"order\"]\n                gi.dataframe.disabled = True\n            else:\n                gi.dataframe.hidden_columns = [\"id\"]\n                gi.dataframe.disabled = False\n\n            # If menu is empty show banner image, otherwise show menu\n            if df.empty:\n                gi.no_menu_col.visible = True\n                gi.main_header_row.visible = False\n                gi.quote.visible = False\n                gi.menu_flexbox.visible = False\n                gi.buttons_flexbox.visible = False\n                gi.results_divider.visible = False\n                gi.res_col.visible = False\n            else:\n                gi.no_menu_col.visible = False\n                gi.main_header_row.visible = True\n                gi.quote.visible = True\n                gi.menu_flexbox.visible = True\n                gi.buttons_flexbox.visible = True\n                gi.results_divider.visible = True\n                gi.res_col.visible = True\n\n            log.debug(\"menu reloaded\")\n\n            # Load results\n            df_dict = self.df_list_by_lunch_time()\n            # Clean columns and load text and dataframes\n            gi.res_col.clear()\n            gi.time_col.clear()\n            if df_dict:\n                # Titles\n                gi.res_col.append(self.config.panel.result_column_text)\n                gi.time_col.append(gi.time_col_title)\n                # Build guests list (one per each guest types)\n                guests_lists = {}\n                for guest_type in self.config.panel.guest_types:\n                    guests_lists[guest_type] = [\n                        user.id\n                        for user in session.scalars(\n                            select(models.Users).where(\n                                models.Users.guest == guest_type\n                            )\n                        ).all()\n                    ]\n                # Loop through lunch times\n                for time, df in df_dict.items():\n                    # Find the number of grumbling stomachs\n                    grumbling_stomachs = len(\n                        [\n                            c\n                            for c in df.columns\n                            if c\n                            not in (\n                                self.config.panel.gui.total_column_name,\n                                self.config.panel.gui.note_column_name,\n                            )\n                        ]\n                    )\n                    # Set different graphics for takeaway lunches\n                    if self.config.panel.gui.takeaway_id in time:\n                        res_col_label_kwargs = {\n                            \"time\": time.replace(\n                                self.config.panel.gui.takeaway_id, \"\"\n                            ),\n                            \"diners_n\": grumbling_stomachs,\n                            \"emoji\": self.config.panel.gui.takeaway_emoji,\n                            \"is_takeaway\": True,\n                            \"takeaway_alert_sign\": f\"&nbsp{gi.takeaway_alert_sign}&nbsp{gi.takeaway_alert_text}\",\n                            \"css_classes\": OmegaConf.to_container(\n                                self.config.panel.gui.takeaway_class_res_col,\n                                resolve=True,\n                            ),\n                            \"stylesheets\": [\n                                self.config.panel.gui.css_files.labels_path\n                            ],\n                        }\n                        time_col_label_kwargs = {\n                            \"time\": time.replace(\n                                self.config.panel.gui.takeaway_id, \"\"\n                            ),\n                            \"diners_n\": str(grumbling_stomachs) + \"&nbsp\",\n                            \"separator\": \"<br>\",\n                            \"emoji\": self.config.panel.gui.takeaway_emoji,\n                            \"align\": (\"center\", \"center\"),\n                            \"sizing_mode\": \"stretch_width\",\n                            \"is_takeaway\": True,\n                            \"takeaway_alert_sign\": gi.takeaway_alert_sign,\n                            \"css_classes\": OmegaConf.to_container(\n                                self.config.panel.gui.takeaway_class_time_col,\n                                resolve=True,\n                            ),\n                            \"stylesheets\": [\n                                self.config.panel.gui.css_files.labels_path\n                            ],\n                        }\n                    else:\n                        res_col_label_kwargs = {\n                            \"time\": time,\n                            \"diners_n\": grumbling_stomachs,\n                            \"emoji\": random.choice(\n                                self.config.panel.gui.food_emoji\n                            ),\n                            \"css_classes\": OmegaConf.to_container(\n                                self.config.panel.gui.time_class_res_col,\n                                resolve=True,\n                            ),\n                            \"stylesheets\": [\n                                self.config.panel.gui.css_files.labels_path\n                            ],\n                        }\n                        time_col_label_kwargs = {\n                            \"time\": time,\n                            \"diners_n\": str(grumbling_stomachs) + \"&nbsp\",\n                            \"separator\": \"<br>\",\n                            \"emoji\": self.config.panel.gui.restaurant_emoji,\n                            \"per_icon\": \"&#10006; \",\n                            \"align\": (\"center\", \"center\"),\n                            \"sizing_mode\": \"stretch_width\",\n                            \"css_classes\": OmegaConf.to_container(\n                                self.config.panel.gui.time_class_time_col,\n                                resolve=True,\n                            ),\n                            \"stylesheets\": [\n                                self.config.panel.gui.css_files.labels_path\n                            ],\n                        }\n                    # Add text to result column\n                    gi.res_col.append(pn.Spacer(height=15))\n                    gi.res_col.append(\n                        gi.build_time_label(**res_col_label_kwargs)\n                    )\n                    # Add non editable table to result column\n                    gi.res_col.append(pn.Spacer(height=5))\n                    gi.res_col.append(\n                        gi.build_order_table(\n                            self.config,\n                            df=df,\n                            time=time,\n                            guests_lists=guests_lists,\n                        )\n                    )\n                    # Add also a label to lunch time column\n                    gi.time_col.append(\n                        gi.build_time_label(**time_col_label_kwargs)\n                    )\n\n            log.debug(\"results reloaded\")\n\n            # Clean stats column\n            gi.sidebar_stats_col.clear()\n            # Update stats\n            # Find how many people eat today (total number) and add value to database\n            # stats table (when adding a stats if guest is not specified None is used\n            # as default)\n            today_locals_count = session.scalar(\n                select(func.count(models.Users.id)).where(\n                    models.Users.guest == \"NotAGuest\"\n                )\n            )\n            new_stat = models.Stats(hungry_people=today_locals_count)\n            # Use an upsert for postgresql, a simple session add otherwise\n            models.session_add_with_upsert(\n                session=session, constraint=\"stats_pkey\", new_record=new_stat\n            )\n            # For each guest type find how many guests eat today\n            for guest_type in self.config.panel.guest_types:\n                today_guests_count = session.scalar(\n                    select(func.count(models.Users.id)).where(\n                        models.Users.guest == guest_type\n                    )\n                )\n                new_stat = models.Stats(\n                    guest=guest_type, hungry_people=today_guests_count\n                )\n                # Use an upsert for postgresql, a simple session add otherwise\n                models.session_add_with_upsert(\n                    session=session,\n                    constraint=\"stats_pkey\",\n                    new_record=new_stat,\n                )\n\n            # Commit stats\n            session.commit()\n\n            # Group stats by month and return how many people had lunch\n            df_stats = pd.read_sql_query(\n                self.config.db.stats_query.format(\n                    schema=self.config.db.get(\"schema\", models.SCHEMA)\n                ),\n                engine,\n            )\n            # Stats top text\n            stats_and_info_text = gi.build_stats_and_info_text(\n                config=self.config,\n                df_stats=df_stats,\n                user=pn_user(self.config),\n                version=__version__,\n                host_name=self.get_host_name(),\n                stylesheets=[self.config.panel.gui.css_files.stats_info_path],\n            )\n            # Remove NotAGuest (non-guest users)\n            df_stats.Guest = df_stats.Guest.replace(\n                \"NotAGuest\", self.config.panel.stats_locals_column_name\n            )\n            # Pivot table on guest type\n            df_stats = df_stats.pivot(\n                columns=\"Guest\",\n                index=self.config.panel.stats_id_cols,\n                values=\"Hungry People\",\n            ).reset_index()\n            df_stats[self.config.panel.gui.total_column_name.title()] = (\n                df_stats.sum(axis=\"columns\", numeric_only=True)\n            )\n            # Add value and non-editable option to stats table\n            gi.stats_widget.editors = {c: None for c in df_stats.columns}\n            gi.stats_widget.value = df_stats\n            gi.sidebar_stats_col.append(stats_and_info_text[\"stats\"])\n            gi.sidebar_stats_col.append(gi.stats_widget)\n            # Add info below person widget (an empty placeholder was left as last\n            # element)\n            gi.sidebar_person_column.objects[-1] = stats_and_info_text[\"info\"]\n            log.debug(\"stats and info updated\")\n\n    def send_order(\n        self,\n        event: param.parameterized.Event,\n        app: pn.Template,\n        person: gui.Person,\n        gi: gui.GraphicInterface,\n    ) -> None:\n        \"\"\"Upload orders and user to database tables.\n\n        The user target of the order is uploaded to `users` table, while the order\n        is uploaded to `orders` table.\n\n        Consistency checks about the user and the order are carried out here (existing user, only one order, etc.).\n        The status of the `stop_orders` flag is checked to avoid that an order is uploaded when it shouldn't.\n\n        Orders for guest users are marked as such before uploading them.\n\n        Args:\n            event (param.parameterized.Event): Panel button event.\n            app (pn.Template): Panel app template (used to open modal windows in case of database errors).\n            person (gui.Person): class that collect order data for the user that is the target of the order.\n            gi (gui.GraphicInterface): graphic interface object (used to interact with Panel widgets).\n        \"\"\"\n        # Get username updated at each key press\n        username_key_press = gi.person_widget._widgets[\"username\"].value_input\n\n        # Hide messages\n        gi.error_message.visible = False\n\n        # Create session\n        session = models.create_session(self.config)\n\n        with session:\n            # Check if the \"no more order\" toggle button is pressed\n            if models.get_flag(config=self.config, id=\"no_more_orders\"):\n                pn.state.notifications.error(\n                    \"It is not possible to place new orders\",\n                    duration=self.config.panel.notifications.duration,\n                )\n\n                # Reload the menu\n                self.reload_menu(\n                    None,\n                    gi,\n                )\n\n                return\n\n            # If auth is active, check if a guests is using a name reserved to a\n            # privileged user\n            if (\n                auth.is_guest(user=pn_user(self.config), config=self.config)\n                and (username_key_press in auth.list_users(config=self.config))\n                and (auth.is_auth_active(config=self.config))\n            ):\n                pn.state.notifications.error(\n                    f\"{username_key_press} is a reserved name<br>Please choose a different one\",\n                    duration=self.config.panel.notifications.duration,\n                )\n\n                # Reload the menu\n                self.reload_menu(\n                    None,\n                    gi,\n                )\n\n                return\n\n            # Check if a privileged user is ordering for an invalid name\n            if (\n                not auth.is_guest(\n                    user=pn_user(self.config), config=self.config\n                )\n                and (\n                    username_key_press\n                    not in (\n                        name\n                        for name in auth.list_users(config=self.config)\n                        if name != \"guest\"\n                    )\n                )\n                and (auth.is_auth_active(config=self.config))\n            ):\n                pn.state.notifications.error(\n                    f\"{username_key_press} is not a valid name<br>for a privileged user<br>Please choose a different one\",\n                    duration=self.config.panel.notifications.duration,\n                )\n\n                # Reload the menu\n                self.reload_menu(\n                    None,\n                    gi,\n                )\n\n                return\n\n            # Write order into database table\n            df = gi.dataframe.value.copy()\n            df_order = df[df.order]\n            # If username is missing or the order is empty return an error message\n            if username_key_press and not df_order.empty:\n                # Check if the user already placed an order\n                if session.get(models.Users, username_key_press):\n                    pn.state.notifications.warning(\n                        f\"Cannot overwrite an order<br>Delete {username_key_press}'s order first and retry\",\n                        duration=self.config.panel.notifications.duration,\n                    )\n                    log.warning(\n                        f\"an order already exist for {username_key_press}\"\n                    )\n                else:\n                    # Place order\n                    try:\n                        # Add User\n                        # Do not pass guest for privileged users (default to NotAGuest)\n                        if auth.is_guest(\n                            user=pn_user(self.config), config=self.config\n                        ):\n                            new_user = models.Users(\n                                id=username_key_press,\n                                guest=person.guest,\n                                lunch_time=person.lunch_time,\n                                takeaway=person.takeaway,\n                            )\n                        else:\n                            new_user = models.Users(\n                                id=username_key_press,\n                                lunch_time=person.lunch_time,\n                                takeaway=person.takeaway,\n                            )\n                        session.add(new_user)\n                        session.commit()\n                        # Add orders as long table (one row for each item selected by a user)\n                        for row in df_order.itertuples(name=\"OrderTuple\"):\n                            # Order\n                            new_order = models.Orders(\n                                user=username_key_press,\n                                menu_item_id=row.Index,\n                                note=getattr(\n                                    row, self.config.panel.gui.note_column_name\n                                ).lower(),\n                            )\n                            session.add(new_order)\n                            session.commit()\n\n                        # Update dataframe widget\n                        self.reload_menu(\n                            None,\n                            gi,\n                        )\n\n                        pn.state.notifications.success(\n                            \"Order sent\",\n                            duration=self.config.panel.notifications.duration,\n                        )\n                        log.info(f\"{username_key_press}'s order saved\")\n                    except Exception as e:\n                        # Any exception here is a database fault\n                        pn.state.notifications.error(\n                            \"Database error\",\n                            duration=self.config.panel.notifications.duration,\n                        )\n                        gi.error_message.object = (\n                            f\"DATABASE ERROR<br><br>ERROR:<br>{str(e)}\"\n                        )\n                        gi.error_message.visible = True\n                        log.error(\"database error\")\n                        # Open modal window\n                        app.open_modal()\n            else:\n                if not username_key_press:\n                    pn.state.notifications.warning(\n                        \"Please insert user name\",\n                        duration=self.config.panel.notifications.duration,\n                    )\n                    log.warning(\"missing username\")\n                else:\n                    pn.state.notifications.warning(\n                        \"Please make a selection\",\n                        duration=self.config.panel.notifications.duration,\n                    )\n                    log.warning(\"no selection made\")\n\n    def delete_order(\n        self,\n        event: param.parameterized.Event,\n        app: pn.Template,\n        gi: gui.GraphicInterface,\n    ) -> None:\n        \"\"\"Delete an existing order.\n\n        Consistency checks about the user and the order are carried out here (existing user, only one order, etc.).\n        The status of the `stop_orders` flag is checked to avoid that an order is uploaded when it shouldn't.\n\n        In addition privileges are taken into account (guest users cannot delete orders that targets a privileged user).\n\n        Args:\n            event (param.parameterized.Event): Panel button event.\n            app (pn.Template): Panel app template (used to open modal windows in case of database errors).\n            gi (gui.GraphicInterface): graphic interface object (used to interact with Panel widgets).\n        \"\"\"\n        # Get username, updated on every keypress\n        username_key_press = gi.person_widget._widgets[\"username\"].value_input\n\n        # Hide messages\n        gi.error_message.visible = False\n\n        # Create session\n        session = models.create_session(self.config)\n\n        with session:\n            # Check if the \"no more order\" toggle button is pressed\n            if models.get_flag(config=self.config, id=\"no_more_orders\"):\n                pn.state.notifications.error(\n                    \"It is not possible to delete orders\",\n                    duration=self.config.panel.notifications.duration,\n                )\n\n                # Reload the menu\n                self.reload_menu(\n                    None,\n                    gi,\n                )\n\n                return\n\n            if username_key_press:\n                # If auth is active, check if a guests is deleting an order of a\n                # privileged user\n                if (\n                    auth.is_guest(\n                        user=pn_user(self.config), config=self.config\n                    )\n                    and (\n                        username_key_press\n                        in auth.list_users(config=self.config)\n                    )\n                    and (auth.is_auth_active(config=self.config))\n                ):\n                    pn.state.notifications.error(\n                        f\"You do not have enough privileges<br>to delete<br>{username_key_press}'s order\",\n                        duration=self.config.panel.notifications.duration,\n                    )\n\n                    # Reload the menu\n                    self.reload_menu(\n                        None,\n                        gi,\n                    )\n\n                    return\n\n                # Delete user\n                try:\n                    num_rows_deleted_users = session.execute(\n                        delete(models.Users).where(\n                            models.Users.id == username_key_press\n                        )\n                    )\n                    # Delete also orders (hotfix for Debian)\n                    num_rows_deleted_orders = session.execute(\n                        delete(models.Orders).where(\n                            models.Orders.user == username_key_press\n                        )\n                    )\n                    session.commit()\n                    if (num_rows_deleted_users.rowcount > 0) or (\n                        num_rows_deleted_orders.rowcount > 0\n                    ):\n                        # Update dataframe widget\n                        self.reload_menu(\n                            None,\n                            gi,\n                        )\n\n                        pn.state.notifications.success(\n                            \"Order canceled\",\n                            duration=self.config.panel.notifications.duration,\n                        )\n                        log.info(f\"{username_key_press}'s order canceled\")\n                    else:\n                        pn.state.notifications.warning(\n                            f'No order for user named<br>\"{username_key_press}\"',\n                            duration=self.config.panel.notifications.duration,\n                        )\n                        log.info(\n                            f\"no order for user named {username_key_press}\"\n                        )\n                except Exception as e:\n                    # Any exception here is a database fault\n                    pn.state.notifications.error(\n                        \"Database error\",\n                        duration=self.config.panel.notifications.duration,\n                    )\n                    gi.error_message.object = (\n                        f\"DATABASE ERROR<br><br>ERROR:<br>{str(e)}\"\n                    )\n                    gi.error_message.visible = True\n                    log.error(\"database error\")\n                    # Open modal window\n                    app.open_modal()\n            else:\n                pn.state.notifications.warning(\n                    \"Please insert user name\",\n                    duration=self.config.panel.notifications.duration,\n                )\n                log.warning(\"missing username\")\n\n    def change_order_time_takeaway(\n        self,\n        event: param.parameterized.Event,\n        person: gui.Person,\n        gi: gui.GraphicInterface,\n    ) -> None:\n        \"\"\"Change the time and the takeaway flag of an existing order.\n\n        Args:\n            event (param.parameterized.Event): Panel button event.\n            person (gui.Person): class that collect order data for the user that is the target of the order.\n            gi (gui.GraphicInterface): graphic interface object (used to interact with Panel widgets).\n        \"\"\"\n        # Get username, updated on every keypress\n        username_key_press = gi.person_widget._widgets[\"username\"].value_input\n\n        # Create session\n        session = models.create_session(self.config)\n\n        with session:\n            # Check if the \"no more order\" toggle button is pressed\n            if models.get_flag(config=self.config, id=\"no_more_orders\"):\n                pn.state.notifications.error(\n                    \"It is not possible to update orders (time)\",\n                    duration=self.config.panel.notifications.duration,\n                )\n\n                # Reload the menu\n                self.reload_menu(\n                    None,\n                    gi,\n                )\n\n                return\n\n            if username_key_press:\n                # Build and execute the update statement\n                update_statement = (\n                    update(models.Users)\n                    .where(models.Users.id == username_key_press)\n                    .values(\n                        lunch_time=person.lunch_time, takeaway=person.takeaway\n                    )\n                    .returning(models.Users)\n                )\n\n                updated_user = session.scalars(update_statement).one_or_none()\n\n                session.commit()\n\n                if updated_user:\n                    # Find updated values\n                    updated_time = updated_user.lunch_time\n                    updated_takeaway = (\n                        (\" \" + self.config.panel.gui.takeaway_id)\n                        if updated_user.takeaway\n                        else \"\"\n                    )\n                    updated_items_names = [\n                        order.menu_item.item for order in updated_user.orders\n                    ]\n                    # Update dataframe widget\n                    self.reload_menu(\n                        None,\n                        gi,\n                    )\n\n                    pn.state.notifications.success(\n                        f\"{username_key_press}'s<br>lunch time changed to<br>{updated_time}{updated_takeaway}<br>({', '.join(updated_items_names)})\",\n                        duration=self.config.panel.notifications.duration,\n                    )\n                    log.info(f\"{username_key_press}'s order updated\")\n                else:\n                    pn.state.notifications.warning(\n                        f'No order for user named<br>\"{username_key_press}\"',\n                        duration=self.config.panel.notifications.duration,\n                    )\n                    log.info(f\"no order for user named {username_key_press}\")\n            else:\n                pn.state.notifications.warning(\n                    \"Please insert user name\",\n                    duration=self.config.panel.notifications.duration,\n                )\n                log.warning(\"missing username\")\n\n    def df_list_by_lunch_time(\n        self,\n    ) -> dict:\n        \"\"\"Build a dictionary of dataframes for each lunch-time, with takeaways included in a dedicated dataframe.\n\n        Each datframe includes orders grouped by users, notes and a total column (with the total value\n        for a specific item).\n\n        The keys of the dataframe are `lunch-times` and `lunch-times + takeaway_id`.\n\n        Returns:\n            dict: dictionary with dataframes summarizing the orders for each lunch-time/takeaway-time.\n        \"\"\"\n        # Create database engine and session\n        engine = models.create_engine(self.config)\n        # Read menu and save how menu items are sorted (first courses, second courses, etc.)\n        original_order = models.Menu.read_as_df(\n            config=self.config,\n            index_col=\"id\",\n        ).item\n        # Create session\n        session = models.create_session(self.config)\n\n        with session:\n            # Build takeaway list\n            takeaway_list = [\n                user.id\n                for user in session.scalars(\n                    select(models.Users).where(\n                        models.Users.takeaway == sql_true()\n                    )\n                ).all()\n            ]\n        # Read orders dataframe (including notes)\n        df = pd.read_sql_query(\n            self.config.db.orders_query.format(\n                schema=self.config.db.get(\"schema\", models.SCHEMA)\n            ),\n            engine,\n        )\n\n        # The following function prepare the dataframe before saving it into\n        # the dictionary that will be returned\n        def _clean_up_table(\n            config: DictConfig,\n            df_in: pd.DataFrame,\n            df_complete: pd.DataFrame,\n        ):\n            df = df_in.copy()\n            # Group notes per menu item by concat users notes\n            # Use value counts to keep track of how many time a note is repeated\n            df_notes = (\n                df_complete[\n                    (df_complete.lunch_time == time)\n                    & (df_complete.note != \"\")\n                    & (df_complete.user.isin(df.columns))\n                ]\n                .drop(columns=[\"user\", \"lunch_time\"])\n                .value_counts()\n                .reset_index(level=\"note\")\n            )\n            df_notes.note = (\n                df_notes[\"count\"]\n                .astype(str)\n                .str.cat(df_notes.note, sep=config.panel.gui.note_sep.count)\n            )\n            df_notes = df_notes.drop(columns=\"count\")\n            df_notes = (\n                df_notes.groupby(\"item\")[\"note\"]\n                .apply(config.panel.gui.note_sep.element.join)\n                .to_frame()\n            )\n            # Add columns of totals\n            df[config.panel.gui.total_column_name] = df.sum(axis=1)\n            # Drop unused rows if requested\n            if config.panel.drop_unused_menu_items:\n                df = df[df[config.panel.gui.total_column_name] > 0]\n            # Add notes\n            df = df.join(df_notes)\n            df = df.rename(columns={\"note\": config.panel.gui.note_column_name})\n            # Change NaNs to '-'\n            df = df.fillna(\"-\")\n            # Avoid mixed types (float and notes str)\n            df = df.astype(object)\n\n            return df\n\n        # Build a dict of dataframes, one for each lunch time\n        df_dict = {}\n        for time in df.lunch_time.sort_values().unique():\n            # Take only one lunch time (and remove notes so they do not alter\n            # numeric counters inside the pivot table)\n            temp_df = (\n                df[df.lunch_time == time]\n                .drop(columns=[\"lunch_time\", \"note\"])\n                .reset_index(drop=True)\n            )\n            # Users' selections\n            df_users = temp_df.pivot_table(\n                index=\"item\", columns=\"user\", aggfunc=len\n            )\n            # Reorder index in accordance with original menu\n            df_users = df_users.reindex(original_order)\n            # Split restaurant lunches from takeaway lunches\n            df_users_restaurant = df_users.loc[\n                :, [c for c in df_users.columns if c not in takeaway_list]\n            ]\n            df_users_takeaways = df_users.loc[\n                :, [c for c in df_users.columns if c in takeaway_list]\n            ]\n\n            # Clean and add resulting dataframes to dict\n            # RESTAURANT LUNCH\n            if not df_users_restaurant.empty:\n                df_users_restaurant = _clean_up_table(\n                    self.config, df_users_restaurant, df\n                )\n                df_dict[time] = df_users_restaurant\n            # TAKEAWAY\n            if not df_users_takeaways.empty:\n                df_users_takeaways = _clean_up_table(\n                    self.config, df_users_takeaways, df\n                )\n                df_dict[f\"{time} {self.config.panel.gui.takeaway_id}\"] = (\n                    df_users_takeaways\n                )\n\n        return df_dict\n\n    def download_dataframe(\n        self,\n        gi: gui.GraphicInterface,\n    ) -> BytesIO:\n        \"\"\"Build an Excel file with tables representing orders for every lunch-time/takeaway-time.\n\n        Tables are created by the function `df_list_by_lunch_time` and exported on dedicated Excel worksheets\n        (inside the same workbook).\n\n        The result is returned as bytes stream to satisfy panel.widgets.FileDownload class requirements.\n\n        Args:\n            gi (gui.GraphicInterface): graphic interface object (used to interact with Panel widgets).\n\n        Returns:\n            BytesIO: download stream for the Excel file.\n        \"\"\"\n\n        # Build a dict of dataframes, one for each lunch time (the key contains\n        # a lunch time)\n        df_dict = self.df_list_by_lunch_time()\n        # Export one dataframe for each lunch time\n        bytes_io = BytesIO()\n        writer = pd.ExcelWriter(bytes_io)\n        # If the dataframe dict is non-empty export one dataframe for each sheet\n        if df_dict:\n            for time, df in df_dict.items():\n                log.info(f\"writing sheet {time}\")\n\n                # Find users that placed an order for a given time\n                users_n = len(\n                    [\n                        c\n                        for c in df.columns\n                        if c\n                        not in (\n                            self.config.panel.gui.total_column_name,\n                            self.config.panel.gui.note_column_name,\n                        )\n                    ]\n                )\n\n                # Export dataframe to new sheet\n                worksheet_name = time.replace(\":\", \".\")\n                df.to_excel(writer, sheet_name=worksheet_name, startrow=1)\n                # Add title\n                worksheet = writer.sheets[worksheet_name]\n                worksheet.cell(\n                    1,\n                    1,\n                    f\"Time - {time} | # {users_n}\",\n                )\n\n                # HEADER FORMAT\n                worksheet[\"A1\"].font = Font(\n                    size=13, bold=True, color=\"00FF0000\"\n                )\n\n                # INDEX ALIGNMENT\n                for row in worksheet[worksheet.min_row : worksheet.max_row]:\n                    cell = row[0]  # column A\n                    cell.alignment = Alignment(horizontal=\"left\")\n                    cell = row[users_n + 2]  # column note\n                    cell.alignment = Alignment(horizontal=\"left\")\n                    cells = row[1 : users_n + 2]  # from column B to note-1\n                    for cell in cells:\n                        cell.alignment = Alignment(horizontal=\"center\")\n\n                # AUTO SIZE\n                # Set auto-size for all columns\n                # Use end +1 for ID column, and +2 for 'total' and 'note' columns\n                column_letters = get_column_interval(\n                    start=1, end=users_n + 1 + 2\n                )\n                # Get columns\n                columns = worksheet[column_letters[0] : column_letters[-1]]\n                for column_letter, column in zip(column_letters, columns):\n                    # Instantiate max length then loop on cells to find max value\n                    max_length = 0\n                    # Cell loop\n                    for cell in column:\n                        log.debug(\n                            f\"autosize for cell {cell.coordinate} with value '{cell.value}'\"\n                        )\n                        try:  # Necessary to avoid error on empty cells\n                            if len(str(cell.value)) > max_length:\n                                max_length = len(cell.value)\n                                log.debug(\n                                    f\"new max length set to {max_length}\"\n                                )\n                        except Exception:\n                            log.debug(\"empty cell\")\n                    log.debug(f\"final max length is {max_length}\")\n                    adjusted_width = (max_length + 2) * 0.85\n                    log.debug(\n                        f\"adjusted width for column '{column_letter}' is {adjusted_width}\"\n                    )\n                    worksheet.column_dimensions[column_letter].width = (\n                        adjusted_width\n                    )\n                # Since grouping fix width equal to first column width (openpyxl\n                # bug), set first column of users' order equal to max width of\n                # all users columns to avoid issues\n                max_width = 0\n                log.debug(\n                    f\"find max width for users' columns '{column_letters[1]}:{column_letters[-3]}'\"\n                )\n                for column_letter in column_letters[1:-2]:\n                    max_width = max(\n                        max_width,\n                        worksheet.column_dimensions[column_letter].width,\n                    )\n                log.debug(f\"max width for first users' columns is {max_width}\")\n                worksheet.column_dimensions[column_letters[1]].width = (\n                    max_width\n                )\n\n                # GROUPING\n                # Group and hide columns, leave only ID, total and note\n                column_letters = get_column_interval(start=2, end=users_n + 1)\n                worksheet.column_dimensions.group(\n                    column_letters[0], column_letters[-1], hidden=True\n                )\n\n                # Close and reset bytes_io for the next dataframe\n                writer.close()  # Important!\n                bytes_io.seek(0)  # Important!\n\n            # Message prompt\n            pn.state.notifications.success(\n                \"File with orders downloaded\",\n                duration=self.config.panel.notifications.duration,\n            )\n            log.info(\"xlsx downloaded\")\n        else:\n            gi.dataframe.value.drop(columns=[\"order\"]).to_excel(\n                writer, sheet_name=\"MENU\", index=False\n            )\n            writer.close()  # Important!\n            bytes_io.seek(0)  # Important!\n            # Message prompt\n            pn.state.notifications.warning(\n                \"No order<br>Menu downloaded\",\n                duration=self.config.panel.notifications.duration,\n            )\n            log.warning(\n                \"no order, menu exported to excel in place of orders' list\"\n            )\n\n        return bytes_io\n
"},{"location":"reference/dlunch/core/#dlunch.core.Waiter.config","title":"config instance-attribute","text":"
config: DictConfig | None = None\n

Hydra configuration object

"},{"location":"reference/dlunch/core/#dlunch.core.Waiter.__init__","title":"__init__","text":"
__init__(config: DictConfig | None = None)\n
Source code in dlunch/core.py
def __init__(self, config: DictConfig | None = None):\n    self.config: DictConfig | None = None\n    \"\"\"Hydra configuration object\"\"\"\n\n    # Define decorator to raise an error if the configuration is not set\n    def _common_decorator(func):\n        def wrapper(*args, **kwargs):\n            if self.config is None:\n                raise ValueError(\"waiter configuration is not set\")\n            return func(*args, **kwargs)\n\n        return wrapper\n\n    # Set decorator to unmangled methods (do not apply to set_config method)\n    for method_name in [\n        item\n        for item in dir(self)\n        if not item.startswith(\"_\") and not (item == \"set_config\")\n    ]:\n        attr = getattr(self, method_name)\n        # Check if the attribute is a method\n        if callable(attr):\n            wrapped = _common_decorator(attr)\n            setattr(self, method_name, wrapped)\n
"},{"location":"reference/dlunch/core/#dlunch.core.Waiter.build_menu","title":"build_menu","text":"
build_menu(\n    event: Event, app: Template, gi: GraphicInterface\n) -> None\n

Read menu from file (Excel or image) and upload menu items to database menu table.

Parameters:

Name Type Description Default event Event

Panel button event.

required app Template

Panel app template (used to open modal windows in case of database errors).

required gi GraphicInterface

graphic interface object (used to interact with Panel widgets).

required Source code in dlunch/core.py
def build_menu(\n    self,\n    event: param.parameterized.Event,\n    app: pn.Template,\n    gi: gui.GraphicInterface,\n) -> None:\n    \"\"\"Read menu from file (Excel or image) and upload menu items to database `menu` table.\n\n    Args:\n        event (param.parameterized.Event): Panel button event.\n        app (pn.Template): Panel app template (used to open modal windows in case of database errors).\n        gi (gui.GraphicInterface): graphic interface object (used to interact with Panel widgets).\n    \"\"\"\n    # Hide messages\n    gi.error_message.visible = False\n\n    # Build image path\n    menu_filename = str(\n        pathlib.Path(self.config.db.shared_data_folder)\n        / self.config.panel.file_name\n    )\n\n    # Delete menu file if exist (every extension)\n    self.delete_files()\n\n    # Load file from widget\n    if gi.file_widget.value is not None:\n        # Find file extension\n        file_ext = pathlib.PurePath(gi.file_widget.filename).suffix\n\n        # Save file locally\n        local_menu_filename = menu_filename + file_ext\n        gi.file_widget.save(local_menu_filename)\n\n        # Clean tables\n        self.clean_tables()\n\n        # File can be either an excel file or an image\n        if file_ext == \".png\" or file_ext == \".jpg\" or file_ext == \".jpeg\":\n            # Transform image into a pandas DataFrame\n            # Open image with PIL\n            img = Image.open(local_menu_filename)\n            # Extract text from image\n            text = pytesseract.image_to_string(img, lang=\"ita\")\n            # Process rows (rows that are completely uppercase are section titles)\n            rows = [\n                row\n                for row in text.split(\"\\n\")\n                if row and not row.isupper()\n            ]\n            df = pd.DataFrame({\"item\": rows})\n            # Concat additional items\n            df = pd.concat(\n                [\n                    df,\n                    pd.DataFrame(\n                        {\n                            \"item\": [\n                                item[\"name\"]\n                                for item in self.config.panel.additional_items_to_concat\n                            ]\n                        }\n                    ),\n                ],\n                axis=\"index\",\n            )\n\n        elif file_ext == \".xlsx\":\n            log.info(\"excel file uploaded\")\n            df = pd.read_excel(\n                local_menu_filename, names=[\"item\"], header=None\n            )\n            # Concat additional items\n            df = pd.concat(\n                [\n                    df,\n                    pd.DataFrame(\n                        {\n                            \"item\": [\n                                item[\"name\"]\n                                for item in self.config.panel.additional_items_to_concat\n                            ]\n                        }\n                    ),\n                ],\n                axis=\"index\",\n                ignore_index=True,\n            )\n        else:\n            df = pd.DataFrame()\n            pn.state.notifications.error(\n                \"Wrong file type\",\n                duration=self.config.panel.notifications.duration,\n            )\n            log.warning(\"wrong file type\")\n            return\n\n        # Upload to database menu table\n        engine = models.create_engine(self.config)\n        try:\n            df.drop_duplicates(subset=\"item\").to_sql(\n                models.Menu.__tablename__,\n                engine,\n                schema=self.config.db.get(\"schema\", models.SCHEMA),\n                index=False,\n                if_exists=\"append\",\n            )\n            # Update dataframe widget\n            self.reload_menu(\n                None,\n                gi,\n            )\n\n            pn.state.notifications.success(\n                \"Menu uploaded\",\n                duration=self.config.panel.notifications.duration,\n            )\n            log.info(\"menu uploaded\")\n        except Exception as e:\n            # Any exception here is a database fault\n            pn.state.notifications.error(\n                \"Database error\",\n                duration=self.config.panel.notifications.duration,\n            )\n            gi.error_message.object = (\n                f\"DATABASE ERROR<br><br>ERROR:<br>{str(e)}\"\n            )\n            gi.error_message.visible = True\n            log.warning(\"database error\")\n            # Open modal window\n            app.open_modal()\n\n    else:\n        pn.state.notifications.warning(\n            \"No file selected\",\n            duration=self.config.panel.notifications.duration,\n        )\n        log.warning(\"no file selected\")\n
"},{"location":"reference/dlunch/core/#dlunch.core.Waiter.change_order_time_takeaway","title":"change_order_time_takeaway","text":"
change_order_time_takeaway(\n    event: Event, person: Person, gi: GraphicInterface\n) -> None\n

Change the time and the takeaway flag of an existing order.

Parameters:

Name Type Description Default event Event

Panel button event.

required person Person

class that collect order data for the user that is the target of the order.

required gi GraphicInterface

graphic interface object (used to interact with Panel widgets).

required Source code in dlunch/core.py
def change_order_time_takeaway(\n    self,\n    event: param.parameterized.Event,\n    person: gui.Person,\n    gi: gui.GraphicInterface,\n) -> None:\n    \"\"\"Change the time and the takeaway flag of an existing order.\n\n    Args:\n        event (param.parameterized.Event): Panel button event.\n        person (gui.Person): class that collect order data for the user that is the target of the order.\n        gi (gui.GraphicInterface): graphic interface object (used to interact with Panel widgets).\n    \"\"\"\n    # Get username, updated on every keypress\n    username_key_press = gi.person_widget._widgets[\"username\"].value_input\n\n    # Create session\n    session = models.create_session(self.config)\n\n    with session:\n        # Check if the \"no more order\" toggle button is pressed\n        if models.get_flag(config=self.config, id=\"no_more_orders\"):\n            pn.state.notifications.error(\n                \"It is not possible to update orders (time)\",\n                duration=self.config.panel.notifications.duration,\n            )\n\n            # Reload the menu\n            self.reload_menu(\n                None,\n                gi,\n            )\n\n            return\n\n        if username_key_press:\n            # Build and execute the update statement\n            update_statement = (\n                update(models.Users)\n                .where(models.Users.id == username_key_press)\n                .values(\n                    lunch_time=person.lunch_time, takeaway=person.takeaway\n                )\n                .returning(models.Users)\n            )\n\n            updated_user = session.scalars(update_statement).one_or_none()\n\n            session.commit()\n\n            if updated_user:\n                # Find updated values\n                updated_time = updated_user.lunch_time\n                updated_takeaway = (\n                    (\" \" + self.config.panel.gui.takeaway_id)\n                    if updated_user.takeaway\n                    else \"\"\n                )\n                updated_items_names = [\n                    order.menu_item.item for order in updated_user.orders\n                ]\n                # Update dataframe widget\n                self.reload_menu(\n                    None,\n                    gi,\n                )\n\n                pn.state.notifications.success(\n                    f\"{username_key_press}'s<br>lunch time changed to<br>{updated_time}{updated_takeaway}<br>({', '.join(updated_items_names)})\",\n                    duration=self.config.panel.notifications.duration,\n                )\n                log.info(f\"{username_key_press}'s order updated\")\n            else:\n                pn.state.notifications.warning(\n                    f'No order for user named<br>\"{username_key_press}\"',\n                    duration=self.config.panel.notifications.duration,\n                )\n                log.info(f\"no order for user named {username_key_press}\")\n        else:\n            pn.state.notifications.warning(\n                \"Please insert user name\",\n                duration=self.config.panel.notifications.duration,\n            )\n            log.warning(\"missing username\")\n
"},{"location":"reference/dlunch/core/#dlunch.core.Waiter.clean_tables","title":"clean_tables","text":"
clean_tables() -> None\n

Clean tables that should be reset when a new menu is uploaded.

Source code in dlunch/core.py
def clean_tables(self) -> None:\n    \"\"\"Clean tables that should be reset when a new menu is uploaded.\"\"\"\n    # Clean tables\n    # Clean orders\n    models.Orders.clear(config=self.config)\n    # Clean menu\n    models.Menu.clear(config=self.config)\n    # Clean users\n    models.Users.clear(config=self.config)\n    # Clean flags\n    models.Flags.clear_guest_override(config=self.config)\n    # Reset flags\n    models.set_flag(config=self.config, id=\"no_more_orders\", value=False)\n    log.info(\"reset values in table 'flags'\")\n    # Clean cache\n    pn.state.clear_caches()\n    log.info(\"cache cleaned\")\n
"},{"location":"reference/dlunch/core/#dlunch.core.Waiter.delete_files","title":"delete_files","text":"
delete_files() -> None\n

Delete local temporary files.

Source code in dlunch/core.py
def delete_files(self) -> None:\n    \"\"\"Delete local temporary files.\"\"\"\n    # Delete menu file if exist (every extension)\n    files = list(\n        pathlib.Path(self.config.db.shared_data_folder).glob(\n            self.config.panel.file_name + \"*\"\n        )\n    )\n    log.info(f\"delete files {', '.join([f.name for f in files])}\")\n    for file in files:\n        file.unlink(missing_ok=True)\n
"},{"location":"reference/dlunch/core/#dlunch.core.Waiter.delete_order","title":"delete_order","text":"
delete_order(\n    event: Event, app: Template, gi: GraphicInterface\n) -> None\n

Delete an existing order.

Consistency checks about the user and the order are carried out here (existing user, only one order, etc.). The status of the stop_orders flag is checked to avoid that an order is uploaded when it shouldn't.

In addition privileges are taken into account (guest users cannot delete orders that targets a privileged user).

Parameters:

Name Type Description Default event Event

Panel button event.

required app Template

Panel app template (used to open modal windows in case of database errors).

required gi GraphicInterface

graphic interface object (used to interact with Panel widgets).

required Source code in dlunch/core.py
def delete_order(\n    self,\n    event: param.parameterized.Event,\n    app: pn.Template,\n    gi: gui.GraphicInterface,\n) -> None:\n    \"\"\"Delete an existing order.\n\n    Consistency checks about the user and the order are carried out here (existing user, only one order, etc.).\n    The status of the `stop_orders` flag is checked to avoid that an order is uploaded when it shouldn't.\n\n    In addition privileges are taken into account (guest users cannot delete orders that targets a privileged user).\n\n    Args:\n        event (param.parameterized.Event): Panel button event.\n        app (pn.Template): Panel app template (used to open modal windows in case of database errors).\n        gi (gui.GraphicInterface): graphic interface object (used to interact with Panel widgets).\n    \"\"\"\n    # Get username, updated on every keypress\n    username_key_press = gi.person_widget._widgets[\"username\"].value_input\n\n    # Hide messages\n    gi.error_message.visible = False\n\n    # Create session\n    session = models.create_session(self.config)\n\n    with session:\n        # Check if the \"no more order\" toggle button is pressed\n        if models.get_flag(config=self.config, id=\"no_more_orders\"):\n            pn.state.notifications.error(\n                \"It is not possible to delete orders\",\n                duration=self.config.panel.notifications.duration,\n            )\n\n            # Reload the menu\n            self.reload_menu(\n                None,\n                gi,\n            )\n\n            return\n\n        if username_key_press:\n            # If auth is active, check if a guests is deleting an order of a\n            # privileged user\n            if (\n                auth.is_guest(\n                    user=pn_user(self.config), config=self.config\n                )\n                and (\n                    username_key_press\n                    in auth.list_users(config=self.config)\n                )\n                and (auth.is_auth_active(config=self.config))\n            ):\n                pn.state.notifications.error(\n                    f\"You do not have enough privileges<br>to delete<br>{username_key_press}'s order\",\n                    duration=self.config.panel.notifications.duration,\n                )\n\n                # Reload the menu\n                self.reload_menu(\n                    None,\n                    gi,\n                )\n\n                return\n\n            # Delete user\n            try:\n                num_rows_deleted_users = session.execute(\n                    delete(models.Users).where(\n                        models.Users.id == username_key_press\n                    )\n                )\n                # Delete also orders (hotfix for Debian)\n                num_rows_deleted_orders = session.execute(\n                    delete(models.Orders).where(\n                        models.Orders.user == username_key_press\n                    )\n                )\n                session.commit()\n                if (num_rows_deleted_users.rowcount > 0) or (\n                    num_rows_deleted_orders.rowcount > 0\n                ):\n                    # Update dataframe widget\n                    self.reload_menu(\n                        None,\n                        gi,\n                    )\n\n                    pn.state.notifications.success(\n                        \"Order canceled\",\n                        duration=self.config.panel.notifications.duration,\n                    )\n                    log.info(f\"{username_key_press}'s order canceled\")\n                else:\n                    pn.state.notifications.warning(\n                        f'No order for user named<br>\"{username_key_press}\"',\n                        duration=self.config.panel.notifications.duration,\n                    )\n                    log.info(\n                        f\"no order for user named {username_key_press}\"\n                    )\n            except Exception as e:\n                # Any exception here is a database fault\n                pn.state.notifications.error(\n                    \"Database error\",\n                    duration=self.config.panel.notifications.duration,\n                )\n                gi.error_message.object = (\n                    f\"DATABASE ERROR<br><br>ERROR:<br>{str(e)}\"\n                )\n                gi.error_message.visible = True\n                log.error(\"database error\")\n                # Open modal window\n                app.open_modal()\n        else:\n            pn.state.notifications.warning(\n                \"Please insert user name\",\n                duration=self.config.panel.notifications.duration,\n            )\n            log.warning(\"missing username\")\n
"},{"location":"reference/dlunch/core/#dlunch.core.Waiter.df_list_by_lunch_time","title":"df_list_by_lunch_time","text":"
df_list_by_lunch_time() -> dict\n

Build a dictionary of dataframes for each lunch-time, with takeaways included in a dedicated dataframe.

Each datframe includes orders grouped by users, notes and a total column (with the total value for a specific item).

The keys of the dataframe are lunch-times and lunch-times + takeaway_id.

Returns:

Type Description dict

dictionary with dataframes summarizing the orders for each lunch-time/takeaway-time.

Source code in dlunch/core.py
def df_list_by_lunch_time(\n    self,\n) -> dict:\n    \"\"\"Build a dictionary of dataframes for each lunch-time, with takeaways included in a dedicated dataframe.\n\n    Each datframe includes orders grouped by users, notes and a total column (with the total value\n    for a specific item).\n\n    The keys of the dataframe are `lunch-times` and `lunch-times + takeaway_id`.\n\n    Returns:\n        dict: dictionary with dataframes summarizing the orders for each lunch-time/takeaway-time.\n    \"\"\"\n    # Create database engine and session\n    engine = models.create_engine(self.config)\n    # Read menu and save how menu items are sorted (first courses, second courses, etc.)\n    original_order = models.Menu.read_as_df(\n        config=self.config,\n        index_col=\"id\",\n    ).item\n    # Create session\n    session = models.create_session(self.config)\n\n    with session:\n        # Build takeaway list\n        takeaway_list = [\n            user.id\n            for user in session.scalars(\n                select(models.Users).where(\n                    models.Users.takeaway == sql_true()\n                )\n            ).all()\n        ]\n    # Read orders dataframe (including notes)\n    df = pd.read_sql_query(\n        self.config.db.orders_query.format(\n            schema=self.config.db.get(\"schema\", models.SCHEMA)\n        ),\n        engine,\n    )\n\n    # The following function prepare the dataframe before saving it into\n    # the dictionary that will be returned\n    def _clean_up_table(\n        config: DictConfig,\n        df_in: pd.DataFrame,\n        df_complete: pd.DataFrame,\n    ):\n        df = df_in.copy()\n        # Group notes per menu item by concat users notes\n        # Use value counts to keep track of how many time a note is repeated\n        df_notes = (\n            df_complete[\n                (df_complete.lunch_time == time)\n                & (df_complete.note != \"\")\n                & (df_complete.user.isin(df.columns))\n            ]\n            .drop(columns=[\"user\", \"lunch_time\"])\n            .value_counts()\n            .reset_index(level=\"note\")\n        )\n        df_notes.note = (\n            df_notes[\"count\"]\n            .astype(str)\n            .str.cat(df_notes.note, sep=config.panel.gui.note_sep.count)\n        )\n        df_notes = df_notes.drop(columns=\"count\")\n        df_notes = (\n            df_notes.groupby(\"item\")[\"note\"]\n            .apply(config.panel.gui.note_sep.element.join)\n            .to_frame()\n        )\n        # Add columns of totals\n        df[config.panel.gui.total_column_name] = df.sum(axis=1)\n        # Drop unused rows if requested\n        if config.panel.drop_unused_menu_items:\n            df = df[df[config.panel.gui.total_column_name] > 0]\n        # Add notes\n        df = df.join(df_notes)\n        df = df.rename(columns={\"note\": config.panel.gui.note_column_name})\n        # Change NaNs to '-'\n        df = df.fillna(\"-\")\n        # Avoid mixed types (float and notes str)\n        df = df.astype(object)\n\n        return df\n\n    # Build a dict of dataframes, one for each lunch time\n    df_dict = {}\n    for time in df.lunch_time.sort_values().unique():\n        # Take only one lunch time (and remove notes so they do not alter\n        # numeric counters inside the pivot table)\n        temp_df = (\n            df[df.lunch_time == time]\n            .drop(columns=[\"lunch_time\", \"note\"])\n            .reset_index(drop=True)\n        )\n        # Users' selections\n        df_users = temp_df.pivot_table(\n            index=\"item\", columns=\"user\", aggfunc=len\n        )\n        # Reorder index in accordance with original menu\n        df_users = df_users.reindex(original_order)\n        # Split restaurant lunches from takeaway lunches\n        df_users_restaurant = df_users.loc[\n            :, [c for c in df_users.columns if c not in takeaway_list]\n        ]\n        df_users_takeaways = df_users.loc[\n            :, [c for c in df_users.columns if c in takeaway_list]\n        ]\n\n        # Clean and add resulting dataframes to dict\n        # RESTAURANT LUNCH\n        if not df_users_restaurant.empty:\n            df_users_restaurant = _clean_up_table(\n                self.config, df_users_restaurant, df\n            )\n            df_dict[time] = df_users_restaurant\n        # TAKEAWAY\n        if not df_users_takeaways.empty:\n            df_users_takeaways = _clean_up_table(\n                self.config, df_users_takeaways, df\n            )\n            df_dict[f\"{time} {self.config.panel.gui.takeaway_id}\"] = (\n                df_users_takeaways\n            )\n\n    return df_dict\n
"},{"location":"reference/dlunch/core/#dlunch.core.Waiter.download_dataframe","title":"download_dataframe","text":"
download_dataframe(gi: GraphicInterface) -> BytesIO\n

Build an Excel file with tables representing orders for every lunch-time/takeaway-time.

Tables are created by the function df_list_by_lunch_time and exported on dedicated Excel worksheets (inside the same workbook).

The result is returned as bytes stream to satisfy panel.widgets.FileDownload class requirements.

Parameters:

Name Type Description Default gi GraphicInterface

graphic interface object (used to interact with Panel widgets).

required

Returns:

Type Description BytesIO

download stream for the Excel file.

Source code in dlunch/core.py
def download_dataframe(\n    self,\n    gi: gui.GraphicInterface,\n) -> BytesIO:\n    \"\"\"Build an Excel file with tables representing orders for every lunch-time/takeaway-time.\n\n    Tables are created by the function `df_list_by_lunch_time` and exported on dedicated Excel worksheets\n    (inside the same workbook).\n\n    The result is returned as bytes stream to satisfy panel.widgets.FileDownload class requirements.\n\n    Args:\n        gi (gui.GraphicInterface): graphic interface object (used to interact with Panel widgets).\n\n    Returns:\n        BytesIO: download stream for the Excel file.\n    \"\"\"\n\n    # Build a dict of dataframes, one for each lunch time (the key contains\n    # a lunch time)\n    df_dict = self.df_list_by_lunch_time()\n    # Export one dataframe for each lunch time\n    bytes_io = BytesIO()\n    writer = pd.ExcelWriter(bytes_io)\n    # If the dataframe dict is non-empty export one dataframe for each sheet\n    if df_dict:\n        for time, df in df_dict.items():\n            log.info(f\"writing sheet {time}\")\n\n            # Find users that placed an order for a given time\n            users_n = len(\n                [\n                    c\n                    for c in df.columns\n                    if c\n                    not in (\n                        self.config.panel.gui.total_column_name,\n                        self.config.panel.gui.note_column_name,\n                    )\n                ]\n            )\n\n            # Export dataframe to new sheet\n            worksheet_name = time.replace(\":\", \".\")\n            df.to_excel(writer, sheet_name=worksheet_name, startrow=1)\n            # Add title\n            worksheet = writer.sheets[worksheet_name]\n            worksheet.cell(\n                1,\n                1,\n                f\"Time - {time} | # {users_n}\",\n            )\n\n            # HEADER FORMAT\n            worksheet[\"A1\"].font = Font(\n                size=13, bold=True, color=\"00FF0000\"\n            )\n\n            # INDEX ALIGNMENT\n            for row in worksheet[worksheet.min_row : worksheet.max_row]:\n                cell = row[0]  # column A\n                cell.alignment = Alignment(horizontal=\"left\")\n                cell = row[users_n + 2]  # column note\n                cell.alignment = Alignment(horizontal=\"left\")\n                cells = row[1 : users_n + 2]  # from column B to note-1\n                for cell in cells:\n                    cell.alignment = Alignment(horizontal=\"center\")\n\n            # AUTO SIZE\n            # Set auto-size for all columns\n            # Use end +1 for ID column, and +2 for 'total' and 'note' columns\n            column_letters = get_column_interval(\n                start=1, end=users_n + 1 + 2\n            )\n            # Get columns\n            columns = worksheet[column_letters[0] : column_letters[-1]]\n            for column_letter, column in zip(column_letters, columns):\n                # Instantiate max length then loop on cells to find max value\n                max_length = 0\n                # Cell loop\n                for cell in column:\n                    log.debug(\n                        f\"autosize for cell {cell.coordinate} with value '{cell.value}'\"\n                    )\n                    try:  # Necessary to avoid error on empty cells\n                        if len(str(cell.value)) > max_length:\n                            max_length = len(cell.value)\n                            log.debug(\n                                f\"new max length set to {max_length}\"\n                            )\n                    except Exception:\n                        log.debug(\"empty cell\")\n                log.debug(f\"final max length is {max_length}\")\n                adjusted_width = (max_length + 2) * 0.85\n                log.debug(\n                    f\"adjusted width for column '{column_letter}' is {adjusted_width}\"\n                )\n                worksheet.column_dimensions[column_letter].width = (\n                    adjusted_width\n                )\n            # Since grouping fix width equal to first column width (openpyxl\n            # bug), set first column of users' order equal to max width of\n            # all users columns to avoid issues\n            max_width = 0\n            log.debug(\n                f\"find max width for users' columns '{column_letters[1]}:{column_letters[-3]}'\"\n            )\n            for column_letter in column_letters[1:-2]:\n                max_width = max(\n                    max_width,\n                    worksheet.column_dimensions[column_letter].width,\n                )\n            log.debug(f\"max width for first users' columns is {max_width}\")\n            worksheet.column_dimensions[column_letters[1]].width = (\n                max_width\n            )\n\n            # GROUPING\n            # Group and hide columns, leave only ID, total and note\n            column_letters = get_column_interval(start=2, end=users_n + 1)\n            worksheet.column_dimensions.group(\n                column_letters[0], column_letters[-1], hidden=True\n            )\n\n            # Close and reset bytes_io for the next dataframe\n            writer.close()  # Important!\n            bytes_io.seek(0)  # Important!\n\n        # Message prompt\n        pn.state.notifications.success(\n            \"File with orders downloaded\",\n            duration=self.config.panel.notifications.duration,\n        )\n        log.info(\"xlsx downloaded\")\n    else:\n        gi.dataframe.value.drop(columns=[\"order\"]).to_excel(\n            writer, sheet_name=\"MENU\", index=False\n        )\n        writer.close()  # Important!\n        bytes_io.seek(0)  # Important!\n        # Message prompt\n        pn.state.notifications.warning(\n            \"No order<br>Menu downloaded\",\n            duration=self.config.panel.notifications.duration,\n        )\n        log.warning(\n            \"no order, menu exported to excel in place of orders' list\"\n        )\n\n    return bytes_io\n
"},{"location":"reference/dlunch/core/#dlunch.core.Waiter.get_host_name","title":"get_host_name","text":"
get_host_name() -> str\n

Return hostname.

This function behavior changes if called from localhost, Docker container or production server.

Returns:

Type Description str

hostname.

Source code in dlunch/core.py
def get_host_name(self) -> str:\n    \"\"\"Return hostname.\n\n    This function behavior changes if called from localhost, Docker container or\n    production server.\n\n    Returns:\n        str: hostname.\n    \"\"\"\n    try:\n        ip_address = socket.gethostbyname(socket.gethostname())\n        dig_res = subprocess.run(\n            [\"dig\", \"+short\", \"-x\", ip_address], stdout=subprocess.PIPE\n        ).stdout\n        host_name = (\n            subprocess.run(\n                [\"cut\", \"-d.\", \"-f1\"],\n                stdout=subprocess.PIPE,\n                input=dig_res,\n            )\n            .stdout.decode(\"utf-8\")\n            .strip()\n        )\n        if host_name:\n            host_name = host_name.replace(\n                f\"{self.config.docker_username}_\", \"\"\n            )\n        else:\n            host_name = \"no info\"\n    except Exception:\n        host_name = \"not available\"\n\n    return host_name\n
"},{"location":"reference/dlunch/core/#dlunch.core.Waiter.reload_menu","title":"reload_menu","text":"
reload_menu(event: Event, gi: GraphicInterface) -> None\n

Main core function that sync Panel widget with database tables.

Stop orders and guest override checks are carried out by this function. Also the banner image is shown based on a check run by this function.

menu, orders and users tables are used to build a list of orders for each lunch time. Takeaway orders are evaluated separately.

At the end stats about lunches are calculated and loaded to database. Finally statistics (values and table) shown inside the app are updated accordingly.

Parameters:

Name Type Description Default event Event

Panel button event.

required gi GraphicInterface

graphic interface object (used to interact with Panel widgets).

required Source code in dlunch/core.py
def reload_menu(\n    self,\n    event: param.parameterized.Event,\n    gi: gui.GraphicInterface,\n) -> None:\n    \"\"\"Main core function that sync Panel widget with database tables.\n\n    Stop orders and guest override checks are carried out by this function.\n    Also the banner image is shown based on a check run by this function.\n\n    `menu`, `orders` and `users` tables are used to build a list of orders for each lunch time.\n    Takeaway orders are evaluated separately.\n\n    At the end stats about lunches are calculated and loaded to database. Finally\n    statistics (values and table) shown inside the app are updated accordingly.\n\n    Args:\n        event (param.parameterized.Event): Panel button event.\n        gi (gui.GraphicInterface): graphic interface object (used to interact with Panel widgets).\n    \"\"\"\n    # Create session\n    session = models.create_session(self.config)\n\n    with session:\n        # Check if someone changed the \"no_more_order\" toggle\n        if gi.toggle_no_more_order_button.value != models.get_flag(\n            config=self.config, id=\"no_more_orders\"\n        ):\n            # The following statement will trigger the toggle callback\n            # which will call reload_menu once again\n            # This is the reason why this if contains a return (without the return\n            # the content will be reloaded twice)\n            gi.toggle_no_more_order_button.value = models.get_flag(\n                config=self.config, id=\"no_more_orders\"\n            )\n\n            return\n\n        # Check guest override button status (if not in table use False)\n        gi.toggle_guest_override_button.value = models.get_flag(\n            config=self.config,\n            id=f\"{pn_user(self.config)}_guest_override\",\n            value_if_missing=False,\n        )\n\n        # Set no more orders toggle button and the change order time button\n        # visibility and activation\n        if auth.is_guest(\n            user=pn_user(self.config),\n            config=self.config,\n            allow_override=False,\n        ):\n            # Deactivate the no_more_orders_button for guest users\n            gi.toggle_no_more_order_button.disabled = True\n            gi.toggle_no_more_order_button.visible = False\n            # Deactivate the change_order_time_button for guest users\n            gi.change_order_time_takeaway_button.disabled = True\n            gi.change_order_time_takeaway_button.visible = False\n        else:\n            # Activate the no_more_orders_button for privileged users\n            gi.toggle_no_more_order_button.disabled = False\n            gi.toggle_no_more_order_button.visible = True\n            # Show the change_order_time_button for privileged users\n            # It is disabled by the no more order button if necessary\n            gi.change_order_time_takeaway_button.visible = True\n\n        # Guest graphic configuration\n        if auth.is_guest(user=pn_user(self.config), config=self.config):\n            # If guest show guest type selection group\n            gi.person_widget.widgets[\"guest\"].disabled = False\n            gi.person_widget.widgets[\"guest\"].visible = True\n        else:\n            # If user is privileged hide guest type selection group\n            gi.person_widget.widgets[\"guest\"].disabled = True\n            gi.person_widget.widgets[\"guest\"].visible = False\n\n        # Reload menu\n        engine = models.create_engine(self.config)\n        df = models.Menu.read_as_df(\n            config=self.config,\n            index_col=\"id\",\n        )\n        # Add order (for selecting items) and note columns\n        df[\"order\"] = False\n        df[self.config.panel.gui.note_column_name] = \"\"\n        gi.dataframe.value = df\n        gi.dataframe.formatters = {\"order\": {\"type\": \"tickCross\"}}\n        gi.dataframe.editors = {\n            \"id\": None,\n            \"item\": None,\n            \"order\": CheckboxEditor(),\n            self.config.panel.gui.note_column_name: \"input\",\n        }\n        gi.dataframe.header_align = OmegaConf.to_container(\n            self.config.panel.gui.menu_column_align, resolve=True\n        )\n        gi.dataframe.text_align = OmegaConf.to_container(\n            self.config.panel.gui.menu_column_align, resolve=True\n        )\n\n        if gi.toggle_no_more_order_button.value:\n            gi.dataframe.hidden_columns = [\"id\", \"order\"]\n            gi.dataframe.disabled = True\n        else:\n            gi.dataframe.hidden_columns = [\"id\"]\n            gi.dataframe.disabled = False\n\n        # If menu is empty show banner image, otherwise show menu\n        if df.empty:\n            gi.no_menu_col.visible = True\n            gi.main_header_row.visible = False\n            gi.quote.visible = False\n            gi.menu_flexbox.visible = False\n            gi.buttons_flexbox.visible = False\n            gi.results_divider.visible = False\n            gi.res_col.visible = False\n        else:\n            gi.no_menu_col.visible = False\n            gi.main_header_row.visible = True\n            gi.quote.visible = True\n            gi.menu_flexbox.visible = True\n            gi.buttons_flexbox.visible = True\n            gi.results_divider.visible = True\n            gi.res_col.visible = True\n\n        log.debug(\"menu reloaded\")\n\n        # Load results\n        df_dict = self.df_list_by_lunch_time()\n        # Clean columns and load text and dataframes\n        gi.res_col.clear()\n        gi.time_col.clear()\n        if df_dict:\n            # Titles\n            gi.res_col.append(self.config.panel.result_column_text)\n            gi.time_col.append(gi.time_col_title)\n            # Build guests list (one per each guest types)\n            guests_lists = {}\n            for guest_type in self.config.panel.guest_types:\n                guests_lists[guest_type] = [\n                    user.id\n                    for user in session.scalars(\n                        select(models.Users).where(\n                            models.Users.guest == guest_type\n                        )\n                    ).all()\n                ]\n            # Loop through lunch times\n            for time, df in df_dict.items():\n                # Find the number of grumbling stomachs\n                grumbling_stomachs = len(\n                    [\n                        c\n                        for c in df.columns\n                        if c\n                        not in (\n                            self.config.panel.gui.total_column_name,\n                            self.config.panel.gui.note_column_name,\n                        )\n                    ]\n                )\n                # Set different graphics for takeaway lunches\n                if self.config.panel.gui.takeaway_id in time:\n                    res_col_label_kwargs = {\n                        \"time\": time.replace(\n                            self.config.panel.gui.takeaway_id, \"\"\n                        ),\n                        \"diners_n\": grumbling_stomachs,\n                        \"emoji\": self.config.panel.gui.takeaway_emoji,\n                        \"is_takeaway\": True,\n                        \"takeaway_alert_sign\": f\"&nbsp{gi.takeaway_alert_sign}&nbsp{gi.takeaway_alert_text}\",\n                        \"css_classes\": OmegaConf.to_container(\n                            self.config.panel.gui.takeaway_class_res_col,\n                            resolve=True,\n                        ),\n                        \"stylesheets\": [\n                            self.config.panel.gui.css_files.labels_path\n                        ],\n                    }\n                    time_col_label_kwargs = {\n                        \"time\": time.replace(\n                            self.config.panel.gui.takeaway_id, \"\"\n                        ),\n                        \"diners_n\": str(grumbling_stomachs) + \"&nbsp\",\n                        \"separator\": \"<br>\",\n                        \"emoji\": self.config.panel.gui.takeaway_emoji,\n                        \"align\": (\"center\", \"center\"),\n                        \"sizing_mode\": \"stretch_width\",\n                        \"is_takeaway\": True,\n                        \"takeaway_alert_sign\": gi.takeaway_alert_sign,\n                        \"css_classes\": OmegaConf.to_container(\n                            self.config.panel.gui.takeaway_class_time_col,\n                            resolve=True,\n                        ),\n                        \"stylesheets\": [\n                            self.config.panel.gui.css_files.labels_path\n                        ],\n                    }\n                else:\n                    res_col_label_kwargs = {\n                        \"time\": time,\n                        \"diners_n\": grumbling_stomachs,\n                        \"emoji\": random.choice(\n                            self.config.panel.gui.food_emoji\n                        ),\n                        \"css_classes\": OmegaConf.to_container(\n                            self.config.panel.gui.time_class_res_col,\n                            resolve=True,\n                        ),\n                        \"stylesheets\": [\n                            self.config.panel.gui.css_files.labels_path\n                        ],\n                    }\n                    time_col_label_kwargs = {\n                        \"time\": time,\n                        \"diners_n\": str(grumbling_stomachs) + \"&nbsp\",\n                        \"separator\": \"<br>\",\n                        \"emoji\": self.config.panel.gui.restaurant_emoji,\n                        \"per_icon\": \"&#10006; \",\n                        \"align\": (\"center\", \"center\"),\n                        \"sizing_mode\": \"stretch_width\",\n                        \"css_classes\": OmegaConf.to_container(\n                            self.config.panel.gui.time_class_time_col,\n                            resolve=True,\n                        ),\n                        \"stylesheets\": [\n                            self.config.panel.gui.css_files.labels_path\n                        ],\n                    }\n                # Add text to result column\n                gi.res_col.append(pn.Spacer(height=15))\n                gi.res_col.append(\n                    gi.build_time_label(**res_col_label_kwargs)\n                )\n                # Add non editable table to result column\n                gi.res_col.append(pn.Spacer(height=5))\n                gi.res_col.append(\n                    gi.build_order_table(\n                        self.config,\n                        df=df,\n                        time=time,\n                        guests_lists=guests_lists,\n                    )\n                )\n                # Add also a label to lunch time column\n                gi.time_col.append(\n                    gi.build_time_label(**time_col_label_kwargs)\n                )\n\n        log.debug(\"results reloaded\")\n\n        # Clean stats column\n        gi.sidebar_stats_col.clear()\n        # Update stats\n        # Find how many people eat today (total number) and add value to database\n        # stats table (when adding a stats if guest is not specified None is used\n        # as default)\n        today_locals_count = session.scalar(\n            select(func.count(models.Users.id)).where(\n                models.Users.guest == \"NotAGuest\"\n            )\n        )\n        new_stat = models.Stats(hungry_people=today_locals_count)\n        # Use an upsert for postgresql, a simple session add otherwise\n        models.session_add_with_upsert(\n            session=session, constraint=\"stats_pkey\", new_record=new_stat\n        )\n        # For each guest type find how many guests eat today\n        for guest_type in self.config.panel.guest_types:\n            today_guests_count = session.scalar(\n                select(func.count(models.Users.id)).where(\n                    models.Users.guest == guest_type\n                )\n            )\n            new_stat = models.Stats(\n                guest=guest_type, hungry_people=today_guests_count\n            )\n            # Use an upsert for postgresql, a simple session add otherwise\n            models.session_add_with_upsert(\n                session=session,\n                constraint=\"stats_pkey\",\n                new_record=new_stat,\n            )\n\n        # Commit stats\n        session.commit()\n\n        # Group stats by month and return how many people had lunch\n        df_stats = pd.read_sql_query(\n            self.config.db.stats_query.format(\n                schema=self.config.db.get(\"schema\", models.SCHEMA)\n            ),\n            engine,\n        )\n        # Stats top text\n        stats_and_info_text = gi.build_stats_and_info_text(\n            config=self.config,\n            df_stats=df_stats,\n            user=pn_user(self.config),\n            version=__version__,\n            host_name=self.get_host_name(),\n            stylesheets=[self.config.panel.gui.css_files.stats_info_path],\n        )\n        # Remove NotAGuest (non-guest users)\n        df_stats.Guest = df_stats.Guest.replace(\n            \"NotAGuest\", self.config.panel.stats_locals_column_name\n        )\n        # Pivot table on guest type\n        df_stats = df_stats.pivot(\n            columns=\"Guest\",\n            index=self.config.panel.stats_id_cols,\n            values=\"Hungry People\",\n        ).reset_index()\n        df_stats[self.config.panel.gui.total_column_name.title()] = (\n            df_stats.sum(axis=\"columns\", numeric_only=True)\n        )\n        # Add value and non-editable option to stats table\n        gi.stats_widget.editors = {c: None for c in df_stats.columns}\n        gi.stats_widget.value = df_stats\n        gi.sidebar_stats_col.append(stats_and_info_text[\"stats\"])\n        gi.sidebar_stats_col.append(gi.stats_widget)\n        # Add info below person widget (an empty placeholder was left as last\n        # element)\n        gi.sidebar_person_column.objects[-1] = stats_and_info_text[\"info\"]\n        log.debug(\"stats and info updated\")\n
"},{"location":"reference/dlunch/core/#dlunch.core.Waiter.send_order","title":"send_order","text":"
send_order(\n    event: Event,\n    app: Template,\n    person: Person,\n    gi: GraphicInterface,\n) -> None\n

Upload orders and user to database tables.

The user target of the order is uploaded to users table, while the order is uploaded to orders table.

Consistency checks about the user and the order are carried out here (existing user, only one order, etc.). The status of the stop_orders flag is checked to avoid that an order is uploaded when it shouldn't.

Orders for guest users are marked as such before uploading them.

Parameters:

Name Type Description Default event Event

Panel button event.

required app Template

Panel app template (used to open modal windows in case of database errors).

required person Person

class that collect order data for the user that is the target of the order.

required gi GraphicInterface

graphic interface object (used to interact with Panel widgets).

required Source code in dlunch/core.py
def send_order(\n    self,\n    event: param.parameterized.Event,\n    app: pn.Template,\n    person: gui.Person,\n    gi: gui.GraphicInterface,\n) -> None:\n    \"\"\"Upload orders and user to database tables.\n\n    The user target of the order is uploaded to `users` table, while the order\n    is uploaded to `orders` table.\n\n    Consistency checks about the user and the order are carried out here (existing user, only one order, etc.).\n    The status of the `stop_orders` flag is checked to avoid that an order is uploaded when it shouldn't.\n\n    Orders for guest users are marked as such before uploading them.\n\n    Args:\n        event (param.parameterized.Event): Panel button event.\n        app (pn.Template): Panel app template (used to open modal windows in case of database errors).\n        person (gui.Person): class that collect order data for the user that is the target of the order.\n        gi (gui.GraphicInterface): graphic interface object (used to interact with Panel widgets).\n    \"\"\"\n    # Get username updated at each key press\n    username_key_press = gi.person_widget._widgets[\"username\"].value_input\n\n    # Hide messages\n    gi.error_message.visible = False\n\n    # Create session\n    session = models.create_session(self.config)\n\n    with session:\n        # Check if the \"no more order\" toggle button is pressed\n        if models.get_flag(config=self.config, id=\"no_more_orders\"):\n            pn.state.notifications.error(\n                \"It is not possible to place new orders\",\n                duration=self.config.panel.notifications.duration,\n            )\n\n            # Reload the menu\n            self.reload_menu(\n                None,\n                gi,\n            )\n\n            return\n\n        # If auth is active, check if a guests is using a name reserved to a\n        # privileged user\n        if (\n            auth.is_guest(user=pn_user(self.config), config=self.config)\n            and (username_key_press in auth.list_users(config=self.config))\n            and (auth.is_auth_active(config=self.config))\n        ):\n            pn.state.notifications.error(\n                f\"{username_key_press} is a reserved name<br>Please choose a different one\",\n                duration=self.config.panel.notifications.duration,\n            )\n\n            # Reload the menu\n            self.reload_menu(\n                None,\n                gi,\n            )\n\n            return\n\n        # Check if a privileged user is ordering for an invalid name\n        if (\n            not auth.is_guest(\n                user=pn_user(self.config), config=self.config\n            )\n            and (\n                username_key_press\n                not in (\n                    name\n                    for name in auth.list_users(config=self.config)\n                    if name != \"guest\"\n                )\n            )\n            and (auth.is_auth_active(config=self.config))\n        ):\n            pn.state.notifications.error(\n                f\"{username_key_press} is not a valid name<br>for a privileged user<br>Please choose a different one\",\n                duration=self.config.panel.notifications.duration,\n            )\n\n            # Reload the menu\n            self.reload_menu(\n                None,\n                gi,\n            )\n\n            return\n\n        # Write order into database table\n        df = gi.dataframe.value.copy()\n        df_order = df[df.order]\n        # If username is missing or the order is empty return an error message\n        if username_key_press and not df_order.empty:\n            # Check if the user already placed an order\n            if session.get(models.Users, username_key_press):\n                pn.state.notifications.warning(\n                    f\"Cannot overwrite an order<br>Delete {username_key_press}'s order first and retry\",\n                    duration=self.config.panel.notifications.duration,\n                )\n                log.warning(\n                    f\"an order already exist for {username_key_press}\"\n                )\n            else:\n                # Place order\n                try:\n                    # Add User\n                    # Do not pass guest for privileged users (default to NotAGuest)\n                    if auth.is_guest(\n                        user=pn_user(self.config), config=self.config\n                    ):\n                        new_user = models.Users(\n                            id=username_key_press,\n                            guest=person.guest,\n                            lunch_time=person.lunch_time,\n                            takeaway=person.takeaway,\n                        )\n                    else:\n                        new_user = models.Users(\n                            id=username_key_press,\n                            lunch_time=person.lunch_time,\n                            takeaway=person.takeaway,\n                        )\n                    session.add(new_user)\n                    session.commit()\n                    # Add orders as long table (one row for each item selected by a user)\n                    for row in df_order.itertuples(name=\"OrderTuple\"):\n                        # Order\n                        new_order = models.Orders(\n                            user=username_key_press,\n                            menu_item_id=row.Index,\n                            note=getattr(\n                                row, self.config.panel.gui.note_column_name\n                            ).lower(),\n                        )\n                        session.add(new_order)\n                        session.commit()\n\n                    # Update dataframe widget\n                    self.reload_menu(\n                        None,\n                        gi,\n                    )\n\n                    pn.state.notifications.success(\n                        \"Order sent\",\n                        duration=self.config.panel.notifications.duration,\n                    )\n                    log.info(f\"{username_key_press}'s order saved\")\n                except Exception as e:\n                    # Any exception here is a database fault\n                    pn.state.notifications.error(\n                        \"Database error\",\n                        duration=self.config.panel.notifications.duration,\n                    )\n                    gi.error_message.object = (\n                        f\"DATABASE ERROR<br><br>ERROR:<br>{str(e)}\"\n                    )\n                    gi.error_message.visible = True\n                    log.error(\"database error\")\n                    # Open modal window\n                    app.open_modal()\n        else:\n            if not username_key_press:\n                pn.state.notifications.warning(\n                    \"Please insert user name\",\n                    duration=self.config.panel.notifications.duration,\n                )\n                log.warning(\"missing username\")\n            else:\n                pn.state.notifications.warning(\n                    \"Please make a selection\",\n                    duration=self.config.panel.notifications.duration,\n                )\n                log.warning(\"no selection made\")\n
"},{"location":"reference/dlunch/core/#dlunch.core.Waiter.set_config","title":"set_config","text":"
set_config(config: DictConfig)\n

Set the configuration for the Waiter instance.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration object.

required Source code in dlunch/core.py
def set_config(self, config: DictConfig):\n    \"\"\"Set the configuration for the Waiter instance.\n\n    Args:\n        config (DictConfig): Hydra configuration object.\n    \"\"\"\n    self.config = config\n
"},{"location":"reference/dlunch/gui/","title":"gui","text":"

Module that defines main graphic interface and backend graphic interface.

Classes that uses param are then used to create Panel widget directly (see Panel docs <https://panel.holoviz.org/how_to/param/uis.html>__).

Modules:

Name Description auth

Module with classes and functions used for authentication and password handling.

core

Module that defines main functions used to manage Data-Lunch operations.

models

Module with database tables definitions.

Classes:

Name Description BackendAddPrivilegedUser

Param class used inside the backend to create the widget add new users to the privileged_user table.

BackendInterface

Class with widgets for the backend graphic interface.

BackendPasswordRenewer

Param class used inside the backend to create the widget that collect info to renew users password.

BackendUserEraser

Param class used inside the backend to create the widget that delete users.

GraphicInterface

Class with widgets for the main graphic interface.

PasswordRenewer

Param class used to create the widget that collect info to renew users password.

Person

Param class that define user data and lunch preferences for its order.

Attributes:

Name Type Description backend_min_height int

Backend minimum height.

df_quote DataFrame

Dataframe with the quote of the day.

df_quotes DataFrame

Dataframe with quotes.

download_text str

info Text used in Download Orders tab.

generic_button_height int

Button height.

guest_user_text str

info Text used in guest Password widget.

header_button_width int

Width for buttons used in top header.

header_row_height int

Top header height.

log Logger

Module logger.

main_area_min_width int

Main area width. It's the area with menu and order summary.

person_text str

info Text used in User tab.

quotes_filename Path

Excel file with quotes.

seed_day int

seed to Select the quote of the day.

sidebar_content_width int

Sidebar content width. Should be smaller than sidebar width.

sidebar_width int

Sidebar width.

time_col_spacer_width int

Time column spacer width.

time_col_width int

Time column width (the time column is on the side of the menu table).

upload_text str

info Text used in Menu Upload tab.

"},{"location":"reference/dlunch/gui/#dlunch.gui.backend_min_height","title":"backend_min_height module-attribute","text":"
backend_min_height: int = 500\n

Backend minimum height.

"},{"location":"reference/dlunch/gui/#dlunch.gui.df_quote","title":"df_quote module-attribute","text":"
df_quote: DataFrame = sample(n=1, random_state=seed_day)\n

Dataframe with the quote of the day.

"},{"location":"reference/dlunch/gui/#dlunch.gui.df_quotes","title":"df_quotes module-attribute","text":"
df_quotes: DataFrame = read_excel(quotes_filename)\n

Dataframe with quotes.

"},{"location":"reference/dlunch/gui/#dlunch.gui.download_text","title":"download_text module-attribute","text":"
download_text: str = (\n    \"\\n### Download Orders\\nDownload the order list.\\n\"\n)\n

info Text used in Download Orders tab.

"},{"location":"reference/dlunch/gui/#dlunch.gui.generic_button_height","title":"generic_button_height module-attribute","text":"
generic_button_height: int = 45\n

Button height.

"},{"location":"reference/dlunch/gui/#dlunch.gui.guest_user_text","title":"guest_user_text module-attribute","text":"
guest_user_text: str = '\\n### Guest user\\n'\n

info Text used in guest Password widget.

"},{"location":"reference/dlunch/gui/#dlunch.gui.header_button_width","title":"header_button_width module-attribute","text":"
header_button_width: int = 50\n

Width for buttons used in top header.

"},{"location":"reference/dlunch/gui/#dlunch.gui.header_row_height","title":"header_row_height module-attribute","text":"
header_row_height: int = 55\n

Top header height.

"},{"location":"reference/dlunch/gui/#dlunch.gui.log","title":"log module-attribute","text":"
log: Logger = getLogger(__name__)\n

Module logger.

"},{"location":"reference/dlunch/gui/#dlunch.gui.main_area_min_width","title":"main_area_min_width module-attribute","text":"
main_area_min_width: int = (\n    580 + time_col_spacer_width + time_col_width\n)\n

Main area width. It's the area with menu and order summary.

"},{"location":"reference/dlunch/gui/#dlunch.gui.person_text","title":"person_text module-attribute","text":"
person_text: str = (\n    \"\\n### User Data\\n\\n_Privileged users_ do not need to fill the username.<br>\\n_Guest users_ shall use a valid _unique_ name and select a guest type.\\n\"\n)\n

info Text used in User tab.

"},{"location":"reference/dlunch/gui/#dlunch.gui.quotes_filename","title":"quotes_filename module-attribute","text":"
quotes_filename: Path = parent / 'quotes.xlsx'\n

Excel file with quotes.

"},{"location":"reference/dlunch/gui/#dlunch.gui.seed_day","title":"seed_day module-attribute","text":"
seed_day: int = int(strftime('%Y%m%d'))\n

seed to Select the quote of the day.

"},{"location":"reference/dlunch/gui/#dlunch.gui.sidebar_content_width","title":"sidebar_content_width module-attribute","text":"
sidebar_content_width: int = sidebar_width - 10\n

Sidebar content width. Should be smaller than sidebar width.

"},{"location":"reference/dlunch/gui/#dlunch.gui.sidebar_width","title":"sidebar_width module-attribute","text":"
sidebar_width: int = 400\n

Sidebar width.

"},{"location":"reference/dlunch/gui/#dlunch.gui.time_col_spacer_width","title":"time_col_spacer_width module-attribute","text":"
time_col_spacer_width: int = 5\n

Time column spacer width.

"},{"location":"reference/dlunch/gui/#dlunch.gui.time_col_width","title":"time_col_width module-attribute","text":"
time_col_width: int = 90\n

Time column width (the time column is on the side of the menu table).

"},{"location":"reference/dlunch/gui/#dlunch.gui.upload_text","title":"upload_text module-attribute","text":"
upload_text: str = (\n    \"\\n### Menu Upload\\nSelect a .png, .jpg or .xlsx file with the menu.<br>\\nThe app may add some default items to the menu.\\n\\n**For .xlsx:** list menu items starting from cell A1, one per each row.\\n\"\n)\n

info Text used in Menu Upload tab.

"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendAddPrivilegedUser","title":"BackendAddPrivilegedUser","text":"

Bases: Parameterized

Param class used inside the backend to create the widget add new users to the privileged_user table.

Methods:

Name Description __str__

String representation of this object.

Attributes:

Name Type Description admin Boolean

Admin flag (true if admin).

user String

Username of the new user.

Source code in dlunch/gui.py
class BackendAddPrivilegedUser(param.Parameterized):\n    \"\"\"Param class used inside the backend to create the widget add new users to the `privileged_user` table.\"\"\"\n\n    user: param.String = param.String(default=\"\", doc=\"user to add\")\n    \"\"\"Username of the new user.\"\"\"\n    admin: param.Boolean = param.Boolean(\n        default=False, doc=\"add admin privileges\"\n    )\n    \"\"\"Admin flag (true if admin).\"\"\"\n\n    def __str__(self):\n        \"\"\"String representation of this object.\n\n        Returns:\n            (str): string representation.\n        \"\"\"\n        return \"BackendAddUser\"\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendAddPrivilegedUser.admin","title":"admin class-attribute instance-attribute","text":"
admin: Boolean = Boolean(\n    default=False, doc=\"add admin privileges\"\n)\n

Admin flag (true if admin).

"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendAddPrivilegedUser.user","title":"user class-attribute instance-attribute","text":"
user: String = String(default='', doc='user to add')\n

Username of the new user.

"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendAddPrivilegedUser.__str__","title":"__str__","text":"
__str__()\n

String representation of this object.

Returns:

Type Description str

string representation.

Source code in dlunch/gui.py
def __str__(self):\n    \"\"\"String representation of this object.\n\n    Returns:\n        (str): string representation.\n    \"\"\"\n    return \"BackendAddUser\"\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendInterface","title":"BackendInterface","text":"

Class with widgets for the backend graphic interface.

All widgets are instantiated at class initialization.

Class methods handle specific operations that may be repeated multiple time after class instantiation.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Methods:

Name Description __init__ exit_backend

Return to main homepage.

reload_backend

Reload backend by updating user lists and privileges.

Attributes:

Name Type Description access_denied_text add_privileged_user_button add_privileged_user_column add_privileged_user_widget add_update_user_column backend_controls clear_flags_button clear_flags_column delete_user_button delete_user_column exit_button flags_content header_row list_user_column password_widget submit_password_button user_eraser users_tabulator Source code in dlunch/gui.py
class BackendInterface:\n    \"\"\"Class with widgets for the backend graphic interface.\n\n    All widgets are instantiated at class initialization.\n\n    Class methods handle specific operations that may be repeated multiple time after class instantiation.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n    \"\"\"\n\n    def __init__(\n        self,\n        config: DictConfig,\n    ):\n        # HEADER SECTION ------------------------------------------------------\n        # WIDGET\n\n        # BUTTONS\n        self.exit_button = pnw.Button(\n            name=\"\",\n            button_type=\"primary\",\n            button_style=\"solid\",\n            width=header_button_width,\n            height=generic_button_height,\n            icon=\"home-move\",\n            icon_size=\"2em\",\n        )\n\n        # ROW\n        # Create column for person data (add logout button only if auth is active)\n        self.header_row = pn.Row(\n            height=header_row_height,\n            sizing_mode=\"stretch_width\",\n        )\n        # Append a controls to the right side of header\n        self.header_row.append(pn.HSpacer())\n        self.header_row.append(self.exit_button)\n        self.header_row.append(\n            pn.pane.HTML(styles=dict(background=\"white\"), width=2, height=45)\n        )\n\n        # CALLBACKS\n        # Exit callback\n        self.exit_button.on_click(lambda e: self.exit_backend())\n\n        # MAIN SECTION --------------------------------------------------------\n        # Backend main section\n\n        # TEXTS\n        # \"no more order\" message\n        self.access_denied_text = pn.pane.HTML(\n            \"\"\"\n            <div class=\"no-more-order-flag\">\n                <div class=\"icon-container\">\n                    <svg class=\"flashing-animation\" xmlns=\"http://www.w3.org/2000/svg\" class=\"icon icon-tabler icon-tabler-shield-lock-filled\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n                        <path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\"></path>\n                        <path d=\"M11.998 2l.118 .007l.059 .008l.061 .013l.111 .034a.993 .993 0 0 1 .217 .112l.104 .082l.255 .218a11 11 0 0 0 7.189 2.537l.342 -.01a1 1 0 0 1 1.005 .717a13 13 0 0 1 -9.208 16.25a1 1 0 0 1 -.502 0a13 13 0 0 1 -9.209 -16.25a1 1 0 0 1 1.005 -.717a11 11 0 0 0 7.531 -2.527l.263 -.225l.096 -.075a.993 .993 0 0 1 .217 -.112l.112 -.034a.97 .97 0 0 1 .119 -.021l.115 -.007zm.002 7a2 2 0 0 0 -1.995 1.85l-.005 .15l.005 .15a2 2 0 0 0 .995 1.581v1.769l.007 .117a1 1 0 0 0 1.993 -.117l.001 -1.768a2 2 0 0 0 -1.001 -3.732z\" stroke-width=\"0\" fill=\"currentColor\"></path>\n                    </svg>\n                    <span><strong>Insufficient privileges!</strong></span>\n                </div>\n            </div>\n            \"\"\",\n            margin=5,\n            sizing_mode=\"stretch_width\",\n            stylesheets=[config.panel.gui.css_files.no_more_orders_path],\n        )\n\n        # WIDGET\n        # Password renewer (only basic auth)\n        self.password_widget = pn.Param(\n            BackendPasswordRenewer().param,\n            widgets={\n                \"new_password\": pnw.PasswordInput(\n                    name=\"New password\", placeholder=\"New Password\"\n                ),\n                \"repeat_new_password\": pnw.PasswordInput(\n                    name=\"Repeat new password\",\n                    placeholder=\"Repeat New Password\",\n                ),\n            },\n            name=\"Add/Update User Credentials\",\n            width=sidebar_content_width,\n        )\n        # Add user (only oauth)\n        self.add_privileged_user_widget = pn.Param(\n            BackendAddPrivilegedUser().param,\n            name=\"Add Privileged User\",\n            width=sidebar_content_width,\n        )\n        # User eraser\n        self.user_eraser = pn.Param(\n            BackendUserEraser().param,\n            name=\"Delete User\",\n            width=sidebar_content_width,\n        )\n        # User list\n        self.users_tabulator = pn.widgets.Tabulator(\n            value=auth.list_users_guests_and_privileges(config),\n            sizing_mode=\"stretch_height\",\n        )\n        # Flags content (use empty dataframe to instantiate)\n        df_flags = models.Flags.read_as_df(\n            config=config,\n            index_col=\"id\",\n        )\n        self.flags_content = pn.widgets.Tabulator(\n            value=df_flags,\n            sizing_mode=\"stretch_height\",\n        )\n\n        # BUTTONS\n        # Exit button\n        # Password button\n        self.submit_password_button = pnw.Button(\n            name=\"Submit\",\n            button_type=\"success\",\n            height=generic_button_height,\n            icon=\"key\",\n            icon_size=\"2em\",\n            sizing_mode=\"stretch_width\",\n        )\n        # Delete User button\n        self.add_privileged_user_button = pnw.Button(\n            name=\"Add\",\n            button_type=\"success\",\n            height=generic_button_height,\n            icon=\"user-plus\",\n            icon_size=\"2em\",\n            sizing_mode=\"stretch_width\",\n        )\n        # Delete User button\n        self.delete_user_button = pnw.Button(\n            name=\"Delete\",\n            button_type=\"danger\",\n            height=generic_button_height,\n            icon=\"user-minus\",\n            icon_size=\"2em\",\n            sizing_mode=\"stretch_width\",\n        )\n        # Clear flags table button\n        self.clear_flags_button = pnw.Button(\n            name=\"Clear Guest Override Flags\",\n            button_type=\"danger\",\n            height=generic_button_height,\n            icon=\"file-shredder\",\n            icon_size=\"2em\",\n            sizing_mode=\"stretch_width\",\n        )\n\n        # COLUMN\n        # Create column with user credentials controls (basic auth)\n        self.add_update_user_column = pn.Column(\n            config.panel.gui.psw_text,\n            self.password_widget,\n            pn.VSpacer(),\n            self.submit_password_button,\n            width=sidebar_width,\n            sizing_mode=\"stretch_height\",\n            min_height=backend_min_height,\n        )\n        # Create column with user authenthication controls (oauth)\n        self.add_privileged_user_column = pn.Column(\n            self.add_privileged_user_widget,\n            pn.VSpacer(),\n            self.add_privileged_user_button,\n            width=sidebar_width,\n            sizing_mode=\"stretch_height\",\n            min_height=backend_min_height,\n        )\n        # Create column for deleting users\n        self.delete_user_column = pn.Column(\n            self.user_eraser,\n            pn.VSpacer(),\n            self.delete_user_button,\n            width=sidebar_width,\n            sizing_mode=\"stretch_height\",\n            min_height=backend_min_height,\n        )\n        # Create column with flags' list\n        self.clear_flags_column = pn.Column(\n            pn.pane.HTML(\"<b>Flags Table Content</b>\"),\n            self.flags_content,\n            self.clear_flags_button,\n            width=sidebar_width,\n            sizing_mode=\"stretch_height\",\n            min_height=backend_min_height,\n        )\n        # Create column for users' list\n        self.list_user_column = pn.Column(\n            pn.pane.HTML(\"<b>Users and Privileges</b>\"),\n            self.users_tabulator,\n            width=sidebar_width,\n            sizing_mode=\"stretch_height\",\n            min_height=backend_min_height,\n        )\n\n        # ROWS\n        self.backend_controls = pn.Row(\n            name=\"Actions\",\n            sizing_mode=\"stretch_both\",\n            min_height=backend_min_height,\n        )\n        # Add controls only for admin users\n        if not auth.is_admin(user=pn_user(config), config=config):\n            self.backend_controls.append(self.access_denied_text)\n            self.backend_controls.append(pn.Spacer(height=15))\n        else:\n            # For basic auth use a password renewer, for oauth a widget for\n            # adding privileged users\n            if auth.is_basic_auth_active(config=config):\n                self.backend_controls.append(self.add_update_user_column)\n            else:\n                self.backend_controls.append(self.add_privileged_user_column)\n            self.backend_controls.append(\n                pn.pane.HTML(\n                    styles=dict(background=\"lightgray\"),\n                    width=2,\n                    sizing_mode=\"stretch_height\",\n                )\n            )\n            self.backend_controls.append(self.delete_user_column)\n            self.backend_controls.append(\n                pn.pane.HTML(\n                    styles=dict(background=\"lightgray\"),\n                    width=2,\n                    sizing_mode=\"stretch_height\",\n                )\n            )\n            self.backend_controls.append(self.clear_flags_column)\n            self.backend_controls.append(\n                pn.pane.HTML(\n                    styles=dict(background=\"lightgray\"),\n                    width=2,\n                    sizing_mode=\"stretch_height\",\n                )\n            )\n            self.backend_controls.append(self.list_user_column)\n\n        # CALLBACKS\n        # Submit password button callback\n        def submit_password_button_callback(self, config):\n            success = auth.backend_submit_password(\n                gi=self,\n                user_is_admin=self.password_widget.object.admin,\n                user_is_guest=self.password_widget.object.guest,\n                config=config,\n            )\n            if success:\n                self.reload_backend(config)\n\n        self.submit_password_button.on_click(\n            lambda e: submit_password_button_callback(self, config)\n        )\n\n        # Add privileged user callback\n        def add_privileged_user_button_callback(self):\n            # Get username, updated at each key press\n            username_key_press = self.add_privileged_user_widget._widgets[\n                \"user\"\n            ].value_input\n            # Add user\n            auth.add_privileged_user(\n                username_key_press,\n                is_admin=self.add_privileged_user_widget.object.admin,\n                config=config,\n            )\n\n            self.reload_backend(config)\n            pn.state.notifications.success(\n                f\"User '{username_key_press}' added\",\n                duration=config.panel.notifications.duration,\n            )\n\n        self.add_privileged_user_button.on_click(\n            lambda e: add_privileged_user_button_callback(self)\n        )\n\n        # Delete user callback\n        def delete_user_button_callback(self):\n            # Get username, updated at each key press\n            username_key_press = self.user_eraser._widgets[\"user\"].value_input\n            # Delete user\n            deleted_data = auth.remove_user(\n                user=username_key_press, config=config\n            )\n            if (deleted_data[\"privileged_users_deleted\"] > 0) or (\n                deleted_data[\"credentials_deleted\"] > 0\n            ):\n                self.reload_backend(config)\n                pn.state.notifications.success(\n                    f\"User '{self.user_eraser.object.user}' deleted<br>auth: {deleted_data['privileged_users_deleted']}<br>cred: {deleted_data['credentials_deleted']}\",\n                    duration=config.panel.notifications.duration,\n                )\n            else:\n                pn.state.notifications.error(\n                    f\"User '{username_key_press}' does not exist\",\n                    duration=config.panel.notifications.duration,\n                )\n\n        self.delete_user_button.on_click(\n            lambda e: delete_user_button_callback(self)\n        )\n\n        # Clear flags callback\n        def clear_flags_button_callback(self):\n            # Clear flags\n            num_rows_deleted = models.Flags.clear_guest_override(config=config)\n            # Reload and notify user\n            self.reload_backend(config)\n            pn.state.notifications.success(\n                f\"Guest override flags cleared<br>{num_rows_deleted} rows deleted\",\n                duration=config.panel.notifications.duration,\n            )\n\n        self.clear_flags_button.on_click(\n            lambda e: clear_flags_button_callback(self)\n        )\n\n    # UTILITY FUNCTIONS\n    # MAIN SECTION\n    def reload_backend(self, config: DictConfig) -> None:\n        \"\"\"Reload backend by updating user lists and privileges.\n        Read also flags from `flags` table.\n\n        Args:\n            config (DictConfig): Hydra configuration dictionary.\n        \"\"\"\n        # Users and guests lists\n        self.users_tabulator.value = auth.list_users_guests_and_privileges(\n            config\n        )\n        # Flags table content\n        df_flags = models.Flags.read_as_df(\n            config=config,\n            index_col=\"id\",\n        )\n        self.flags_content.value = df_flags\n\n    def exit_backend(self) -> None:\n        \"\"\"Return to main homepage.\"\"\"\n        # Edit pathname to force exit\n        pn.state.location.pathname = (\n            pn.state.location.pathname.split(\"/\")[0] + \"/\"\n        )\n        pn.state.location.reload = True\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendInterface.access_denied_text","title":"access_denied_text instance-attribute","text":"
access_denied_text = HTML(\n    '\\n            <div class=\"no-more-order-flag\">\\n                <div class=\"icon-container\">\\n                    <svg class=\"flashing-animation\" xmlns=\"http://www.w3.org/2000/svg\" class=\"icon icon-tabler icon-tabler-shield-lock-filled\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\\n                        <path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\"></path>\\n                        <path d=\"M11.998 2l.118 .007l.059 .008l.061 .013l.111 .034a.993 .993 0 0 1 .217 .112l.104 .082l.255 .218a11 11 0 0 0 7.189 2.537l.342 -.01a1 1 0 0 1 1.005 .717a13 13 0 0 1 -9.208 16.25a1 1 0 0 1 -.502 0a13 13 0 0 1 -9.209 -16.25a1 1 0 0 1 1.005 -.717a11 11 0 0 0 7.531 -2.527l.263 -.225l.096 -.075a.993 .993 0 0 1 .217 -.112l.112 -.034a.97 .97 0 0 1 .119 -.021l.115 -.007zm.002 7a2 2 0 0 0 -1.995 1.85l-.005 .15l.005 .15a2 2 0 0 0 .995 1.581v1.769l.007 .117a1 1 0 0 0 1.993 -.117l.001 -1.768a2 2 0 0 0 -1.001 -3.732z\" stroke-width=\"0\" fill=\"currentColor\"></path>\\n                    </svg>\\n                    <span><strong>Insufficient privileges!</strong></span>\\n                </div>\\n            </div>\\n            ',\n    margin=5,\n    sizing_mode=\"stretch_width\",\n    stylesheets=[no_more_orders_path],\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendInterface.add_privileged_user_button","title":"add_privileged_user_button instance-attribute","text":"
add_privileged_user_button = Button(\n    name=\"Add\",\n    button_type=\"success\",\n    height=generic_button_height,\n    icon=\"user-plus\",\n    icon_size=\"2em\",\n    sizing_mode=\"stretch_width\",\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendInterface.add_privileged_user_column","title":"add_privileged_user_column instance-attribute","text":"
add_privileged_user_column = Column(\n    add_privileged_user_widget,\n    VSpacer(),\n    add_privileged_user_button,\n    width=sidebar_width,\n    sizing_mode=\"stretch_height\",\n    min_height=backend_min_height,\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendInterface.add_privileged_user_widget","title":"add_privileged_user_widget instance-attribute","text":"
add_privileged_user_widget = Param(\n    param,\n    name=\"Add Privileged User\",\n    width=sidebar_content_width,\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendInterface.add_update_user_column","title":"add_update_user_column instance-attribute","text":"
add_update_user_column = Column(\n    psw_text,\n    password_widget,\n    VSpacer(),\n    submit_password_button,\n    width=sidebar_width,\n    sizing_mode=\"stretch_height\",\n    min_height=backend_min_height,\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendInterface.backend_controls","title":"backend_controls instance-attribute","text":"
backend_controls = Row(\n    name=\"Actions\",\n    sizing_mode=\"stretch_both\",\n    min_height=backend_min_height,\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendInterface.clear_flags_button","title":"clear_flags_button instance-attribute","text":"
clear_flags_button = Button(\n    name=\"Clear Guest Override Flags\",\n    button_type=\"danger\",\n    height=generic_button_height,\n    icon=\"file-shredder\",\n    icon_size=\"2em\",\n    sizing_mode=\"stretch_width\",\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendInterface.clear_flags_column","title":"clear_flags_column instance-attribute","text":"
clear_flags_column = Column(\n    HTML(\"<b>Flags Table Content</b>\"),\n    flags_content,\n    clear_flags_button,\n    width=sidebar_width,\n    sizing_mode=\"stretch_height\",\n    min_height=backend_min_height,\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendInterface.delete_user_button","title":"delete_user_button instance-attribute","text":"
delete_user_button = Button(\n    name=\"Delete\",\n    button_type=\"danger\",\n    height=generic_button_height,\n    icon=\"user-minus\",\n    icon_size=\"2em\",\n    sizing_mode=\"stretch_width\",\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendInterface.delete_user_column","title":"delete_user_column instance-attribute","text":"
delete_user_column = Column(\n    user_eraser,\n    VSpacer(),\n    delete_user_button,\n    width=sidebar_width,\n    sizing_mode=\"stretch_height\",\n    min_height=backend_min_height,\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendInterface.exit_button","title":"exit_button instance-attribute","text":"
exit_button = Button(\n    name=\"\",\n    button_type=\"primary\",\n    button_style=\"solid\",\n    width=header_button_width,\n    height=generic_button_height,\n    icon=\"home-move\",\n    icon_size=\"2em\",\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendInterface.flags_content","title":"flags_content instance-attribute","text":"
flags_content = Tabulator(\n    value=df_flags, sizing_mode=\"stretch_height\"\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendInterface.header_row","title":"header_row instance-attribute","text":"
header_row = Row(\n    height=header_row_height, sizing_mode=\"stretch_width\"\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendInterface.list_user_column","title":"list_user_column instance-attribute","text":"
list_user_column = Column(\n    HTML(\"<b>Users and Privileges</b>\"),\n    users_tabulator,\n    width=sidebar_width,\n    sizing_mode=\"stretch_height\",\n    min_height=backend_min_height,\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendInterface.password_widget","title":"password_widget instance-attribute","text":"
password_widget = Param(\n    param,\n    widgets={\n        \"new_password\": PasswordInput(\n            name=\"New password\", placeholder=\"New Password\"\n        ),\n        \"repeat_new_password\": PasswordInput(\n            name=\"Repeat new password\",\n            placeholder=\"Repeat New Password\",\n        ),\n    },\n    name=\"Add/Update User Credentials\",\n    width=sidebar_content_width,\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendInterface.submit_password_button","title":"submit_password_button instance-attribute","text":"
submit_password_button = Button(\n    name=\"Submit\",\n    button_type=\"success\",\n    height=generic_button_height,\n    icon=\"key\",\n    icon_size=\"2em\",\n    sizing_mode=\"stretch_width\",\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendInterface.user_eraser","title":"user_eraser instance-attribute","text":"
user_eraser = Param(\n    param, name=\"Delete User\", width=sidebar_content_width\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendInterface.users_tabulator","title":"users_tabulator instance-attribute","text":"
users_tabulator = Tabulator(\n    value=list_users_guests_and_privileges(config),\n    sizing_mode=\"stretch_height\",\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendInterface.__init__","title":"__init__","text":"
__init__(config: DictConfig)\n
Source code in dlunch/gui.py
def __init__(\n    self,\n    config: DictConfig,\n):\n    # HEADER SECTION ------------------------------------------------------\n    # WIDGET\n\n    # BUTTONS\n    self.exit_button = pnw.Button(\n        name=\"\",\n        button_type=\"primary\",\n        button_style=\"solid\",\n        width=header_button_width,\n        height=generic_button_height,\n        icon=\"home-move\",\n        icon_size=\"2em\",\n    )\n\n    # ROW\n    # Create column for person data (add logout button only if auth is active)\n    self.header_row = pn.Row(\n        height=header_row_height,\n        sizing_mode=\"stretch_width\",\n    )\n    # Append a controls to the right side of header\n    self.header_row.append(pn.HSpacer())\n    self.header_row.append(self.exit_button)\n    self.header_row.append(\n        pn.pane.HTML(styles=dict(background=\"white\"), width=2, height=45)\n    )\n\n    # CALLBACKS\n    # Exit callback\n    self.exit_button.on_click(lambda e: self.exit_backend())\n\n    # MAIN SECTION --------------------------------------------------------\n    # Backend main section\n\n    # TEXTS\n    # \"no more order\" message\n    self.access_denied_text = pn.pane.HTML(\n        \"\"\"\n        <div class=\"no-more-order-flag\">\n            <div class=\"icon-container\">\n                <svg class=\"flashing-animation\" xmlns=\"http://www.w3.org/2000/svg\" class=\"icon icon-tabler icon-tabler-shield-lock-filled\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n                    <path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\"></path>\n                    <path d=\"M11.998 2l.118 .007l.059 .008l.061 .013l.111 .034a.993 .993 0 0 1 .217 .112l.104 .082l.255 .218a11 11 0 0 0 7.189 2.537l.342 -.01a1 1 0 0 1 1.005 .717a13 13 0 0 1 -9.208 16.25a1 1 0 0 1 -.502 0a13 13 0 0 1 -9.209 -16.25a1 1 0 0 1 1.005 -.717a11 11 0 0 0 7.531 -2.527l.263 -.225l.096 -.075a.993 .993 0 0 1 .217 -.112l.112 -.034a.97 .97 0 0 1 .119 -.021l.115 -.007zm.002 7a2 2 0 0 0 -1.995 1.85l-.005 .15l.005 .15a2 2 0 0 0 .995 1.581v1.769l.007 .117a1 1 0 0 0 1.993 -.117l.001 -1.768a2 2 0 0 0 -1.001 -3.732z\" stroke-width=\"0\" fill=\"currentColor\"></path>\n                </svg>\n                <span><strong>Insufficient privileges!</strong></span>\n            </div>\n        </div>\n        \"\"\",\n        margin=5,\n        sizing_mode=\"stretch_width\",\n        stylesheets=[config.panel.gui.css_files.no_more_orders_path],\n    )\n\n    # WIDGET\n    # Password renewer (only basic auth)\n    self.password_widget = pn.Param(\n        BackendPasswordRenewer().param,\n        widgets={\n            \"new_password\": pnw.PasswordInput(\n                name=\"New password\", placeholder=\"New Password\"\n            ),\n            \"repeat_new_password\": pnw.PasswordInput(\n                name=\"Repeat new password\",\n                placeholder=\"Repeat New Password\",\n            ),\n        },\n        name=\"Add/Update User Credentials\",\n        width=sidebar_content_width,\n    )\n    # Add user (only oauth)\n    self.add_privileged_user_widget = pn.Param(\n        BackendAddPrivilegedUser().param,\n        name=\"Add Privileged User\",\n        width=sidebar_content_width,\n    )\n    # User eraser\n    self.user_eraser = pn.Param(\n        BackendUserEraser().param,\n        name=\"Delete User\",\n        width=sidebar_content_width,\n    )\n    # User list\n    self.users_tabulator = pn.widgets.Tabulator(\n        value=auth.list_users_guests_and_privileges(config),\n        sizing_mode=\"stretch_height\",\n    )\n    # Flags content (use empty dataframe to instantiate)\n    df_flags = models.Flags.read_as_df(\n        config=config,\n        index_col=\"id\",\n    )\n    self.flags_content = pn.widgets.Tabulator(\n        value=df_flags,\n        sizing_mode=\"stretch_height\",\n    )\n\n    # BUTTONS\n    # Exit button\n    # Password button\n    self.submit_password_button = pnw.Button(\n        name=\"Submit\",\n        button_type=\"success\",\n        height=generic_button_height,\n        icon=\"key\",\n        icon_size=\"2em\",\n        sizing_mode=\"stretch_width\",\n    )\n    # Delete User button\n    self.add_privileged_user_button = pnw.Button(\n        name=\"Add\",\n        button_type=\"success\",\n        height=generic_button_height,\n        icon=\"user-plus\",\n        icon_size=\"2em\",\n        sizing_mode=\"stretch_width\",\n    )\n    # Delete User button\n    self.delete_user_button = pnw.Button(\n        name=\"Delete\",\n        button_type=\"danger\",\n        height=generic_button_height,\n        icon=\"user-minus\",\n        icon_size=\"2em\",\n        sizing_mode=\"stretch_width\",\n    )\n    # Clear flags table button\n    self.clear_flags_button = pnw.Button(\n        name=\"Clear Guest Override Flags\",\n        button_type=\"danger\",\n        height=generic_button_height,\n        icon=\"file-shredder\",\n        icon_size=\"2em\",\n        sizing_mode=\"stretch_width\",\n    )\n\n    # COLUMN\n    # Create column with user credentials controls (basic auth)\n    self.add_update_user_column = pn.Column(\n        config.panel.gui.psw_text,\n        self.password_widget,\n        pn.VSpacer(),\n        self.submit_password_button,\n        width=sidebar_width,\n        sizing_mode=\"stretch_height\",\n        min_height=backend_min_height,\n    )\n    # Create column with user authenthication controls (oauth)\n    self.add_privileged_user_column = pn.Column(\n        self.add_privileged_user_widget,\n        pn.VSpacer(),\n        self.add_privileged_user_button,\n        width=sidebar_width,\n        sizing_mode=\"stretch_height\",\n        min_height=backend_min_height,\n    )\n    # Create column for deleting users\n    self.delete_user_column = pn.Column(\n        self.user_eraser,\n        pn.VSpacer(),\n        self.delete_user_button,\n        width=sidebar_width,\n        sizing_mode=\"stretch_height\",\n        min_height=backend_min_height,\n    )\n    # Create column with flags' list\n    self.clear_flags_column = pn.Column(\n        pn.pane.HTML(\"<b>Flags Table Content</b>\"),\n        self.flags_content,\n        self.clear_flags_button,\n        width=sidebar_width,\n        sizing_mode=\"stretch_height\",\n        min_height=backend_min_height,\n    )\n    # Create column for users' list\n    self.list_user_column = pn.Column(\n        pn.pane.HTML(\"<b>Users and Privileges</b>\"),\n        self.users_tabulator,\n        width=sidebar_width,\n        sizing_mode=\"stretch_height\",\n        min_height=backend_min_height,\n    )\n\n    # ROWS\n    self.backend_controls = pn.Row(\n        name=\"Actions\",\n        sizing_mode=\"stretch_both\",\n        min_height=backend_min_height,\n    )\n    # Add controls only for admin users\n    if not auth.is_admin(user=pn_user(config), config=config):\n        self.backend_controls.append(self.access_denied_text)\n        self.backend_controls.append(pn.Spacer(height=15))\n    else:\n        # For basic auth use a password renewer, for oauth a widget for\n        # adding privileged users\n        if auth.is_basic_auth_active(config=config):\n            self.backend_controls.append(self.add_update_user_column)\n        else:\n            self.backend_controls.append(self.add_privileged_user_column)\n        self.backend_controls.append(\n            pn.pane.HTML(\n                styles=dict(background=\"lightgray\"),\n                width=2,\n                sizing_mode=\"stretch_height\",\n            )\n        )\n        self.backend_controls.append(self.delete_user_column)\n        self.backend_controls.append(\n            pn.pane.HTML(\n                styles=dict(background=\"lightgray\"),\n                width=2,\n                sizing_mode=\"stretch_height\",\n            )\n        )\n        self.backend_controls.append(self.clear_flags_column)\n        self.backend_controls.append(\n            pn.pane.HTML(\n                styles=dict(background=\"lightgray\"),\n                width=2,\n                sizing_mode=\"stretch_height\",\n            )\n        )\n        self.backend_controls.append(self.list_user_column)\n\n    # CALLBACKS\n    # Submit password button callback\n    def submit_password_button_callback(self, config):\n        success = auth.backend_submit_password(\n            gi=self,\n            user_is_admin=self.password_widget.object.admin,\n            user_is_guest=self.password_widget.object.guest,\n            config=config,\n        )\n        if success:\n            self.reload_backend(config)\n\n    self.submit_password_button.on_click(\n        lambda e: submit_password_button_callback(self, config)\n    )\n\n    # Add privileged user callback\n    def add_privileged_user_button_callback(self):\n        # Get username, updated at each key press\n        username_key_press = self.add_privileged_user_widget._widgets[\n            \"user\"\n        ].value_input\n        # Add user\n        auth.add_privileged_user(\n            username_key_press,\n            is_admin=self.add_privileged_user_widget.object.admin,\n            config=config,\n        )\n\n        self.reload_backend(config)\n        pn.state.notifications.success(\n            f\"User '{username_key_press}' added\",\n            duration=config.panel.notifications.duration,\n        )\n\n    self.add_privileged_user_button.on_click(\n        lambda e: add_privileged_user_button_callback(self)\n    )\n\n    # Delete user callback\n    def delete_user_button_callback(self):\n        # Get username, updated at each key press\n        username_key_press = self.user_eraser._widgets[\"user\"].value_input\n        # Delete user\n        deleted_data = auth.remove_user(\n            user=username_key_press, config=config\n        )\n        if (deleted_data[\"privileged_users_deleted\"] > 0) or (\n            deleted_data[\"credentials_deleted\"] > 0\n        ):\n            self.reload_backend(config)\n            pn.state.notifications.success(\n                f\"User '{self.user_eraser.object.user}' deleted<br>auth: {deleted_data['privileged_users_deleted']}<br>cred: {deleted_data['credentials_deleted']}\",\n                duration=config.panel.notifications.duration,\n            )\n        else:\n            pn.state.notifications.error(\n                f\"User '{username_key_press}' does not exist\",\n                duration=config.panel.notifications.duration,\n            )\n\n    self.delete_user_button.on_click(\n        lambda e: delete_user_button_callback(self)\n    )\n\n    # Clear flags callback\n    def clear_flags_button_callback(self):\n        # Clear flags\n        num_rows_deleted = models.Flags.clear_guest_override(config=config)\n        # Reload and notify user\n        self.reload_backend(config)\n        pn.state.notifications.success(\n            f\"Guest override flags cleared<br>{num_rows_deleted} rows deleted\",\n            duration=config.panel.notifications.duration,\n        )\n\n    self.clear_flags_button.on_click(\n        lambda e: clear_flags_button_callback(self)\n    )\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendInterface.exit_backend","title":"exit_backend","text":"
exit_backend() -> None\n

Return to main homepage.

Source code in dlunch/gui.py
def exit_backend(self) -> None:\n    \"\"\"Return to main homepage.\"\"\"\n    # Edit pathname to force exit\n    pn.state.location.pathname = (\n        pn.state.location.pathname.split(\"/\")[0] + \"/\"\n    )\n    pn.state.location.reload = True\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendInterface.reload_backend","title":"reload_backend","text":"
reload_backend(config: DictConfig) -> None\n

Reload backend by updating user lists and privileges. Read also flags from flags table.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required Source code in dlunch/gui.py
def reload_backend(self, config: DictConfig) -> None:\n    \"\"\"Reload backend by updating user lists and privileges.\n    Read also flags from `flags` table.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n    \"\"\"\n    # Users and guests lists\n    self.users_tabulator.value = auth.list_users_guests_and_privileges(\n        config\n    )\n    # Flags table content\n    df_flags = models.Flags.read_as_df(\n        config=config,\n        index_col=\"id\",\n    )\n    self.flags_content.value = df_flags\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendPasswordRenewer","title":"BackendPasswordRenewer","text":"

Bases: Parameterized

Param class used inside the backend to create the widget that collect info to renew users password.

It has more options compared to the standard PasswordRenewer.

This widget is used only if basic authentication is active.

Methods:

Name Description __str__

String representation of this object.

Attributes:

Name Type Description admin Boolean

Admin flag (true if admin).

guest Boolean

Guest flag (true if guest).

new_password String

New password.

repeat_new_password String

Repeat the new password. This field tests if the new password is as intended.

user String

Username.

Source code in dlunch/gui.py
class BackendPasswordRenewer(param.Parameterized):\n    \"\"\"Param class used inside the backend to create the widget that collect info to renew users password.\n\n    It has more options compared to the standard `PasswordRenewer`.\n\n    This widget is used only if basic authentication is active.\"\"\"\n\n    user: param.String = param.String(\n        default=\"\",\n        doc=\"username for password update (use 'guest' for guest user)\",\n    )\n    \"\"\"Username.\"\"\"\n    new_password: param.String = param.String(default=\"\")\n    \"\"\"New password.\"\"\"\n    repeat_new_password: param.String = param.String(default=\"\")\n    \"\"\"Repeat the new password. This field tests if the new password is as intended.\"\"\"\n    admin: param.Boolean = param.Boolean(\n        default=False, doc=\"add admin privileges\"\n    )\n    \"\"\"Admin flag (true if admin).\"\"\"\n    guest: param.Boolean = param.Boolean(\n        default=False,\n        doc=\"guest account (don't add user to privileged users' table)\",\n    )\n    \"\"\"Guest flag (true if guest).\n\n    User credentials are added to `credentials` table, but the user is not listed in `privileged_users` table.\"\"\"\n\n    def __str__(self):\n        \"\"\"String representation of this object.\n\n        Returns:\n            (str): string representation.\n        \"\"\"\n        return \"BackendPasswordRenewer\"\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendPasswordRenewer.admin","title":"admin class-attribute instance-attribute","text":"
admin: Boolean = Boolean(\n    default=False, doc=\"add admin privileges\"\n)\n

Admin flag (true if admin).

"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendPasswordRenewer.guest","title":"guest class-attribute instance-attribute","text":"
guest: Boolean = Boolean(\n    default=False,\n    doc=\"guest account (don't add user to privileged users' table)\",\n)\n

Guest flag (true if guest).

User credentials are added to credentials table, but the user is not listed in privileged_users table.

"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendPasswordRenewer.new_password","title":"new_password class-attribute instance-attribute","text":"
new_password: String = String(default='')\n

New password.

"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendPasswordRenewer.repeat_new_password","title":"repeat_new_password class-attribute instance-attribute","text":"
repeat_new_password: String = String(default='')\n

Repeat the new password. This field tests if the new password is as intended.

"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendPasswordRenewer.user","title":"user class-attribute instance-attribute","text":"
user: String = String(\n    default=\"\",\n    doc=\"username for password update (use 'guest' for guest user)\",\n)\n

Username.

"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendPasswordRenewer.__str__","title":"__str__","text":"
__str__()\n

String representation of this object.

Returns:

Type Description str

string representation.

Source code in dlunch/gui.py
def __str__(self):\n    \"\"\"String representation of this object.\n\n    Returns:\n        (str): string representation.\n    \"\"\"\n    return \"BackendPasswordRenewer\"\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendUserEraser","title":"BackendUserEraser","text":"

Bases: Parameterized

Param class used inside the backend to create the widget that delete users.

Users are deleted from both credentials and privileged_user tables.

Methods:

Name Description __str__

String representation of this object.

Attributes:

Name Type Description user String

User to be deleted.

Source code in dlunch/gui.py
class BackendUserEraser(param.Parameterized):\n    \"\"\"Param class used inside the backend to create the widget that delete users.\n\n    Users are deleted from both `credentials` and `privileged_user` tables.\"\"\"\n\n    user: param.String = param.String(default=\"\", doc=\"user to be deleted\")\n    \"\"\"User to be deleted.\"\"\"\n\n    def __str__(self):\n        \"\"\"String representation of this object.\n\n        Returns:\n            (str): string representation.\n        \"\"\"\n        return \"BackendUserEraser\"\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendUserEraser.user","title":"user class-attribute instance-attribute","text":"
user: String = String(default='', doc='user to be deleted')\n

User to be deleted.

"},{"location":"reference/dlunch/gui/#dlunch.gui.BackendUserEraser.__str__","title":"__str__","text":"
__str__()\n

String representation of this object.

Returns:

Type Description str

string representation.

Source code in dlunch/gui.py
def __str__(self):\n    \"\"\"String representation of this object.\n\n    Returns:\n        (str): string representation.\n    \"\"\"\n    return \"BackendUserEraser\"\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface","title":"GraphicInterface","text":"

Class with widgets for the main graphic interface.

All widgets are instantiated at class initialization.

Class methods handle specific operations that may be repeated multiple time after class instantiation.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required waiter Waiter

Waiter object with methods to handle user requests.

required app Template

App panel template (see Panel docs <https://panel.holoviz.org/how_to/templates/index.html>__).

required person Person

Object with user data and preferences for the lunch order.

required guest_password str

guest password to show in password tab. Used only if basic authentication is active. Defaults to empty string (\"\").

''

Methods:

Name Description __init__ build_order_table

Build Tabulator object to display placed orders.

build_stats_and_info_text

Build text used for statistics under the stats tab, and info under the user tab.

build_time_label

Build HTML field to display the time label.

load_sidebar_tabs

Append tabs to the app template sidebar.

Attributes:

Name Type Description additional_items_details backend_button build_menu_button buttons_flexbox change_order_time_takeaway_button dataframe delete_order_button download_button error_message file_widget guest_override_alert guest_password_widget guest_username_widget header_object header_row logout_button main_header_row menu_flexbox no_menu_col no_menu_image no_menu_image_attribution no_more_order_alert password_widget person_widget quote refresh_button reload_on_guest_override reload_on_no_more_order res_col results_divider send_order_button sidebar_download_orders_col sidebar_menu_upload_col sidebar_password sidebar_person_column sidebar_stats_col sidebar_tabs stats_widget submit_password_button takeaway_alert_sign takeaway_alert_text time_col time_col_title toggle_guest_override_button toggle_no_more_order_button Source code in dlunch/gui.py
class GraphicInterface:\n    \"\"\"Class with widgets for the main graphic interface.\n\n    All widgets are instantiated at class initialization.\n\n    Class methods handle specific operations that may be repeated multiple time after class instantiation.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n        waiter (core.Waiter): Waiter object with methods to handle user requests.\n        app (pn.Template): App panel template (see `Panel docs <https://panel.holoviz.org/how_to/templates/index.html>`__).\n        person (Person): Object with user data and preferences for the lunch order.\n        guest_password (str, optional): guest password to show in password tab. Used only if basic authentication is active.\n            Defaults to empty string (`\"\"`).\n\n    \"\"\"\n\n    def __init__(\n        self,\n        config: DictConfig,\n        waiter: core.Waiter,\n        app: pn.Template,\n        person: Person,\n        guest_password: str = \"\",\n    ):\n        # HEADER SECTION ------------------------------------------------------\n        # WIDGET\n        # Create PNG pane with app icon\n        self.header_object = instantiate(config.panel.gui.header_object)\n\n        # BUTTONS\n        # Backend button\n        self.backend_button = pnw.Button(\n            name=\"\",\n            button_type=\"primary\",\n            button_style=\"solid\",\n            width=header_button_width,\n            height=generic_button_height,\n            icon=\"adjustments\",\n            icon_size=\"2em\",\n        )\n        # Guest override toggle button (if pressed the user act as a guest)\n        self.toggle_guest_override_button = pnw.Toggle(\n            button_type=\"primary\",\n            button_style=\"solid\",\n            width=header_button_width,\n            height=generic_button_height,\n            icon=\"user-bolt\",\n            icon_size=\"2em\",\n            stylesheets=[config.panel.gui.css_files.guest_override_path],\n        )\n        # Logout button\n        self.logout_button = pnw.Button(\n            name=\"\",\n            button_type=\"primary\",\n            button_style=\"solid\",\n            width=header_button_width,\n            height=generic_button_height,\n            icon=\"door-exit\",\n            icon_size=\"2em\",\n        )\n\n        # ROW\n        # Create column for person data (add logout button only if auth is active)\n        self.header_row = pn.Row(\n            height=header_row_height,\n            sizing_mode=\"stretch_width\",\n        )\n        # Append a graphic element to the left side of header\n        if config.panel.gui.header_object:\n            self.header_row.append(self.header_object)\n        # Append a controls to the right side of header\n        if auth.is_auth_active(config=config):\n            self.header_row.append(pn.HSpacer())\n            # Backend only for admin\n            if auth.is_admin(user=pn_user(config), config=config):\n                self.header_row.append(self.backend_button)\n            # Guest override only for non guests\n            if not auth.is_guest(\n                user=pn_user(config), config=config, allow_override=False\n            ):\n                self.header_row.append(self.toggle_guest_override_button)\n            self.header_row.append(self.logout_button)\n            self.header_row.append(\n                pn.pane.HTML(\n                    styles=dict(background=\"white\"), width=2, height=45\n                )\n            )\n\n        # CALLBACKS\n        # Backend callback\n        self.backend_button.on_click(lambda e: auth.open_backend())\n\n        # Guest override callback\n        @pn.depends(self.toggle_guest_override_button, watch=True)\n        def reload_on_guest_override_callback(\n            toggle: pnw.ToggleIcon, reload: bool = True\n        ):\n            # Update global variable that control guest override\n            # Only non guest can store this value in 'flags' table (guest users\n            # are always guests, there is no use in sotring a flag for them)\n            if not auth.is_guest(\n                user=pn_user(config), config=config, allow_override=False\n            ):\n                models.set_flag(\n                    config=config,\n                    id=f\"{pn_user(config)}_guest_override\",\n                    value=toggle,\n                )\n            # Show banner if override is active\n            self.guest_override_alert.visible = toggle\n            # Simply reload the menu when the toggle button value changes\n            if reload:\n                waiter.reload_menu(\n                    None,\n                    self,\n                )\n\n        # Add callback to attribute\n        self.reload_on_guest_override = reload_on_guest_override_callback\n\n        # Logout callback\n        self.logout_button.on_click(lambda e: auth.force_logout())\n\n        # MAIN SECTION --------------------------------------------------------\n        # Elements required for build the main section of the web app\n\n        # TEXTS\n        # Quote of the day\n        self.quote = pn.pane.Markdown(\n            f\"\"\"\n            _{df_quote.quote.iloc[0]}_\n\n            **{df_quote.author.iloc[0]}**\n            \"\"\"\n        )\n        # Time column title\n        self.time_col_title = pn.pane.Markdown(\n            config.panel.time_column_text,\n            sizing_mode=\"stretch_width\",\n            styles={\"text-align\": \"center\"},\n        )\n        # \"no more order\" message\n        self.no_more_order_alert = pn.pane.HTML(\n            \"\"\"\n            <div class=\"no-more-order-flag\">\n                <div class=\"icon-container\">\n                    <svg class=\"flashing-animation\" xmlns=\"http://www.w3.org/2000/svg\" class=\"icon icon-tabler icon-tabler-alert-circle-filled\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n                        <path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\"></path>\n                        <path d=\"M12 2c5.523 0 10 4.477 10 10a10 10 0 0 1 -19.995 .324l-.005 -.324l.004 -.28c.148 -5.393 4.566 -9.72 9.996 -9.72zm.01 13l-.127 .007a1 1 0 0 0 0 1.986l.117 .007l.127 -.007a1 1 0 0 0 0 -1.986l-.117 -.007zm-.01 -8a1 1 0 0 0 -.993 .883l-.007 .117v4l.007 .117a1 1 0 0 0 1.986 0l.007 -.117v-4l-.007 -.117a1 1 0 0 0 -.993 -.883z\" stroke-width=\"0\" fill=\"currentColor\"></path>\n                    </svg>\n                    <span><strong>Oh no! You missed this train...</strong></span>\n                </div>\n                <div>\n                    Orders are closed, better luck next time.\n                </div>\n            </div>\n            \"\"\",\n            margin=5,\n            sizing_mode=\"stretch_width\",\n            stylesheets=[config.panel.gui.css_files.no_more_orders_path],\n        )\n        # Alert for guest override\n        self.guest_override_alert = pn.pane.HTML(\n            \"\"\"\n            <div class=\"guest-override-flag\">\n                <div class=\"icon-container\">\n                    <svg class=\"flashing-animation\" xmlns=\"http://www.w3.org/2000/svg\" class=\"icon icon-tabler icon-tabler-radioactive-filled\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\"/>\n                        <path d=\"M21 11a1 1 0 0 1 1 1a10 10 0 0 1 -5 8.656a1 1 0 0 1 -1.302 -.268l-.064 -.098l-3 -5.19a.995 .995 0 0 1 -.133 -.542l.01 -.11l.023 -.106l.034 -.106l.046 -.1l.056 -.094l.067 -.089a.994 .994 0 0 1 .165 -.155l.098 -.064a2 2 0 0 0 .993 -1.57l.007 -.163a1 1 0 0 1 .883 -.994l.117 -.007h6z\" stroke-width=\"0\" fill=\"currentColor\" />\n                        <path d=\"M7 3.344a10 10 0 0 1 10 0a1 1 0 0 1 .418 1.262l-.052 .104l-3 5.19l-.064 .098a.994 .994 0 0 1 -.155 .165l-.089 .067a1 1 0 0 1 -.195 .102l-.105 .034l-.107 .022a1.003 1.003 0 0 1 -.547 -.07l-.104 -.052a2 2 0 0 0 -1.842 -.082l-.158 .082a1 1 0 0 1 -1.302 -.268l-.064 -.098l-3 -5.19a1 1 0 0 1 .366 -1.366z\" stroke-width=\"0\" fill=\"currentColor\" />\n                        <path d=\"M9 11a1 1 0 0 1 .993 .884l.007 .117a2 2 0 0 0 .861 1.645l.237 .152a.994 .994 0 0 1 .165 .155l.067 .089l.056 .095l.045 .099c.014 .036 .026 .07 .035 .106l.022 .107l.011 .11a.994 .994 0 0 1 -.08 .437l-.053 .104l-3 5.19a1 1 0 0 1 -1.366 .366a10 10 0 0 1 -5 -8.656a1 1 0 0 1 .883 -.993l.117 -.007h6z\" stroke-width=\"0\" fill=\"currentColor\" />\n                    </svg>\n                    <span><strong>Watch out! You are a guest now...</strong></span>\n                </div>\n                <div>\n                    Guest override is active.\n                </div>\n            </div>\n            \"\"\",\n            margin=5,\n            sizing_mode=\"stretch_width\",\n            stylesheets=[config.panel.gui.css_files.guest_override_path],\n        )\n        # Takeaway alert\n        self.takeaway_alert_sign = f\"<span {config.panel.gui.takeaway_alert_icon_options}>{config.panel.gui.takeaway_svg_icon}</span>\"\n        self.takeaway_alert_text = f\"<span {config.panel.gui.takeaway_alert_text_options}>{config.panel.gui.takeaway_id}</span> \"\n        # No menu image attribution\n        self.no_menu_image_attribution = pn.pane.HTML(\n            \"\"\"\n            <i>\n                Image by\n                <a\n                    href=\"https://www.freepik.com/free-vector/tiny-cooks-making-spaghetti-dinner-isolated-flat-illustration_11235909.htm\"\n                    referrerpolicy=\"no-referrer\"\n                    rel=\"external\"\n                    target=\"_blank\"\n                >\n                    pch.vector\n                </a>\n                on Freepik\n            </i>\n            \"\"\",\n            align=\"end\",\n            styles={\n                \"color\": \"darkgray\",\n                \"font-size\": \"10px\",\n                \"font-weight\": \"light\",\n            },\n        )\n\n        # WIDGETS\n        # JPG shown when no menu is available\n        self.no_menu_image = pn.pane.JPG(\n            config.panel.gui.no_menu_image_path, alt_text=\"no menu\"\n        )\n        # Create dataframe instance\n        self.dataframe = pnw.Tabulator(\n            name=\"Order\",\n            widths={config.panel.gui.note_column_name: 180},\n            selectable=False,\n            stylesheets=[config.panel.gui.css_files.custom_tabulator_path],\n        )\n\n        # BUTTONS\n        # Create refresh button\n        self.refresh_button = pnw.Button(\n            name=\"\",\n            button_style=\"outline\",\n            button_type=\"light\",\n            width=45,\n            height=generic_button_height,\n            icon=\"reload\",\n            icon_size=\"2em\",\n        )\n        # Create send button\n        self.send_order_button = pnw.Button(\n            name=\"Send Order\",\n            button_type=\"success\",\n            height=generic_button_height,\n            icon=\"circle-check-filled\",\n            icon_size=\"2em\",\n            sizing_mode=\"stretch_width\",\n        )\n        # Create toggle button that stop orders (used in time column)\n        # Initialized to False, but checked on app creation\n        self.toggle_no_more_order_button = pnw.Toggle(\n            name=\"Stop Orders\",\n            button_style=\"outline\",\n            button_type=\"warning\",\n            height=generic_button_height,\n            icon=\"hand-stop\",\n            icon_size=\"2em\",\n            sizing_mode=\"stretch_width\",\n        )\n        # Create change time\n        self.change_order_time_takeaway_button = pnw.Button(\n            name=\"Change Time/Takeaway\",\n            button_type=\"primary\",\n            button_style=\"outline\",\n            height=generic_button_height,\n            icon=\"clock-edit\",\n            icon_size=\"2em\",\n            sizing_mode=\"stretch_width\",\n        )\n        # Create delete order\n        self.delete_order_button = pnw.Button(\n            name=\"Delete Order\",\n            button_type=\"danger\",\n            height=generic_button_height,\n            icon=\"trash-filled\",\n            icon_size=\"2em\",\n            sizing_mode=\"stretch_width\",\n        )\n\n        # ROWS\n        self.main_header_row = pn.Row(\n            \"# Menu\",\n            pn.layout.HSpacer(),\n            self.refresh_button,\n        )\n\n        # COLUMNS\n        # Create column shown when no menu is available\n        self.no_menu_col = pn.Column(\n            self.no_menu_image,\n            self.no_menu_image_attribution,\n            sizing_mode=\"stretch_width\",\n            min_width=main_area_min_width,\n        )\n        # Create column for lunch time labels\n        self.time_col = pn.Column(width=time_col_width)\n        # Create column for resulting menus\n        self.res_col = pn.Column(\n            sizing_mode=\"stretch_width\", min_width=main_area_min_width\n        )\n\n        # FLEXBOXES\n        self.menu_flexbox = pn.FlexBox(\n            *[\n                self.dataframe,\n                pn.Spacer(width=time_col_spacer_width),\n                self.time_col,\n            ],\n            min_width=main_area_min_width,\n        )\n        self.buttons_flexbox = pn.FlexBox(\n            *[\n                self.send_order_button,\n                self.toggle_no_more_order_button,\n                self.change_order_time_takeaway_button,\n                self.delete_order_button,\n            ],\n            flex_wrap=\"nowrap\",\n            min_width=main_area_min_width,\n            sizing_mode=\"stretch_width\",\n        )\n        self.results_divider = pn.layout.Divider(\n            sizing_mode=\"stretch_width\", min_width=main_area_min_width\n        )\n\n        # CALLBACKS\n        # Callback on every \"toggle\" action\n        @pn.depends(self.toggle_no_more_order_button, watch=True)\n        def reload_on_no_more_order_callback(\n            toggle: pnw.Toggle, reload: bool = True\n        ):\n            # Update global variable\n            models.set_flag(config=config, id=\"no_more_orders\", value=toggle)\n\n            # Show \"no more order\" text\n            self.no_more_order_alert.visible = toggle\n\n            # Deactivate send, delete and change order buttons\n            self.send_order_button.disabled = toggle\n            self.delete_order_button.disabled = toggle\n            self.change_order_time_takeaway_button.disabled = toggle\n\n            # Simply reload the menu when the toggle button value changes\n            if reload:\n                waiter.reload_menu(\n                    None,\n                    self,\n                )\n\n        # Add callback to attribute\n        self.reload_on_no_more_order = reload_on_no_more_order_callback\n\n        # Refresh button callback\n        self.refresh_button.on_click(\n            lambda e: waiter.reload_menu(\n                e,\n                self,\n            )\n        )\n        # Send order button callback\n        self.send_order_button.on_click(\n            lambda e: waiter.send_order(\n                e,\n                app,\n                person,\n                self,\n            )\n        )\n        # Delete order button callback\n        self.delete_order_button.on_click(\n            lambda e: waiter.delete_order(\n                e,\n                app,\n                self,\n            )\n        )\n        # Change order time button callback\n        self.change_order_time_takeaway_button.on_click(\n            lambda e: waiter.change_order_time_takeaway(\n                e,\n                person,\n                self,\n            )\n        )\n\n        # MODAL WINDOW --------------------------------------------------------\n        # Error message\n        self.error_message = pn.pane.HTML(\n            styles={\"color\": \"red\", \"font-weight\": \"bold\"},\n            sizing_mode=\"stretch_width\",\n        )\n        self.error_message.visible = False\n\n        # SIDEBAR -------------------------------------------------------------\n        # TEXTS\n        # Foldable additional item details dropdown menu\n        jinja_template = jinja2.Environment(\n            loader=jinja2.BaseLoader\n        ).from_string(config.panel.gui.additional_item_details_template)\n        self.additional_items_details = pn.pane.HTML(\n            jinja_template.render(\n                items=config.panel.additional_items_to_concat\n            ),\n            width=sidebar_content_width,\n        )\n\n        # WIDGET\n        # Person data\n        self.person_widget = pn.Param(\n            person.param,\n            widgets={\n                \"guest\": pnw.RadioButtonGroup(\n                    options=OmegaConf.to_container(\n                        config.panel.guest_types, resolve=True\n                    ),\n                    button_type=\"primary\",\n                    button_style=\"outline\",\n                ),\n                \"username\": pnw.TextInput(\n                    value=person.username,\n                    value_input=person.username,\n                    description=person.param.username.doc,\n                ),\n            },\n            width=sidebar_content_width,\n        )\n        # File upload\n        self.file_widget = pnw.FileInput(\n            accept=\".png,.jpg,.jpeg,.xlsx\", sizing_mode=\"stretch_width\"\n        )\n        # Stats table\n        # Create stats table (non-editable)\n        self.stats_widget = pnw.Tabulator(\n            name=\"Statistics\",\n            hidden_columns=[\"index\"],\n            width=sidebar_content_width - 20,\n            layout=\"fit_columns\",\n            stylesheets=[\n                config.panel.gui.css_files.custom_tabulator_path,\n                config.panel.gui.css_files.stats_tabulator_path,\n            ],\n        )\n        # Password renewer\n        self.password_widget = pn.Param(\n            PasswordRenewer().param,\n            widgets={\n                \"old_password\": pnw.PasswordInput(\n                    name=\"Old password\", placeholder=\"Old Password\"\n                ),\n                \"new_password\": pnw.PasswordInput(\n                    name=\"New password\", placeholder=\"New Password\"\n                ),\n                \"repeat_new_password\": pnw.PasswordInput(\n                    name=\"Repeat new password\",\n                    placeholder=\"Repeat New Password\",\n                ),\n            },\n            name=\"Change password\",\n            width=sidebar_content_width,\n        )\n        # Guest password text\n        self.guest_username_widget = pnw.TextInput(\n            name=\"Username\",\n            placeholder=\"If empty reload this page.\",\n            value=\"guest\",\n        )\n        self.guest_password_widget = pnw.PasswordInput(\n            name=\"Password\",\n            placeholder=\"If empty reload this page.\",\n            value=guest_password,\n        )\n        # Turn off guest user if no password is set (empty string)\n        if not guest_password:\n            self.guest_username_widget.value = \"\"\n            self.guest_username_widget.disabled = True\n            self.guest_username_widget.placeholder = \"NOT ACTIVE\"\n            self.guest_password_widget.value = \"\"\n            self.guest_password_widget.disabled = True\n            self.guest_password_widget.placeholder = \"NOT ACTIVE\"\n\n        # BUTTONS\n        # Create menu button\n        self.build_menu_button = pnw.Button(\n            name=\"Build Menu\",\n            button_type=\"primary\",\n            sizing_mode=\"stretch_width\",\n            icon=\"tools-kitchen-2\",\n            icon_size=\"2em\",\n        )\n        # Download button and callback\n        self.download_button = pn.widgets.FileDownload(\n            callback=lambda: waiter.download_dataframe(self),\n            filename=config.panel.file_name + \".xlsx\",\n            sizing_mode=\"stretch_width\",\n            icon=\"download\",\n            icon_size=\"2em\",\n        )\n        # Password button\n        self.submit_password_button = pnw.Button(\n            name=\"Submit\",\n            button_type=\"success\",\n            button_style=\"outline\",\n            height=generic_button_height,\n            icon=\"key\",\n            icon_size=\"2em\",\n            sizing_mode=\"stretch_width\",\n        )\n\n        # COLUMNS\n        # Create column for person data\n        self.sidebar_person_column = pn.Column(\n            person_text,\n            self.person_widget,\n            pn.Spacer(height=5),\n            self.additional_items_details,\n            name=\"User\",\n            width=sidebar_content_width,\n        )\n        # Leave an empty widget for the 'other info' section\n        self.sidebar_person_column.append(\n            pn.pane.HTML(),\n        )\n\n        # Create column for uploading image/Excel with the menu\n        self.sidebar_menu_upload_col = pn.Column(\n            upload_text,\n            self.file_widget,\n            self.build_menu_button,\n            name=\"Menu Upload\",\n            width=sidebar_content_width,\n        )\n        # Create column for downloading Excel with orders\n        self.sidebar_download_orders_col = pn.Column(\n            download_text,\n            self.download_button,\n            name=\"Download Orders\",\n            width=sidebar_content_width,\n        )\n        # Create column for statistics\n        self.sidebar_stats_col = pn.Column(\n            name=\"Stats\", width=sidebar_content_width\n        )\n\n        self.sidebar_password = pn.Column(\n            config.panel.gui.psw_text,\n            self.password_widget,\n            self.submit_password_button,\n            pn.Spacer(height=5),\n            pn.layout.Divider(),\n            guest_user_text,\n            self.guest_username_widget,\n            self.guest_password_widget,\n            name=\"Password\",\n            width=sidebar_content_width,\n        )\n\n        # TABS\n        # The person widget is defined in the app factory function because\n        # lunch times are configurable\n        self.sidebar_tabs = pn.Tabs(\n            width=sidebar_content_width,\n        )\n        # Reload tabs according to auth.is_guest results and guest_override\n        # flag (no need to cleans, tabs are already empty)\n        self.load_sidebar_tabs(config=config, clear_before_loading=False)\n\n        # CALLBACKS\n        # Build menu button callback\n        self.build_menu_button.on_click(\n            lambda e: waiter.build_menu(\n                e,\n                app,\n                self,\n            )\n        )\n        # Submit password button callback\n        self.submit_password_button.on_click(\n            lambda e: auth.submit_password(gi=self, config=config)\n        )\n\n    # UTILITY FUNCTIONS\n    # MAIN SECTION\n    def build_order_table(\n        self,\n        config: DictConfig,\n        df: pd.DataFrame,\n        time: str,\n        guests_lists: dict = {},\n    ) -> pnw.Tabulator:\n        \"\"\"Build `Tabulator` object to display placed orders.\n\n        Args:\n            config (DictConfig): Hydra configuration dictionary.\n            df (pd.DataFrame): Table with orders. It has columns for each user that placed an order, total and a note columns.\n            time (str): Lunch time.\n            guests_lists (dict, optional): Dictionary with lists of users dived by guest type.\n                Keys of the dictionary are the type of guest listed.\n                Defaults to empty dictionary (`{}`).\n\n        Returns:\n            pnw.Tabulator: Panel `Tabulator` object representing placed orders.\n        \"\"\"\n        # Add guest icon to users' id\n        columns_with_guests_icons = df.columns.to_series()\n        for guest_type, guests_list in guests_lists.items():\n            columns_with_guests_icons[\n                columns_with_guests_icons.isin(guests_list)\n            ] += f\" {config.panel.gui.guest_icons[guest_type]}\"\n        df.columns = columns_with_guests_icons.to_list()\n        # Create table widget\n        orders_table_widget = pnw.Tabulator(\n            name=time,\n            value=df,\n            frozen_columns=[0],\n            layout=\"fit_data_table\",\n            stylesheets=[config.panel.gui.css_files.custom_tabulator_path],\n        )\n        # Make the table non-editable\n        orders_table_widget.editors = {c: None for c in df.columns}\n        return orders_table_widget\n\n    def build_time_label(\n        self,\n        time: str,\n        diners_n: str,\n        separator: str = \" &#10072; \",\n        emoji: str = \"&#127829;\",\n        per_icon: str = \" &#10006; \",\n        is_takeaway: bool = False,\n        takeaway_alert_sign: str = \"TAKEAWAY\",\n        css_classes: list = [],\n        stylesheets: list = [],\n        **kwargs,\n    ) -> pn.pane.HTML:\n        \"\"\"Build HTML field to display the time label.\n\n        This function is used to display labels that summarize an order.\n\n        Those are shown on the side of the menu table as well as labels above each order table.\n\n        Args:\n            time (str): Lunch time.\n            diners_n (str): Number of people that placed an order.\n            separator (str, optional): Separator between lunch time and order data. Defaults to \" &#10072; \".\n            emoji (str, optional): Emoji used as number lunch symbol. Defaults to \"&#127829;\".\n            per_icon (str, optional): icon used between the lunch emoji and the number of people that placed an order.\n                Usually a multiply operator.\n                Defaults to \" &#10006; \".\n            is_takeaway (bool, optional): takeaway flag (true if the order is to takeaway). Defaults to False.\n            takeaway_alert_sign (str, optional): warning text to highlight that the order is to takeaway. Defaults to \"TAKEAWAY\".\n            css_classes (list, optional): CSS classes to assign to the resulting HTML pane. Defaults to [].\n            stylesheets (list, optional): Stylesheets to assign to the resulting HTML pane\n                (see `Panel docs <https://panel.holoviz.org/how_to/styling/apply_css.html>`__). Defaults to [].\n\n        Returns:\n            pn.pane.HTML: HTML pane representing a label with order summary.\n        \"\"\"\n        # If takeaway add alert sign\n        if is_takeaway:\n            takeaway = f\"{separator}{takeaway_alert_sign}\"\n        else:\n            takeaway = \"\"\n        # Time label pane\n        classes_str = \" \".join(css_classes)\n        time_label = pn.pane.HTML(\n            f'<span class=\"{classes_str}\">{time}{separator}{emoji}{per_icon}{diners_n}{takeaway}</span>',\n            stylesheets=stylesheets,\n            **kwargs,\n        )\n\n        return time_label\n\n    # SIDEBAR SECTION\n    def load_sidebar_tabs(\n        self, config: DictConfig, clear_before_loading: bool = True\n    ) -> None:\n        \"\"\"Append tabs to the app template sidebar.\n\n        The flag `clear_before_loading` is set to true only during first instantiation, because the sidebar is empty at first.\n        Use the default value during normal operation to avoid tabs duplication.\n\n        Args:\n            config (DictConfig): Hydra configuration dictionary.\n            clear_before_loading (bool, optional): Set to true to remove all tabs before appending the new ones. Defaults to True.\n        \"\"\"\n        # Clean tabs\n        if clear_before_loading:\n            self.sidebar_tabs.clear()\n        # Append User tab\n        self.sidebar_tabs.append(self.sidebar_person_column)\n        # Append upload, download and stats only for non-guest\n        # Append password only for non-guest users if auth is active\n        if not auth.is_guest(\n            user=pn_user(config), config=config, allow_override=False\n        ):\n            self.sidebar_tabs.append(self.sidebar_menu_upload_col)\n            self.sidebar_tabs.append(self.sidebar_download_orders_col)\n            self.sidebar_tabs.append(self.sidebar_stats_col)\n            if auth.is_basic_auth_active(config=config):\n                self.sidebar_tabs.append(self.sidebar_password)\n\n    def build_stats_and_info_text(\n        self,\n        config: DictConfig,\n        df_stats: pd.DataFrame,\n        user: str,\n        version: str,\n        host_name: str,\n        stylesheets: list = [],\n    ) -> dict:\n        \"\"\"Build text used for statistics under the `stats` tab, and info under the `user` tab.\n\n        This functions needs Data-Lunch version and the name of the hosting machine to populate the info section.\n\n        Args:\n            config (DictConfig): Hydra configuration dictionary.\n            df_stats (pd.DataFrame): dataframe with statistics.\n            user (str): username.\n            version (str): Data-Lunch version.\n            host_name (str): host name.\n            stylesheets (list, optional): Stylesheets to assign to the resulting HTML pane\n                (see `Panel docs <https://panel.holoviz.org/how_to/styling/apply_css.html>`__). Defaults to [].\n\n        Returns:\n            dict: _description_\n        \"\"\"\n        # Stats top text\n        stats = pn.pane.HTML(\n            f\"\"\"\n            <h3>Statistics</h3>\n            <div>\n                Grumbling stomachs fed:<br>\n                <span id=\"stats-locals\">Locals&nbsp;&nbsp;{df_stats[df_stats[\"Guest\"] == \"NotAGuest\"]['Hungry People'].sum()}</span><br>\n                <span id=\"stats-guests\">Guests&nbsp;&nbsp;{df_stats[df_stats[\"Guest\"] != \"NotAGuest\"]['Hungry People'].sum()}</span><br>\n                =================<br>\n                <strong>TOTAL&nbsp;&nbsp;{df_stats['Hungry People'].sum()}</strong><br>\n                <br>\n            </div>\n            <div>\n                <i>See the table for details</i>\n            </div>\n            \"\"\",\n            stylesheets=stylesheets,\n        )\n        # Define user group\n        if auth.is_guest(user=user, config=config, allow_override=False):\n            user_group = \"guest\"\n        elif auth.is_admin(user=user, config=config):\n            user_group = \"admin\"\n        else:\n            user_group = \"user\"\n        # Other info\n        other_info = pn.pane.HTML(\n            f\"\"\"\n            <details>\n                <summary><strong>Other Info</strong></summary>\n                <div class=\"icon-container\">\n                    <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"icon icon-tabler icon-tabler-user-square\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\"/>\n                        <path d=\"M9 10a3 3 0 1 0 6 0a3 3 0 0 0 -6 0\" />\n                        <path d=\"M6 21v-1a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v1\" />\n                        <path d=\"M3 5a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v14a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2v-14z\" />\n                    </svg>\n                    <span>\n                        <strong>User:</strong> <i>{user}</i>\n                    </span>\n                </div>\n                <div class=\"icon-container\">\n                    <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"icon icon-tabler icon-tabler-users-group\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\"/>\n                        <path d=\"M10 13a2 2 0 1 0 4 0a2 2 0 0 0 -4 0\" />\n                        <path d=\"M8 21v-1a2 2 0 0 1 2 -2h4a2 2 0 0 1 2 2v1\" />\n                        <path d=\"M15 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0\" />\n                        <path d=\"M17 10h2a2 2 0 0 1 2 2v1\" />\n                        <path d=\"M5 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0\" />\n                        <path d=\"M3 13v-1a2 2 0 0 1 2 -2h2\" />\n                    </svg>\n                    <span>\n                        <strong>Group:</strong> <i>{user_group}</i>\n                    </span>\n                </div>\n                <div class=\"icon-container\">\n                    <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"icon icon-tabler icon-tabler-pizza\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\"/>\n                        <path d=\"M12 21.5c-3.04 0 -5.952 -.714 -8.5 -1.983l8.5 -16.517l8.5 16.517a19.09 19.09 0 0 1 -8.5 1.983z\" />\n                        <path d=\"M5.38 15.866a14.94 14.94 0 0 0 6.815 1.634a14.944 14.944 0 0 0 6.502 -1.479\" />\n                        <path d=\"M13 11.01v-.01\" />\n                        <path d=\"M11 14v-.01\" />\n                    </svg>\n                    <span>\n                        <strong>Data-Lunch:</strong> <i>v{version}</i>\n                    </span>\n                </div>\n                <div class=\"icon-container\">\n                    <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"icon icon-tabler icon-tabler-cpu\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n                        <path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\"></path>\n                        <path d=\"M5 5m0 1a1 1 0 0 1 1 -1h12a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-12a1 1 0 0 1 -1 -1z\"></path>\n                        <path d=\"M9 9h6v6h-6z\"></path>\n                        <path d=\"M3 10h2\"></path>\n                        <path d=\"M3 14h2\"></path>\n                        <path d=\"M10 3v2\"></path>\n                        <path d=\"M14 3v2\"></path>\n                        <path d=\"M21 10h-2\"></path>\n                        <path d=\"M21 14h-2\"></path>\n                        <path d=\"M14 21v-2\"></path>\n                        <path d=\"M10 21v-2\"></path>\n                    </svg>\n                    <span>\n                        <strong>Host:</strong> <i>{host_name}</i>\n                    </span>\n                </div>\n            </details>\n            \"\"\",\n            sizing_mode=\"stretch_width\",\n            stylesheets=stylesheets,\n        )\n\n        return {\"stats\": stats, \"info\": other_info}\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.additional_items_details","title":"additional_items_details instance-attribute","text":"
additional_items_details = HTML(\n    render(items=additional_items_to_concat),\n    width=sidebar_content_width,\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.backend_button","title":"backend_button instance-attribute","text":"
backend_button = Button(\n    name=\"\",\n    button_type=\"primary\",\n    button_style=\"solid\",\n    width=header_button_width,\n    height=generic_button_height,\n    icon=\"adjustments\",\n    icon_size=\"2em\",\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.build_menu_button","title":"build_menu_button instance-attribute","text":"
build_menu_button = Button(\n    name=\"Build Menu\",\n    button_type=\"primary\",\n    sizing_mode=\"stretch_width\",\n    icon=\"tools-kitchen-2\",\n    icon_size=\"2em\",\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.buttons_flexbox","title":"buttons_flexbox instance-attribute","text":"
buttons_flexbox = FlexBox(\n    *[\n        send_order_button,\n        toggle_no_more_order_button,\n        change_order_time_takeaway_button,\n        delete_order_button,\n    ],\n    flex_wrap=\"nowrap\",\n    min_width=main_area_min_width,\n    sizing_mode=\"stretch_width\"\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.change_order_time_takeaway_button","title":"change_order_time_takeaway_button instance-attribute","text":"
change_order_time_takeaway_button = Button(\n    name=\"Change Time/Takeaway\",\n    button_type=\"primary\",\n    button_style=\"outline\",\n    height=generic_button_height,\n    icon=\"clock-edit\",\n    icon_size=\"2em\",\n    sizing_mode=\"stretch_width\",\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.dataframe","title":"dataframe instance-attribute","text":"
dataframe = Tabulator(\n    name=\"Order\",\n    widths={note_column_name: 180},\n    selectable=False,\n    stylesheets=[custom_tabulator_path],\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.delete_order_button","title":"delete_order_button instance-attribute","text":"
delete_order_button = Button(\n    name=\"Delete Order\",\n    button_type=\"danger\",\n    height=generic_button_height,\n    icon=\"trash-filled\",\n    icon_size=\"2em\",\n    sizing_mode=\"stretch_width\",\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.download_button","title":"download_button instance-attribute","text":"
download_button = FileDownload(\n    callback=lambda: download_dataframe(self),\n    filename=file_name + \".xlsx\",\n    sizing_mode=\"stretch_width\",\n    icon=\"download\",\n    icon_size=\"2em\",\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.error_message","title":"error_message instance-attribute","text":"
error_message = HTML(\n    styles={\"color\": \"red\", \"font-weight\": \"bold\"},\n    sizing_mode=\"stretch_width\",\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.file_widget","title":"file_widget instance-attribute","text":"
file_widget = FileInput(\n    accept=\".png,.jpg,.jpeg,.xlsx\",\n    sizing_mode=\"stretch_width\",\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.guest_override_alert","title":"guest_override_alert instance-attribute","text":"
guest_override_alert = HTML(\n    '\\n            <div class=\"guest-override-flag\">\\n                <div class=\"icon-container\">\\n                    <svg class=\"flashing-animation\" xmlns=\"http://www.w3.org/2000/svg\" class=\"icon icon-tabler icon-tabler-radioactive-filled\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\"/>\\n                        <path d=\"M21 11a1 1 0 0 1 1 1a10 10 0 0 1 -5 8.656a1 1 0 0 1 -1.302 -.268l-.064 -.098l-3 -5.19a.995 .995 0 0 1 -.133 -.542l.01 -.11l.023 -.106l.034 -.106l.046 -.1l.056 -.094l.067 -.089a.994 .994 0 0 1 .165 -.155l.098 -.064a2 2 0 0 0 .993 -1.57l.007 -.163a1 1 0 0 1 .883 -.994l.117 -.007h6z\" stroke-width=\"0\" fill=\"currentColor\" />\\n                        <path d=\"M7 3.344a10 10 0 0 1 10 0a1 1 0 0 1 .418 1.262l-.052 .104l-3 5.19l-.064 .098a.994 .994 0 0 1 -.155 .165l-.089 .067a1 1 0 0 1 -.195 .102l-.105 .034l-.107 .022a1.003 1.003 0 0 1 -.547 -.07l-.104 -.052a2 2 0 0 0 -1.842 -.082l-.158 .082a1 1 0 0 1 -1.302 -.268l-.064 -.098l-3 -5.19a1 1 0 0 1 .366 -1.366z\" stroke-width=\"0\" fill=\"currentColor\" />\\n                        <path d=\"M9 11a1 1 0 0 1 .993 .884l.007 .117a2 2 0 0 0 .861 1.645l.237 .152a.994 .994 0 0 1 .165 .155l.067 .089l.056 .095l.045 .099c.014 .036 .026 .07 .035 .106l.022 .107l.011 .11a.994 .994 0 0 1 -.08 .437l-.053 .104l-3 5.19a1 1 0 0 1 -1.366 .366a10 10 0 0 1 -5 -8.656a1 1 0 0 1 .883 -.993l.117 -.007h6z\" stroke-width=\"0\" fill=\"currentColor\" />\\n                    </svg>\\n                    <span><strong>Watch out! You are a guest now...</strong></span>\\n                </div>\\n                <div>\\n                    Guest override is active.\\n                </div>\\n            </div>\\n            ',\n    margin=5,\n    sizing_mode=\"stretch_width\",\n    stylesheets=[guest_override_path],\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.guest_password_widget","title":"guest_password_widget instance-attribute","text":"
guest_password_widget = PasswordInput(\n    name=\"Password\",\n    placeholder=\"If empty reload this page.\",\n    value=guest_password,\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.guest_username_widget","title":"guest_username_widget instance-attribute","text":"
guest_username_widget = TextInput(\n    name=\"Username\",\n    placeholder=\"If empty reload this page.\",\n    value=\"guest\",\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.header_object","title":"header_object instance-attribute","text":"
header_object = instantiate(header_object)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.header_row","title":"header_row instance-attribute","text":"
header_row = Row(\n    height=header_row_height, sizing_mode=\"stretch_width\"\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.logout_button","title":"logout_button instance-attribute","text":"
logout_button = Button(\n    name=\"\",\n    button_type=\"primary\",\n    button_style=\"solid\",\n    width=header_button_width,\n    height=generic_button_height,\n    icon=\"door-exit\",\n    icon_size=\"2em\",\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.main_header_row","title":"main_header_row instance-attribute","text":"
main_header_row = Row('# Menu', HSpacer(), refresh_button)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.menu_flexbox","title":"menu_flexbox instance-attribute","text":"
menu_flexbox = FlexBox(\n    *[\n        dataframe,\n        Spacer(width=time_col_spacer_width),\n        time_col,\n    ],\n    min_width=main_area_min_width\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.no_menu_col","title":"no_menu_col instance-attribute","text":"
no_menu_col = Column(\n    no_menu_image,\n    no_menu_image_attribution,\n    sizing_mode=\"stretch_width\",\n    min_width=main_area_min_width,\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.no_menu_image","title":"no_menu_image instance-attribute","text":"
no_menu_image = JPG(no_menu_image_path, alt_text='no menu')\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.no_menu_image_attribution","title":"no_menu_image_attribution instance-attribute","text":"
no_menu_image_attribution = HTML(\n    '\\n            <i>\\n                Image by\\n                <a\\n                    href=\"https://www.freepik.com/free-vector/tiny-cooks-making-spaghetti-dinner-isolated-flat-illustration_11235909.htm\"\\n                    referrerpolicy=\"no-referrer\"\\n                    rel=\"external\"\\n                    target=\"_blank\"\\n                >\\n                    pch.vector\\n                </a>\\n                on Freepik\\n            </i>\\n            ',\n    align=\"end\",\n    styles={\n        \"color\": \"darkgray\",\n        \"font-size\": \"10px\",\n        \"font-weight\": \"light\",\n    },\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.no_more_order_alert","title":"no_more_order_alert instance-attribute","text":"
no_more_order_alert = HTML(\n    '\\n            <div class=\"no-more-order-flag\">\\n                <div class=\"icon-container\">\\n                    <svg class=\"flashing-animation\" xmlns=\"http://www.w3.org/2000/svg\" class=\"icon icon-tabler icon-tabler-alert-circle-filled\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\\n                        <path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\"></path>\\n                        <path d=\"M12 2c5.523 0 10 4.477 10 10a10 10 0 0 1 -19.995 .324l-.005 -.324l.004 -.28c.148 -5.393 4.566 -9.72 9.996 -9.72zm.01 13l-.127 .007a1 1 0 0 0 0 1.986l.117 .007l.127 -.007a1 1 0 0 0 0 -1.986l-.117 -.007zm-.01 -8a1 1 0 0 0 -.993 .883l-.007 .117v4l.007 .117a1 1 0 0 0 1.986 0l.007 -.117v-4l-.007 -.117a1 1 0 0 0 -.993 -.883z\" stroke-width=\"0\" fill=\"currentColor\"></path>\\n                    </svg>\\n                    <span><strong>Oh no! You missed this train...</strong></span>\\n                </div>\\n                <div>\\n                    Orders are closed, better luck next time.\\n                </div>\\n            </div>\\n            ',\n    margin=5,\n    sizing_mode=\"stretch_width\",\n    stylesheets=[no_more_orders_path],\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.password_widget","title":"password_widget instance-attribute","text":"
password_widget = Param(\n    param,\n    widgets={\n        \"old_password\": PasswordInput(\n            name=\"Old password\", placeholder=\"Old Password\"\n        ),\n        \"new_password\": PasswordInput(\n            name=\"New password\", placeholder=\"New Password\"\n        ),\n        \"repeat_new_password\": PasswordInput(\n            name=\"Repeat new password\",\n            placeholder=\"Repeat New Password\",\n        ),\n    },\n    name=\"Change password\",\n    width=sidebar_content_width,\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.person_widget","title":"person_widget instance-attribute","text":"
person_widget = Param(\n    param,\n    widgets={\n        \"guest\": RadioButtonGroup(\n            options=to_container(guest_types, resolve=True),\n            button_type=\"primary\",\n            button_style=\"outline\",\n        ),\n        \"username\": TextInput(\n            value=username,\n            value_input=username,\n            description=doc,\n        ),\n    },\n    width=sidebar_content_width,\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.quote","title":"quote instance-attribute","text":"
quote = Markdown(f'\n            _{iloc[0]}_\n\n            **{iloc[0]}**\n            ')\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.refresh_button","title":"refresh_button instance-attribute","text":"
refresh_button = Button(\n    name=\"\",\n    button_style=\"outline\",\n    button_type=\"light\",\n    width=45,\n    height=generic_button_height,\n    icon=\"reload\",\n    icon_size=\"2em\",\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.reload_on_guest_override","title":"reload_on_guest_override instance-attribute","text":"
reload_on_guest_override = reload_on_guest_override_callback\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.reload_on_no_more_order","title":"reload_on_no_more_order instance-attribute","text":"
reload_on_no_more_order = reload_on_no_more_order_callback\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.res_col","title":"res_col instance-attribute","text":"
res_col = Column(\n    sizing_mode=\"stretch_width\",\n    min_width=main_area_min_width,\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.results_divider","title":"results_divider instance-attribute","text":"
results_divider = Divider(\n    sizing_mode=\"stretch_width\",\n    min_width=main_area_min_width,\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.send_order_button","title":"send_order_button instance-attribute","text":"
send_order_button = Button(\n    name=\"Send Order\",\n    button_type=\"success\",\n    height=generic_button_height,\n    icon=\"circle-check-filled\",\n    icon_size=\"2em\",\n    sizing_mode=\"stretch_width\",\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.sidebar_download_orders_col","title":"sidebar_download_orders_col instance-attribute","text":"
sidebar_download_orders_col = Column(\n    download_text,\n    download_button,\n    name=\"Download Orders\",\n    width=sidebar_content_width,\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.sidebar_menu_upload_col","title":"sidebar_menu_upload_col instance-attribute","text":"
sidebar_menu_upload_col = Column(\n    upload_text,\n    file_widget,\n    build_menu_button,\n    name=\"Menu Upload\",\n    width=sidebar_content_width,\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.sidebar_password","title":"sidebar_password instance-attribute","text":"
sidebar_password = Column(\n    psw_text,\n    password_widget,\n    submit_password_button,\n    Spacer(height=5),\n    Divider(),\n    guest_user_text,\n    guest_username_widget,\n    guest_password_widget,\n    name=\"Password\",\n    width=sidebar_content_width,\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.sidebar_person_column","title":"sidebar_person_column instance-attribute","text":"
sidebar_person_column = Column(\n    person_text,\n    person_widget,\n    Spacer(height=5),\n    additional_items_details,\n    name=\"User\",\n    width=sidebar_content_width,\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.sidebar_stats_col","title":"sidebar_stats_col instance-attribute","text":"
sidebar_stats_col = Column(\n    name=\"Stats\", width=sidebar_content_width\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.sidebar_tabs","title":"sidebar_tabs instance-attribute","text":"
sidebar_tabs = Tabs(width=sidebar_content_width)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.stats_widget","title":"stats_widget instance-attribute","text":"
stats_widget = Tabulator(\n    name=\"Statistics\",\n    hidden_columns=[\"index\"],\n    width=sidebar_content_width - 20,\n    layout=\"fit_columns\",\n    stylesheets=[\n        custom_tabulator_path,\n        stats_tabulator_path,\n    ],\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.submit_password_button","title":"submit_password_button instance-attribute","text":"
submit_password_button = Button(\n    name=\"Submit\",\n    button_type=\"success\",\n    button_style=\"outline\",\n    height=generic_button_height,\n    icon=\"key\",\n    icon_size=\"2em\",\n    sizing_mode=\"stretch_width\",\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.takeaway_alert_sign","title":"takeaway_alert_sign instance-attribute","text":"
takeaway_alert_sign = f\"<span {takeaway_alert_icon_options}>{takeaway_svg_icon}</span>\"\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.takeaway_alert_text","title":"takeaway_alert_text instance-attribute","text":"
takeaway_alert_text = f\"<span {takeaway_alert_text_options}>{takeaway_id}</span> \"\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.time_col","title":"time_col instance-attribute","text":"
time_col = Column(width=time_col_width)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.time_col_title","title":"time_col_title instance-attribute","text":"
time_col_title = Markdown(\n    time_column_text,\n    sizing_mode=\"stretch_width\",\n    styles={\"text-align\": \"center\"},\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.toggle_guest_override_button","title":"toggle_guest_override_button instance-attribute","text":"
toggle_guest_override_button = Toggle(\n    button_type=\"primary\",\n    button_style=\"solid\",\n    width=header_button_width,\n    height=generic_button_height,\n    icon=\"user-bolt\",\n    icon_size=\"2em\",\n    stylesheets=[guest_override_path],\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.toggle_no_more_order_button","title":"toggle_no_more_order_button instance-attribute","text":"
toggle_no_more_order_button = Toggle(\n    name=\"Stop Orders\",\n    button_style=\"outline\",\n    button_type=\"warning\",\n    height=generic_button_height,\n    icon=\"hand-stop\",\n    icon_size=\"2em\",\n    sizing_mode=\"stretch_width\",\n)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.__init__","title":"__init__","text":"
__init__(\n    config: DictConfig,\n    waiter: Waiter,\n    app: Template,\n    person: Person,\n    guest_password: str = \"\",\n)\n
Source code in dlunch/gui.py
def __init__(\n    self,\n    config: DictConfig,\n    waiter: core.Waiter,\n    app: pn.Template,\n    person: Person,\n    guest_password: str = \"\",\n):\n    # HEADER SECTION ------------------------------------------------------\n    # WIDGET\n    # Create PNG pane with app icon\n    self.header_object = instantiate(config.panel.gui.header_object)\n\n    # BUTTONS\n    # Backend button\n    self.backend_button = pnw.Button(\n        name=\"\",\n        button_type=\"primary\",\n        button_style=\"solid\",\n        width=header_button_width,\n        height=generic_button_height,\n        icon=\"adjustments\",\n        icon_size=\"2em\",\n    )\n    # Guest override toggle button (if pressed the user act as a guest)\n    self.toggle_guest_override_button = pnw.Toggle(\n        button_type=\"primary\",\n        button_style=\"solid\",\n        width=header_button_width,\n        height=generic_button_height,\n        icon=\"user-bolt\",\n        icon_size=\"2em\",\n        stylesheets=[config.panel.gui.css_files.guest_override_path],\n    )\n    # Logout button\n    self.logout_button = pnw.Button(\n        name=\"\",\n        button_type=\"primary\",\n        button_style=\"solid\",\n        width=header_button_width,\n        height=generic_button_height,\n        icon=\"door-exit\",\n        icon_size=\"2em\",\n    )\n\n    # ROW\n    # Create column for person data (add logout button only if auth is active)\n    self.header_row = pn.Row(\n        height=header_row_height,\n        sizing_mode=\"stretch_width\",\n    )\n    # Append a graphic element to the left side of header\n    if config.panel.gui.header_object:\n        self.header_row.append(self.header_object)\n    # Append a controls to the right side of header\n    if auth.is_auth_active(config=config):\n        self.header_row.append(pn.HSpacer())\n        # Backend only for admin\n        if auth.is_admin(user=pn_user(config), config=config):\n            self.header_row.append(self.backend_button)\n        # Guest override only for non guests\n        if not auth.is_guest(\n            user=pn_user(config), config=config, allow_override=False\n        ):\n            self.header_row.append(self.toggle_guest_override_button)\n        self.header_row.append(self.logout_button)\n        self.header_row.append(\n            pn.pane.HTML(\n                styles=dict(background=\"white\"), width=2, height=45\n            )\n        )\n\n    # CALLBACKS\n    # Backend callback\n    self.backend_button.on_click(lambda e: auth.open_backend())\n\n    # Guest override callback\n    @pn.depends(self.toggle_guest_override_button, watch=True)\n    def reload_on_guest_override_callback(\n        toggle: pnw.ToggleIcon, reload: bool = True\n    ):\n        # Update global variable that control guest override\n        # Only non guest can store this value in 'flags' table (guest users\n        # are always guests, there is no use in sotring a flag for them)\n        if not auth.is_guest(\n            user=pn_user(config), config=config, allow_override=False\n        ):\n            models.set_flag(\n                config=config,\n                id=f\"{pn_user(config)}_guest_override\",\n                value=toggle,\n            )\n        # Show banner if override is active\n        self.guest_override_alert.visible = toggle\n        # Simply reload the menu when the toggle button value changes\n        if reload:\n            waiter.reload_menu(\n                None,\n                self,\n            )\n\n    # Add callback to attribute\n    self.reload_on_guest_override = reload_on_guest_override_callback\n\n    # Logout callback\n    self.logout_button.on_click(lambda e: auth.force_logout())\n\n    # MAIN SECTION --------------------------------------------------------\n    # Elements required for build the main section of the web app\n\n    # TEXTS\n    # Quote of the day\n    self.quote = pn.pane.Markdown(\n        f\"\"\"\n        _{df_quote.quote.iloc[0]}_\n\n        **{df_quote.author.iloc[0]}**\n        \"\"\"\n    )\n    # Time column title\n    self.time_col_title = pn.pane.Markdown(\n        config.panel.time_column_text,\n        sizing_mode=\"stretch_width\",\n        styles={\"text-align\": \"center\"},\n    )\n    # \"no more order\" message\n    self.no_more_order_alert = pn.pane.HTML(\n        \"\"\"\n        <div class=\"no-more-order-flag\">\n            <div class=\"icon-container\">\n                <svg class=\"flashing-animation\" xmlns=\"http://www.w3.org/2000/svg\" class=\"icon icon-tabler icon-tabler-alert-circle-filled\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n                    <path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\"></path>\n                    <path d=\"M12 2c5.523 0 10 4.477 10 10a10 10 0 0 1 -19.995 .324l-.005 -.324l.004 -.28c.148 -5.393 4.566 -9.72 9.996 -9.72zm.01 13l-.127 .007a1 1 0 0 0 0 1.986l.117 .007l.127 -.007a1 1 0 0 0 0 -1.986l-.117 -.007zm-.01 -8a1 1 0 0 0 -.993 .883l-.007 .117v4l.007 .117a1 1 0 0 0 1.986 0l.007 -.117v-4l-.007 -.117a1 1 0 0 0 -.993 -.883z\" stroke-width=\"0\" fill=\"currentColor\"></path>\n                </svg>\n                <span><strong>Oh no! You missed this train...</strong></span>\n            </div>\n            <div>\n                Orders are closed, better luck next time.\n            </div>\n        </div>\n        \"\"\",\n        margin=5,\n        sizing_mode=\"stretch_width\",\n        stylesheets=[config.panel.gui.css_files.no_more_orders_path],\n    )\n    # Alert for guest override\n    self.guest_override_alert = pn.pane.HTML(\n        \"\"\"\n        <div class=\"guest-override-flag\">\n            <div class=\"icon-container\">\n                <svg class=\"flashing-animation\" xmlns=\"http://www.w3.org/2000/svg\" class=\"icon icon-tabler icon-tabler-radioactive-filled\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\"/>\n                    <path d=\"M21 11a1 1 0 0 1 1 1a10 10 0 0 1 -5 8.656a1 1 0 0 1 -1.302 -.268l-.064 -.098l-3 -5.19a.995 .995 0 0 1 -.133 -.542l.01 -.11l.023 -.106l.034 -.106l.046 -.1l.056 -.094l.067 -.089a.994 .994 0 0 1 .165 -.155l.098 -.064a2 2 0 0 0 .993 -1.57l.007 -.163a1 1 0 0 1 .883 -.994l.117 -.007h6z\" stroke-width=\"0\" fill=\"currentColor\" />\n                    <path d=\"M7 3.344a10 10 0 0 1 10 0a1 1 0 0 1 .418 1.262l-.052 .104l-3 5.19l-.064 .098a.994 .994 0 0 1 -.155 .165l-.089 .067a1 1 0 0 1 -.195 .102l-.105 .034l-.107 .022a1.003 1.003 0 0 1 -.547 -.07l-.104 -.052a2 2 0 0 0 -1.842 -.082l-.158 .082a1 1 0 0 1 -1.302 -.268l-.064 -.098l-3 -5.19a1 1 0 0 1 .366 -1.366z\" stroke-width=\"0\" fill=\"currentColor\" />\n                    <path d=\"M9 11a1 1 0 0 1 .993 .884l.007 .117a2 2 0 0 0 .861 1.645l.237 .152a.994 .994 0 0 1 .165 .155l.067 .089l.056 .095l.045 .099c.014 .036 .026 .07 .035 .106l.022 .107l.011 .11a.994 .994 0 0 1 -.08 .437l-.053 .104l-3 5.19a1 1 0 0 1 -1.366 .366a10 10 0 0 1 -5 -8.656a1 1 0 0 1 .883 -.993l.117 -.007h6z\" stroke-width=\"0\" fill=\"currentColor\" />\n                </svg>\n                <span><strong>Watch out! You are a guest now...</strong></span>\n            </div>\n            <div>\n                Guest override is active.\n            </div>\n        </div>\n        \"\"\",\n        margin=5,\n        sizing_mode=\"stretch_width\",\n        stylesheets=[config.panel.gui.css_files.guest_override_path],\n    )\n    # Takeaway alert\n    self.takeaway_alert_sign = f\"<span {config.panel.gui.takeaway_alert_icon_options}>{config.panel.gui.takeaway_svg_icon}</span>\"\n    self.takeaway_alert_text = f\"<span {config.panel.gui.takeaway_alert_text_options}>{config.panel.gui.takeaway_id}</span> \"\n    # No menu image attribution\n    self.no_menu_image_attribution = pn.pane.HTML(\n        \"\"\"\n        <i>\n            Image by\n            <a\n                href=\"https://www.freepik.com/free-vector/tiny-cooks-making-spaghetti-dinner-isolated-flat-illustration_11235909.htm\"\n                referrerpolicy=\"no-referrer\"\n                rel=\"external\"\n                target=\"_blank\"\n            >\n                pch.vector\n            </a>\n            on Freepik\n        </i>\n        \"\"\",\n        align=\"end\",\n        styles={\n            \"color\": \"darkgray\",\n            \"font-size\": \"10px\",\n            \"font-weight\": \"light\",\n        },\n    )\n\n    # WIDGETS\n    # JPG shown when no menu is available\n    self.no_menu_image = pn.pane.JPG(\n        config.panel.gui.no_menu_image_path, alt_text=\"no menu\"\n    )\n    # Create dataframe instance\n    self.dataframe = pnw.Tabulator(\n        name=\"Order\",\n        widths={config.panel.gui.note_column_name: 180},\n        selectable=False,\n        stylesheets=[config.panel.gui.css_files.custom_tabulator_path],\n    )\n\n    # BUTTONS\n    # Create refresh button\n    self.refresh_button = pnw.Button(\n        name=\"\",\n        button_style=\"outline\",\n        button_type=\"light\",\n        width=45,\n        height=generic_button_height,\n        icon=\"reload\",\n        icon_size=\"2em\",\n    )\n    # Create send button\n    self.send_order_button = pnw.Button(\n        name=\"Send Order\",\n        button_type=\"success\",\n        height=generic_button_height,\n        icon=\"circle-check-filled\",\n        icon_size=\"2em\",\n        sizing_mode=\"stretch_width\",\n    )\n    # Create toggle button that stop orders (used in time column)\n    # Initialized to False, but checked on app creation\n    self.toggle_no_more_order_button = pnw.Toggle(\n        name=\"Stop Orders\",\n        button_style=\"outline\",\n        button_type=\"warning\",\n        height=generic_button_height,\n        icon=\"hand-stop\",\n        icon_size=\"2em\",\n        sizing_mode=\"stretch_width\",\n    )\n    # Create change time\n    self.change_order_time_takeaway_button = pnw.Button(\n        name=\"Change Time/Takeaway\",\n        button_type=\"primary\",\n        button_style=\"outline\",\n        height=generic_button_height,\n        icon=\"clock-edit\",\n        icon_size=\"2em\",\n        sizing_mode=\"stretch_width\",\n    )\n    # Create delete order\n    self.delete_order_button = pnw.Button(\n        name=\"Delete Order\",\n        button_type=\"danger\",\n        height=generic_button_height,\n        icon=\"trash-filled\",\n        icon_size=\"2em\",\n        sizing_mode=\"stretch_width\",\n    )\n\n    # ROWS\n    self.main_header_row = pn.Row(\n        \"# Menu\",\n        pn.layout.HSpacer(),\n        self.refresh_button,\n    )\n\n    # COLUMNS\n    # Create column shown when no menu is available\n    self.no_menu_col = pn.Column(\n        self.no_menu_image,\n        self.no_menu_image_attribution,\n        sizing_mode=\"stretch_width\",\n        min_width=main_area_min_width,\n    )\n    # Create column for lunch time labels\n    self.time_col = pn.Column(width=time_col_width)\n    # Create column for resulting menus\n    self.res_col = pn.Column(\n        sizing_mode=\"stretch_width\", min_width=main_area_min_width\n    )\n\n    # FLEXBOXES\n    self.menu_flexbox = pn.FlexBox(\n        *[\n            self.dataframe,\n            pn.Spacer(width=time_col_spacer_width),\n            self.time_col,\n        ],\n        min_width=main_area_min_width,\n    )\n    self.buttons_flexbox = pn.FlexBox(\n        *[\n            self.send_order_button,\n            self.toggle_no_more_order_button,\n            self.change_order_time_takeaway_button,\n            self.delete_order_button,\n        ],\n        flex_wrap=\"nowrap\",\n        min_width=main_area_min_width,\n        sizing_mode=\"stretch_width\",\n    )\n    self.results_divider = pn.layout.Divider(\n        sizing_mode=\"stretch_width\", min_width=main_area_min_width\n    )\n\n    # CALLBACKS\n    # Callback on every \"toggle\" action\n    @pn.depends(self.toggle_no_more_order_button, watch=True)\n    def reload_on_no_more_order_callback(\n        toggle: pnw.Toggle, reload: bool = True\n    ):\n        # Update global variable\n        models.set_flag(config=config, id=\"no_more_orders\", value=toggle)\n\n        # Show \"no more order\" text\n        self.no_more_order_alert.visible = toggle\n\n        # Deactivate send, delete and change order buttons\n        self.send_order_button.disabled = toggle\n        self.delete_order_button.disabled = toggle\n        self.change_order_time_takeaway_button.disabled = toggle\n\n        # Simply reload the menu when the toggle button value changes\n        if reload:\n            waiter.reload_menu(\n                None,\n                self,\n            )\n\n    # Add callback to attribute\n    self.reload_on_no_more_order = reload_on_no_more_order_callback\n\n    # Refresh button callback\n    self.refresh_button.on_click(\n        lambda e: waiter.reload_menu(\n            e,\n            self,\n        )\n    )\n    # Send order button callback\n    self.send_order_button.on_click(\n        lambda e: waiter.send_order(\n            e,\n            app,\n            person,\n            self,\n        )\n    )\n    # Delete order button callback\n    self.delete_order_button.on_click(\n        lambda e: waiter.delete_order(\n            e,\n            app,\n            self,\n        )\n    )\n    # Change order time button callback\n    self.change_order_time_takeaway_button.on_click(\n        lambda e: waiter.change_order_time_takeaway(\n            e,\n            person,\n            self,\n        )\n    )\n\n    # MODAL WINDOW --------------------------------------------------------\n    # Error message\n    self.error_message = pn.pane.HTML(\n        styles={\"color\": \"red\", \"font-weight\": \"bold\"},\n        sizing_mode=\"stretch_width\",\n    )\n    self.error_message.visible = False\n\n    # SIDEBAR -------------------------------------------------------------\n    # TEXTS\n    # Foldable additional item details dropdown menu\n    jinja_template = jinja2.Environment(\n        loader=jinja2.BaseLoader\n    ).from_string(config.panel.gui.additional_item_details_template)\n    self.additional_items_details = pn.pane.HTML(\n        jinja_template.render(\n            items=config.panel.additional_items_to_concat\n        ),\n        width=sidebar_content_width,\n    )\n\n    # WIDGET\n    # Person data\n    self.person_widget = pn.Param(\n        person.param,\n        widgets={\n            \"guest\": pnw.RadioButtonGroup(\n                options=OmegaConf.to_container(\n                    config.panel.guest_types, resolve=True\n                ),\n                button_type=\"primary\",\n                button_style=\"outline\",\n            ),\n            \"username\": pnw.TextInput(\n                value=person.username,\n                value_input=person.username,\n                description=person.param.username.doc,\n            ),\n        },\n        width=sidebar_content_width,\n    )\n    # File upload\n    self.file_widget = pnw.FileInput(\n        accept=\".png,.jpg,.jpeg,.xlsx\", sizing_mode=\"stretch_width\"\n    )\n    # Stats table\n    # Create stats table (non-editable)\n    self.stats_widget = pnw.Tabulator(\n        name=\"Statistics\",\n        hidden_columns=[\"index\"],\n        width=sidebar_content_width - 20,\n        layout=\"fit_columns\",\n        stylesheets=[\n            config.panel.gui.css_files.custom_tabulator_path,\n            config.panel.gui.css_files.stats_tabulator_path,\n        ],\n    )\n    # Password renewer\n    self.password_widget = pn.Param(\n        PasswordRenewer().param,\n        widgets={\n            \"old_password\": pnw.PasswordInput(\n                name=\"Old password\", placeholder=\"Old Password\"\n            ),\n            \"new_password\": pnw.PasswordInput(\n                name=\"New password\", placeholder=\"New Password\"\n            ),\n            \"repeat_new_password\": pnw.PasswordInput(\n                name=\"Repeat new password\",\n                placeholder=\"Repeat New Password\",\n            ),\n        },\n        name=\"Change password\",\n        width=sidebar_content_width,\n    )\n    # Guest password text\n    self.guest_username_widget = pnw.TextInput(\n        name=\"Username\",\n        placeholder=\"If empty reload this page.\",\n        value=\"guest\",\n    )\n    self.guest_password_widget = pnw.PasswordInput(\n        name=\"Password\",\n        placeholder=\"If empty reload this page.\",\n        value=guest_password,\n    )\n    # Turn off guest user if no password is set (empty string)\n    if not guest_password:\n        self.guest_username_widget.value = \"\"\n        self.guest_username_widget.disabled = True\n        self.guest_username_widget.placeholder = \"NOT ACTIVE\"\n        self.guest_password_widget.value = \"\"\n        self.guest_password_widget.disabled = True\n        self.guest_password_widget.placeholder = \"NOT ACTIVE\"\n\n    # BUTTONS\n    # Create menu button\n    self.build_menu_button = pnw.Button(\n        name=\"Build Menu\",\n        button_type=\"primary\",\n        sizing_mode=\"stretch_width\",\n        icon=\"tools-kitchen-2\",\n        icon_size=\"2em\",\n    )\n    # Download button and callback\n    self.download_button = pn.widgets.FileDownload(\n        callback=lambda: waiter.download_dataframe(self),\n        filename=config.panel.file_name + \".xlsx\",\n        sizing_mode=\"stretch_width\",\n        icon=\"download\",\n        icon_size=\"2em\",\n    )\n    # Password button\n    self.submit_password_button = pnw.Button(\n        name=\"Submit\",\n        button_type=\"success\",\n        button_style=\"outline\",\n        height=generic_button_height,\n        icon=\"key\",\n        icon_size=\"2em\",\n        sizing_mode=\"stretch_width\",\n    )\n\n    # COLUMNS\n    # Create column for person data\n    self.sidebar_person_column = pn.Column(\n        person_text,\n        self.person_widget,\n        pn.Spacer(height=5),\n        self.additional_items_details,\n        name=\"User\",\n        width=sidebar_content_width,\n    )\n    # Leave an empty widget for the 'other info' section\n    self.sidebar_person_column.append(\n        pn.pane.HTML(),\n    )\n\n    # Create column for uploading image/Excel with the menu\n    self.sidebar_menu_upload_col = pn.Column(\n        upload_text,\n        self.file_widget,\n        self.build_menu_button,\n        name=\"Menu Upload\",\n        width=sidebar_content_width,\n    )\n    # Create column for downloading Excel with orders\n    self.sidebar_download_orders_col = pn.Column(\n        download_text,\n        self.download_button,\n        name=\"Download Orders\",\n        width=sidebar_content_width,\n    )\n    # Create column for statistics\n    self.sidebar_stats_col = pn.Column(\n        name=\"Stats\", width=sidebar_content_width\n    )\n\n    self.sidebar_password = pn.Column(\n        config.panel.gui.psw_text,\n        self.password_widget,\n        self.submit_password_button,\n        pn.Spacer(height=5),\n        pn.layout.Divider(),\n        guest_user_text,\n        self.guest_username_widget,\n        self.guest_password_widget,\n        name=\"Password\",\n        width=sidebar_content_width,\n    )\n\n    # TABS\n    # The person widget is defined in the app factory function because\n    # lunch times are configurable\n    self.sidebar_tabs = pn.Tabs(\n        width=sidebar_content_width,\n    )\n    # Reload tabs according to auth.is_guest results and guest_override\n    # flag (no need to cleans, tabs are already empty)\n    self.load_sidebar_tabs(config=config, clear_before_loading=False)\n\n    # CALLBACKS\n    # Build menu button callback\n    self.build_menu_button.on_click(\n        lambda e: waiter.build_menu(\n            e,\n            app,\n            self,\n        )\n    )\n    # Submit password button callback\n    self.submit_password_button.on_click(\n        lambda e: auth.submit_password(gi=self, config=config)\n    )\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.build_order_table","title":"build_order_table","text":"
build_order_table(\n    config: DictConfig,\n    df: DataFrame,\n    time: str,\n    guests_lists: dict = {},\n) -> Tabulator\n

Build Tabulator object to display placed orders.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required df DataFrame

Table with orders. It has columns for each user that placed an order, total and a note columns.

required time str

Lunch time.

required guests_lists dict

Dictionary with lists of users dived by guest type. Keys of the dictionary are the type of guest listed. Defaults to empty dictionary ({}).

{}

Returns:

Type Description Tabulator

Panel Tabulator object representing placed orders.

Source code in dlunch/gui.py
def build_order_table(\n    self,\n    config: DictConfig,\n    df: pd.DataFrame,\n    time: str,\n    guests_lists: dict = {},\n) -> pnw.Tabulator:\n    \"\"\"Build `Tabulator` object to display placed orders.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n        df (pd.DataFrame): Table with orders. It has columns for each user that placed an order, total and a note columns.\n        time (str): Lunch time.\n        guests_lists (dict, optional): Dictionary with lists of users dived by guest type.\n            Keys of the dictionary are the type of guest listed.\n            Defaults to empty dictionary (`{}`).\n\n    Returns:\n        pnw.Tabulator: Panel `Tabulator` object representing placed orders.\n    \"\"\"\n    # Add guest icon to users' id\n    columns_with_guests_icons = df.columns.to_series()\n    for guest_type, guests_list in guests_lists.items():\n        columns_with_guests_icons[\n            columns_with_guests_icons.isin(guests_list)\n        ] += f\" {config.panel.gui.guest_icons[guest_type]}\"\n    df.columns = columns_with_guests_icons.to_list()\n    # Create table widget\n    orders_table_widget = pnw.Tabulator(\n        name=time,\n        value=df,\n        frozen_columns=[0],\n        layout=\"fit_data_table\",\n        stylesheets=[config.panel.gui.css_files.custom_tabulator_path],\n    )\n    # Make the table non-editable\n    orders_table_widget.editors = {c: None for c in df.columns}\n    return orders_table_widget\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.build_stats_and_info_text","title":"build_stats_and_info_text","text":"
build_stats_and_info_text(\n    config: DictConfig,\n    df_stats: DataFrame,\n    user: str,\n    version: str,\n    host_name: str,\n    stylesheets: list = [],\n) -> dict\n

Build text used for statistics under the stats tab, and info under the user tab.

This functions needs Data-Lunch version and the name of the hosting machine to populate the info section.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required df_stats DataFrame

dataframe with statistics.

required user str

username.

required version str

Data-Lunch version.

required host_name str

host name.

required stylesheets list

Stylesheets to assign to the resulting HTML pane (see Panel docs <https://panel.holoviz.org/how_to/styling/apply_css.html>__). Defaults to [].

[]

Returns:

Type Description dict

description

Source code in dlunch/gui.py
def build_stats_and_info_text(\n    self,\n    config: DictConfig,\n    df_stats: pd.DataFrame,\n    user: str,\n    version: str,\n    host_name: str,\n    stylesheets: list = [],\n) -> dict:\n    \"\"\"Build text used for statistics under the `stats` tab, and info under the `user` tab.\n\n    This functions needs Data-Lunch version and the name of the hosting machine to populate the info section.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n        df_stats (pd.DataFrame): dataframe with statistics.\n        user (str): username.\n        version (str): Data-Lunch version.\n        host_name (str): host name.\n        stylesheets (list, optional): Stylesheets to assign to the resulting HTML pane\n            (see `Panel docs <https://panel.holoviz.org/how_to/styling/apply_css.html>`__). Defaults to [].\n\n    Returns:\n        dict: _description_\n    \"\"\"\n    # Stats top text\n    stats = pn.pane.HTML(\n        f\"\"\"\n        <h3>Statistics</h3>\n        <div>\n            Grumbling stomachs fed:<br>\n            <span id=\"stats-locals\">Locals&nbsp;&nbsp;{df_stats[df_stats[\"Guest\"] == \"NotAGuest\"]['Hungry People'].sum()}</span><br>\n            <span id=\"stats-guests\">Guests&nbsp;&nbsp;{df_stats[df_stats[\"Guest\"] != \"NotAGuest\"]['Hungry People'].sum()}</span><br>\n            =================<br>\n            <strong>TOTAL&nbsp;&nbsp;{df_stats['Hungry People'].sum()}</strong><br>\n            <br>\n        </div>\n        <div>\n            <i>See the table for details</i>\n        </div>\n        \"\"\",\n        stylesheets=stylesheets,\n    )\n    # Define user group\n    if auth.is_guest(user=user, config=config, allow_override=False):\n        user_group = \"guest\"\n    elif auth.is_admin(user=user, config=config):\n        user_group = \"admin\"\n    else:\n        user_group = \"user\"\n    # Other info\n    other_info = pn.pane.HTML(\n        f\"\"\"\n        <details>\n            <summary><strong>Other Info</strong></summary>\n            <div class=\"icon-container\">\n                <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"icon icon-tabler icon-tabler-user-square\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\"/>\n                    <path d=\"M9 10a3 3 0 1 0 6 0a3 3 0 0 0 -6 0\" />\n                    <path d=\"M6 21v-1a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v1\" />\n                    <path d=\"M3 5a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v14a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2v-14z\" />\n                </svg>\n                <span>\n                    <strong>User:</strong> <i>{user}</i>\n                </span>\n            </div>\n            <div class=\"icon-container\">\n                <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"icon icon-tabler icon-tabler-users-group\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\"/>\n                    <path d=\"M10 13a2 2 0 1 0 4 0a2 2 0 0 0 -4 0\" />\n                    <path d=\"M8 21v-1a2 2 0 0 1 2 -2h4a2 2 0 0 1 2 2v1\" />\n                    <path d=\"M15 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0\" />\n                    <path d=\"M17 10h2a2 2 0 0 1 2 2v1\" />\n                    <path d=\"M5 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0\" />\n                    <path d=\"M3 13v-1a2 2 0 0 1 2 -2h2\" />\n                </svg>\n                <span>\n                    <strong>Group:</strong> <i>{user_group}</i>\n                </span>\n            </div>\n            <div class=\"icon-container\">\n                <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"icon icon-tabler icon-tabler-pizza\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\"/>\n                    <path d=\"M12 21.5c-3.04 0 -5.952 -.714 -8.5 -1.983l8.5 -16.517l8.5 16.517a19.09 19.09 0 0 1 -8.5 1.983z\" />\n                    <path d=\"M5.38 15.866a14.94 14.94 0 0 0 6.815 1.634a14.944 14.944 0 0 0 6.502 -1.479\" />\n                    <path d=\"M13 11.01v-.01\" />\n                    <path d=\"M11 14v-.01\" />\n                </svg>\n                <span>\n                    <strong>Data-Lunch:</strong> <i>v{version}</i>\n                </span>\n            </div>\n            <div class=\"icon-container\">\n                <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"icon icon-tabler icon-tabler-cpu\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n                    <path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\"></path>\n                    <path d=\"M5 5m0 1a1 1 0 0 1 1 -1h12a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-12a1 1 0 0 1 -1 -1z\"></path>\n                    <path d=\"M9 9h6v6h-6z\"></path>\n                    <path d=\"M3 10h2\"></path>\n                    <path d=\"M3 14h2\"></path>\n                    <path d=\"M10 3v2\"></path>\n                    <path d=\"M14 3v2\"></path>\n                    <path d=\"M21 10h-2\"></path>\n                    <path d=\"M21 14h-2\"></path>\n                    <path d=\"M14 21v-2\"></path>\n                    <path d=\"M10 21v-2\"></path>\n                </svg>\n                <span>\n                    <strong>Host:</strong> <i>{host_name}</i>\n                </span>\n            </div>\n        </details>\n        \"\"\",\n        sizing_mode=\"stretch_width\",\n        stylesheets=stylesheets,\n    )\n\n    return {\"stats\": stats, \"info\": other_info}\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.build_time_label","title":"build_time_label","text":"
build_time_label(\n    time: str,\n    diners_n: str,\n    separator: str = \" &#10072; \",\n    emoji: str = \"&#127829;\",\n    per_icon: str = \" &#10006; \",\n    is_takeaway: bool = False,\n    takeaway_alert_sign: str = \"TAKEAWAY\",\n    css_classes: list = [],\n    stylesheets: list = [],\n    **kwargs\n) -> HTML\n

Build HTML field to display the time label.

This function is used to display labels that summarize an order.

Those are shown on the side of the menu table as well as labels above each order table.

Parameters:

Name Type Description Default time str

Lunch time.

required diners_n str

Number of people that placed an order.

required separator str

Separator between lunch time and order data. Defaults to \" \u2758 \".

' &#10072; ' emoji str

Emoji used as number lunch symbol. Defaults to \"\ud83c\udf55\".

'&#127829;' per_icon str

icon used between the lunch emoji and the number of people that placed an order. Usually a multiply operator. Defaults to \" \u2716 \".

' &#10006; ' is_takeaway bool

takeaway flag (true if the order is to takeaway). Defaults to False.

False takeaway_alert_sign str

warning text to highlight that the order is to takeaway. Defaults to \"TAKEAWAY\".

'TAKEAWAY' css_classes list

CSS classes to assign to the resulting HTML pane. Defaults to [].

[] stylesheets list

Stylesheets to assign to the resulting HTML pane (see Panel docs <https://panel.holoviz.org/how_to/styling/apply_css.html>__). Defaults to [].

[]

Returns:

Type Description HTML

HTML pane representing a label with order summary.

Source code in dlunch/gui.py
def build_time_label(\n    self,\n    time: str,\n    diners_n: str,\n    separator: str = \" &#10072; \",\n    emoji: str = \"&#127829;\",\n    per_icon: str = \" &#10006; \",\n    is_takeaway: bool = False,\n    takeaway_alert_sign: str = \"TAKEAWAY\",\n    css_classes: list = [],\n    stylesheets: list = [],\n    **kwargs,\n) -> pn.pane.HTML:\n    \"\"\"Build HTML field to display the time label.\n\n    This function is used to display labels that summarize an order.\n\n    Those are shown on the side of the menu table as well as labels above each order table.\n\n    Args:\n        time (str): Lunch time.\n        diners_n (str): Number of people that placed an order.\n        separator (str, optional): Separator between lunch time and order data. Defaults to \" &#10072; \".\n        emoji (str, optional): Emoji used as number lunch symbol. Defaults to \"&#127829;\".\n        per_icon (str, optional): icon used between the lunch emoji and the number of people that placed an order.\n            Usually a multiply operator.\n            Defaults to \" &#10006; \".\n        is_takeaway (bool, optional): takeaway flag (true if the order is to takeaway). Defaults to False.\n        takeaway_alert_sign (str, optional): warning text to highlight that the order is to takeaway. Defaults to \"TAKEAWAY\".\n        css_classes (list, optional): CSS classes to assign to the resulting HTML pane. Defaults to [].\n        stylesheets (list, optional): Stylesheets to assign to the resulting HTML pane\n            (see `Panel docs <https://panel.holoviz.org/how_to/styling/apply_css.html>`__). Defaults to [].\n\n    Returns:\n        pn.pane.HTML: HTML pane representing a label with order summary.\n    \"\"\"\n    # If takeaway add alert sign\n    if is_takeaway:\n        takeaway = f\"{separator}{takeaway_alert_sign}\"\n    else:\n        takeaway = \"\"\n    # Time label pane\n    classes_str = \" \".join(css_classes)\n    time_label = pn.pane.HTML(\n        f'<span class=\"{classes_str}\">{time}{separator}{emoji}{per_icon}{diners_n}{takeaway}</span>',\n        stylesheets=stylesheets,\n        **kwargs,\n    )\n\n    return time_label\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.GraphicInterface.load_sidebar_tabs","title":"load_sidebar_tabs","text":"
load_sidebar_tabs(\n    config: DictConfig, clear_before_loading: bool = True\n) -> None\n

Append tabs to the app template sidebar.

The flag clear_before_loading is set to true only during first instantiation, because the sidebar is empty at first. Use the default value during normal operation to avoid tabs duplication.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required clear_before_loading bool

Set to true to remove all tabs before appending the new ones. Defaults to True.

True Source code in dlunch/gui.py
def load_sidebar_tabs(\n    self, config: DictConfig, clear_before_loading: bool = True\n) -> None:\n    \"\"\"Append tabs to the app template sidebar.\n\n    The flag `clear_before_loading` is set to true only during first instantiation, because the sidebar is empty at first.\n    Use the default value during normal operation to avoid tabs duplication.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n        clear_before_loading (bool, optional): Set to true to remove all tabs before appending the new ones. Defaults to True.\n    \"\"\"\n    # Clean tabs\n    if clear_before_loading:\n        self.sidebar_tabs.clear()\n    # Append User tab\n    self.sidebar_tabs.append(self.sidebar_person_column)\n    # Append upload, download and stats only for non-guest\n    # Append password only for non-guest users if auth is active\n    if not auth.is_guest(\n        user=pn_user(config), config=config, allow_override=False\n    ):\n        self.sidebar_tabs.append(self.sidebar_menu_upload_col)\n        self.sidebar_tabs.append(self.sidebar_download_orders_col)\n        self.sidebar_tabs.append(self.sidebar_stats_col)\n        if auth.is_basic_auth_active(config=config):\n            self.sidebar_tabs.append(self.sidebar_password)\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.PasswordRenewer","title":"PasswordRenewer","text":"

Bases: Parameterized

Param class used to create the widget that collect info to renew users password.

This widget is used only if basic authentication is active.

Methods:

Name Description __str__

String representation of this object.

Attributes:

Name Type Description new_password String

New password.

old_password String

Old password.

repeat_new_password String

Repeat the new password. This field tests if the new password is as intended.

Source code in dlunch/gui.py
class PasswordRenewer(param.Parameterized):\n    \"\"\"Param class used to create the widget that collect info to renew users password.\n\n    This widget is used only if basic authentication is active.\"\"\"\n\n    old_password: param.String = param.String(default=\"\")\n    \"\"\"Old password.\"\"\"\n    new_password: param.String = param.String(default=\"\")\n    \"\"\"New password.\"\"\"\n    repeat_new_password: param.String = param.String(default=\"\")\n    \"\"\"Repeat the new password. This field tests if the new password is as intended.\"\"\"\n\n    def __str__(self):\n        \"\"\"String representation of this object.\n\n        Returns:\n            (str): string representation.\n        \"\"\"\n        return \"PasswordRenewer\"\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.PasswordRenewer.new_password","title":"new_password class-attribute instance-attribute","text":"
new_password: String = String(default='')\n

New password.

"},{"location":"reference/dlunch/gui/#dlunch.gui.PasswordRenewer.old_password","title":"old_password class-attribute instance-attribute","text":"
old_password: String = String(default='')\n

Old password.

"},{"location":"reference/dlunch/gui/#dlunch.gui.PasswordRenewer.repeat_new_password","title":"repeat_new_password class-attribute instance-attribute","text":"
repeat_new_password: String = String(default='')\n

Repeat the new password. This field tests if the new password is as intended.

"},{"location":"reference/dlunch/gui/#dlunch.gui.PasswordRenewer.__str__","title":"__str__","text":"
__str__()\n

String representation of this object.

Returns:

Type Description str

string representation.

Source code in dlunch/gui.py
def __str__(self):\n    \"\"\"String representation of this object.\n\n    Returns:\n        (str): string representation.\n    \"\"\"\n    return \"PasswordRenewer\"\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.Person","title":"Person","text":"

Bases: Parameterized

Param class that define user data and lunch preferences for its order.

username is automatically set for privileged users. It's left empty for guest users.

lunch_time and guest available value are set when instantiation happens. Check panel.lunch_times_options and panel.guest_types config keys.

Methods:

Name Description __init__ __str__

String representation of this object.

Attributes:

Name Type Description guest ObjectSelector

List of available guest types.

lunch_time ObjectSelector

List of available lunch times.

takeaway Boolean

Takeaway flag (true if takeaway).

username String

Username

Source code in dlunch/gui.py
class Person(param.Parameterized):\n    \"\"\"Param class that define user data and lunch preferences for its order.\n\n    `username` is automatically set for privileged users. It's left empty for guest users.\n\n    `lunch_time` and `guest` available value are set when instantiation happens.\n    Check `panel.lunch_times_options` and `panel.guest_types` config keys.\n    \"\"\"\n\n    username: param.String = param.String(default=\"\", doc=\"your name\")\n    \"\"\"Username\"\"\"\n    lunch_time: param.ObjectSelector = param.ObjectSelector(\n        default=\"12:30\", doc=\"choose your lunch time\", objects=[\"12:30\"]\n    )\n    \"\"\"List of available lunch times.\"\"\"\n    guest: param.ObjectSelector = param.ObjectSelector(\n        default=\"Guest\", doc=\"select guest type\", objects=[\"Guest\"]\n    )\n    \"\"\"List of available guest types.\"\"\"\n    takeaway: param.Boolean = param.Boolean(\n        default=False, doc=\"tick to order a takeaway meal\"\n    )\n    \"\"\"Takeaway flag (true if takeaway).\"\"\"\n\n    def __init__(self, config: OmegaConf, **params):\n        super().__init__(**params)\n        # Set lunch times from config\n        self.param.lunch_time.objects = config.panel.lunch_times_options\n        # Set guest type from config\n        self.param.guest.objects = config.panel.guest_types\n        self.param.guest.default = config.panel.guest_types[0]\n        self.guest = config.panel.guest_types[0]\n        # Check user (a username is already set for privileged users)\n        username = pn_user(config)\n        if not auth.is_guest(\n            user=username, config=config, allow_override=False\n        ) and (username is not None):\n            self.username = username\n\n    def __str__(self):\n        \"\"\"String representation of this object.\n\n        Returns:\n            (str): string representation.\n        \"\"\"\n        return f\"PERSON:{self.name}\"\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.Person.guest","title":"guest class-attribute instance-attribute","text":"
guest: ObjectSelector = guest_types[0]\n

List of available guest types.

"},{"location":"reference/dlunch/gui/#dlunch.gui.Person.lunch_time","title":"lunch_time class-attribute instance-attribute","text":"
lunch_time: ObjectSelector = ObjectSelector(\n    default=\"12:30\",\n    doc=\"choose your lunch time\",\n    objects=[\"12:30\"],\n)\n

List of available lunch times.

"},{"location":"reference/dlunch/gui/#dlunch.gui.Person.takeaway","title":"takeaway class-attribute instance-attribute","text":"
takeaway: Boolean = Boolean(\n    default=False, doc=\"tick to order a takeaway meal\"\n)\n

Takeaway flag (true if takeaway).

"},{"location":"reference/dlunch/gui/#dlunch.gui.Person.username","title":"username class-attribute instance-attribute","text":"
username: String = String(default='', doc='your name')\n

Username

"},{"location":"reference/dlunch/gui/#dlunch.gui.Person.__init__","title":"__init__","text":"
__init__(config: OmegaConf, **params)\n
Source code in dlunch/gui.py
def __init__(self, config: OmegaConf, **params):\n    super().__init__(**params)\n    # Set lunch times from config\n    self.param.lunch_time.objects = config.panel.lunch_times_options\n    # Set guest type from config\n    self.param.guest.objects = config.panel.guest_types\n    self.param.guest.default = config.panel.guest_types[0]\n    self.guest = config.panel.guest_types[0]\n    # Check user (a username is already set for privileged users)\n    username = pn_user(config)\n    if not auth.is_guest(\n        user=username, config=config, allow_override=False\n    ) and (username is not None):\n        self.username = username\n
"},{"location":"reference/dlunch/gui/#dlunch.gui.Person.__str__","title":"__str__","text":"
__str__()\n

String representation of this object.

Returns:

Type Description str

string representation.

Source code in dlunch/gui.py
def __str__(self):\n    \"\"\"String representation of this object.\n\n    Returns:\n        (str): string representation.\n    \"\"\"\n    return f\"PERSON:{self.name}\"\n
"},{"location":"reference/dlunch/models/","title":"models","text":"

Module with database tables definitions.

Helper classes and utility functions for data management are defined here.

Modules:

Name Description auth

Module with classes and functions used for authentication and password handling.

Classes:

Name Description Credentials

Table with users credentials, used only if basic authentication is active.

Encrypted

Allows storing and retrieving password hashes using PasswordHash.

Flags

Table with global flags used by Data-Lunch.

Menu

Table with menu items.

Orders

Table with items that belongs to an order.

Password

Allows storing and retrieving password hashes using PasswordHash.

PrivilegedUsers

Table with user that have privileges (normal users and admin).

Stats

Table with number of users that ate a lunch, grouped by guest type.

Users

Table with users that placed an order.

Functions:

Name Description create_database

Function to create the database through SQLAlchemy models.

create_engine

Factory function for SQLAlchemy engine.

create_exclusive_session

Factory function for database exclusive session.

create_session

Factory function for database session.

get_db_dialect

Return database type (postgresql, sqlite, etc.) based on the database object passed as input.

get_flag

Get the value of a flag.

session_add_with_upsert

Use an upsert statement for postgresql to add a new record to a table,

set_flag

Set a key,value pair inside flag table.

set_sqlite_pragma

Force foreign key constraints for sqlite connections.

Attributes:

Name Type Description Data DeclarativeMeta

SQLAlchemy declarative base.

SCHEMA str

Schema name from environment (may be overridden by configuration files).

log Logger

Module logger.

metadata_obj MetaData

Database metadata (SQLAlchemy).

"},{"location":"reference/dlunch/models/#dlunch.models.Data","title":"Data module-attribute","text":"
Data: DeclarativeMeta = declarative_base(\n    metadata=metadata_obj\n)\n

SQLAlchemy declarative base.

"},{"location":"reference/dlunch/models/#dlunch.models.SCHEMA","title":"SCHEMA module-attribute","text":"
SCHEMA: str = get('DATA_LUNCH_DB_SCHEMA', None)\n

Schema name from environment (may be overridden by configuration files).

"},{"location":"reference/dlunch/models/#dlunch.models.log","title":"log module-attribute","text":"
log: Logger = getLogger(__name__)\n

Module logger.

"},{"location":"reference/dlunch/models/#dlunch.models.metadata_obj","title":"metadata_obj module-attribute","text":"
metadata_obj: MetaData = MetaData(schema=SCHEMA)\n

Database metadata (SQLAlchemy).

"},{"location":"reference/dlunch/models/#dlunch.models.Credentials","title":"Credentials","text":"

Bases: Data

Table with users credentials, used only if basic authentication is active.

Methods:

Name Description __repr__

Simple object representation.

clear

Clear table and return deleted rows.

read_as_df

Read table as pandas DataFrame.

Attributes:

Name Type Description __tablename__

Name of the table.

password_encrypted

Encryped password.

password_hash

Hashed password.

user

Username.

Source code in dlunch/models.py
class Credentials(Data):\n    \"\"\"Table with users credentials, used only if basic authentication is active.\"\"\"\n\n    __tablename__ = \"credentials\"\n    \"\"\"Name of the table.\"\"\"\n    user = Column(\n        String(100),\n        primary_key=True,\n        sqlite_on_conflict_primary_key=\"REPLACE\",\n    )\n    \"\"\"Username.\"\"\"\n    password_hash = Column(Password(150), unique=False, nullable=False)\n    \"\"\"Hashed password.\"\"\"\n    password_encrypted = Column(\n        Encrypted(150),\n        unique=False,\n        nullable=True,\n        default=None,\n        server_default=None,\n    )\n    \"\"\"Encryped password.\n\n    Used only if basic authentication and guest users are both active.\"\"\"\n\n    @classmethod\n    def clear(self, config: DictConfig) -> int:\n        \"\"\"Clear table and return deleted rows.\n\n        Args:\n            config (DictConfig): Hydra configuration dictionary.\n\n        Returns:\n            int: deleted rows.\n        \"\"\"\n\n        session = create_session(config)\n        with session:\n            # Clean menu\n            num_rows_deleted = session.execute(delete(self))\n            session.commit()\n            log.info(\n                f\"deleted {num_rows_deleted.rowcount} rows from table '{self.__tablename__}'\"\n            )\n\n        return num_rows_deleted.rowcount\n\n    @classmethod\n    def read_as_df(self, config: DictConfig, **kwargs) -> pd.DataFrame:\n        \"\"\"Read table as pandas DataFrame.\n\n        Args:\n            config (DictConfig): Hydra configuration dictionary.\n\n        Returns:\n            pd.DataFrame: dataframe with table content.\n        \"\"\"\n        df = pd.read_sql_table(\n            self.__tablename__,\n            create_engine(config=config),\n            schema=config.db.get(\"schema\", SCHEMA),\n            **kwargs,\n        )\n\n        return df\n\n    def __repr__(self) -> str:\n        \"\"\"Simple object representation.\n\n        Returns:\n            str: string representation.\n        \"\"\"\n        return f\"<CREDENTIAL:{self.user}>\"\n\n    @validates(\"password_hash\")\n    def _validate_password(\n        self, key: str, password: str\n    ) -> auth.PasswordHash | None:\n        \"\"\"Function that validate password input.\n\n        It converts string to auth.PasswordHash if necessary.\n\n        Args:\n            key (str): validated attribute name.\n            password (str): hashed password to be validated.\n\n        Returns:\n            auth.PasswordHash | None: validated hashed password.\n        \"\"\"\n        return getattr(type(self), key).type.validator(password)\n\n    @validates(\"password_encrypted\")\n    def _validate_encrypted(\n        self, key: str, password: str\n    ) -> auth.PasswordEncrypt | None:\n        \"\"\"Function that validate encrypted input.\n\n        It converts string to auth.PasswordEncrypt if necessary.\n\n        Args:\n            key (str): validated attribute name.\n            password (str): encrypted password to be validated.\n\n        Returns:\n            auth.PasswordEncrypt | None: validated encrypted password.\n        \"\"\"\n        return getattr(type(self), key).type.validator(password)\n
"},{"location":"reference/dlunch/models/#dlunch.models.Credentials.__tablename__","title":"__tablename__ class-attribute instance-attribute","text":"
__tablename__ = 'credentials'\n

Name of the table.

"},{"location":"reference/dlunch/models/#dlunch.models.Credentials.password_encrypted","title":"password_encrypted class-attribute instance-attribute","text":"
password_encrypted = Column(\n    Encrypted(150),\n    unique=False,\n    nullable=True,\n    default=None,\n    server_default=None,\n)\n

Encryped password.

Used only if basic authentication and guest users are both active.

"},{"location":"reference/dlunch/models/#dlunch.models.Credentials.password_hash","title":"password_hash class-attribute instance-attribute","text":"
password_hash = Column(\n    Password(150), unique=False, nullable=False\n)\n

Hashed password.

"},{"location":"reference/dlunch/models/#dlunch.models.Credentials.user","title":"user class-attribute instance-attribute","text":"
user = Column(\n    String(100),\n    primary_key=True,\n    sqlite_on_conflict_primary_key=\"REPLACE\",\n)\n

Username.

"},{"location":"reference/dlunch/models/#dlunch.models.Credentials.__repr__","title":"__repr__","text":"
__repr__() -> str\n

Simple object representation.

Returns:

Type Description str

string representation.

Source code in dlunch/models.py
def __repr__(self) -> str:\n    \"\"\"Simple object representation.\n\n    Returns:\n        str: string representation.\n    \"\"\"\n    return f\"<CREDENTIAL:{self.user}>\"\n
"},{"location":"reference/dlunch/models/#dlunch.models.Credentials.clear","title":"clear classmethod","text":"
clear(config: DictConfig) -> int\n

Clear table and return deleted rows.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description int

deleted rows.

Source code in dlunch/models.py
@classmethod\ndef clear(self, config: DictConfig) -> int:\n    \"\"\"Clear table and return deleted rows.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        int: deleted rows.\n    \"\"\"\n\n    session = create_session(config)\n    with session:\n        # Clean menu\n        num_rows_deleted = session.execute(delete(self))\n        session.commit()\n        log.info(\n            f\"deleted {num_rows_deleted.rowcount} rows from table '{self.__tablename__}'\"\n        )\n\n    return num_rows_deleted.rowcount\n
"},{"location":"reference/dlunch/models/#dlunch.models.Credentials.read_as_df","title":"read_as_df classmethod","text":"
read_as_df(config: DictConfig, **kwargs) -> DataFrame\n

Read table as pandas DataFrame.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description DataFrame

dataframe with table content.

Source code in dlunch/models.py
@classmethod\ndef read_as_df(self, config: DictConfig, **kwargs) -> pd.DataFrame:\n    \"\"\"Read table as pandas DataFrame.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        pd.DataFrame: dataframe with table content.\n    \"\"\"\n    df = pd.read_sql_table(\n        self.__tablename__,\n        create_engine(config=config),\n        schema=config.db.get(\"schema\", SCHEMA),\n        **kwargs,\n    )\n\n    return df\n
"},{"location":"reference/dlunch/models/#dlunch.models.Encrypted","title":"Encrypted","text":"

Bases: TypeDecorator

Allows storing and retrieving password hashes using PasswordHash.

Methods:

Name Description process_bind_param

Ensure the value is a PasswordEncrypt and then return the encrypted password.

process_result_value

Convert the hash to a PasswordEncrypt, if it's non-NULL.

validator

Provides a validator/converter used by @validates.

Attributes:

Name Type Description impl String

Base column implementation.

Source code in dlunch/models.py
class Encrypted(TypeDecorator):\n    \"\"\"Allows storing and retrieving password hashes using PasswordHash.\"\"\"\n\n    impl: String = String\n    \"\"\"Base column implementation.\"\"\"\n\n    def process_bind_param(\n        self, value: auth.PasswordEncrypt | str | None, dialect\n    ) -> str | None:\n        \"\"\"Ensure the value is a PasswordEncrypt and then return the encrypted password.\n\n        Args:\n            value (auth.PasswordEncrypt | str | None): input value (plain password or encrypted or `None` if empty)\n            dialect (Any): dialect (not used).\n\n        Returns:\n            str | None: encrypted password or `None` if empty.\n        \"\"\"\n        converted_value = self._convert(value)\n        if converted_value:\n            return converted_value.encrypted_password\n        else:\n            return None\n\n    def process_result_value(\n        self, value: str | None, dialect\n    ) -> auth.PasswordEncrypt | None:\n        \"\"\"Convert the hash to a PasswordEncrypt, if it's non-NULL.\n\n        Args:\n            value (str | None): input value (plain password or encrypted or `None` if empty)\n            dialect (Any): dialect (not used).\n\n        Returns:\n            auth.PasswordEncrypt | None: encrypted password as object or `None` (if nothing is passed as value).\n        \"\"\"\n        if value is not None:\n            return auth.PasswordEncrypt(value)\n\n    def validator(\n        self, password: auth.PasswordEncrypt | str | None\n    ) -> auth.PasswordEncrypt | None:\n        \"\"\"Provides a validator/converter used by @validates.\n\n        Args:\n            password (auth.PasswordEncrypt | str | None): input value (plain password or encrypted or `None` if empty)\n\n        Returns:\n            auth.PasswordEncrypt | None: encrypted password as object or `None` (if nothing is passed as value).\n        \"\"\"\n        return self._convert(password)\n\n    def _convert(\n        self, value: auth.PasswordEncrypt | str | None\n    ) -> auth.PasswordEncrypt | None:\n        \"\"\"Returns a PasswordEncrypt from the given string.\n\n        PasswordEncrypt instances or None values will return unchanged.\n        Strings will be encrypted and the resulting PasswordEncrypt returned.\n        Any other input will result in a TypeError.\n\n        Args:\n            value (auth.PasswordEncrypt | str | None): input value (plain password or encrypted or `None` if empty)\n\n        Raises:\n            TypeError: unknown type.\n\n        Returns:\n            auth.PasswordEncrypt | None: encrypted password as object or `None` (if nothing is passed as value).\n        \"\"\"\n        if isinstance(value, auth.PasswordEncrypt):\n            return value\n        elif isinstance(value, str):\n            return auth.PasswordEncrypt.from_str(value)\n        elif value is not None:\n            raise TypeError(\n                f\"Cannot initialize PasswordEncrypt from type '{type(value)}'\"\n            )\n\n        # Reached only if value is None\n        return None\n
"},{"location":"reference/dlunch/models/#dlunch.models.Encrypted.impl","title":"impl class-attribute instance-attribute","text":"
impl: String = String\n

Base column implementation.

"},{"location":"reference/dlunch/models/#dlunch.models.Encrypted.process_bind_param","title":"process_bind_param","text":"
process_bind_param(\n    value: PasswordEncrypt | str | None, dialect\n) -> str | None\n

Ensure the value is a PasswordEncrypt and then return the encrypted password.

Parameters:

Name Type Description Default value PasswordEncrypt | str | None

input value (plain password or encrypted or None if empty)

required dialect Any

dialect (not used).

required

Returns:

Type Description str | None

encrypted password or None if empty.

Source code in dlunch/models.py
def process_bind_param(\n    self, value: auth.PasswordEncrypt | str | None, dialect\n) -> str | None:\n    \"\"\"Ensure the value is a PasswordEncrypt and then return the encrypted password.\n\n    Args:\n        value (auth.PasswordEncrypt | str | None): input value (plain password or encrypted or `None` if empty)\n        dialect (Any): dialect (not used).\n\n    Returns:\n        str | None: encrypted password or `None` if empty.\n    \"\"\"\n    converted_value = self._convert(value)\n    if converted_value:\n        return converted_value.encrypted_password\n    else:\n        return None\n
"},{"location":"reference/dlunch/models/#dlunch.models.Encrypted.process_result_value","title":"process_result_value","text":"
process_result_value(\n    value: str | None, dialect\n) -> PasswordEncrypt | None\n

Convert the hash to a PasswordEncrypt, if it's non-NULL.

Parameters:

Name Type Description Default value str | None

input value (plain password or encrypted or None if empty)

required dialect Any

dialect (not used).

required

Returns:

Type Description PasswordEncrypt | None

encrypted password as object or None (if nothing is passed as value).

Source code in dlunch/models.py
def process_result_value(\n    self, value: str | None, dialect\n) -> auth.PasswordEncrypt | None:\n    \"\"\"Convert the hash to a PasswordEncrypt, if it's non-NULL.\n\n    Args:\n        value (str | None): input value (plain password or encrypted or `None` if empty)\n        dialect (Any): dialect (not used).\n\n    Returns:\n        auth.PasswordEncrypt | None: encrypted password as object or `None` (if nothing is passed as value).\n    \"\"\"\n    if value is not None:\n        return auth.PasswordEncrypt(value)\n
"},{"location":"reference/dlunch/models/#dlunch.models.Encrypted.validator","title":"validator","text":"
validator(\n    password: PasswordEncrypt | str | None,\n) -> PasswordEncrypt | None\n

Provides a validator/converter used by @validates.

Parameters:

Name Type Description Default password PasswordEncrypt | str | None

input value (plain password or encrypted or None if empty)

required

Returns:

Type Description PasswordEncrypt | None

encrypted password as object or None (if nothing is passed as value).

Source code in dlunch/models.py
def validator(\n    self, password: auth.PasswordEncrypt | str | None\n) -> auth.PasswordEncrypt | None:\n    \"\"\"Provides a validator/converter used by @validates.\n\n    Args:\n        password (auth.PasswordEncrypt | str | None): input value (plain password or encrypted or `None` if empty)\n\n    Returns:\n        auth.PasswordEncrypt | None: encrypted password as object or `None` (if nothing is passed as value).\n    \"\"\"\n    return self._convert(password)\n
"},{"location":"reference/dlunch/models/#dlunch.models.Flags","title":"Flags","text":"

Bases: Data

Table with global flags used by Data-Lunch.

'No more orders' flag and guest override flags are stored here.

Methods:

Name Description __repr__

Simple object representation.

clear

Clear table and return deleted rows.

clear_guest_override

Clear 'guest_override' flags and return deleted rows

read_as_df

Read table as pandas DataFrame.

Attributes:

Name Type Description __tablename__

Name of the table.

id

Flag ID (name).

value

Flag value.

Source code in dlunch/models.py
class Flags(Data):\n    \"\"\"Table with global flags used by Data-Lunch.\n\n    'No more orders' flag and guest override flags are stored here.\n    \"\"\"\n\n    __tablename__ = \"flags\"\n    \"\"\"Name of the table.\"\"\"\n    id = Column(\n        String(50),\n        primary_key=True,\n        nullable=False,\n        sqlite_on_conflict_primary_key=\"REPLACE\",\n    )\n    \"\"\"Flag ID (name).\"\"\"\n    value = Column(Boolean, nullable=False)\n    \"\"\"Flag value.\"\"\"\n\n    @classmethod\n    def clear(self, config: DictConfig) -> int:\n        \"\"\"Clear table and return deleted rows.\n\n        Args:\n            config (DictConfig): Hydra configuration dictionary.\n\n        Returns:\n            int: deleted rows.\n        \"\"\"\n\n        session = create_session(config)\n        with session:\n            # Clean menu\n            num_rows_deleted = session.execute(delete(self))\n            session.commit()\n            log.info(\n                f\"deleted {num_rows_deleted.rowcount} rows from table '{self.__tablename__}'\"\n            )\n\n        return num_rows_deleted.rowcount\n\n    @classmethod\n    def clear_guest_override(self, config: DictConfig) -> int:\n        \"\"\"Clear 'guest_override' flags and return deleted rows\n\n        Args:\n            config (DictConfig): Hydra configuration dictionary.\n\n        Returns:\n            int: deleted rows.\n        \"\"\"\n\n        session = create_session(config)\n        with session:\n            # Clean menu\n            num_rows_deleted = session.execute(\n                delete(self).where(self.id.like(\"%_guest_override\"))\n            )\n            session.commit()\n            log.info(\n                f\"deleted {num_rows_deleted.rowcount} rows (guest override) from table '{self.__tablename__}'\"\n            )\n\n        return num_rows_deleted.rowcount\n\n    @classmethod\n    def read_as_df(self, config: DictConfig, **kwargs) -> pd.DataFrame:\n        \"\"\"Read table as pandas DataFrame.\n\n        Args:\n            config (DictConfig): Hydra configuration dictionary.\n\n        Returns:\n            pd.DataFrame: dataframe with table content.\n        \"\"\"\n        df = pd.read_sql_table(\n            self.__tablename__,\n            create_engine(config=config),\n            schema=config.db.get(\"schema\", SCHEMA),\n            **kwargs,\n        )\n\n        return df\n\n    def __repr__(self) -> str:\n        \"\"\"Simple object representation.\n\n        Returns:\n            str: string representation.\n        \"\"\"\n        return f\"<FLAG:{self.id} - value:{self.value}>\"\n
"},{"location":"reference/dlunch/models/#dlunch.models.Flags.__tablename__","title":"__tablename__ class-attribute instance-attribute","text":"
__tablename__ = 'flags'\n

Name of the table.

"},{"location":"reference/dlunch/models/#dlunch.models.Flags.id","title":"id class-attribute instance-attribute","text":"
id = Column(\n    String(50),\n    primary_key=True,\n    nullable=False,\n    sqlite_on_conflict_primary_key=\"REPLACE\",\n)\n

Flag ID (name).

"},{"location":"reference/dlunch/models/#dlunch.models.Flags.value","title":"value class-attribute instance-attribute","text":"
value = Column(Boolean, nullable=False)\n

Flag value.

"},{"location":"reference/dlunch/models/#dlunch.models.Flags.__repr__","title":"__repr__","text":"
__repr__() -> str\n

Simple object representation.

Returns:

Type Description str

string representation.

Source code in dlunch/models.py
def __repr__(self) -> str:\n    \"\"\"Simple object representation.\n\n    Returns:\n        str: string representation.\n    \"\"\"\n    return f\"<FLAG:{self.id} - value:{self.value}>\"\n
"},{"location":"reference/dlunch/models/#dlunch.models.Flags.clear","title":"clear classmethod","text":"
clear(config: DictConfig) -> int\n

Clear table and return deleted rows.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description int

deleted rows.

Source code in dlunch/models.py
@classmethod\ndef clear(self, config: DictConfig) -> int:\n    \"\"\"Clear table and return deleted rows.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        int: deleted rows.\n    \"\"\"\n\n    session = create_session(config)\n    with session:\n        # Clean menu\n        num_rows_deleted = session.execute(delete(self))\n        session.commit()\n        log.info(\n            f\"deleted {num_rows_deleted.rowcount} rows from table '{self.__tablename__}'\"\n        )\n\n    return num_rows_deleted.rowcount\n
"},{"location":"reference/dlunch/models/#dlunch.models.Flags.clear_guest_override","title":"clear_guest_override classmethod","text":"
clear_guest_override(config: DictConfig) -> int\n

Clear 'guest_override' flags and return deleted rows

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description int

deleted rows.

Source code in dlunch/models.py
@classmethod\ndef clear_guest_override(self, config: DictConfig) -> int:\n    \"\"\"Clear 'guest_override' flags and return deleted rows\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        int: deleted rows.\n    \"\"\"\n\n    session = create_session(config)\n    with session:\n        # Clean menu\n        num_rows_deleted = session.execute(\n            delete(self).where(self.id.like(\"%_guest_override\"))\n        )\n        session.commit()\n        log.info(\n            f\"deleted {num_rows_deleted.rowcount} rows (guest override) from table '{self.__tablename__}'\"\n        )\n\n    return num_rows_deleted.rowcount\n
"},{"location":"reference/dlunch/models/#dlunch.models.Flags.read_as_df","title":"read_as_df classmethod","text":"
read_as_df(config: DictConfig, **kwargs) -> DataFrame\n

Read table as pandas DataFrame.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description DataFrame

dataframe with table content.

Source code in dlunch/models.py
@classmethod\ndef read_as_df(self, config: DictConfig, **kwargs) -> pd.DataFrame:\n    \"\"\"Read table as pandas DataFrame.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        pd.DataFrame: dataframe with table content.\n    \"\"\"\n    df = pd.read_sql_table(\n        self.__tablename__,\n        create_engine(config=config),\n        schema=config.db.get(\"schema\", SCHEMA),\n        **kwargs,\n    )\n\n    return df\n
"},{"location":"reference/dlunch/models/#dlunch.models.Menu","title":"Menu","text":"

Bases: Data

Table with menu items.

Methods:

Name Description __repr__

Simple object representation.

clear

Clear table and return deleted rows.

read_as_df

Read table as pandas DataFrame.

Attributes:

Name Type Description __tablename__

Name of the table.

id

Menu item ID.

item

Item name.

orders

Orders connected to each menu item.

Source code in dlunch/models.py
class Menu(Data):\n    \"\"\"Table with menu items.\"\"\"\n\n    __tablename__ = \"menu\"\n    \"\"\"Name of the table.\"\"\"\n    id = Column(Integer, Identity(start=1, cycle=True), primary_key=True)\n    \"\"\"Menu item ID.\"\"\"\n    item = Column(String(250), unique=False, nullable=False)\n    \"\"\"Item name.\"\"\"\n    orders = relationship(\n        \"Orders\",\n        back_populates=\"menu_item\",\n        cascade=\"all, delete-orphan\",\n        passive_deletes=True,\n    )\n    \"\"\"Orders connected to each menu item.\"\"\"\n\n    @classmethod\n    def clear(self, config: DictConfig) -> int:\n        \"\"\"Clear table and return deleted rows.\n\n        Args:\n            config (DictConfig): Hydra configuration dictionary.\n\n        Returns:\n            int: deleted rows.\n        \"\"\"\n\n        session = create_session(config)\n        with session:\n            # Clean menu\n            num_rows_deleted = session.execute(delete(self))\n            session.commit()\n            log.info(\n                f\"deleted {num_rows_deleted.rowcount} rows from table '{self.__tablename__}'\"\n            )\n\n        return num_rows_deleted.rowcount\n\n    @classmethod\n    def read_as_df(self, config: DictConfig, **kwargs) -> pd.DataFrame:\n        \"\"\"Read table as pandas DataFrame.\n\n        Args:\n            config (DictConfig): Hydra configuration dictionary.\n\n        Returns:\n            pd.DataFrame: dataframe with table content.\n        \"\"\"\n        df = pd.read_sql_table(\n            self.__tablename__,\n            create_engine(config=config),\n            schema=config.db.get(\"schema\", SCHEMA),\n            **kwargs,\n        )\n\n        return df\n\n    def __repr__(self) -> str:\n        \"\"\"Simple object representation.\n\n        Returns:\n            str: string representation.\n        \"\"\"\n        return f\"<MENU_ITEM:{self.id} - {self.item}>\"\n
"},{"location":"reference/dlunch/models/#dlunch.models.Menu.__tablename__","title":"__tablename__ class-attribute instance-attribute","text":"
__tablename__ = 'menu'\n

Name of the table.

"},{"location":"reference/dlunch/models/#dlunch.models.Menu.id","title":"id class-attribute instance-attribute","text":"
id = Column(\n    Integer, Identity(start=1, cycle=True), primary_key=True\n)\n

Menu item ID.

"},{"location":"reference/dlunch/models/#dlunch.models.Menu.item","title":"item class-attribute instance-attribute","text":"
item = Column(String(250), unique=False, nullable=False)\n

Item name.

"},{"location":"reference/dlunch/models/#dlunch.models.Menu.orders","title":"orders class-attribute instance-attribute","text":"
orders = relationship(\n    \"Orders\",\n    back_populates=\"menu_item\",\n    cascade=\"all, delete-orphan\",\n    passive_deletes=True,\n)\n

Orders connected to each menu item.

"},{"location":"reference/dlunch/models/#dlunch.models.Menu.__repr__","title":"__repr__","text":"
__repr__() -> str\n

Simple object representation.

Returns:

Type Description str

string representation.

Source code in dlunch/models.py
def __repr__(self) -> str:\n    \"\"\"Simple object representation.\n\n    Returns:\n        str: string representation.\n    \"\"\"\n    return f\"<MENU_ITEM:{self.id} - {self.item}>\"\n
"},{"location":"reference/dlunch/models/#dlunch.models.Menu.clear","title":"clear classmethod","text":"
clear(config: DictConfig) -> int\n

Clear table and return deleted rows.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description int

deleted rows.

Source code in dlunch/models.py
@classmethod\ndef clear(self, config: DictConfig) -> int:\n    \"\"\"Clear table and return deleted rows.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        int: deleted rows.\n    \"\"\"\n\n    session = create_session(config)\n    with session:\n        # Clean menu\n        num_rows_deleted = session.execute(delete(self))\n        session.commit()\n        log.info(\n            f\"deleted {num_rows_deleted.rowcount} rows from table '{self.__tablename__}'\"\n        )\n\n    return num_rows_deleted.rowcount\n
"},{"location":"reference/dlunch/models/#dlunch.models.Menu.read_as_df","title":"read_as_df classmethod","text":"
read_as_df(config: DictConfig, **kwargs) -> DataFrame\n

Read table as pandas DataFrame.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description DataFrame

dataframe with table content.

Source code in dlunch/models.py
@classmethod\ndef read_as_df(self, config: DictConfig, **kwargs) -> pd.DataFrame:\n    \"\"\"Read table as pandas DataFrame.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        pd.DataFrame: dataframe with table content.\n    \"\"\"\n    df = pd.read_sql_table(\n        self.__tablename__,\n        create_engine(config=config),\n        schema=config.db.get(\"schema\", SCHEMA),\n        **kwargs,\n    )\n\n    return df\n
"},{"location":"reference/dlunch/models/#dlunch.models.Orders","title":"Orders","text":"

Bases: Data

Table with items that belongs to an order.

Methods:

Name Description __repr__

Simple object representation.

clear

Clear table and return deleted rows.

read_as_df

Read table as pandas DataFrame.

Attributes:

Name Type Description __tablename__

Name of the table.

id

Order ID.

menu_item

Menu items connected to each order (see menu table).

menu_item_id

ID of the menu item included in the order.

note

Note field attached to the order.

user

User that placed the order.

user_record

User connected to this order.

Source code in dlunch/models.py
class Orders(Data):\n    \"\"\"Table with items that belongs to an order.\"\"\"\n\n    __tablename__ = \"orders\"\n    \"\"\"Name of the table.\"\"\"\n    id = Column(Integer, Identity(start=1, cycle=True), primary_key=True)\n    \"\"\"Order ID.\"\"\"\n    user = Column(\n        String(100),\n        ForeignKey(\"users.id\", ondelete=\"CASCADE\"),\n        index=True,\n        nullable=False,\n    )\n    \"\"\"User that placed the order.\"\"\"\n    user_record = relationship(\"Users\", back_populates=\"orders\", uselist=False)\n    \"\"\"User connected to this order.\"\"\"\n    menu_item_id = Column(\n        Integer,\n        ForeignKey(\"menu.id\", ondelete=\"CASCADE\"),\n        nullable=False,\n    )\n    \"\"\"ID of the menu item included in the order.\"\"\"\n    menu_item = relationship(\"Menu\", back_populates=\"orders\")\n    \"\"\"Menu items connected to each order (see `menu` table).\"\"\"\n    note = Column(String(300), unique=False, nullable=True)\n    \"\"\"Note field attached to the order.\"\"\"\n\n    @classmethod\n    def clear(self, config: DictConfig) -> int:\n        \"\"\"Clear table and return deleted rows.\n\n        Args:\n            config (DictConfig): Hydra configuration dictionary.\n\n        Returns:\n            int: deleted rows.\n        \"\"\"\n\n        session = create_session(config)\n        with session:\n            # Clean menu\n            num_rows_deleted = session.execute(delete(self))\n            session.commit()\n            log.info(\n                f\"deleted {num_rows_deleted.rowcount} rows from table '{self.__tablename__}'\"\n            )\n\n        return num_rows_deleted.rowcount\n\n    @classmethod\n    def read_as_df(self, config: DictConfig, **kwargs) -> pd.DataFrame:\n        \"\"\"Read table as pandas DataFrame.\n\n        Args:\n            config (DictConfig): Hydra configuration dictionary.\n\n        Returns:\n            pd.DataFrame: dataframe with table content.\n        \"\"\"\n        df = pd.read_sql_table(\n            self.__tablename__,\n            create_engine(config=config),\n            schema=config.db.get(\"schema\", SCHEMA),\n            **kwargs,\n        )\n\n        return df\n\n    def __repr__(self) -> str:\n        \"\"\"Simple object representation.\n\n        Returns:\n            str: string representation.\n        \"\"\"\n        return f\"<ORDER:{self.user}, {self.menu_item.item}>\"\n
"},{"location":"reference/dlunch/models/#dlunch.models.Orders.__tablename__","title":"__tablename__ class-attribute instance-attribute","text":"
__tablename__ = 'orders'\n

Name of the table.

"},{"location":"reference/dlunch/models/#dlunch.models.Orders.id","title":"id class-attribute instance-attribute","text":"
id = Column(\n    Integer, Identity(start=1, cycle=True), primary_key=True\n)\n

Order ID.

"},{"location":"reference/dlunch/models/#dlunch.models.Orders.menu_item","title":"menu_item class-attribute instance-attribute","text":"
menu_item = relationship('Menu', back_populates='orders')\n

Menu items connected to each order (see menu table).

"},{"location":"reference/dlunch/models/#dlunch.models.Orders.menu_item_id","title":"menu_item_id class-attribute instance-attribute","text":"
menu_item_id = Column(\n    Integer,\n    ForeignKey(\"menu.id\", ondelete=\"CASCADE\"),\n    nullable=False,\n)\n

ID of the menu item included in the order.

"},{"location":"reference/dlunch/models/#dlunch.models.Orders.note","title":"note class-attribute instance-attribute","text":"
note = Column(String(300), unique=False, nullable=True)\n

Note field attached to the order.

"},{"location":"reference/dlunch/models/#dlunch.models.Orders.user","title":"user class-attribute instance-attribute","text":"
user = Column(\n    String(100),\n    ForeignKey(\"users.id\", ondelete=\"CASCADE\"),\n    index=True,\n    nullable=False,\n)\n

User that placed the order.

"},{"location":"reference/dlunch/models/#dlunch.models.Orders.user_record","title":"user_record class-attribute instance-attribute","text":"
user_record = relationship(\n    \"Users\", back_populates=\"orders\", uselist=False\n)\n

User connected to this order.

"},{"location":"reference/dlunch/models/#dlunch.models.Orders.__repr__","title":"__repr__","text":"
__repr__() -> str\n

Simple object representation.

Returns:

Type Description str

string representation.

Source code in dlunch/models.py
def __repr__(self) -> str:\n    \"\"\"Simple object representation.\n\n    Returns:\n        str: string representation.\n    \"\"\"\n    return f\"<ORDER:{self.user}, {self.menu_item.item}>\"\n
"},{"location":"reference/dlunch/models/#dlunch.models.Orders.clear","title":"clear classmethod","text":"
clear(config: DictConfig) -> int\n

Clear table and return deleted rows.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description int

deleted rows.

Source code in dlunch/models.py
@classmethod\ndef clear(self, config: DictConfig) -> int:\n    \"\"\"Clear table and return deleted rows.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        int: deleted rows.\n    \"\"\"\n\n    session = create_session(config)\n    with session:\n        # Clean menu\n        num_rows_deleted = session.execute(delete(self))\n        session.commit()\n        log.info(\n            f\"deleted {num_rows_deleted.rowcount} rows from table '{self.__tablename__}'\"\n        )\n\n    return num_rows_deleted.rowcount\n
"},{"location":"reference/dlunch/models/#dlunch.models.Orders.read_as_df","title":"read_as_df classmethod","text":"
read_as_df(config: DictConfig, **kwargs) -> DataFrame\n

Read table as pandas DataFrame.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description DataFrame

dataframe with table content.

Source code in dlunch/models.py
@classmethod\ndef read_as_df(self, config: DictConfig, **kwargs) -> pd.DataFrame:\n    \"\"\"Read table as pandas DataFrame.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        pd.DataFrame: dataframe with table content.\n    \"\"\"\n    df = pd.read_sql_table(\n        self.__tablename__,\n        create_engine(config=config),\n        schema=config.db.get(\"schema\", SCHEMA),\n        **kwargs,\n    )\n\n    return df\n
"},{"location":"reference/dlunch/models/#dlunch.models.Password","title":"Password","text":"

Bases: TypeDecorator

Allows storing and retrieving password hashes using PasswordHash.

Methods:

Name Description process_bind_param

Ensure the value is a PasswordHash and then return its hash.

process_result_value

Convert the hash to a PasswordHash, if it's non-NULL.

validator

Provides a validator/converter used by @validates.

Attributes:

Name Type Description impl String

Base column implementation.

Source code in dlunch/models.py
class Password(TypeDecorator):\n    \"\"\"Allows storing and retrieving password hashes using PasswordHash.\"\"\"\n\n    impl: String = String\n    \"\"\"Base column implementation.\"\"\"\n\n    def process_bind_param(\n        self, value: auth.PasswordHash | str | None, dialect\n    ) -> str:\n        \"\"\"Ensure the value is a PasswordHash and then return its hash.\n\n        Args:\n            value (auth.PasswordHash | str): input value (plain password or hash, or `None` if empty).\n            dialect (Any): dialect (not used).\n\n        Returns:\n            str: password hash.\n        \"\"\"\n        return self._convert(value).hashed_password\n\n    def process_result_value(\n        self, value: str | None, dialect\n    ) -> auth.PasswordHash | None:\n        \"\"\"Convert the hash to a PasswordHash, if it's non-NULL.\n\n        Args:\n            value (str | None): password hash (or `None` if empty).\n            dialect (Any): dialect (not used).\n\n        Returns:\n            auth.PasswordHash | None: hashed password as object or `None` (if nothing is passed as value).\n        \"\"\"\n        if value is not None:\n            return auth.PasswordHash(value)\n\n    def validator(\n        self, password: auth.PasswordHash | str | None\n    ) -> auth.PasswordHash | None:\n        \"\"\"Provides a validator/converter used by @validates.\n\n        Args:\n            password (auth.PasswordHash | str | None): input value (plain password or hash or `None` if empty).\n\n        Returns:\n            auth.PasswordHash | None: hashed password as object or `None` (if nothing is passed as value).\n        \"\"\"\n        return self._convert(password)\n\n    def _convert(\n        self, value: auth.PasswordHash | str | None\n    ) -> auth.PasswordHash | None:\n        \"\"\"Returns a PasswordHash from the given string.\n\n        PasswordHash instances or None values will return unchanged.\n        Strings will be hashed and the resulting PasswordHash returned.\n        Any other input will result in a TypeError.\n\n        Args:\n            value (auth.PasswordHash | str | None): input value (plain password or hash or `None` if empty).\n\n        Raises:\n            TypeError: unknown type.\n\n        Returns:\n            auth.PasswordHash | None: hashed password as object or `None` (if nothing is passed as value).\n        \"\"\"\n        if isinstance(value, auth.PasswordHash):\n            return value\n        elif isinstance(value, str):\n            return auth.PasswordHash.from_str(value)\n        elif value is not None:\n            raise TypeError(\n                f\"Cannot initialize PasswordHash from type '{type(value)}'\"\n            )\n\n        # Reached only if value is None\n        return None\n
"},{"location":"reference/dlunch/models/#dlunch.models.Password.impl","title":"impl class-attribute instance-attribute","text":"
impl: String = String\n

Base column implementation.

"},{"location":"reference/dlunch/models/#dlunch.models.Password.process_bind_param","title":"process_bind_param","text":"
process_bind_param(\n    value: PasswordHash | str | None, dialect\n) -> str\n

Ensure the value is a PasswordHash and then return its hash.

Parameters:

Name Type Description Default value PasswordHash | str

input value (plain password or hash, or None if empty).

required dialect Any

dialect (not used).

required

Returns:

Type Description str

password hash.

Source code in dlunch/models.py
def process_bind_param(\n    self, value: auth.PasswordHash | str | None, dialect\n) -> str:\n    \"\"\"Ensure the value is a PasswordHash and then return its hash.\n\n    Args:\n        value (auth.PasswordHash | str): input value (plain password or hash, or `None` if empty).\n        dialect (Any): dialect (not used).\n\n    Returns:\n        str: password hash.\n    \"\"\"\n    return self._convert(value).hashed_password\n
"},{"location":"reference/dlunch/models/#dlunch.models.Password.process_result_value","title":"process_result_value","text":"
process_result_value(\n    value: str | None, dialect\n) -> PasswordHash | None\n

Convert the hash to a PasswordHash, if it's non-NULL.

Parameters:

Name Type Description Default value str | None

password hash (or None if empty).

required dialect Any

dialect (not used).

required

Returns:

Type Description PasswordHash | None

hashed password as object or None (if nothing is passed as value).

Source code in dlunch/models.py
def process_result_value(\n    self, value: str | None, dialect\n) -> auth.PasswordHash | None:\n    \"\"\"Convert the hash to a PasswordHash, if it's non-NULL.\n\n    Args:\n        value (str | None): password hash (or `None` if empty).\n        dialect (Any): dialect (not used).\n\n    Returns:\n        auth.PasswordHash | None: hashed password as object or `None` (if nothing is passed as value).\n    \"\"\"\n    if value is not None:\n        return auth.PasswordHash(value)\n
"},{"location":"reference/dlunch/models/#dlunch.models.Password.validator","title":"validator","text":"
validator(\n    password: PasswordHash | str | None,\n) -> PasswordHash | None\n

Provides a validator/converter used by @validates.

Parameters:

Name Type Description Default password PasswordHash | str | None

input value (plain password or hash or None if empty).

required

Returns:

Type Description PasswordHash | None

hashed password as object or None (if nothing is passed as value).

Source code in dlunch/models.py
def validator(\n    self, password: auth.PasswordHash | str | None\n) -> auth.PasswordHash | None:\n    \"\"\"Provides a validator/converter used by @validates.\n\n    Args:\n        password (auth.PasswordHash | str | None): input value (plain password or hash or `None` if empty).\n\n    Returns:\n        auth.PasswordHash | None: hashed password as object or `None` (if nothing is passed as value).\n    \"\"\"\n    return self._convert(password)\n
"},{"location":"reference/dlunch/models/#dlunch.models.PrivilegedUsers","title":"PrivilegedUsers","text":"

Bases: Data

Table with user that have privileges (normal users and admin).

If enabled guests are all the authenticated users that do not belong to this table (see config key auth.authorize_guest_users and basic_auth.guest_user)

Methods:

Name Description __repr__

Simple object representation.

clear

Clear table and return deleted rows.

read_as_df

Read table as pandas DataFrame.

Attributes:

Name Type Description __tablename__

Name of the table.

admin

Admin flag (true if admin).

user

User name.

Source code in dlunch/models.py
class PrivilegedUsers(Data):\n    \"\"\"Table with user that have privileges (normal users and admin).\n\n    If enabled guests are all the authenticated users that do not belong to this table\n    (see config key `auth.authorize_guest_users` and `basic_auth.guest_user`)\n    \"\"\"\n\n    __tablename__ = \"privileged_users\"\n    \"\"\"Name of the table.\"\"\"\n    user = Column(\n        String(100),\n        primary_key=True,\n        sqlite_on_conflict_primary_key=\"REPLACE\",\n    )\n    \"\"\"User name.\"\"\"\n    admin = Column(\n        Boolean, nullable=False, default=False, server_default=sql_false()\n    )\n    \"\"\"Admin flag (true if admin).\"\"\"\n\n    @classmethod\n    def clear(self, config: DictConfig) -> int:\n        \"\"\"Clear table and return deleted rows.\n\n        Args:\n            config (DictConfig): Hydra configuration dictionary.\n\n        Returns:\n            int: deleted rows.\n        \"\"\"\n\n        session = create_session(config)\n        with session:\n            # Clean menu\n            num_rows_deleted = session.execute(delete(self))\n            session.commit()\n            log.info(\n                f\"deleted {num_rows_deleted.rowcount} rows from table '{self.__tablename__}'\"\n            )\n\n        return num_rows_deleted.rowcount\n\n    @classmethod\n    def read_as_df(self, config: DictConfig, **kwargs) -> pd.DataFrame:\n        \"\"\"Read table as pandas DataFrame.\n\n        Args:\n            config (DictConfig): Hydra configuration dictionary.\n\n        Returns:\n            pd.DataFrame: dataframe with table content.\n        \"\"\"\n        df = pd.read_sql_table(\n            self.__tablename__,\n            create_engine(config=config),\n            schema=config.db.get(\"schema\", SCHEMA),\n            **kwargs,\n        )\n\n        return df\n\n    def __repr__(self) -> str:\n        \"\"\"Simple object representation.\n\n        Returns:\n            str: string representation.\n        \"\"\"\n        return f\"<PRIVILEGED_USER:{self.id}>\"\n
"},{"location":"reference/dlunch/models/#dlunch.models.PrivilegedUsers.__tablename__","title":"__tablename__ class-attribute instance-attribute","text":"
__tablename__ = 'privileged_users'\n

Name of the table.

"},{"location":"reference/dlunch/models/#dlunch.models.PrivilegedUsers.admin","title":"admin class-attribute instance-attribute","text":"
admin = Column(\n    Boolean,\n    nullable=False,\n    default=False,\n    server_default=false(),\n)\n

Admin flag (true if admin).

"},{"location":"reference/dlunch/models/#dlunch.models.PrivilegedUsers.user","title":"user class-attribute instance-attribute","text":"
user = Column(\n    String(100),\n    primary_key=True,\n    sqlite_on_conflict_primary_key=\"REPLACE\",\n)\n

User name.

"},{"location":"reference/dlunch/models/#dlunch.models.PrivilegedUsers.__repr__","title":"__repr__","text":"
__repr__() -> str\n

Simple object representation.

Returns:

Type Description str

string representation.

Source code in dlunch/models.py
def __repr__(self) -> str:\n    \"\"\"Simple object representation.\n\n    Returns:\n        str: string representation.\n    \"\"\"\n    return f\"<PRIVILEGED_USER:{self.id}>\"\n
"},{"location":"reference/dlunch/models/#dlunch.models.PrivilegedUsers.clear","title":"clear classmethod","text":"
clear(config: DictConfig) -> int\n

Clear table and return deleted rows.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description int

deleted rows.

Source code in dlunch/models.py
@classmethod\ndef clear(self, config: DictConfig) -> int:\n    \"\"\"Clear table and return deleted rows.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        int: deleted rows.\n    \"\"\"\n\n    session = create_session(config)\n    with session:\n        # Clean menu\n        num_rows_deleted = session.execute(delete(self))\n        session.commit()\n        log.info(\n            f\"deleted {num_rows_deleted.rowcount} rows from table '{self.__tablename__}'\"\n        )\n\n    return num_rows_deleted.rowcount\n
"},{"location":"reference/dlunch/models/#dlunch.models.PrivilegedUsers.read_as_df","title":"read_as_df classmethod","text":"
read_as_df(config: DictConfig, **kwargs) -> DataFrame\n

Read table as pandas DataFrame.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description DataFrame

dataframe with table content.

Source code in dlunch/models.py
@classmethod\ndef read_as_df(self, config: DictConfig, **kwargs) -> pd.DataFrame:\n    \"\"\"Read table as pandas DataFrame.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        pd.DataFrame: dataframe with table content.\n    \"\"\"\n    df = pd.read_sql_table(\n        self.__tablename__,\n        create_engine(config=config),\n        schema=config.db.get(\"schema\", SCHEMA),\n        **kwargs,\n    )\n\n    return df\n
"},{"location":"reference/dlunch/models/#dlunch.models.Stats","title":"Stats","text":"

Bases: Data

Table with number of users that ate a lunch, grouped by guest type.

Methods:

Name Description __repr__

Simple object representation.

clear

Clear table and return deleted rows.

read_as_df

Read table as pandas DataFrame.

Attributes:

Name Type Description __table_args__

Table arguments, used to create primary key (ON CONFLICT options for composite

__tablename__

Name of the table.

date

Day to which the statistics refers to.

guest

Different kind of guests are identified by the value defined in config files

hungry_people

Number of people that ate in a certain day.

Source code in dlunch/models.py
class Stats(Data):\n    \"\"\"Table with number of users that ate a lunch, grouped by guest type.\"\"\"\n\n    # Primary key handled with __table_args__ because ON CONFLICT for composite\n    # primary key is available only with __table_args__\n    __tablename__ = \"stats\"\n    \"\"\"Name of the table.\"\"\"\n    __table_args__ = (\n        PrimaryKeyConstraint(\n            \"date\", \"guest\", name=\"stats_pkey\", sqlite_on_conflict=\"REPLACE\"\n        ),\n    )\n    \"\"\"Table arguments, used to create primary key (ON CONFLICT options for composite\n    primary key is available only with __table_args__).\"\"\"\n    date = Column(\n        Date,\n        nullable=False,\n        server_default=func.current_date(),\n    )\n    \"\"\"Day to which the statistics refers to.\"\"\"\n    guest = Column(\n        String(20),\n        nullable=True,\n        default=\"NotAGuest\",\n        server_default=\"NotAGuest\",\n    )\n    \"\"\"Different kind of guests are identified by the value defined in config files\n    (see config key `panel.guest_types`).\n    'NotAGuest' is the value used for locals.\n    \"\"\"\n    hungry_people = Column(\n        Integer, nullable=False, default=0, server_default=\"0\"\n    )\n    \"\"\"Number of people that ate in a certain day.\n    different kind of guests are identified by the value in guest column.\n    \"\"\"\n\n    @classmethod\n    def clear(self, config: DictConfig) -> int:\n        \"\"\"Clear table and return deleted rows.\n\n        Args:\n            config (DictConfig): Hydra configuration dictionary.\n\n        Returns:\n            int: deleted rows.\n        \"\"\"\n\n        session = create_session(config)\n        with session:\n            # Clean menu\n            num_rows_deleted = session.execute(delete(self))\n            session.commit()\n            log.info(\n                f\"deleted {num_rows_deleted.rowcount} rows from table '{self.__tablename__}'\"\n            )\n\n        return num_rows_deleted.rowcount\n\n    @classmethod\n    def read_as_df(self, config: DictConfig, **kwargs) -> pd.DataFrame:\n        \"\"\"Read table as pandas DataFrame.\n\n        Args:\n            config (DictConfig): Hydra configuration dictionary.\n\n        Returns:\n            pd.DataFrame: dataframe with table content.\n        \"\"\"\n        df = pd.read_sql_table(\n            self.__tablename__,\n            create_engine(config=config),\n            schema=config.db.get(\"schema\", SCHEMA),\n            **kwargs,\n        )\n\n        return df\n\n    def __repr__(self) -> str:\n        \"\"\"Simple object representation.\n\n        Returns:\n            str: string representation.\n        \"\"\"\n        return f\"<STAT:{self.id} - HP:{self.hungry_people} - HG:{self.hungry_guests}>\"\n
"},{"location":"reference/dlunch/models/#dlunch.models.Stats.__table_args__","title":"__table_args__ class-attribute instance-attribute","text":"
__table_args__ = (\n    PrimaryKeyConstraint(\n        \"date\",\n        \"guest\",\n        name=\"stats_pkey\",\n        sqlite_on_conflict=\"REPLACE\",\n    ),\n)\n

Table arguments, used to create primary key (ON CONFLICT options for composite primary key is available only with table_args).

"},{"location":"reference/dlunch/models/#dlunch.models.Stats.__tablename__","title":"__tablename__ class-attribute instance-attribute","text":"
__tablename__ = 'stats'\n

Name of the table.

"},{"location":"reference/dlunch/models/#dlunch.models.Stats.date","title":"date class-attribute instance-attribute","text":"
date = Column(\n    Date, nullable=False, server_default=current_date()\n)\n

Day to which the statistics refers to.

"},{"location":"reference/dlunch/models/#dlunch.models.Stats.guest","title":"guest class-attribute instance-attribute","text":"
guest = Column(\n    String(20),\n    nullable=True,\n    default=\"NotAGuest\",\n    server_default=\"NotAGuest\",\n)\n

Different kind of guests are identified by the value defined in config files (see config key panel.guest_types). 'NotAGuest' is the value used for locals.

"},{"location":"reference/dlunch/models/#dlunch.models.Stats.hungry_people","title":"hungry_people class-attribute instance-attribute","text":"
hungry_people = Column(\n    Integer, nullable=False, default=0, server_default=\"0\"\n)\n

Number of people that ate in a certain day. different kind of guests are identified by the value in guest column.

"},{"location":"reference/dlunch/models/#dlunch.models.Stats.__repr__","title":"__repr__","text":"
__repr__() -> str\n

Simple object representation.

Returns:

Type Description str

string representation.

Source code in dlunch/models.py
def __repr__(self) -> str:\n    \"\"\"Simple object representation.\n\n    Returns:\n        str: string representation.\n    \"\"\"\n    return f\"<STAT:{self.id} - HP:{self.hungry_people} - HG:{self.hungry_guests}>\"\n
"},{"location":"reference/dlunch/models/#dlunch.models.Stats.clear","title":"clear classmethod","text":"
clear(config: DictConfig) -> int\n

Clear table and return deleted rows.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description int

deleted rows.

Source code in dlunch/models.py
@classmethod\ndef clear(self, config: DictConfig) -> int:\n    \"\"\"Clear table and return deleted rows.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        int: deleted rows.\n    \"\"\"\n\n    session = create_session(config)\n    with session:\n        # Clean menu\n        num_rows_deleted = session.execute(delete(self))\n        session.commit()\n        log.info(\n            f\"deleted {num_rows_deleted.rowcount} rows from table '{self.__tablename__}'\"\n        )\n\n    return num_rows_deleted.rowcount\n
"},{"location":"reference/dlunch/models/#dlunch.models.Stats.read_as_df","title":"read_as_df classmethod","text":"
read_as_df(config: DictConfig, **kwargs) -> DataFrame\n

Read table as pandas DataFrame.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description DataFrame

dataframe with table content.

Source code in dlunch/models.py
@classmethod\ndef read_as_df(self, config: DictConfig, **kwargs) -> pd.DataFrame:\n    \"\"\"Read table as pandas DataFrame.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        pd.DataFrame: dataframe with table content.\n    \"\"\"\n    df = pd.read_sql_table(\n        self.__tablename__,\n        create_engine(config=config),\n        schema=config.db.get(\"schema\", SCHEMA),\n        **kwargs,\n    )\n\n    return df\n
"},{"location":"reference/dlunch/models/#dlunch.models.Users","title":"Users","text":"

Bases: Data

Table with users that placed an order.

Methods:

Name Description __repr__

Simple object representation.

clear

Clear table and return deleted rows.

read_as_df

Read table as pandas DataFrame.

Attributes:

Name Type Description __tablename__

Name of the table.

guest

Guest flag (true if guest).

id

User ID.

lunch_time

User selected lunch time.

orders

Orders connected to each user.

takeaway

Takeaway flag (true if takeaway).

Source code in dlunch/models.py
class Users(Data):\n    \"\"\"Table with users that placed an order.\"\"\"\n\n    __tablename__ = \"users\"\n    \"\"\"Name of the table.\"\"\"\n    id = Column(\n        String(100),\n        primary_key=True,\n        nullable=False,\n    )\n    \"\"\"User ID.\"\"\"\n    guest = Column(\n        String(20),\n        nullable=False,\n        default=\"NotAGuest\",\n        server_default=\"NotAGuest\",\n    )\n    \"\"\"Guest flag (true if guest).\"\"\"\n    lunch_time = Column(String(7), index=True, nullable=False)\n    \"\"\"User selected lunch time.\"\"\"\n    takeaway = Column(\n        Boolean, nullable=False, default=False, server_default=sql_false()\n    )\n    \"\"\"Takeaway flag (true if takeaway).\"\"\"\n    orders = relationship(\n        \"Orders\",\n        back_populates=\"user_record\",\n        cascade=\"all, delete-orphan\",\n        passive_deletes=True,\n    )\n    \"\"\"Orders connected to each user.\"\"\"\n\n    @classmethod\n    def clear(self, config: DictConfig) -> int:\n        \"\"\"Clear table and return deleted rows.\n\n        Args:\n            config (DictConfig): Hydra configuration dictionary.\n\n        Returns:\n            int: deleted rows.\n        \"\"\"\n\n        session = create_session(config)\n        with session:\n            # Clean menu\n            num_rows_deleted = session.execute(delete(self))\n            session.commit()\n            log.info(\n                f\"deleted {num_rows_deleted.rowcount} rows from table '{self.__tablename__}'\"\n            )\n\n        return num_rows_deleted.rowcount\n\n    @classmethod\n    def read_as_df(self, config: DictConfig, **kwargs) -> pd.DataFrame:\n        \"\"\"Read table as pandas DataFrame.\n\n        Args:\n            config (DictConfig): Hydra configuration dictionary.\n\n        Returns:\n            pd.DataFrame: dataframe with table content.\n        \"\"\"\n        df = pd.read_sql_table(\n            self.__tablename__,\n            create_engine(config=config),\n            schema=config.db.get(\"schema\", SCHEMA),\n            **kwargs,\n        )\n\n        return df\n\n    def __repr__(self) -> str:\n        \"\"\"Simple object representation.\n\n        Returns:\n            str: string representation.\n        \"\"\"\n        return f\"<USER:{self.id}>\"\n
"},{"location":"reference/dlunch/models/#dlunch.models.Users.__tablename__","title":"__tablename__ class-attribute instance-attribute","text":"
__tablename__ = 'users'\n

Name of the table.

"},{"location":"reference/dlunch/models/#dlunch.models.Users.guest","title":"guest class-attribute instance-attribute","text":"
guest = Column(\n    String(20),\n    nullable=False,\n    default=\"NotAGuest\",\n    server_default=\"NotAGuest\",\n)\n

Guest flag (true if guest).

"},{"location":"reference/dlunch/models/#dlunch.models.Users.id","title":"id class-attribute instance-attribute","text":"
id = Column(String(100), primary_key=True, nullable=False)\n

User ID.

"},{"location":"reference/dlunch/models/#dlunch.models.Users.lunch_time","title":"lunch_time class-attribute instance-attribute","text":"
lunch_time = Column(String(7), index=True, nullable=False)\n

User selected lunch time.

"},{"location":"reference/dlunch/models/#dlunch.models.Users.orders","title":"orders class-attribute instance-attribute","text":"
orders = relationship(\n    \"Orders\",\n    back_populates=\"user_record\",\n    cascade=\"all, delete-orphan\",\n    passive_deletes=True,\n)\n

Orders connected to each user.

"},{"location":"reference/dlunch/models/#dlunch.models.Users.takeaway","title":"takeaway class-attribute instance-attribute","text":"
takeaway = Column(\n    Boolean,\n    nullable=False,\n    default=False,\n    server_default=false(),\n)\n

Takeaway flag (true if takeaway).

"},{"location":"reference/dlunch/models/#dlunch.models.Users.__repr__","title":"__repr__","text":"
__repr__() -> str\n

Simple object representation.

Returns:

Type Description str

string representation.

Source code in dlunch/models.py
def __repr__(self) -> str:\n    \"\"\"Simple object representation.\n\n    Returns:\n        str: string representation.\n    \"\"\"\n    return f\"<USER:{self.id}>\"\n
"},{"location":"reference/dlunch/models/#dlunch.models.Users.clear","title":"clear classmethod","text":"
clear(config: DictConfig) -> int\n

Clear table and return deleted rows.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description int

deleted rows.

Source code in dlunch/models.py
@classmethod\ndef clear(self, config: DictConfig) -> int:\n    \"\"\"Clear table and return deleted rows.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        int: deleted rows.\n    \"\"\"\n\n    session = create_session(config)\n    with session:\n        # Clean menu\n        num_rows_deleted = session.execute(delete(self))\n        session.commit()\n        log.info(\n            f\"deleted {num_rows_deleted.rowcount} rows from table '{self.__tablename__}'\"\n        )\n\n    return num_rows_deleted.rowcount\n
"},{"location":"reference/dlunch/models/#dlunch.models.Users.read_as_df","title":"read_as_df classmethod","text":"
read_as_df(config: DictConfig, **kwargs) -> DataFrame\n

Read table as pandas DataFrame.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description DataFrame

dataframe with table content.

Source code in dlunch/models.py
@classmethod\ndef read_as_df(self, config: DictConfig, **kwargs) -> pd.DataFrame:\n    \"\"\"Read table as pandas DataFrame.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        pd.DataFrame: dataframe with table content.\n    \"\"\"\n    df = pd.read_sql_table(\n        self.__tablename__,\n        create_engine(config=config),\n        schema=config.db.get(\"schema\", SCHEMA),\n        **kwargs,\n    )\n\n    return df\n
"},{"location":"reference/dlunch/models/#dlunch.models.create_database","title":"create_database","text":"
create_database(\n    config: DictConfig, add_basic_auth_users=False\n) -> None\n

Function to create the database through SQLAlchemy models.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required add_basic_auth_users bool

set to true to add admin and guest users. These users are of interest only if 'basic authentication' is selected. Defaults to False.

False Source code in dlunch/models.py
def create_database(config: DictConfig, add_basic_auth_users=False) -> None:\n    \"\"\"Function to create the database through SQLAlchemy models.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n        add_basic_auth_users (bool, optional): set to true to add admin and guest users.\n            These users are of interest only if 'basic authentication' is selected.\n            Defaults to False.\n    \"\"\"\n    # Create directory if missing\n    log.debug(\"create 'shared_data' folder\")\n    pathlib.Path(config.db.shared_data_folder).mkdir(exist_ok=True)\n\n    # In case the database is not ready use a retry mechanism\n    @tenacity.retry(\n        retry=tenacity.retry_if_exception_type(OperationalError),\n        wait=tenacity.wait_fixed(config.db.create_retries.wait),\n        stop=(\n            tenacity.stop_after_delay(config.db.create_retries.stop.delay)\n            | tenacity.stop_after_attempt(\n                config.db.create_retries.stop.attempts\n            )\n        ),\n    )\n    def _create_database_with_retries(config: DictConfig) -> None:\n        engine = create_engine(config)\n        Data.metadata.create_all(engine)\n\n    # Create tables\n    log.debug(f\"attempt database creation: {config.db.attempt_creation}\")\n    if config.db.attempt_creation:\n        _create_database_with_retries(config)\n\n        # Retries stats\n        log.debug(\n            f\"create database attempts: {_create_database_with_retries.retry.statistics}\"\n        )\n\n    # If requested add users for basic auth (admin and guest)\n    if add_basic_auth_users:\n        log.debug(\"add basic auth standard users\")\n        # If no user exist create the default admin\n        session = create_session(config)\n\n        with session:\n            # Check if admin exists\n            if session.get(Credentials, \"admin\") is None:\n                # Add authorization and credentials for admin\n                auth.add_privileged_user(\n                    user=\"admin\",\n                    is_admin=True,\n                    config=config,\n                )\n                auth.add_user_hashed_password(\n                    user=\"admin\",\n                    password=\"admin\",\n                    config=config,\n                )\n            # Check if guest exists\n            if (\n                session.get(Credentials, \"guest\") is None\n            ) and config.basic_auth.guest_user:\n                # Add only credentials for guest (guest users are not included\n                # in privileged_users table)\n                auth.add_user_hashed_password(\n                    user=\"guest\",\n                    password=\"guest\",\n                    config=config,\n                )\n
"},{"location":"reference/dlunch/models/#dlunch.models.create_engine","title":"create_engine","text":"
create_engine(config: DictConfig) -> Engine\n

Factory function for SQLAlchemy engine.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description Engine

SQLAlchemy engine.

Source code in dlunch/models.py
def create_engine(config: DictConfig) -> Engine:\n    \"\"\"Factory function for SQLAlchemy engine.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        Engine: SQLAlchemy engine.\n    \"\"\"\n    engine = hydra.utils.instantiate(config.db.engine)\n\n    # Change schema with change_execution_options\n    # If schema exist in config.db it will override the schema selected through\n    # the environment variable\n    if \"schema\" in config.db:\n        engine.update_execution_options(\n            schema_translate_map={SCHEMA: config.db.schema}\n        )\n\n    return engine\n
"},{"location":"reference/dlunch/models/#dlunch.models.create_exclusive_session","title":"create_exclusive_session","text":"
create_exclusive_session(config: DictConfig) -> Session\n

Factory function for database exclusive session. Database is locked until the transaction is completed (to be used to avoid race conditions).

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description Session

SQLAlchemy exclusive session.

Note

This function is not used inside Data-Lunch code.

Source code in dlunch/models.py
def create_exclusive_session(config: DictConfig) -> Session:\n    \"\"\"Factory function for database exclusive session.\n    Database is locked until the transaction is completed (to be used to avoid\n    race conditions).\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        Session: SQLAlchemy exclusive session.\n\n    Note:\n        This function is not used inside Data-Lunch code.\n\n    \"\"\"\n    engine = create_engine(config)\n\n    # Alter begin statement\n    @event.listens_for(engine, \"connect\")\n    def _do_connect(dbapi_connection, connection_record):\n        # disable pysqlite's emitting of the BEGIN statement entirely.\n        # also stops it from emitting COMMIT before any DDL.\n        dbapi_connection.isolation_level = None\n\n    @event.listens_for(engine, \"begin\")\n    def _do_begin(conn):\n        # Emit exclusive BEGIN\n        conn.exec_driver_sql(\"BEGIN EXCLUSIVE\")\n\n    session = Session(engine)\n\n    return session\n
"},{"location":"reference/dlunch/models/#dlunch.models.create_session","title":"create_session","text":"
create_session(config: DictConfig) -> Session\n

Factory function for database session.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description Session

SQLAlchemy session.

Source code in dlunch/models.py
def create_session(config: DictConfig) -> Session:\n    \"\"\"Factory function for database session.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        Session: SQLAlchemy session.\n    \"\"\"\n    engine = create_engine(config)\n    session = Session(engine)\n\n    return session\n
"},{"location":"reference/dlunch/models/#dlunch.models.get_db_dialect","title":"get_db_dialect","text":"
get_db_dialect(\n    db_obj: Session | Connection | Connection,\n) -> str\n

Return database type (postgresql, sqlite, etc.) based on the database object passed as input. If a session is passed, the database type is set based on an internal map (see models._DBTYPE_MAP).

Parameters:

Name Type Description Default db_obj Session | Connection | Connection

session or connection object.

required

Raises:

Type Description TypeError

db_obj shall be a session or a connection object.

Returns:

Type Description str

database dialect.

Source code in dlunch/models.py
def get_db_dialect(\n    db_obj: Session | ConnectionSqlite | ConnectionPostgresql,\n) -> str:\n    \"\"\"Return database type (postgresql, sqlite, etc.) based on the database object passed as input.\n    If a session is passed, the database type is set based on an internal map (see `models._DBTYPE_MAP`).\n\n    Args:\n        db_obj (Session | ConnectionSqlite | ConnectionPostgresql): session or connection object.\n\n    Raises:\n        TypeError: db_obj shall be a session or a connection object.\n\n    Returns:\n        str: database dialect.\n    \"\"\"\n    if isinstance(db_obj, Session):\n        dialect = db_obj.bind.dialect.name\n    elif isinstance(db_obj, ConnectionSqlite) or isinstance(\n        db_obj, ConnectionPostgresql\n    ):\n        module = db_obj.__class__.__module__.split(\".\", 1)[0]\n        dialect = _MODULE_TO_DIALECT_MAP[module]\n    else:\n        raise TypeError(\"db_obj should be a session or connection object\")\n\n    return dialect\n
"},{"location":"reference/dlunch/models/#dlunch.models.get_flag","title":"get_flag","text":"
get_flag(\n    config: DictConfig,\n    id: str,\n    value_if_missing: bool | None = None,\n) -> bool | None\n

Get the value of a flag. Optionally select the values to return if the flag is missing (default to None).

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required id str

flag id (name).

required value_if_missing bool | None

value to return if the flag does not exist. Defaults to None.

None

Returns:

Type Description bool | None

flag value.

Source code in dlunch/models.py
def get_flag(\n    config: DictConfig, id: str, value_if_missing: bool | None = None\n) -> bool | None:\n    \"\"\"Get the value of a flag.\n    Optionally select the values to return if the flag is missing (default to None).\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n        id (str): flag id (name).\n        value_if_missing (bool | None, optional): value to return if the flag does not exist. Defaults to None.\n\n    Returns:\n        bool | None: flag value.\n    \"\"\"\n\n    session = create_session(config)\n    flag = session.get(Flags, id)\n    if flag is None:\n        value = value_if_missing\n    else:\n        value = flag.value\n    return value\n
"},{"location":"reference/dlunch/models/#dlunch.models.session_add_with_upsert","title":"session_add_with_upsert","text":"
session_add_with_upsert(\n    session: Session,\n    constraint: str,\n    new_record: Stats | Flags,\n) -> None\n

Use an upsert statement for postgresql to add a new record to a table, a simple session add otherwise.

Parameters:

Name Type Description Default session Session

SQLAlchemy session object.

required constraint str

constraint used for upsert (usually the primary key)

required new_record Stats | Flags

table resord (valid tables are stats or flags)

required Source code in dlunch/models.py
def session_add_with_upsert(\n    session: Session, constraint: str, new_record: Stats | Flags\n) -> None:\n    \"\"\"Use an upsert statement for postgresql to add a new record to a table,\n    a simple session add otherwise.\n\n    Args:\n        session (Session): SQLAlchemy session object.\n        constraint (str): constraint used for upsert (usually the primary key)\n        new_record (Stats | Flags): table resord (valid tables are `stats` or `flags`)\n    \"\"\"\n    # Use an upsert for postgresql (for sqlite an 'on conflict replace' is set\n    # on table, so session.add is fine)\n    if get_db_dialect(session) == \"postgresql\":\n        insert_statement = postgresql_upsert(new_record.__table__).values(\n            {\n                column.name: getattr(new_record, column.name)\n                for column in new_record.__table__.c\n                if getattr(new_record, column.name, None) is not None\n            }\n        )\n        upsert_statement = insert_statement.on_conflict_do_update(\n            constraint=constraint,\n            set_={\n                column.name: getattr(insert_statement.excluded, column.name)\n                for column in insert_statement.excluded\n            },\n        )\n        session.execute(upsert_statement)\n    else:\n        session.add(new_record)\n
"},{"location":"reference/dlunch/models/#dlunch.models.set_flag","title":"set_flag","text":"
set_flag(config: DictConfig, id: str, value: bool) -> None\n

Set a key,value pair inside flag table.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required id str

flag id (name).

required value bool

flag value.

required Source code in dlunch/models.py
def set_flag(config: DictConfig, id: str, value: bool) -> None:\n    \"\"\"Set a key,value pair inside `flag` table.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n        id (str): flag id (name).\n        value (bool): flag value.\n    \"\"\"\n\n    session = create_session(config)\n\n    with session:\n        # Write the selected flag (it will be overwritten if exists)\n        new_flag = Flags(id=id, value=value)\n\n        # Use an upsert for postgresql, a simple session add otherwise\n        session_add_with_upsert(\n            session=session, constraint=\"flags_pkey\", new_record=new_flag\n        )\n\n        session.commit()\n
"},{"location":"reference/dlunch/models/#dlunch.models.set_sqlite_pragma","title":"set_sqlite_pragma","text":"
set_sqlite_pragma(dbapi_connection, connection_record)\n

Force foreign key constraints for sqlite connections.

Parameters:

Name Type Description Default dbapi_connection Any

connection to database. Shall have a cursor method.

required connection_record Any

connection record (not used).

required Source code in dlunch/models.py
@event.listens_for(Engine, \"connect\")\ndef set_sqlite_pragma(dbapi_connection, connection_record):\n    \"\"\"Force foreign key constraints for sqlite connections.\n\n    Args:\n        dbapi_connection (Any): connection to database. Shall have a `cursor` method.\n        connection_record (Any): connection record (not used).\n    \"\"\"\n    if get_db_dialect(dbapi_connection) == \"sqlite\":\n        cursor = dbapi_connection.cursor()\n        cursor.execute(\"PRAGMA foreign_keys=ON;\")\n        cursor.close()\n
"},{"location":"reference/dlunch/scheduled_tasks/","title":"scheduled_tasks","text":"

Module with functions used to execute scheduled tasks.

See https://panel.holoviz.org/how_to/callbacks/schedule.html for details.

Modules:

Name Description auth

Module with classes and functions used for authentication and password handling.

cloud

Module with functions to interact with GCP storage service.

models

Module with database tables definitions.

Functions:

Name Description clean_files_db

Return a callable object for cleaning temporary tables and files.

reset_guest_user_password

Return a callable object for resetting guest user password.

upload_db_to_gcp_storage

Return a callable object for uploading database to google cloud storage.

Attributes:

Name Type Description log Logger

Module logger.

"},{"location":"reference/dlunch/scheduled_tasks/#dlunch.scheduled_tasks.log","title":"log module-attribute","text":"
log: Logger = getLogger(__name__)\n

Module logger.

"},{"location":"reference/dlunch/scheduled_tasks/#dlunch.scheduled_tasks.clean_files_db","title":"clean_files_db","text":"
clean_files_db(config: DictConfig) -> callable\n

Return a callable object for cleaning temporary tables and files.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description callable

function to be scheduled.

Source code in dlunch/scheduled_tasks.py
def clean_files_db(config: DictConfig) -> callable:\n    \"\"\"Return a callable object for cleaning temporary tables and files.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        callable: function to be scheduled.\n    \"\"\"\n\n    async def scheduled_function() -> None:\n        \"\"\"Clean menu, orders, users and flags tables. Delete also local files.\"\"\"\n        log.info(f\"clean task (files and db) executed at {dt.datetime.now()}\")\n        # Delete files\n        waiter.delete_files()\n        # Clean tables\n        waiter.clean_tables()\n\n    return scheduled_function\n
"},{"location":"reference/dlunch/scheduled_tasks/#dlunch.scheduled_tasks.reset_guest_user_password","title":"reset_guest_user_password","text":"
reset_guest_user_password(config: DictConfig) -> callable\n

Return a callable object for resetting guest user password.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description callable

function to be scheduled.

Source code in dlunch/scheduled_tasks.py
def reset_guest_user_password(config: DictConfig) -> callable:\n    \"\"\"Return a callable object for resetting guest user password.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        callable: function to be scheduled.\n    \"\"\"\n\n    async def scheduled_function() -> None:\n        \"\"\"Reset guest user password.\"\"\"\n        log.info(f\"reset guest user password executed at {dt.datetime.now()}\")\n        # Change reset flag\n        models.set_flag(\n            config=config, id=\"reset_guest_user_password\", value=True\n        )\n        # Reset password\n        auth.set_guest_user_password(config)\n\n    return scheduled_function\n
"},{"location":"reference/dlunch/scheduled_tasks/#dlunch.scheduled_tasks.upload_db_to_gcp_storage","title":"upload_db_to_gcp_storage","text":"
upload_db_to_gcp_storage(\n    config: DictConfig, **kwargs\n) -> callable\n

Return a callable object for uploading database to google cloud storage.

Parameters:

Name Type Description Default config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description callable

function to be scheduled.

Source code in dlunch/scheduled_tasks.py
def upload_db_to_gcp_storage(config: DictConfig, **kwargs) -> callable:\n    \"\"\"Return a callable object for uploading database to google cloud storage.\n\n    Args:\n        config (DictConfig): Hydra configuration dictionary.\n\n    Returns:\n        callable: function to be scheduled.\n    \"\"\"\n\n    async def scheduled_function() -> None:\n        \"\"\"Upload database to GCP storage.\"\"\"\n        log.info(\n            f\"upload database to gcp storage executed at {dt.datetime.now()}\"\n        )\n        # Upload database\n        cloud.upload_to_gcloud(**kwargs)\n\n    return scheduled_function\n
"},{"location":"reference/dlunch/conf/","title":"conf","text":"

Package with Hydra configuration yaml files.

The following Hydra configuration groups ae available:

  • panel: main Panel configurations (text used in menu and tables, scheduled tasks, other graphic user interface options).
  • db: database dialect (sqlite or postgresql) and specific queries, upload of db to external storage (sqlite only), db table creation at start-up.
  • server: Panel server options and server-level authentication options (basic auth or OAuth).
  • auth: main authentication and authorization options.
  • basic_auth: optional configuration group that add configurations required by basic authentication.
  • hydra/job_logging: override Hydra default logging handlers.
"}]} \ No newline at end of file