Search code examples
python-3.xbuttondownloadfilenamesplotly-dash

Dash download in-memory generated file on button click: How to give filename?


I generate an in-memory Excel file via pd.ExcelWriter and BytesIO for a click event in my Python3.8 dash app.

Everything works as it should be. When I download my file I get this popup message asking me if I would like to continue to download/open the generated file. However, the popup message shows this (I guess base64 encoded) string (or path?), e.g. ...ydaHdjhgk328AAAAnxsAA== and after downloading the download gets a (randomly assigned?) set of characters as the filename (e.g. ZySzsdn1.xlsx).

How can I adjust this so it would display and assign the filename to something like download.xlsx? My guess is that is has something to do with the base64 encoded href.

The function to generate the excel file:

def write_product_file():
    output = BytesIO()
    writer = pd.ExcelWriter(output, engine="xlsxwriter")
    upload_df = pd.DataFrame()
    upload_df.to_excel(writer, index=False, sheet_name="sheet1")
    writer.save()
    return output

The button in my Dash application:

html.Div(
  id="select-upload-form",
  style={"width": "100%"},
  children=[
    dbc.Button(
      "Download the upload form",
      id="download-excel",
      color="secondary",
      external_link="true",
      target="",
      href="",
    ),
  ],
),

And finally my callback:

@app.callback(
    [
        Output("download-excel", "href"),
        Output("download-excel", "color"),
        Output("download-excel", "target"),
    ],
    [Input("download-excel", "n_clicks")],
)
def download_template_file(n_clicks):
    if n_clicks:
        excelfile = write_product_file()
        excelfile.seek(0)
        media_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
        data = base64.b64encode(excelfile.read()).decode("utf-8")
        href_data = f"data:{media_type};base64,{data}"
        return href_data, "success", href_data,
    else:
        return None, "secondary", None


Solution

  • You could use the Download component from the dash-extensions package,

    pip install dash-extensions == 0.0.18
    

    The syntax is simpler, and it has a filename argument. Here is a small example,

    import dash
    import dash_html_components as html
    import numpy as np
    import pandas as pd
    
    from dash.dependencies import Output, Input
    from dash_extensions import Download
    from dash_extensions.snippets import send_bytes
    
    # Generate some example data.
    data = np.column_stack((np.arange(10), np.arange(10) * 2))
    df = pd.DataFrame(columns=["a column", "another column"], data=data)
    # Create app.
    app = dash.Dash(prevent_initial_callbacks=True)
    app.layout = html.Div([html.Button("Download xlsx", id="btn"), Download(id="download")])
    
    
    @app.callback(Output("download", "data"), [Input("btn", "n_clicks")])
    def generate_xlsx(n_nlicks):
    
        def to_xlsx(bytes_io):
            xslx_writer = pd.ExcelWriter(bytes_io, engine="xlsxwriter")
            df.to_excel(xslx_writer, index=False, sheet_name="sheet1")
            xslx_writer.save()
    
        return send_bytes(to_xlsx, "some_name.xlsx")
    
    
    if __name__ == '__main__':
        app.run_server()
    

    Disclaimer: I am the author of the dash-extensions package.