Download data to excel button - works locally but not on website

Hello, I am able to download the desired data on the button click action when running locally (based on this Allow users to dowload an Excel in a click - #2 by chriddyp).

However, when running this on a webserver I simply end up at the download url and nothing happens.

Here is the dash layout

html.Div(
    style={'padding-top': '2%', 'alignItems': 'center'},
    children=[
        html.A(
            html.Button(
                'Download Data',
                id='download-button'
            ),
            id='pg1-download-excel'
        )
    ]
)
                   

And here is the associated callback:

@app.callback(Output('pg1-download-excel', 'href'),
              [Input('download-button', 'n_clicks')])
def download_url(n_clicks):
    if n_clicks > 0:
        return '/example-dashboard/main-page/urlToDownload'
@app.server.route('/example-dashboard/main-page/urlToDownload')
def download_excel():
    """use click event to run func"""
    df = datahandler.csdata
    buf = io.BytesIO()
    xl_writer = pd.ExcelWriter(buf, engine='xlsxwriter')
    df.to_excel(xl_writer, sheet_name='Data', index=False)
    xl_writer.save()
    xl_data = buf.getvalue()
    buf.seek(0)
    return send_file(
        buf,
        mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
        attachment_filename="page_1_chart_data.xlsx",
        as_attachment=True,
        cache_timeout=0
    )

Has anyone made this work or had a similar problem?

Where is your app deployed? I ran into a web processing timeout issue on Heroku wherein Heroku limits web processes to 30 seconds.

In case it helps, here is my implementation (I use openpyxl to write the excel file but that shouldn’t matter):

from tempfile import NamedTemporaryFile
import io

...

@app.server.route('/download_single_current/')
def download_file():
    # Pre-processing
    ...setup variables / download data / etc

    # Write excel file (wb is an openpyxl workbook object)
    wb = write_excel_file()

    with NamedTemporaryFile() as tmp:
        wb.save(tmp.name)
        str_io = io.BytesIO(tmp.read())

    return send_file(str_io,
                     attachment_filename='myexcelfile.xlsx',
                     as_attachment=True)

I used to have a similar Problem, in my case I’m running it in an offline Domino Environment.

If I tried it localy on my pc it worked fine but on the DockerContainer the wrong Url was created.
I Guess may your WebServer adds specific stuff between the actual url.

I fixed it with this, but this has to be adjust to your case:

import io
import socket

downloadlink = '/' + os.environ['DOMINO_PROJECT_OWNER'] + '/Dash/r/notebookSession/' + socket.gethostname()[11:]

…
app.layout = html.Div(children=[
    html.H1('Dummy'),

    html.Div(children='''
        Dummy
    '''),
    generate_table(df),
    dcc.Markdown(children=markdown_text),
    html.A("Download File", href=downloadlink + '/download_excel/'),

])

@app.server.route('/download_excel/')
def download_excel():
    #Create DF
    d = {'col1': [1, 2], 'col2': [3, 4]}
    df = pd.DataFrame(data=d)

    #Convert DF
    strIO = io.BytesIO()
    excel_writer = pd.ExcelWriter(strIO, engine="xlsxwriter")
    df.to_excel(excel_writer, sheet_name="sheet1")
    excel_writer.save()
    excel_data = strIO.getvalue()
    strIO.seek(0)

    return send_file(strIO,
                     attachment_filename='test.xlsx',
                     as_attachment=True)

An easy way to check if it’s the same Problem:

Go to your App where you want to download the .csv
Look at the Url, maybe smth like this: https://< Domain >/< usr >/Dash/r/notebookSession/< randomhash >/
If you click download and it removes anything from the url, the download can’t work.
It has to be: https://< Domain >/< usr >/Dash/r/notebookSession/< randomhash >/download_excel/ after you click download

You can try to Access the correct url after you clicked the button, even though it may generated the wrong url

Thanks for info. This helped me to look into the issue further.

For anyone who find this in the future. The cause was it being hosted on pythonanywhere - see this link for full solution https://www.pythonanywhere.com/forums/topic/13570/.

1 Like

Hi,
thanks for this example. I am using Dash in Domino, and I could get this function to work to download the example Excel file. However, I am having trouble passing in my own dataFrame into the download_excel() function. I tried using a Dash callback for input but it doesn’t seem to work, since this function seems to be a Flask function. could you help me with this? Thanks.

Best way to do this would be to use the Flask caching system and throw the DF in a redis/filesystem cache with cache.set and cache.get.

Note that you don’t need the callback for the button. If you embed a html.Button as a child element of an html.A component with an href set pressing the button automatically triggers the route.

html.A(
    id='pg1-download-excel',
    href='/download_excel',
    children=[
        dbc.Button("Save XLS"),
    ],
)