In this tutorial, we are going to build a simple Flask login Form, it’s going to be a simple demonstration of Flask web flow and field validations.
Versions:
- Python 3.8.5
- Flask 2.0.1
- Max OS X
Pre-Requisites:
Flask-WTF:
The support for web form handling that comes with Flask is a bare minimum, so to handle Web forms in this example, I am going to use flask-wtf
so this extension is going to give me additional features such as field validation.
Flask extensions are regular python packages that are installed with pip
, so pip install flask-wtf
works well.
Note- Make sure to activate the virtual environment.
(venv) flask-tutorials % pip install flask-wtf
Collecting flask-wtf
Downloading Flask_WTF-0.15.1-py2.py3-none-any.whl (13 kB)
Requirement already satisfied: itsdangerous in ./venv/lib/python3.8/site-packages (from flask-wtf) (2.0.1)
Collecting WTForms
Downloading WTForms-2.3.3-py2.py3-none-any.whl (169 kB)
|████████████████████████████████| 169 kB 1.8 MB/s
Requirement already satisfied: Flask in ./venv/lib/python3.8/site-packages (from flask-wtf) (2.0.1)
Requirement already satisfied: MarkupSafe in ./venv/lib/python3.8/site-packages (from WTForms->flask-wtf) (2.0.1)
Requirement already satisfied: click>=7.1.2 in ./venv/lib/python3.8/site-packages (from Flask->flask-wtf) (8.0.1)
Requirement already satisfied: Werkzeug>=2.0 in ./venv/lib/python3.8/site-packages (from Flask->flask-wtf) (2.0.1)
Requirement already satisfied: Jinja2>=3.0 in ./venv/lib/python3.8/site-packages (from Flask->flask-wtf) (3.0.1)
Installing collected packages: WTForms, flask-wtf
Successfully installed WTForms-2.3.3 flask-wtf-0.15.1
To use the flask-wtf
extension, the Flask application instance needs to be configured with a secret key. This secret key is used in the Falsk application for several things related to security.
In our case flask-wtf
is going to use it to protect the web forms against a common attack called cross-site-request-forgery. So we have to configure this secrete-key in the application as a configuration variable.
The configuration variables in Flask can be configured in many ways, the easiest way to set them as app.config using dict syntax.
app.config['SECRET_KEY'] = 'secret-key'
Flask Login Form:
Application Structure:
(venv) flask-login-form %
.
├── app
│ ├── __init__.py
│ ├── forms.py
│ ├── routes.py
│ └── templates
│ ├── header.html
│ ├── index.html
│ └── login.html
├── config.py
Config.py
class Config(object):
SECRET_KEY = 'my-secrete-key'
__init__.py
from flask import Flask
from config import Config
app = Flask(__name__)
app.config.from_object(Config)
from app import routes
When using flask-wtf
to work with web forms, each form is represented by a python class. So here is the LoginForm class which represents the web form.
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired
class LoginForm(FlaskForm):
user_name = StringField('UserName', validators=[DataRequired()])
password = PasswordField('Password', validators=[DataRequired()])
submit = SubmitField('Sign In')
Form classes are inherited from FlaskForm
, provided by flask-wtf
and the fields of the form are represented as class variables. On the above form, I have taken two form fields user_name
and password
, where user_name is StringField
type and password is PasswordField
type.
You may be noticed in the import statements, the *Field
classes are not directly coming from flask_wtf
directly but coming from one of its dependencies called wtforms
*Field
classes accept some arguments, the first argument that I passed to StringField and PasswordField classes is that the label name that I want to use in the form
when it is present to the user.
As a second argument, I defined validators, this is a very important aspect of creating a form and one of the reasons why flask-wtf
and wtforms
are great. We can define validation functions for each field and these are going to be executed automatically by the framework itself.
In the above example, I defined a required
field validation, so that, I used a DataRequired
validator. This is going to prevent the user from submitting the field empty. I did the same thing for the password field too.
Note that in both cases the validator argument was set to a list, this is because we can assign more than one validator to each field, and also note that the validator itself is an object that needs to be created.
Create an HTML template for the page that renders the above form.
This template is going to extends
from the header template as we discussed in the jinja template inheritance.
{% extends "header.html" %}
{% block content %}
<h1> Sign In</h1>
<form action="", method="post">
{{ form.hidden_tag() }}
<p>{{form.user_name.label}}<br>
{{form.user_name(size=32)}}<br>
</p>
<p>{{form.password.label}}<br>
{{form.password(size=32)}}<br>
</p>
<p>{{ form.submit() }}</p>
</form>
{% endblock %}
The form
element is going to have two attributes action
and method
. The action attribute indicates that what URL that browser should use when submitting the information when the user entered in the form. In the above form action, I have given an empty string, which means I am telling the browser to submit the form to the URL that currently in the address bar.
The method attribute sets the type of request, that is going to be used for the form submission.
Inside the form I have to set my form fields, this is an area where flask-wtf
helps a lot because all the fields that I added to the form class already know how to render them selfs in an HTML page, so all we need to do is just use the template place holders for the label and fields.
The form.user_name.label
referenced to the label that I set in the user_name class variable inLoginForm
class, and in the next line I invoked the user_name field as a function, this is going to output the HTML for this field.
If we need to customize in any way appearance of the field element, we can add additional attributes as keyword arguments in this function call. In this case, I just added size
argument to make the user_name and password fields both 32 characters long.
Finally, we need a submit button.
The form.hidden_tag()
is going to render a hidden field that going to add to the form to protect against the cross-site-scripting.
Create a route that maps the login URL to the form page.
from app import app
from flask import render_template, flash, redirect
from app.forms import LoginForm
@app.route('/')
@app.route('/index')
def index():
user_info = {
'name':'User'
}
return render_template('index.html', user=user_info)
@app.route('/login', methods=['GET','POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
if form.user_name.data == 'admin' and form.password.data == 'admin':
flash('login successful')
return redirect('index')
return render_template('login.html', form=form)
In the above, I have created a new route for /login
URL, I just started this function by creating an instance of LoginForm class. To make this example simple I am validating the user with hardcoded values.
form.validate_on_submit()
function is going to evaluate to true when the request comes with the form data and all those fields in the form are valid.
flash()
function is going to print a message on the webpage that we are going to redirect, In this case, it’s going to print the ‘login success’ message on the index page when the user validation successful.
Create header.html template.
<html>
<head>
<title>Generic Blog</title>
</head>
<body>
<h2 style="color:#093657">My Blog</h2>
<a href="/login">Login</a>
<hr>
{% with messages = get_flashed_messages()%}
{% if messages %}
<ul>
{% for message in messages %}
<li> {{message}}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
{% block content %} {% endblock %}
</body>
</html>
In the above header.html template, I obtained the flask messages that is coming from the /login
route using get_flash_messages()
function.
Create an index page that will appear on successful login.
index.html
{% extends "header.html" %}
{% block content %}
<h1 style="color:#093657"> Welcome
{% if user %}
{{user.name}}
{% else %}
Buddy!
{% endif %}
</h1>
{% endblock %}
Finally, prepare the login form, it typically includes a header and content blocks.
login.html
{% extends "header.html" %}
{% block content %}
<h1> Sign In</h1>
<form action="", method="post">
{{ form.hidden_tag() }}
<p>{{form.user_name.label}}<br>
{{form.user_name(size=32)}}<br>
</p>
<p>{{form.password.label}}<br>
{{form.password(size=32)}}<br>
</p>
<p>{{ form.submit() }}</p>
</form>
{% endblock %}
Run the app:
(venv) flask-login-form % flask run
* Serving Flask app 'login.py' (lazy loading)
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
Output:
Accessing Login Form
Login Success:
Login Error:
References:
Download Source from GIT:
Happy Learning 🙂