2

Im trying to set a download button for my pandas dataframe (as csv) but I didn't get any success.

Here are the relevant functions of my views.py:

from flask import render_template, make_response
from flask import send_file
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import random
import seaborn as sns




@app.route("/jinja")
def jinja():

    my_name = "Testes"

    return render_template("/public/jinja.html", my_name=my_name)

@app.route("/about")
def about():
    return render_template("/public/about.html")

#defining parameters
bast_param = [0,5] #ba prefix for below average student
avst_param = [5,7] #av prefix for average student
aast_param = [7,10] #aa prefix for above average student

min_bast = bast_param[0]
max_bast = bast_param[1]
min_avst = avst_param[0]
max_avst = avst_param[1]
min_aast = aast_param[0]
max_aast = aast_param[1]


@app.route("/")
def index():


    #students quantities per parameter
    bast_qtd = 5
    avst_qtd = 3
    aast_qtd = 8
    st_total = bast_qtd + avst_qtd + aast_qtd

    #Defining Subjects
    subjects = ["Disciplina 1", "Disciplina 2", "Disciplina 3", "Disciplina 4", "Disciplina 5"]

    students = []

    #counter for students ids creation
    i = 0

    #counters and variable for grades creation
    a = 0
    b = 0
    newgradeline = []

    grade = []

    #creating students and grades
    while(i < st_total):
        newstudent = random.randint(100000,199999)
        #excluding duplicates
        if newstudent not in students:
            students.append(newstudent)
            i = i+1


    # In[3]:


    #below averagge students
    while (a < bast_qtd):
        b = 0
        newgradeline = []
        grade.append(newgradeline)
        while (b<len(subjects)):
            gen_grade = round(random.uniform(bast_param[0],bast_param[1]),2)
            newgradeline.append(gen_grade)
            b = b+1
        a = a +1
    a = 0

    #average students
    while (a < avst_qtd):
        b = 0
        newgradeline = []
        grade.append(newgradeline)
        while (b<len(subjects)):
            gen_grade = round(random.uniform(avst_param[0],avst_param[1]),2)
            newgradeline.append(gen_grade)
            b = b+1
        a = a +1
    a = 0

    #above average students
    while (a < aast_qtd):
        b = 0
        newgradeline = []
        grade.append(newgradeline)
        while (b<len(subjects)):
            gen_grade = round(random.uniform(aast_param[0],aast_param[1]),2)
            newgradeline.append(gen_grade)
            b = b+1
        a = a +1


    # In[4]:


    #generating table
    simulation = pd.DataFrame (grade,index=students, columns=subjects)
    return render_template('/public/index.html',table=simulation.to_html(),download_csv=simulation.to_csv(index=True, sep=";"))


@app.route("/parameters")
def parameters():
    return render_template('/public/parameters.html', min_bast=min_bast, max_bast=max_bast, min_avst=min_avst,max_avst=max_avst, min_aast=min_aast, max_aast=max_aast)

And this is the html page where I'm currently displaying the csv as a text on the page {{download_csv | safe}}:

{% extends "/public/templates/public_template.html" %}

{% block  title %}Simulador{% endblock%}

{% block main %}
  <div class="container">
    <div class="row">
      <div class="col">
      </br>
        <h1>Simulador</h1>
      <hr/>
        {{table| safe}}
        <br />
      <a class="btn btn-primary" href="/" role="button">New simulation</a>
      <a class="btn btn-primary" href="/parameters" role="button">Edit Parameters</a>
      </div>
    </div>
    <div class="row">
      <div class="col">
      </br>
      <hr/>
        <h1>CSV File</h1>
        {{download_csv | safe}}
      </br></br>
        <a class="btn btn-primary" href="" role="button">Download CSV</a>
        <hr/>
    </div>
    </div>
  </div>

{% endblock %}

but I would like this button <a class="btn btn-primary" href="" role="button">Download CSV</a> to trigger a CSV download of the dataframe called simulation created on @app.route("/") def index(): of my views.py

I do understand that I need to set up a new route on my views.py to make this download happen, but how do I do that?

2 Answers2

0

Currently your button download doesn't do anything.

1 - Add OnClick=someFunction() to it so when the user clicks on it, someFunction() gets executed.

<a class="btn btn-primary" onClick="someFunction()" href="" role="button">Download CSV</a>

2 - In your somFunction() redirect the webpage to the route you want so say location.replace("/download");

function someFunction()
{
    //..... You can here send user input from HTML to Flask through Ajax POST request if you wish.........//
    location.replace("/download");

}

3 - Finally, in your Flask make sure you have a /download route where the csv gets processed.

Update If you don't want to use javascript function to redirect then try this:

1 - Change your index() route to something more obvious like @app.route("/download")

2 - Assign the href for Download CSV button to the function's route

<a class="btn btn-primary" href="{{ url_for('index') }}" role="button">Download CSV</a>
O Yahya
  • 366
  • 2
  • 7
  • Isn't there a way to do that only using Flask? I was thinking about to create a new route and def on views.py like ```@app.route("/download") def download(): return *something*``` and then on the html button just set the href pointing to download. What do you think of that? Is it possible? If so, what should I do inside my download function? – Guilherme Atihe de Oliveira Dec 06 '19 at 04:07
  • I suggested this way because this is how I implement my HTML and Flask, but your method sounds like it would work as well given that you don't want to send data from HTML to flask. I mean as long as you can get HTML to redirect to the route where def index() is located it should work. – O Yahya Dec 06 '19 at 04:28
  • Im sorry but I can't really understand how would this return a csv file. The index contains my home page, where the Dataframe is being displayed, so I can't just change I think. Also I didn't understand how would this return an csv file of my dataframe. Im not sure I made my self clear. I would like to create a new route that takes the created dataframe on index (called simulation) and returns the download of an csv file (which values I have already stored on 'download_csv' variable returned on index()), so I can point my href to this new route. How would I adapt your suggestion to that? – Guilherme Atihe de Oliveira Dec 06 '19 at 04:51
0

What I would do is move the creation of the simulation to a separate function create_simulation() and create a new route for the download like this:

from flask import render_template, make_response, Response
from flask import send_file
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import random
import seaborn as sns




@app.route("/jinja")
def jinja():

    my_name = "Testes"

    return render_template("/public/jinja.html", my_name=my_name)

@app.route("/about")
def about():
    return render_template("/public/about.html")

#defining parameters
bast_param = [0,5] #ba prefix for below average student
avst_param = [5,7] #av prefix for average student
aast_param = [7,10] #aa prefix for above average student

min_bast = bast_param[0]
max_bast = bast_param[1]
min_avst = avst_param[0]
max_avst = avst_param[1]
min_aast = aast_param[0]
max_aast = aast_param[1]


@app.route("/")
def index():
    simulation = create_simulation()
    return render_template('/public/index.html',table=simulation.to_html(),download_csv=simulation.to_csv(index=True, sep=";"))


@app.route('/download')
def download():
    # stream the response as the data is generated
    response = Response(create_simulation(), mimetype='text/csv')
    # add a filename
    response.headers.set("Content-Disposition", "attachment", filename="simulation.csv")
    return response


@app.route("/parameters")
def parameters():

    return render_template('/public/parameters.html', min_bast=min_bast, max_bast=max_bast, min_avst=min_avst,max_avst=max_avst, min_aast=min_aast, max_aast=max_aast)


def create_simulation():

    #students quantities per parameter
    bast_qtd = 5
    avst_qtd = 3
    aast_qtd = 8
    st_total = bast_qtd + avst_qtd + aast_qtd

    #Defining Subjects
    subjects = ["Disciplina 1", "Disciplina 2", "Disciplina 3", "Disciplina 4", "Disciplina 5"]

    students = []

    #counter for students ids creation
    i = 0

    #counters and variable for grades creation
    a = 0
    b = 0
    newgradeline = []

    grade = []

    #creating students and grades
    while(i < st_total):
        newstudent = random.randint(100000,199999)
        #excluding duplicates
        if newstudent not in students:
            students.append(newstudent)
            i = i+1


    # In[3]:


    #below averagge students
    while (a < bast_qtd):
        b = 0
        newgradeline = []
        grade.append(newgradeline)
        while (b<len(subjects)):
            gen_grade = round(random.uniform(bast_param[0],bast_param[1]),2)
            newgradeline.append(gen_grade)
            b = b+1
        a = a +1
    a = 0

    #average students
    while (a < avst_qtd):
        b = 0
        newgradeline = []
        grade.append(newgradeline)
        while (b<len(subjects)):
            gen_grade = round(random.uniform(avst_param[0],avst_param[1]),2)
            newgradeline.append(gen_grade)
            b = b+1
        a = a +1
    a = 0

    #above average students
    while (a < aast_qtd):
        b = 0
        newgradeline = []
        grade.append(newgradeline)
        while (b<len(subjects)):
            gen_grade = round(random.uniform(aast_param[0],aast_param[1]),2)
            newgradeline.append(gen_grade)
            b = b+1
        a = a +1


    # In[4]:
    #generating table
    simulation = pd.DataFrame (grade,index=students, columns=subjects)
    return simulation

Then your html should look something like this where I only changed the href to '/download':

{% extends "/public/templates/public_template.html" %}

{% block  title %}Simulador{% endblock%}

{% block main %}
  <div class="container">
    <div class="row">
      <div class="col">
      </br>
        <h1>Simulador</h1>
      <hr/>
        {{table| safe}}
        <br />
      <a class="btn btn-primary" href="/" role="button">New simulation</a>
      <a class="btn btn-primary" href="/parameters" role="button">Edit Parameters</a>
      </div>
    </div>
    <div class="row">
      <div class="col">
      </br>
      <hr/>
        <h1>CSV File</h1>
        {{download_csv | safe}}
      </br></br>
        <a class="btn btn-primary" href="/download" role="button">Download CSV</a>
        <hr/>
    </div>
    </div>
  </div>

{% endblock %}

I basically followed this example: Create and download a CSV file from a Flask view

Marc
  • 1,539
  • 8
  • 14
  • For some reason the Created CSV file is only returning the CSV headers ([Disciplina 1Disciplina 2Disciplina 3Disciplina 4Disciplina 5]) and with no separator... What could be happening? – Guilherme Atihe de Oliveira Dec 07 '19 at 23:29
  • I'm also worried that I would not like to generate a new simulation when clicking on 'Download'. The goal is to download the already created simulation displayed above on ```

    Simulador


    {{table| safe}}```
    – Guilherme Atihe de Oliveira Dec 07 '19 at 23:31