From a6578f6487eefcadbe699d2d331af9d0d902cd78 Mon Sep 17 00:00:00 2001 From: "j.aschauer" Date: Tue, 12 Dec 2023 19:45:29 +0100 Subject: [PATCH 1/3] reject user input for missing data points in input data --- app/ptxboa_functions.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/app/ptxboa_functions.py b/app/ptxboa_functions.py index fd2594c1..a144ac3c 100644 --- a/app/ptxboa_functions.py +++ b/app/ptxboa_functions.py @@ -3,6 +3,7 @@ from pathlib import Path from typing import Literal +import numpy as np import pandas as pd import streamlit as st @@ -435,7 +436,9 @@ def register_user_changes( columns: str, values: str, df_tab: pd.DataFrame, + df_orig: pd.DataFrame, key: str, + editor_key: str, ): """ Register all user changes in the session state variable "user_changes_df". @@ -444,13 +447,25 @@ def register_user_changes( """ # convert session state dict to dataframe: # Create a list of dictionaries - data_dict = st.session_state[key]["edited_rows"] + data_dict = st.session_state[editor_key]["edited_rows"] if any(data_dict): data_list = [] + rejected_changes = False for k, v in data_dict.items(): for c_name, value in v.items(): - data_list.append({index: k, columns: c_name, values: value}) + if np.isnan(df_orig.iloc[k, :][c_name]): + st.toast("Cannot modify empty value") + rejected_changes = True + else: + data_list.append({index: k, columns: c_name, values: value}) + + if rejected_changes: + # modify key number + st.session_state[f"{key}_number"] += 1 + + if len(data_list) == 0: + return # Convert the list to a DataFrame res = pd.DataFrame(data_list) @@ -529,6 +544,7 @@ def display_and_edit_input_data( pd.DataFrame """ df = get_data_type_from_input_data(api, data_type=data_type, scope=scope) + df_orig = df.copy() if data_type in [ "electricity_generation", @@ -605,6 +621,9 @@ def display_and_edit_input_data( for c in df.columns } + if f"{key}_number" not in st.session_state: + st.session_state[f"{key}_number"] = 0 + editor_key = f"{key}_{st.session_state[f'{key}_number']}" # if editing is enabled, store modifications in session_state: if st.session_state["edit_input_data"]: with st.form(key=f"{key}_form"): @@ -625,7 +644,7 @@ def display_and_edit_input_data( df = st.data_editor( df, use_container_width=True, - key=key, + key=editor_key, num_rows="fixed", disabled=[index], column_config=column_config, @@ -641,7 +660,9 @@ def display_and_edit_input_data( "columns": columns, "values": "value", "df_tab": df, + "df_orig": df_orig, "key": key, + "editor_key": editor_key, }, ) else: From 4b6e9bd352f5299744a1ba216d3d539ecef3722f Mon Sep 17 00:00:00 2001 From: "j.aschauer" Date: Wed, 13 Dec 2023 10:00:36 +0100 Subject: [PATCH 2/3] document session state variables connected to data editing form --- app/ptxboa_functions.py | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/app/ptxboa_functions.py b/app/ptxboa_functions.py index a144ac3c..14e4349b 100644 --- a/app/ptxboa_functions.py +++ b/app/ptxboa_functions.py @@ -455,7 +455,11 @@ def register_user_changes( for k, v in data_dict.items(): for c_name, value in v.items(): if np.isnan(df_orig.iloc[k, :][c_name]): - st.toast("Cannot modify empty value") + msg = ( + f":exclamation: Cannot modify empty value '{c_name}' " + f"for '{df_orig.index[k]}'" + ) + st.toast(msg) rejected_changes = True else: data_list.append({index: k, columns: c_name, values: value}) @@ -537,7 +541,21 @@ def display_and_edit_input_data( data type "conversion_processes" and "transportation_processes" which is not region specific. key : str - A key for the data editing streamlit widget. Need to be unique. + A key for the data editing layout element. Needs to be unique in the app. + Several session state variables are derived from this key:: + + - st.session_state[f"{key}_number"]: + This is initialized with 0 and incremented by 1 whenever any input + value got rejected by the callback function + :func:`register_user_changes`. This will trigger a re-rendering of the + data_editor widget and thus reset modifications on empty values. + - st.session_state[f"{key}_form"]: + the key for the form the editor lives in + - st_session_state[f"{key}_{st.session_state[f'{key}_number']}"]: + the name of this session state variable consists of the `key` and the + current `{key}_number`. It refers to the st.data_editor widget. + Whenever the key_number changes, the editor widget gets a new key and + is initialized from scratch. Returns ------- @@ -621,11 +639,12 @@ def display_and_edit_input_data( for c in df.columns } - if f"{key}_number" not in st.session_state: - st.session_state[f"{key}_number"] = 0 - editor_key = f"{key}_{st.session_state[f'{key}_number']}" # if editing is enabled, store modifications in session_state: if st.session_state["edit_input_data"]: + if f"{key}_number" not in st.session_state: + st.session_state[f"{key}_number"] = 0 + editor_key = f"{key}_{st.session_state[f'{key}_number']}" + with st.form(key=f"{key}_form"): st.info( ( From c9d6faf3e77b735e62026a4b98d70a2ce99065f9 Mon Sep 17 00:00:00 2001 From: "j.aschauer" Date: Wed, 13 Dec 2023 10:02:13 +0100 Subject: [PATCH 3/3] remove warning about editing empty data points --- app/ptxboa_functions.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/app/ptxboa_functions.py b/app/ptxboa_functions.py index 14e4349b..517657cb 100644 --- a/app/ptxboa_functions.py +++ b/app/ptxboa_functions.py @@ -653,13 +653,6 @@ def display_and_edit_input_data( ) ) - if df.isna().any().any(): - # TODO: remove this warning when input data is fixed. - msg = ( - "Draft version: Do no edit empty data points (None), this will " - "throw an error. Missing data has to be filled before publishing." - ) - st.warning(msg) df = st.data_editor( df, use_container_width=True,