Stefan Verweij
Stefan Verweij

Reputation: 172

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

Upvotes: 3

Views: 4194

Answers (1)

emher
emher

Reputation: 6024

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.

Upvotes: 10

Related Questions