In the preceding parts of the series we were creating different types of requests, but primarily these were GET requests. In this part we’ll be talking about POST requests in Flask, so requests used to send data to the server. We usually send date to the server by filling in a form in the browser and clicking the submit button. Another common scenario for a POST request is uploading files. In this part we’ll be talking about both forms and files.
You will find the code for this part on Github.
Table of Contents
Personal Information Template and Route
Let’s start by creating a new template and route. Add a new personal_info.html file to the templates folder and add the following code to it:
{% extends 'base.html' %}
{% block title %}Personal Information{% endblock %}
{% block content %}
<h1>Personal Information</h1>
{% endblock %}
As you can see, it’s very basic code. The template extends the base.html template and contains two blocks, one for the title (with the default value set to Personal Information
) and one for the content.
Next, add the following route in the app.py file:
@app.route('/personal_info')
def personal_info():
return render_template('personal_info.html')
If you run the app and navigate to the new URL, you’ll see this:
![new URL](https://i0.wp.com/prosperocoder.com/wp-content/uploads/2025/02/image.png?resize=736%2C333&ssl=1)
Now, let’s create a form that we can use to send data to the server.
Forms
As an example, we’ll create a form for personal information to be entered. To keep it simple, we’ll just add a username and password, so something that imitates a login page. We’ll compare the data entered by the user to some fixed string values.
First of all, as you might remember, we have to specify the methods that we want to be allowed in the route. Only the GET method is allowed by default, but we have to add the POST method as well. Modify the route definition like so:
@app.route('/personal_info', methods=['GET', 'POST'])
def personal_info():
return render_template('personal_info.html')
We need both the GET and POST methods because the former will be used to GET the form displayed in the browser and the latter will be used to send the data we enter in the form to the server.
In the imports section, we have to add request:
from flask import Flask, render_template, redirect, url_for, request
We’ll need it in the route definition to distinguish between the two methods:
@app.route('/personal_info', methods=['GET', 'POST'])
def personal_info():
if request.method == 'GET':
return render_template('personal_info.html')
elif request.method == 'POST':
pass
So, if the request method is GET, we just render the HTML template in the browser. If the request method is POST, we’ll submit the data to the server, but for now let’s leave it unimplemented until we create the form.
Now, back in the personal_info template, let’s create the form in the content block:
{% block content %}
<h1>Personal Information</h1>
<form method="POST" action="{{ url_for('personal_info') }}">
<input type="text" name="username" placeholder="Username">
<input type="password" name="password" placeholder="Password">
<input type="submit" value="Log in">
</form>
{% endblock %}
We have to specify a couple attributes in the form tag. First of all, we set method to ‘POST’ and action to the URL we want to submit the data to, so in our case the personal_info route.
As you can see, in the form we define two inputs where we will enter the username and password. They are respectively of type text and password. The inputs can be identified by their names, so it’s important to remember to set the name attribute on each of them.
Following the inputs is the submit button.
This is how the form is rendered:
![form](https://i0.wp.com/prosperocoder.com/wp-content/uploads/2025/02/image-1.png?resize=678%2C241&ssl=1)
Simple and neat. If you enter any data now and hit the submit button, you’ll get an error because the POST response hasn’t been implemented yet. Speaking of which…
The POST Method
In the POST section of the personal_info function, we’ll retrieve the username and password from the form and compare it to some fixed strings. Here’s how we do that:
@app.route('/personal_info', methods=['GET', 'POST'])
def personal_info():
if request.method == 'GET':
return render_template('personal_info.html')
elif request.method == 'POST':
if 'username' in request.form.keys():
username = request.form['username']
if 'password' in request.form.keys():
password = request.form['password']
if username == 'prospero' and password == 'coder':
return "You're logged in."
else:
return "Wrong username or password!"
Now if you enter anything different than ‘prospero’ in the username input and ‘coder’ in the password input, you’ll see this message returned:
![message](https://i0.wp.com/prosperocoder.com/wp-content/uploads/2025/02/image-2.png?resize=340%2C54&ssl=1)
But if you enter these two words in their respective inputs, you see the success message:
![message](https://i0.wp.com/prosperocoder.com/wp-content/uploads/2025/02/image-3.png?resize=198%2C51&ssl=1)
So far we’ve only entered text data in the form. But some forms require you to upload an image file or some other type of file. Let’s see how to do that.
Uploading Files
Let’s create another form in the personal_info page for uploading files. First, let’s create a new route to handle it:
@app.route('/file_upload', methods=['POST'])
def file_upload():
return ''
Now let’s add the form. Here we also specify the method
and action
parameters, but additionally, we have to set the enctype
parameter to multipart/form-data
to encode the file:
Then, in the form, we can use the input element of type file
. But we also have to specify what type of file we expect. To this end we use the accept
attribute. It can be set in a couple different ways, for example by extension (e.g. accept=".doc, .docx"
for MS Word files) or file type. We’re going to set it to two file types: an image and a CSV file:
{% block content %}
<h1>Personal Information</h1>
<form method="POST" action="{{ url_for('personal_info') }}">
<input type="text" name="username" placeholder="Username">
<input type="password" name="password" placeholder="Password">
<input type="submit" value="Log in">
</form>
<h2>Upload your file...</h2>
<form method="POST" action="{{ url_for('file_upload') }}" enctype="multipart-form-data">
<input type="file" accept="image/*, .csv" name="file">
<input type="submit" value="Upload file">
</form>
{% endblock %}
Then we’ll display the uploaded images in a new template and the CSV files will be rendered as tables using the pandas library.
In order to use pandas, we have to import it:
from flask import Flask, render_template, redirect, url_for, request
import pandas as pd
If you don’t have pandas installed, make sure to install it first like so:
pip install pandas
Anyway, here are the two forms we created in the browser:
![forms](https://i0.wp.com/prosperocoder.com/wp-content/uploads/2025/02/image-4.png?resize=702%2C391&ssl=1)
You can now click on Choose File and pick a file:
![pick a file](https://i0.wp.com/prosperocoder.com/wp-content/uploads/2025/02/image-5.png?resize=1052%2C962&ssl=1)
Image Files
Let’s select an image file first. You will now see the name of the selected file:
![upload your file](https://i0.wp.com/prosperocoder.com/wp-content/uploads/2025/02/image-7.png?resize=674%2C511&ssl=1)
Next, you can click the Upload file button to upload it. As our file_upload
function returns an empty string, you won’t see anything. But we usually upload images to display them, rather than to see nothing displayed. Let’s fix it.
First of all, let’s add a new template to the templates folder, photo.html. This is where our photo will be displayed after uploading. Here’s the code:
{% extends 'base.html' %}
{% block title %}Photo{% endblock %}
{% block content %}
<h3>Your image:</h3>
<img src="{{ image_url }}" alt="uploaded image">
{% endblock %}
The image_url
will be set in the file_upload
function in a moment.
We need to import send_file
from flask and also the os
module. Then, we have to create a folder using the makedirs
function in the os
module and add it to the app’s config
dictionary:
from flask import Flask, render_template, redirect, url_for, request, send_file
import os
app = Flask(__name__, template_folder='templates')
UPLOAD_FOLDER = 'uploads'
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
Here’s how we can implement the file_upload
function to handle image files:
@app.route('/file_upload', methods=['POST'])
def file_upload():
if 'file' not in request.files:
return 'Nothing to display.', 400
file = request.files['file']
filename = file.filename
if filename == '':
return 'No file selected!', 400
if file.content_type.startswith('image/'):
filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
file.save(filepath)
return render_template('photo.html', image_url=f"/uploads/{filename}")
return 'Unsupported file format!', 400
We’ll also need a route for serving the image. Let’s add it in the app.py file:
@app.route('/uploads/<filename>')
def serve_image(filename):
filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
return send_file(filepath, mimetype='image/*')
Now we’re ready to upload the image and display it. So, repeat the steps we described before to select a photo and then hit the Upload image button. Now the image will be displayed in the photo page:
![photo page](https://i0.wp.com/prosperocoder.com/wp-content/uploads/2025/02/image-9.png?resize=651%2C467&ssl=1)
You’ll also notice that the new folder was created and your image file is in it:
![uploads folder](https://i0.wp.com/prosperocoder.com/wp-content/uploads/2025/02/image-11.png?resize=371%2C454&ssl=1)
And now let’s handle the CSV files.
CSV Files
In order to handle CSV files, we have to add the following elif
branch to the file_upload
function:
@app.route('/file_upload', methods=['POST'])
def file_upload():
...
if file.content_type.startswith('image/'):
...
elif file.content_type == 'text/csv':
df = pd.read_csv(file)
filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
file.save(filepath)
return df.to_html()
return 'Unsupported file format!', 400
So, the file will be read in using the read_csv
function from pandas and converted to a DataFrame
, which is the main data structure in pandas
. Then it will be saved in the uploads folder and displayed in the browser as a table. Time to test it. Let’s select a CSV file this time:
![CSV file](https://i0.wp.com/prosperocoder.com/wp-content/uploads/2025/02/image-12.png?resize=698%2C499&ssl=1)
Now we should see the data displayed like this:
![data display](https://i0.wp.com/prosperocoder.com/wp-content/uploads/2025/02/image-13.png?resize=1052%2C347&ssl=1)
And we can also see the file in the uploads folder, along with the image file we uploaded before:
![CSV file](https://i0.wp.com/prosperocoder.com/wp-content/uploads/2025/02/image-15.png?resize=326%2C519&ssl=1)
In the next part of the series we’ll be talking about static files in Flask.