Reputation: 451
I already know about the following approach (link here):
server = Flask(__name__)
app = dash.Dash(server=server)
@server.route("/download/<path:path>")
def download(path):
"""Serve a file from the upload directory."""
return send_from_directory(UPLOAD_DIRECTORY, path, as_attachment=True)
But the problem is, when I use a multi-page-approach like suggested from Plotly (link here (below "Structuring a Multi-Page App" - index.py
)):
app.layout = html.Div([
dcc.Location(id='url', refresh=False),
html.Div(id='page-content')
])
@app.callback(Output('page-content', 'children'),
[Input('url', 'pathname')])
def display_page(pathname):
if pathname == '/apps/app1':
return app1.layout
elif pathname == '/apps/app2':
return app2.layout
else:
return '404'
I cannot use server.route
because it will be caught by the callback
shown above.
What is the best way to still make files downloadable?
Upvotes: 2
Views: 4053
Reputation: 859
I figured it out
Note that the directory stage is where my temporary download files are stored
First this is what my directory tree looks like:
root
|
|---apps
| └── main.py
|---assets
|---layouts
| |--layout.py
| └── error.py
|---stage
|---app.py
|---functions.py
└── index.py
This is what my index.py looks like
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
from app import app
from apps import main
app.layout = html.Div(
[dcc.Location(id="url", refresh=False), html.Div(id="page-content")]
)
@app.callback(Output("page-content", "children"), [Input("url", "pathname")])
def display_page(pathname):
if pathname == "/main":
return main.layout
else:
return main.error_page
if __name__ == "__main__":
app.run_server(debug=True, host="llp-lnx-dt-10200", port=15021)
in functions.py I will dynamically produce a dash-table + the html.A(...
with the link to download, the function is
def display_final_results(table):
import dash_html_components as html
import dash_core_components as dcc
import dash_table
import pandas as pd
return html.Div(
[
html.H5("""File processed and stuff worked"""),
dash_table.DataTable(
id="result_table",
data=table.iloc[:20, :].to_dict("records"),
columns=[{"name": i, "id": i} for i in list(table)],
),
html.Hr(),
dcc.Store(id="result_vault", data=table.to_dict()),
html.A(id="download_link", children=html.Button(children="Download")),
]
)
in main.py I call on the function def_final_results(table)
passing in the table that I want to display in the dash-table and also the link for download.
Here's what the callback in main.py looks like followed by the app.server.route()
@app.callback(
Output("download_link", "href"),
[Input("result_vault","data"),
Input("h5_filename", "children")]
)
def return_download_link(data, upload_filename):
shutil.rmtree("stage")
os.mkdir("stage")
target = pd.DataFrame(data)
download_filename = upload_filename.split(":")[1].strip() + f"""_{filestamp()}.xlsx"""
uri = f"""stage/{download_filename}"""
target.to_excel(
uri, engine="xlsxwriter", index=False, header=True, sheet_name="results"
)
return uri
@app.server.route("/stage/<path:path>")
def serve_static(path):
root_dir = os.getcwd()
return flask.send_from_directory(os.path.join(root_dir, "stage"), filename=path)
in main.py the table target
is saved into the directory /stage
and the uri
object which is the path to the file /stage/filename+filestamp
is sent to object with id download_link as the href
attribute, this is the html.A(...
in the file functions.py. I returned the href
since the download
attribute did not work for me.
The big mistake that I made was that my index.py dcc.Location
url used to be:
if pathname == "apps/main":
return main.layout
So every time the routing would go to https://llp-lnx-dt-10200:15021/apps/stage/filename
rather than https://llp-lnx-dt-10200:15021/stage/filename
.
By removing the apps from the url the problem was promptly solves.
Upvotes: 0
Reputation: 451
Ok, I have solved it now.
In the documentation it says:
The
dcc.Location
component represents the location or address bar in your web browser.
So I used an html.A element with the download option. As stated here, download
Prompts the user to save the linked URL instead of navigating to it.
This means when the user clicks on the link, it doesn't change the address bar. Hence, the callback
from the display_page(pathname)
isn't called and the link is directed to the download(path)
-method via the @server.route
statement.
Upvotes: 2