Insights into Forms in Flask
Tags: flask, python, webdev
Published: 2022-08-14 07:11:00

Hey there!

Today, we will look into implementing `forms` in Flask. We will start with quick rendering of forms and look into details of customising the form.

## Background
[Flask](https://flask.palletsprojects.com/en/2.2.x/) is popular web development framework implemented in python. 
Forms are important part of developing any websites and are used heavily to take inputs from users. 
We would see the usage of [wtform](https://wtforms.readthedocs.io/en/3.0.x/) module for flask. For frontend, we will use [jinja2](https://flask.palletsprojects.com/en/2.2.x/templating/) templating.

## Pre-requisites
A basic understanding of python and flask is required. It is expected that you know how a flask server is started and have it running.

With all the above information, let's get started -
## Setup
We need following things before we can render a form to frontend - 
1. A basic flask app running
2. A python form class implemented
3. A controller which would render the form

It is assumed that you already have (1) running. For (2), add the following code to your forms.py

```
# forms.py
from flask_wtf import FlaskForm
from wtforms import StringField, IntegerField, TextField, SubmitField
from wtforms.fields.html5 import IntegerRangeField
from wtforms.validators import DataRequired, Optional, NumberRange


class SampleForm(FlaskForm):
    name = StringField('Name', validators=[DataRequired()])
    age = IntegerField('Age', validators=[DataRequired()])
    moto = TextField('moto', validators=[Optional()])
    range = IntegerRangeField('range', default=10, validators=[DataRequired(), NumberRange(min=1, max=50)])

    submit = SubmitField('Submit')
```
You can read more details about wtf at https://wtforms.readthedocs.io/en/3.0.x/ and more about validators at https://wtforms.readthedocs.io/en/3.0.x/validators/

For 3, add following code to create controller

```
# routes.py
from flask import Blueprint, url_for, redirect, render_template

from app.sample.forms import SampleForm

sample = Blueprint('sample', __name__)


@sample.route("/sample-form", methods=["GET", "POST"])
def sample_controller():
    form = SampleForm()

    if form.validate_on_submit():
        return redirect(url_for('sample.sample_controller'))

    return render_template('sample/sample_form.html', form=form)

```

To attach the route to your app, add following to the create_app method 
```
# app/__init__.py
from app.sample.routes import sample

app.register_blueprint(sample)
``` 

## Quick form rendering
First, we'll look at the quickest way to render beautiful forms.

create `sample/sample_form.html` in `templates/` folder 
```
{% extends 'base.html' %}
{% import 'bootstrap/wtf.html' as wtf %}

{% block app_content %}
<div class="container-fluid">
    {{ wtf.quick_form(form, button_map={'submit': 'success'}) }}
</div>
{% endblock %}
```

Once you reload the server the output should look like


![Quick form output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yjxqb5r4zipxc5tro9vg.png)

Voila! You have your beautiful form rendered with just one line of html code. Note that, button_map maps the field to bootstrap styling class (btn-success in this case). Another thing to note, the flask setup is using bootstrap in this case so your styling may vary depending on whether you are using bootstrap or not.

## Field wise form rendering
Change your sample html to following
```
{% extends 'base.html' %}
{% import 'bootstrap/wtf.html' as wtf %}

{% block app_content %}
<div class="container-fluid">
    <form class="form form-horizontal" method="post" role="form">
        {{ form.hidden_tag() }}
        {{ wtf.form_field(form.name) }}
        {{ wtf.form_field(form.age) }}
        {{ wtf.form_field(form.moto) }}
        {{ wtf.form_field(form.range, class="form-range") }}
        {{ wtf.form_field(form.submit, class="btn btn-success") }}
    </form>
</div>
{% endblock %}
```

This gives more control over handling fields, as we can render field by field and provide custom styling. The end result should look like following - 

![Field wise form output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l9srf2ec0pi40upzoizg.png)


## Further customising
This is the most interesting part for me as I was not able to locate any corresponding docs online easily.
Consider that in the range field, we also want to show the value that is currently selected and we also want to show the default value initially.

Here's how we can achieve this. Change html file to following - 
```
{% extends 'base.html' %}
{% import 'bootstrap/wtf.html' as wtf %}

{% block app_content %}
<div class="container-fluid">
    <form class="form form-horizontal" method="post" role="form">
        {{ form.hidden_tag() }}
        {{ wtf.form_field(form.name) }}
        {{ wtf.form_field(form.age) }}
        {{ wtf.form_field(form.moto) }}
{#        {{ wtf.form_field(form.range, class="form-range") }}#}
        <label for="range" class="form-label">{{ form.range.label }}</label>
        {{ form.range(min='1', max='50', oninput="r_value.value = this.value", class="form-range") }}
        <input type="text" id="r_value" value="{{ form.range.data }}" disabled></input><br/><br/>

        {{ wtf.form_field(form.submit, class="btn btn-success") }}
    </form>
</div>
{% endblock %}
```

Notice the code for range input, we have separately accessed label and input field and we have added a custom `oninput` to update value of the new input added.

The end result looks like this - 

![Custom output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2m8a6ppdtsov8qyu0co1.png)


![Custom output 2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kbz2esm0atkzunthhfvf.png)

That's all this time!