DEV Community

Yogesh Sharma
Yogesh Sharma

Posted on

Insights into Forms in Flask

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 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 module for flask. For frontend, we will use jinja2 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')
Enter fullscreen mode Exit fullscreen mode

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)

Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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 %}
Enter fullscreen mode Exit fullscreen mode

Once you reload the server the output should look like

Quick form output

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 %}
Enter fullscreen mode Exit fullscreen mode

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

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 %}
Enter fullscreen mode Exit fullscreen mode

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

Custom output 2

That's all this time!

Top comments (0)