Disable Form/Clear Input

Using Streamlit 1.33.0 on a local Ubuntu machine at the moment. Iā€™m so close on getting this upload form to work. Iā€™ve got 2 things that Iā€™m struggling with at the moment, even after reading multiple posts on these 2 issues.

  1. When uploading a large file I didnā€™t want the form to be ā€œeditableā€ so I found some code that shows how to disable the entry fields when the ā€œSubmitā€ button is clicked. However, I have 2 edits that must be done before a file can be uploaded:
    1 - All fields are required (couldnā€™t find a way to force a value)
    2 - Order number is valid

So after the ā€œUploadā€ button is pressed I checked these 2 conditions and use streamlit.error() to show an error message. But since I disabled the form fields when the Submit button was pressed I thought I should re-enable by setting the session variable to ā€œFalseā€. That didnā€™t work and my guess is the application doesnā€™t run in itā€™s entirety when streamlit.error() is called. Should I grow my own error message text box in this case or is there another way to handle that?

  1. After successfully uploading a file I want to clear the fields. Since Iā€™m doing error checking (see first issue) I donā€™t use the ā€œclear_on_submitā€ on the streamlit.form() call. Iā€™ve seen posts where you use the ā€œkeyā€ on the input elements and clear those values in the session_state. Is that the preferred method? If so, does anyone have a good working example? I searched and found a few but they werenā€™t quite exactly what I was looking for.

Hereā€™s my code this far. Note that there are calls to odoo. functions that are not included, those are working fine.

import streamlit as st
from odoo import Odoo
import boto3
import hashlib
import mimetypes
import time

# Clear the session
def clear_session(session):
    if 'uid' in session:
        del session['uid']

# Get a list of Odoo attachment categories
def get_categories():

    category_list = {}
    odoo = st.session_state['odoo']
    results = odoo.execute_kw('attachment.type','search_read', [[]], {'fields': ['id','name']})
    for result in results:
        category_list[result['id']] = result['name']
    st.session_state['category_list'] = category_list

# Format the description for the category based on the Odoo category ID
def format_category(category):
    return st.session_state['category_list'][category]

def check_login_status(session_state):
    pass

def disable():
    st.session_state.submit_disabled = True

# Set the standard image 
st.image("TMG_LOGO_2_COLOR.png", width=250)

if "submit_disabled" not in st.session_state:
    st.session_state.submit_disabled = False

# If not logged in
if 'uid' not in st.session_state:
    with st.form("Login"):
        user = st.text_input("Odoo User")
        password = st.text_input("Odoo Passowrd", type='password')
        submit_form = st.form_submit_button("Submit")

        if submit_form:
            if not user or not password:
                st.error("User and password required")
                # st.rerun()
            else:

                # Get the url and database for Odoo connection
                url = st.secrets.odoo.url
                db = st.secrets.odoo.database
                # Try logging in to Odoo with the supplied credentials
                try:
                    odoo = Odoo(url, db, user, password)
                    st.session_state['odoo'] = odoo
                    # Successful login, get the attachment category list
                    get_categories()
                    # Also need to get the bucket configuration for sale order attachments
                    # Will need the credentials, url and bucket name
                    results = odoo.execute_kw('ir.model','search_read',[[('model','=','sale.order')]], {'fields': ['id']})
                    if results:
                        sale_order_id = results[0]['id']
                        results = odoo.execute_kw('pr1_s3.bucket_filter','search_read',
                                                    [[('res_model','=',sale_order_id)]],{'fields': ['s3_connection_id']})
                        if results:
                            s3_connection_id = results[0]['s3_connection_id'][0]
                            results = odoo.execute_kw('pr1_s3.s3_connection','search_read',
                                                      [[('id','=',s3_connection_id)]],
                                                      {'fields': ['s3_bucket_name','s3_access_key','s3_secret_key','s3_api_url']})
                            if results:
                                st.session_state['s3_connection_id'] = s3_connection_id
                                st.session_state['s3_bucket_name'] = results[0]['s3_bucket_name']
                                st.session_state['s3_access_key'] = results[0]['s3_access_key']
                                st.session_state['s3_secret_key'] = results[0]['s3_secret_key']
                                st.session_state['s3_api_url'] = results[0]['s3_api_url']
                    # Set the Odoo user id in the session state
                    st.session_state['uid'] = odoo.uid
                    st.rerun()
                except Exception as e:
                    st.error("Unable to log into Odoo, verify your connection status and credentials")
# User is logged in, show upload file form
else:

    with st.form("Upload File"):
        st.title("Odoo File Upload")
        st.text(f"Welcome {st.session_state['odoo'].username}!")
        order = st.text_input("Sale Order Number", help="Enter or scan Odoo sale order number", 
                              key="order",
                              disabled=st.session_state.submit_disabled)
        file = st.file_uploader("Select File", disabled=st.session_state.submit_disabled)
        category = st.selectbox("Attachment Type", options=list(st.session_state['category_list'].keys()), 
                                format_func=format_category, help="Select an attachment category", 
                                key="category",
                                disabled=st.session_state.submit_disabled)
        submit_form = st.form_submit_button("Upload", on_click=disable, disabled=st.session_state.submit_disabled)

        if submit_form:
            input_order = order
            input_category = category
            if not input_order or not file or not input_category:
                st.session_state.submit_disabled = False
                st.error("Order number, file to upload and category is required")
            else:
                odoo = st.session_state['odoo']
                # Verify the order number exists
                results = odoo.execute_kw('sale.order','search_read',
                                          [[('name','=',input_order)]], {'fields': ['id','company_id']})
                if not results:
                    st.session_state.submit_disabled = False
                    st.error(f"Order {input_order} not found")
                else:
                    order_id = results[0]['id']
                    company_id = results[0]['company_id'][0]
                    file_content = file.getvalue()
                    wasabi = boto3.resource('s3',aws_access_key_id=st.session_state['s3_access_key'],
                                            aws_secret_access_key=st.session_state['s3_secret_key'], 
                                            endpoint_url=st.session_state['s3_api_url'])
                    wasabi_bucket = wasabi.Bucket(st.session_state['s3_bucket_name'])
                    wasabi_object = f"{input_order}-{order_id}/{file.name}"
                    # wasabi.meta.client.upload_file(file.upload_url, wasabi_bucket, wasabi_object)                    
                    wasabi.meta.client.upload_fileobj(file, st.session_state['s3_bucket_name'], wasabi_object)

                    # Write an attachment record to Odoo for the sale order, setting the URL
                    # based on what was uploaded to S3
                    wasabi_url = st.session_state['s3_api_url'] + "/" + st.session_state['s3_bucket_name'] + "/" + wasabi_object
                    checksum = hashlib.md5()
                    checksum.update(file_content)
                    mime_type = mimetypes.guess_type(file.name)
                    vals = {
                        'name': file.name,
                        'store_fname': file.name,
                        'res_model': 'sale.order',
                        'res_id': order_id,
                        'company_id': company_id,
                        'type': 'url',
                        'url': wasabi_url,
                        'public': True,
                        'mimetype': mime_type[0],
                        'index_content': 'image',
                        's3_connection_id' : st.session_state['s3_connection_id'],
                        'is_in_s3': True,
                        'order_id': order_id,
                        'attachment_category': [(4, input_category, False)],
                    }
                    id = odoo.execute_kw(model='ir.attachment', operation='create', domain=[vals])
                    # Set stored file name, size and checksum
                    vals = [[id], {'store_fname': wasabi_object,
                                   'file_size': file.size,
                                   'checksum': checksum.hexdigest()}]
                    odoo.execute_kw(model='ir.attachment', operation='write', domain=vals)
                    st.session_state.submit_disabled = False

                    # Notify user of successful upload
                    st.toast(f"File {file.name} uploaded to {input_order}")
                    time.sleep(2)
                    
                    st.rerun()

    if st.button("Logout"):
        clear_session(st.session_state)
        st.rerun()

Here is a working, toy example. It doesnā€™t sound like your question has anything to do with the authentication portion, so this just focused on the idea of having a form where you can validate the submission before accepting it and clearing the fields:

import streamlit as st

def lock():
    st.session_state.lock = True

def validated_submission():
    st.write("Provide three non-negative integers that sum to 10")
    a = st.number_input("A", value=None, min_value=0, max_value=10, disabled=st.session_state.lock, key=f"A_{st.session_state.attempt}")
    b = st.number_input("B", value=None, min_value=0, max_value=10, disabled=st.session_state.lock, key=f"B_{st.session_state.attempt}")
    c = st.number_input("C", value=None, min_value=0, max_value=10, disabled=st.session_state.lock, key=f"C_{st.session_state.attempt}")
    submit = st.button("Submit", on_click=lock)
    if "status" in st.session_state:
        st.error(st.session_state.status)
    if submit:
        # Check that all fields are filled
        if a is None or b is None or c is None:
            st.session_state.status = "A value is missing. Please fill in all fields."
            st.session_state.lock = False
            st.rerun()
        # Validate sum
        elif a + b + c != 10:
            st.session_state.status = "The sum of values is not 10. Please update the values."
            st.session_state.lock = False
            st.rerun()
        # Accept the submission
        else:
            st.session_state.A = a
            st.session_state.B = b
            st.session_state.C = c
            st.session_state.lock = False
            if "status" in st.session_state:
                del st.session_state.status
            st.session_state.attempt += 1
            st.rerun()

def delete_submission():
    del st.session_state.A
    del st.session_state.B
    del st.session_state.C

if "attempt" not in st.session_state:
    st.session_state.attempt = 1
if "lock" not in st.session_state:
    st.session_state.lock = False

st.write("Lorem ipsum")

validated_submission()

if "A" in st.session_state:
    st.write("Submitted answers")
    st.write(f"A = {st.session_state.A}")
    st.write(f"B = {st.session_state.B}")
    st.write(f"C = {st.session_state.C}")

st.button("Clear data", on_click=delete_submission)

st.write("Lorem ipsum")

@mathcatsand thank you so much for this example! This definitely helps and I feel like I have a better understanding of some best practices when it comes to writing streamlit apps.

@mathcatsand one question on your example. The use of ā€œkeyā€ on the form widgets; I read the documentation and it does mention the key but does the ā€œkeyā€ attribute basically cause cached values to be set on the widgetā€™s input box? So incrementing the attempt by 1 after every successful submission causes a new cached key to be used? Am I understanding that correctly?

And can these ā€œkeysā€ be removed manually or is that handed strictly by streamlit?

Itā€™s different from ā€œcached valuesā€ but in spirit, yes. There are two reasons I used the incrementation to clear the widgets:

  1. You can clear a group of widgets by just changing that one number as opposed to clearing each one individually.
  2. This method is compatible with st.file_uploader which doesnā€™t have some other explicit way to clear it.

If you remove the keys from the widgets, they will reset the same as if you had incremented their keys.

1 Like

This topic was automatically closed 2 days after the last reply. New replies are no longer allowed.