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.
- 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?
- 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()