In this blog, we will build dynamic forms using wtf forms in Flask. Sometimes we don't know how many fields the form will have and we have to add dynamics field to form on runtime either by javascript or by the backend. You can check out the complete code used in this blog here
In this blog, we will build a dynamic form where we don't know exactly how many fields there going to be. Let's take an example. We want to build a small application in which users will submit a list of their favorite movies. But, some users can submit one movie or 5 movies. For this, we need to build dynamic number of inputs on go.
For this purpose, WTF forms provide the FieldList
field. We will use this field with javascript to build our dynamic fields. Here is the starter code.
# app.py
from flask import Flask, render_template
app = Flask(__name__, template_folder='./templates')
@app.route('/')
def index():
return render_template('index.html')
<!-- templates/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Flask dynamic WTF forms</title>
</head>
<body>
<h1>Hello World</h1>
</body>
</html>
If You ever used flask WTF forms you might already know i. To create a form we need to create a subclass of FlaskForm
. Let's first create a simple form without any dynamic fields and then we will make them dynamic.
from flask import Flask, render_template
from flask_wtf import FlaskForm
from wtforms import StringField, FieldList
from wtforms.validators import DataRequired
class FavouriteMoviesForm(FlaskForm):
username = StringField("Your name", validators=[DataRequired()])
movies = FieldList(StringField('Movie Name'), min_entries=1, max_entries=5)
app = Flask(__name__, template_folder='./templates')
app.config['SECRET_KEY'] = "mysupersecretkey"
@app.route('/', methods=['GET', 'POST'])
def index():
favouriteMoviesForm = FavouriteMoviesForm()
if favouriteMoviesForm.validate_on_submit():
return render_template('submit.html', username=favouriteMoviesForm.username.data, movies=favouriteMoviesForm.movies.entries)
return render_template('index.html', form=favouriteMoviesForm)
we added Secret key in app's config because flask wtf forms uses secret key to create CSRF token. Don't store secret key in plain text in a real application. We here doing here because it's just an example app.
First, we created FavouriteMoviesForm
class which is a subclass of FlaskForm
. and we are using it in our index route
.
The important thing here to take note of is here we are passing StringField
as an argument to FieldList
field with min_entries
and max_entries
values. What that will do is tell flask forms that we want to have a minimum of 1 and a maximum of 5 movie name
fields. But this will not automatically generate those fields for us. This will only make sure we don't break min and max constraints.
Let's render the form and see what we will get.
Here you can see that we have two field one is username field and another is movies list. But as you can see it is only showing one movie name field. Here is html code of index.html
<!-- templates/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Flask dynamic WTF forms</title>
</head>
<body>
<h1 style="text-align: center;">My Favourite movies</h1>
<form method="POST" action="">
{{ form.csrf_token }}
{{form.username.label}}
{{ form.username() }} <br>
<h3>Favourite Movies List</h3>
{{ form.movies.label }}
{{ form.movies() }}
<button type="submit">Post Favourite movies</button>
</form>
</body>
</html>
Let's take a look at how Fieldlist
renders the list of movie name
fields.
As you can see the movies list contains an input field with movies-0
. Because we have min_entries
set to 1 that's why it's only rendered one field if we set it 2 we will two fields render by default. let's see if have two fields what would be the names of movie input fields.
as you can see that the second field name is movies-1
. Now you can see the patterns. If had three movies the name of the third field would have been movies-2
.
Let's write some javascript that will allow users to click a button to add a new movie field. We will also make sure that there will be at least one field and a maximum of 5 fields because that's what our forms accept. We will add one button by clicking that button user can add a new field.
<script>
window.onload = function() {
let addMovieFieldBtn = document.getElementById('add-movie-field');
addMovieFieldBtn.addEventListener('click', function(e){
e.preventDefault();
let allMoviesFieldWrapper = document.getElementById('movies');
let allMoviesField = allMoviesFieldWrapper.getElementsByTagName('input');
if(allMoviesField.length > 4) {
alert('You can have only five movies name');
return;
}
let movieInputIds = []
for(let i = 0; i < allMoviesField.length; i++) {
movieInputIds.push(parseInt(allMoviesField[i].name.split('-')[1]));
}
let newFieldName = `movies-${Math.max(...movieInputIds) + 1}`;
allMoviesFieldWrapper.insertAdjacentHTML('beforeend',`
<li><label for="${newFieldName}">Movie Name</label> <input id="${newFieldName}" name="${newFieldName}" type="text" value=""></li>
`);
});
}
</script>
Here you can see the javascript we need to add a new movie field when the user clicks add add new movie
button.
Let's understand the tricky part of Javascript. Herelet allMoviesField = allMoviesFieldWrapper.getElementsByTagName('input');
we are fetching all the movies input fields. We are doing that so we can know how many fields are there. If the number of fields is already 5 then we will show the user an error. If not we will get names of the all the inputs. and create a new name for our new input field. We don't want to have two fields with the same name. That's why we are getting the integer part of all names then adding one to the max value for new input fields. Using this way we will avoid making two fields with the same name. Here is what the final form looks like. You can check out the complete code used in this blog here.
The important point is that FieldList
doesn't care about the input being sequential. You can have one field name movies-0
and another movies-100
. It only cares about the field name must start with the name of the field which is in our case is movies
.