I am using FastAPI to upload a csv file, perform some modifications on it and then return it to the HTML page. I am using Jinja2 as the template engine and HTML in frontend.
How can I upload the csv file using Jinja2 template, modify it and then return it to the client?
from fastapi.templating import Jinja2Templates
from fastapi import FastAPI, File, UploadFile, Request
from io import BytesIO
import pandas as pd
import uvicorn
app = FastAPI()
templates = Jinja2Templates(directory="templates")
@app.get("/")
def form_post(request: Request):
result = "upload file"
return templates.TemplateResponse('home.html', context={'request': request, 'result': result})
@app.post("/")
def upload(request: Request, file: UploadFile = File(...)):
contents1 = file.file.read()
buffer1 = BytesIO(contents1)
test1 = pd.read_csv(buffer1)
buffer1.close()
file.file.close()
test1 = dict(test1.values)
return templates.TemplateResponse('home.html', context={'request': request, 'result': test1})
if __name__ == "__main__":
uvicorn.run(app)
\<!DOCTYPE html\>
\<html lang="en"\>
\<head\>
\<meta charset="UTF-8"\>
\<title\>RUL_PREDICTION\</title\>
\</head\>
\<body\>
\<h1\>RUL PREDICTION\</h1\>
\<form method="post"\>
\<input type="file" name="file" id="file"/\>
\<button type="submit"\>upload\</button\>
\</form\>
\<p\>{{ result }}\</p\>
\</body\>
\</html\>
The working example below is derived from the answers here, here, as well as here, here and here, at which I would suggest you have a look for more details and explanation.
data.csv
Id,name,age,height,weight
1,Alice,20,62,120.6
2,Freddie,21,74,190.6
3,Bob,17,68,120.0
app.py
from fastapi import FastAPI, File, UploadFile, Request, Response, HTTPException
from fastapi.templating import Jinja2Templates
from io import BytesIO
import pandas as pd
app = FastAPI()
templates = Jinja2Templates(directory='templates')
@app.post('/upload')
def upload(file: UploadFile = File(...)):
try:
contents = file.file.read()
buffer = BytesIO(contents)
df = pd.read_csv(buffer)
except:
raise HTTPException(status_code=500, detail='Something went wrong')
finally:
buffer.close()
file.file.close()
# remove a column from the DataFrame
df.drop('age', axis=1, inplace=True)
headers = {'Content-Disposition': 'attachment; filename="modified_data.csv"'}
return Response(df.to_csv(), headers=headers, media_type='text/csv')
@app.get('/')
def main(request: Request):
return templates.TemplateResponse('index.html', {'request': request})
templates/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<form method="post" action="/upload" enctype="multipart/form-data">
<label for="csvFile">Choose a CSV file</label>
<input type="file" id="csvFile" name="file" onchange="enableSubmitBtn();"><br><br>
<input type="submit" id="submitBtn" value="submit" disabled>
</form>
<script>
function enableSubmitBtn() {
document.getElementById('submitBtn').removeAttribute("disabled");
}
</script>
</body>
</html>
If you would rather like to return a new Jinja2 template with the modified data instead of a csv file as demonstrated above, you could use the below.
Use pandas.DataFrame.to_html()
to render the DataFrame as an HTML table. You could optionally use the classes
parameter in to_html()
function to pass a class
name, or a list of names, that will be used in a style sheet in your frontend to style the table. Additionally, you could remove the border
by specifying border=0
in to_html()
.
app.py
# ... (rest of code is same as in Option 1)
@app.post('/upload')
def upload(request: Request, file: UploadFile = File(...)):
# ... (rest of code is same as in Option 1)
context = {'request': request, 'table': df.to_html()}
return templates.TemplateResponse('results.html', context)
templates/results.html
<!DOCTYPE html>
<html>
<body>{{ table | safe }}</body>
</html>
Use pandas.DataFrame.to_dict()
to convert the DataFrame to a dictionary and return it.
app.py
# ... (rest of code is same as in Option 1)
@app.post('/upload')
def upload(request: Request, file: UploadFile = File(...)):
# ... (rest of code is same as in Option 1)
context = {'request': request, 'data': df.to_dict(orient='records'), 'columns': df.columns.values}
return templates.TemplateResponse('results.html', context)
templates/results.html
<!DOCTYPE html>
<html>
<body>
<table style="width:50%">
<tr>
{% for c in columns %}<td>{{ c }}</td>{% endfor %}
</tr>
{% for d in data %}
<tr>
{% for v in d.values() %}
<td>{{ v }}</td>
{% endfor %}
<br>
</tr>
{% endfor %}
</table>
</body>
</html>