Downloading files by pressing st.form_submit_button

Hi all! In my application, I needed to combine 2 functions at once in st.form_submit_button: 1) updating the data in the form; 2) downloading a file (without additional clicking on a button or link, but immediately). How can I implement this?

1 Like

Hey @konshinmitya,

Can you share a code snippet to show what you’ve tried so far to implement this? Then we can make suggestions from there.

1 Like

Unfortunately, I deleted all the code with attempts, as they did not work. I didn’t get very far anyway, as I don’t know HTML and CSS very well. I am a beginner programmer. I tried various solutions from the Internet and even consulted ChatGPT, but the best result that I achieved was the generation of a link to download the file by clicking st.form_button_submit. My goal is to remove the extra button click to download the file. This one of try:

import streamlit as st

def download_link(object_to_download, download_filename, download_link_text):
    b64 = base64.b64encode(object_to_download).decode()
    href = f'<a href="data:application/vnd.openxmlformats-officedocument.wordprocessingml.document;base64,{b64}" download="{download_filename}">{download_link_text}</a>'
    return href

with st.form(key='download-form'):
    st.write('Нажмите кнопку, чтобы скачать файл')
    if st.form_submit_button('Скачать'):
        href = download_link(open('ТКП PFFS 2 CDM1-2 + CDM S.docx', 'rb').read(), 'example.docx', 'Скачать файл')
        file_path = "1.docx"
        st.markdown(href, unsafe_allow_html=True)
1 Like

Hi @konshinmitya, welcome to the forum! :wave: :partying_face:

Your post looks like a duplicate of Automatic Download / Select and Download File with Single Button Click - #4 by snehankekre

It looks like you’re trying to:

  1. open an existing .docx file from disk,
  2. ask the user to manipulate the contents of the file in a form, and
  3. expect the user to click the form submit button to download another .docx file containing the edited content.

Here’s how you can do that. I assume there exists a Test.docx file containing “Hello world”, and use python-docx to work with .docx files:

import base64
from io import BytesIO

import docx
import streamlit.components.v1 as components
from docx import Document
import streamlit as st

def download_button(object_to_download, download_filename):
    """
    Generates a link to download the given object_to_download.
    Params:
    ------
    object_to_download:  The object to be downloaded.
    download_filename (str): filename and extension of file. e.g. mydata.docx,
    Returns:
    -------
    (str): the anchor tag to download object_to_download
    """
    try:
        # some strings <-> bytes conversions necessary here
        b64 = base64.b64encode(object_to_download.encode()).decode()

    except AttributeError as e:
        b64 = base64.b64encode(object_to_download).decode()

    dl_link = f"""
    <html>
    <head>
    <title>Start Auto Download file</title>
    <script src="http://code.jquery.com/jquery-3.2.1.min.js"></script>
    <script>
    $('<a href="data:application/vnd.openxmlformats-officedocument.wordprocessingml.document;base64,{b64}" download="{download_filename}">')[0].click()
    </script>
    </head>
    </html>
    """
    return dl_link

def load_sample_doc(path):
    # doc = Document(path)
    # return doc
    # You can alternatively use the above to avoid file handling
    f = open(path, "rb")
    document = Document(f)
    f.close()
    return document

def download_docx():
    edited_doc = doc.add_paragraph(st.session_state.paragraph)
    buff = BytesIO()  # create a buffer
    doc.save(buff)  # write the docx to the buffer
    components.html(
        download_button(buff.getvalue(), st.session_state.filename),
        height=0,
    )

doc = load_sample_doc(path="Test.docx")

with st.form("my_form", clear_on_submit=False):
    st.text_area("Add paragraph", key="paragraph")
    st.text_input("Filename (must include .docx)", key="filename")
    submit = st.form_submit_button("Download docx", on_click=download_docx)

auto-download-docx

3 Likes

t works great! Thanks a lot! But I have one problem: it works on my local server, however when I upload to streamlit cloud the file stops downloading. What could be the problem?

1 Like

What does it do instead of downloading?

2 Likes

Just confirms the form and that’s it. A small space also appears, the code of which is shown in the screenshot. But this was also the case in the local version.

1 Like

Without looking at the source code, it’s non-trivial to diagnose the issue. Please your app’s GitHub repo.

1 Like

I tried to remove all the unimportant code. Left the file creation process. The PBS function is called in St.tabs, which is located in st.expander (if this is of course important).Everything works in the local version.

import numpy as np
from matplotlib import pyplot as plt
import scipy.interpolate, scipy.optimize
import math
import pandas as pd
from numpy.polynomial import Polynomial as P
import streamlit as st
from bs4 import BeautifulSoup
import requests
import plotly
import plotly.graph_objs as go
from plotly.subplots import make_subplots
import datetime
from docxtpl import DocxTemplate
import re
from st_aggrid import GridOptionsBuilder, AgGrid, GridUpdateMode, DataReturnMode
from PIL import Image
#import extra_streamlit_components as stx
#from st_btn_select import st_btn_select
from streamlit_extras.mention import mention
import base64
from io import BytesIO
import docx
import streamlit.components.v1 as components
from docx import Document

def download_button(object_to_download, download_filename):
    try:
        # some strings <-> bytes conversions necessary here
        b64 = base64.b64encode(object_to_download.encode()).decode()

    except AttributeError as e:
        b64 = base64.b64encode(object_to_download).decode()

    dl_link = f"""
    <html>
    <head>
    <title>Start Auto Download file</title>
    <script src="http://code.jquery.com/jquery-3.2.1.min.js"></script>
    <script>
    $('<a href="data:application/vnd.openxmlformats-officedocument.wordprocessingml.document;base64,{b64}" download="{download_filename}">')[0].click()
    </script>
    </head>
    </html>
    """
    return dl_link


def load_sample_doc(path):
    f = open(path, "rb")
    document = Document(f)
    f.close()
    return document


def download_docx(file_name):
    doc = load_sample_doc(path=f"{file_name}")
    buff = BytesIO()  # create a buffer
    doc.save(buff)  # write the docx to the buffer
    components.html(
        download_button(buff.getvalue(), f'{file_name}'),
        height=0,
    )

def PBS(k, pump_model):
    with st.form(f'{k} Лист данных'):
 
        doc = DocxTemplate(tpl)

        if pump_type == 'NES':
            context = {'Name': name_station, 'Date': date_object, 'N': n, 'NR': NR, 'Q': q, 'H': H, 'ПП': Hp, 'PN': PN,
                       'Dcol': 'DN' + str(dcol), 'Model': pump_model, 'TypeCol': type_col, 'MatCol': mat_col,
                       'KPD': '-',
                       'Мощность': power, 'МощностьК': power_c,
                       'ContrType': type_contr, 'IP': ip, 'PDG': pd_g_q, 'PDH': pd_h_q, 'PDGmodel': pd_g_m,
                       'PDHmodel': pd_h_m, 'Nzap': n_zap, 'n': n_ob, 'NM': nm, 'NRazd': n_razd, 'NDS': nds,
                       'NRR': nrr, 'DNP1': dnp1, 'DNP2': dnp2}

        doc.render(context)

        doc.replace_pic('pump_curve.png', f"Curve{p_q}{p_h}.png")

        if pump_type != 'NES':
            doc.replace_pic('station_curve.png', f"Curves{p_q}{p_h}.png")

        file_name = "ТКП " + name_station.replace('/', '') + ".docx"

        doc.save(file_name)

        submit = st.form_submit_button('Подтвердить', type='primary')
        if submit:
            download_docx(file_name)

1 Like

I can confirm that your code works in localhost but not in streamlit cloud. Pressing the button does nothing. Nothing is logged or displayed either.

There are messages in the developer console that I won’t pretend to understand but this one might be related:

[Warning] [blocked] The page at about:srcdoc was not allowed to run insecure content from http://code.jquery.com/jquery-3.2.1.min.js.

https://goyodiaz-download-app-9i894x.streamlit.app/
https://github.com/goyodiaz/download

1 Like

Import your jquery with ‘https’ instead of ‘http’. It worked for me.

2 Likes