Templates
Flask has built-in support for Jinja2 templating. Jinja2 templating has a standard defined pattern for rendering the HTML. We can place dynamic content by passing the context argument. Jinja2 gives an ability to render HTML with some expressions and conditions, extending and including template features.
Flask follows a standard templating scaffolding structure to lay out all template files. The following is the scaffolding we followed by creating a templates directory under the project's root directory and then creating subdirectories based on other module names:
Here, we have created templates as per the module and put the generic templates under the root directory.
Similarly, we have maintained the static file's scaffolding:
We kept the static libraries and modules-related files. With the help of the url_for method, we can get the relative path of any static files and routes. Hence, in the following template, we included all static files using a url_for method, such as <link rel="stylesheet" href="{{ url_for('static', filename='bootstrap/css/bootstrap.min.css')}}">.
In the same way, we are going to include all static files in the base template.
File—templates/base.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta name="csrf-token" content="{{ csrf_token() }}">
<meta name="author" content="AbdulWahid AbdulHaque">
<title>Flask Todo App</title>
<link rel="stylesheet" href="{{ url_for('static', filename='bootstrap/css/bootstrap.min.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='bootstrap/css/bootstrap.min.css.map') }}">
{% block css %}{% endblock %}
<script type="text/javascript" src="{{ url_for('static', filename='jquery/jquery-3.3.1.min.js')}}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='bootstrap/js/bootstrap.min.js')}}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='popper/popper.min.js')}}"></script>
{% block js %}{% endblock %}
</head>
<body>
{% include 'navbar.html' %}
{% block body %}{% endblock %}
<script type="text/javascript">
$('.dropdown-toggle').dropdown();
</script>
</body>
</html>
We defined all generic HTML that is required on all other templates. We also created a basic bootstrap navbar and kept this in navbar.html, which is included in the base.html template by {% include 'navbar.html' %}. As you can see, Jinja2 templating makes it super easy to maintain the templates and provide a standard pattern.
The following is a code snippet of the navbar.html template where we created a navbar using Bootstrap CSS classes.
File—templates/navbar.html:
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="#">Todo's</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavDropdown" aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNavDropdown">
{% if current_user.is_authenticated %}
<ul class="navbar-nav ml-auto">
<li class="nav-item dropdown ml-auto">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Welcome <i>{{ current_user.email }}</i>
</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink">
<a class="dropdown-item" href="../auth/logout">Logout</a>
</div>
</li>
</ul>
{% endif %}
</div>
</nav>
While designing navbar.html, we added some conditional statements to display the logged-in user's information and logout options when the user is logged in.
Let's move on to the sign up and login page. The following is the code snippet for the sign up page.
File—templates/auth/signup.html:
{% extends "base.html" %}
{% block body %}
<div class="container align-middle mx-auto" style="width:30%; margin-top:5%">
<div class="card bg-light mb-3">
<div class="card-header"><h3>Sign Up</h3></div>
<div class="card-body">
<form method="post">
{{ form.hidden_tag() }}
{% if form.errors %}
{% for error in form.errors.values() %}
<div class="alert alert-danger" role="alert">
{{error}}
</div>
{% endfor %}
{% endif %}
<div class="form-group">
<label for="exampleInputEmail1">Email address</label>
{{ form.email(class_="form-control", id="exampleInputEmail1", placeholder="Email", maxlength=128)}}
<small id="emailHelp" class="form-text text-muted">We'll never share your email with anyone else.</small>
</div>
<div class="form-group">
<label for="exampleInputPassword1">Password</label>
{{ form.password(class_="form-control", placeholder="Password") }}
</div>
<div class="form-group">
<label for="exampleInputPassword">Confirm Password</label>
{{ form.confirm_password(class_="form-control", placeholder="Confirm Password") }}
</div>
<div class="form-group">
{{ form.submit(class_="btn btn-primary btn-lg") }}
<a class="float-right" href="login">Already have account.</a>
</div>
</form>
</div>
</div>
</div>
{% endblock %}
Here is the output of the Sign Up page:
On the HTTP GET request on the auth.signup view, this will return an empty form and render it through the signup.html template. We also added code to receive the sign up data on the HTTP POST request in the sign up view. We persist the user data on the sign up process using the User model.
Here is the login template.
File—templates/auth/login.html:
{% extends "base.html" %}
{% block body %}
<div class="container align-middle mx-auto" style="width:30%; margin-top:5%">
<div class="card bg-light mb-3">
<div class="card-header"><h3>Login</h3></div>
<div class="card-body">
<form method="post">
{{ form.hidden_tag() }}
{% if form.errors %}
<div class="has-error"><strong>Unable to login. Typo?</strong></div>
{% endif %}
<div class="form-group">
<label for="exampleInputEmail1">Email address</label>
{{ form.email(class_="form-control", id="exampleInputEmail1", placeholder="Email", maxlength=128)}}
<small id="emailHelp" class="form-text text-muted">We'll never share your email with anyone else.</small>
</div>
<div class="form-group">
<label for="exampleInputPassword1">Password</label>
{{ form.password(class_="form-control", id="exampleInputPassword1", placeholder="Password") }}
</div>
<div class="form-group">
{{ form.submit(class_="btn btn-primary btn-lg") }}
<a class="float-right" href="signup">New around here? Sign up</a>
</div>
</form>
</div>
</div>
</div>
{% endblock %}
Now, the user can proceed and log in to the system, For login, we have created the login form and rendered it through the auth.login view. The following is a screenshot of the Login page:
On the HTTP POST request, we are processing the user login mechanism using the Flask-Login extension, which provides a function called login_user and performs the login process. It creates a session and adds user_id into the session to remember the user for the further request until we remove the user from the session or perform the logout with the logout_user method, as mentioned in the auth.logout view.
The authentication process completes here, as the user login executes successfully and redirects the user to another page or template. Now, it's time to move on to the todo module.