Downloading dynamically generated files from a Dash/Flask app

后端 未结 2 1716
抹茶落季
抹茶落季 2020-12-29 13:22

I tried to build a minimal example of a Dash app that illustrates the problem of dynamically generating a file that can then be downloaded via a download button.

If

相关标签:
2条回答
  • 2020-12-29 13:52

    Solution here:

    import uuid
    
    import dash
    from dash.dependencies import Input, Output, State
    import flask
    from flask.helpers import send_file
    
    import dash_core_components as dcc
    import dash_html_components as html
    
    stylesheets = [
        "https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.2/css/bulma.min.css", # Bulma
    ]
    
    server = flask.Flask('app')
    
    # create app
    app = dash.Dash(
        __name__,
        external_stylesheets=stylesheets, 
        server=server                       # <-- do not forget this line
    )
    
    # (...) your code here
    
    @server.route("/downloadable/<path>")
    def download_file (path = None):
        return send_file("downloadable/" + path, as_attachment=True)
    
    0 讨论(0)
  • 2020-12-29 13:59

    Since Dash is built upon Flask, flask is not able to locate the URI for the text file that is generated.

    The solution is to add the flask routes to redirect to download the resources, There is a simple example in the official plotly dash repository, https://github.com/plotly/dash-recipes/blob/master/dash-download-file-link-server.py

    The modified code below solves your problem

    import dash
    import dash_core_components as dcc
    import dash_html_components as html
    from dash.dependencies import Input, Output, State
    
    import uuid
    import os
    import flask
    stylesheets = [
        "https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.2/css/bulma.min.css", # Bulma
    ]
    
    # create app
    app = dash.Dash(
        __name__,
        external_stylesheets=stylesheets
    )
    
    
    app.layout = html.Div(
        className="section",
        children=[
            dcc.Textarea(
                id="text-area",
                className="textarea",
                placeholder='Enter a value...',
                style={'width': '300px'}
            ),
            html.Button(
                id="enter-button",
                className="button is-large is-outlined",
                children=["enter"]
            ),
            html.Div(
                id="download-area",
                className="block",
                children=[]
            )
        ]
    )
    
    def build_download_button(uri):
        """Generates a download button for the resource"""
        button = html.Form(
            action=uri,
            method="get",
            children=[
                html.Button(
                    className="button",
                    type="submit",
                    children=[
                        "download"
                    ]
                )
            ]
        )
        return button
    
    @app.callback(
        Output("download-area", "children"),
        [
            Input("enter-button", "n_clicks")
        ],
        [
            State("text-area", "value")
        ]
    )
    def show_download_button(n_clicks, text):
        if text == None:
            return
        # turn text area content into file
        filename = f"{uuid.uuid1()}.txt"
        path = f"downloadable/{filename}"
        with open(path, "w") as file:
            file.write(text)
        uri = path
        return [build_download_button(uri)]
    
    @app.server.route('/downloadable/<path:path>')
    def serve_static(path):
        root_dir = os.getcwd()
        return flask.send_from_directory(
            os.path.join(root_dir, 'downloadable'), path
        )
    
    if __name__ == '__main__':
        app.run_server(debug=True)
    

    Alternatively, you can use the static directory instead of the downloadable directory, It will work as well.

    More information on flask static directory: http://flask.pocoo.org/docs/1.0/tutorial/static/

    Here is the snippet,

    #your code
    
    def show_download_button(n_clicks, text):
        if text == None:
            return
        filename = f"{uuid.uuid1()}.txt"
        path = f"static/{filename}"      # =====> here change the name of the direcotry to point to the static directory
        with open(path, "w") as file:
            file.write(text)
        uri = path
        return [build_download_button(uri)]
    
    #your code
    
    0 讨论(0)
提交回复
热议问题