This tutorial explains what are Flask Template, Form, View, Response, And Redirect With practical examples:
In general, Templating is used in Programming, to reuse a portion of text with different data. Concerning Web development, Designers use templates to display data in a form that is readable and attractive to human readers.
A template design generally involves the use of a language due to the complexities posed by human interaction.
=> Take A Look At The Flask Beginners Guide Here
Table of Contents:
Introduction
Flask uses a templating engine called Jinja2, which displays the behavior of an application based on the User’s level of interaction. A Jinja template uses variables, expressions, and tags.
Variables and expressions are replaced with values during runtime before page rendering in the browser. Jinja tags help in writing logic, and control statements in the Flask template.
Flask View
The notion of Flask view is derived from a prevalent web application design pattern called Model-View-Controller. A View is one of the three interconnected elements in this paradigm, where it deals with application logic. The view takes care of the presentation of information to the User.
In our previous tutorial, we designed a View by subclassing the BaseView class of the Flask-Appbuilder. In the subsequent part of this tutorial, we shall extend our last example and present ways in which Views can be customized.
Flask Template
Let’s begin and write our first template. Create a file called hello.html under the templates directory.
Write the following code in that file and save it.
<!doctype html> <html> <h1> Software Testing Help</h1><p> Hello World!, from Software Testing Help. </p> </html>
render_template Flask
Now modify the HelloView class in views.py with the following code.
from flask import render_template # rest of the code class HelloWorld(BaseView): route_base = "/hello" @expose("/") def hello(self): # raise Exception("A custom exception to learn DEBUG Mode") return "Hello, World! from Software Testing Help." @expose("/template") def hello_template(self): return render_template("hello.html")
We have added a path to the route as /hello/template and have created a separate handler method for the same. Notice that we have used the Flask render template method in the return statement. Further, please pay attention to the Python statement that we have used to import render_template.
Go to the project’s root directory and create a file called run.py and enter the given code snippet in that file.
Note that we have changed the port to 8080. If required, you can change the port in run.py.
from app import app app.run(host='0.0.0.0', port=8080, debug=True)
Now the development server will run on all network interfaces as we have specified 0.0.0.0 as the host.
Use the below-given command to start the development server. Do not forget to activate the virtual environment if it is not already activated.
python run.py
Please note that this is an alternate way of running the development server. It provides more flexibility for running the development server, and we can use custom config values if required. Going forward, we shall always use the same command to run the development server.
Now navigate to the http://localhost:8080/hello/template to see the output in the browser.
Flask Template Not Found
Web applications generally have a 404 handler. This handler displays a custom message and informs a user that a web application doesn’t have a particular Flask template or page. Let’s create one application-wide controller to post a custom message to cover this use case.
In views.py, create a method as shown in the code below.
from app import appbuilder """ Application wide 404 error handler """ @appbuilder.app.errorhandler(404) def page_not_found(e): return render_template('404.html', base_template=appbuilder.base_template, appbuilder=appbuilder), 404
In the error handler, the Flask Response object is formed with the values that are returned in the return statement. The response is modified with a custom return code 404 instead of 200.
Now create a 404.html file under the templates directory with the following code.
{% extends "appbuilder/base.html" %} {# Inherit from base.html #} {% block content %} {## double `{{` are used to interpolate variables ##} <h2><center>{{_('Page not found')}}<center></h2> <p>Please search the website for something else.</p> {% endblock %}
In the above template:
{# #} is used for writing comments {{ }} is used for variables, values, or expressions.
Flask Template Inheritance
See how we have inherited most of the content from the base.html template by using the extend keyword, and the {% block content %} helps in overriding that portion of the base.html.
However, besides the symbols mentioned above, we use line statements and {% to write control statements in templates. Let’s see an example below.
Let us modify our hello world view to understand a few more concepts. Open views.py and update the HelloWorld class with one more method, as shown below.
See if the code is correctly indented.
@expose("/greetings") def hello_greetings(self): greetings = [ 'Good Morning', 'Good Afternoon', 'Good Evening', 'Good Night', ] return render_template("hello.html", greetings=greetings)
Now update the hello.html template with the following code.
<!doctype html> <html> <h1> Software Testing Help</h1><p> Hello World!, from Software Testing Help. </p> {% for item in greetings %} {% if "Morning" in item %} <i><p><b>{{item}}</b></p></i> {% else %} <p>{{item}}</p> {% endif %} {% endfor %} </html>
Template For loop
In the above Flask template, we have used a for loop to iterate on the items of the list. In our controller or handler, we passed a list with values of greetings to the template. Inside the template, we access each item using the {{item}} syntax.
Template if block
Besides, make a note of the use of an if statement. Here, we test the item for Morning and make it bold and italicized.
Now let’s step ahead to learn more about the concepts of Flask Forms.
Flask Forms
One of the most crucial aspects of templating is to take inputs from the users and write backend logic based on that input. Let us create a form.
We use Flask-Appbuilder SimpleFormView to render our form. However, let’s create a form first. In addition to the creation of a form, we need to use flask fab create-admin command to create an admin user.
Therefore, use the command before starting the development server so that the subsequently created views and forms can be validated with a logged-in user. We log into with the admin user and keep validating that created views are visible under the menu as shown in the screenshots.
Create Admin
Use the below command to create an admin user.
flask fab create-admin
Login with the admin credentials
- Click on Login after you navigate to http://localhost:8080.
- Sign in with the Admin credentials, created in the previous section.
- Click on My Forms category to access your views.
Note: You will be able to perform the last step only after adding the views to the default menu shown in the navbar.
Let’s go ahead and create some form-based views.
Create a file called forms.py under the app directory, and write the following code into it.
from wtforms import Form, StringField from wtforms.validators import DataRequired from flask_appbuilder.fieldwidgets import BS3TextFieldWidget from flask_appbuilder.forms import DynamicForm class GreetingsForm(DynamicForm): greeting1 = StringField(('Morning'), description = ('Your morning Greeting'), validators = [DataRequired()], widget = BS3TextFieldWidget()) greeting2 = StringField(('Afternoon'), description = ('Your Afternoon Greeting'), validators = [DataRequired()], widget = BS3TextFieldWidget()) greeting3 = StringField(('Evening'), description = ('Your Evening Greeting'), widget = BS3TextFieldWidget()) greeting4 = StringField(('Night'), description = ('Your Night Greeting'), widget = BS3TextFieldWidget())
We have created a form based on the DynamicForm from Flask-Appbuilder. There are four text fields. We extend our greetings example. Out of the four fields, two are mandatory, and two are optional because, for the first two greetings, we have mentioned the values for validators.
Now let’s create a view for this form. Write these following lines of code into the file views.py.
from flask import render_template, flash from flask_appbuilder import SimpleFormView from app.forms import GreetingsForm class GreetingsView(SimpleFormView): form = GreetingsForm form_title = 'This is a Greetings form' message = 'Your Greetings are submitted' def form_get(self, form): form.greeting1.data = 'Your Morning Greeting' form.greeting2.data = 'Your Afternoon Greeting' form.greeting3.data = 'Your Evening Greeting' form.greeting4.data = 'Your Night Greeting' def form_post(self, form): flash(self.message, 'info') greetings = [ form.greeting1.data, form.greeting2.data, form.greeting3.data, form.greeting4.data, ] session['greetings']=greetings return redirect(url_for('HelloWorld.hello_greetings2'))
In our view above, we have two methods called form_get and form_post for populating the default values in the fields of the forms and reading the entered values once the form is submitted from the browser, respectively.
The GreetingsView displays the form, as shown in the below image.
We also make use of a Flask session object to store the field values in form_post so that we can access the same in the corresponding new view that we are about to write.
Let’s now modify the HelloWorld class and add another method to display the greetings. We will call it hello_greetings2.
class HelloWorld(BaseView): ## other methods @expose("/greetings2") def hello_greetings2(self): greetings = session['greetings'] return render_template("hello.html", greetings=greetings)
In this view, we read the values from the session object and use the Flask render template to display those values in the user-facing HTML. Notice that hello_greetings2 is an alternate way of achieving the same functionality similar to hello_greetings.
The only difference is that using the hello_greetings2, we are showing the values that are entered by the User, and in hello_greetings we did not take any inputs from the User and hardcoded them while writing the view mapped to the respective route.
Flask Response
It is quite rare that you will find the explicit use of Flask response in the code. Response class in Flask is just a subclass of the Response class from Werkzueg’s Response class, which in turn subclasses its ResponseBase class.
Flask Response object is internally formed by Flask whenever we call a return statement or a method such as render_template.
Furthermore, we can customize the response code and content type if required as a part of the return statement in our views, as shown in the modified HelloWorld view below.
class HelloWorld(BaseView): ## other methods @expose("/greetings2") def hello_greetings2(self): greetings = session['greetings'] return render_template("hello.json", greetings=greetings), 201, {'Content-Type' : 'application/json'
Direct use of the Flask’s Response class can be covered in a use case when we stream the content instead of returning the full content at once because of the constraints of file size and network bandwidth.
We have shown below one example of streaming the content from a large CSV.
from flask import Response @app.route('/largefile.csv') def send_large_csv(): """A controller to stream the content of a large csv file""" def gen(): for row in iter_all_rows(): yield ','.join(row) + '\n' return Response(gen(), mimetype='text/csv')
Flask Redirect
It is not always possible for an application to pre-define the response based on the different requests from the client.
We use Flask Redirect, in scenarios, where it is possible to serve the content that can be fulfilled by the other views or locations in response to a request. We use Flask Redirect along with abort with the standard HTTP return codes.
For example, in the code below, we have used Redirect with HTTP Code 301, and abort with 401.
from flask import Flask, redirect, url_for, request, abort app = Flask(__name__) @app.route('/') def index(): return render_template('log_in.html') # Log In template @app.route('/login',methods = ['POST', 'GET']) def login(): if request.method == 'POST': if request.form['username'] == 'admin' : # if user is admin return redirect(url_for('success')), 301 else: abort(401) # stop processing else: return redirect(url_for('index')) # redirect to another view
Furthermore, check in GreetingsView where we have made use of Flask redirect and url_for to redirect a request internally to a different view by storing the values of greetings in the session object. Flask redirect always returns a response object, with the default or the givens status code to another location in the application.
Flask Debugtoolbar
We already introduced Flask’s interactive debugger in our last tutorial. In this tutorial, we take one more step to make the debugging of the Flask application easier. Once installed, the Flask Debug toolbar is displayed as an overlay over the Flask application.
Install the Flask Debug toolbar.
pip install flask-debugtoolbar
To activate the debugtoolbar, open the __init__.py file in our project and modify the code by adding the following lines of code.
from flask_debugtoolbar import DebugToolbarExtension app.debug = True toolbar = DebugToolbarExtension(app)
Please note that the Flask debug toolbar is enabled only in debug mode. Once enabled, when you reload your application, you will observe two things.
#1) Debug Toolbar appears at the right-hand side of the browser. Click and expand it to see the various features provided by the toolbar.
#2) Whenever a new POST request is sent to the application, it is intercepted by the toolbar so that we can inspect the variables and the other parameters pertaining to the debugging of the application.
This default intercept can be disabled with the below configuration.
app.config['DEBUG_TB_INTERCEPT_REDIRECTS'] = False
Now let’s write a few tests to test our views for the additional features that we have introduced in the sample application.
Before moving ahead with the testing, please disable debugging as shown below in __init__.py. Alternatively, you can comment out the below line.
app.debug = False
Testing Flask Application Views
We need to organize the testing code to make it more manageable. Create a file called conftest.py in the root directory, and move the below-mentioned lines from test_hello.py to this file.
from app import appbuilder import pytest @pytest.fixture def client(): """ A pytest fixture for test client """ appbuilder.app.config["TESTING"] = True with appbuilder.app.test_client() as client: yield client
pytest fixtures are loaded by pytest at run time. These fixtures are available and shared with all tests. Defining a conftest.py in the root path of any project is considered as a best practice because pytest can recognize all modules in the project without specifying an explicit PYTHONPATH.
Add one more test for the test_hello file. An example test is given below. We call the client object’s get method and assert the expected value in response data stored in resp.data.
Similarly, you can write more tests pointing to various views. We shall write more tests in the subsequent tutorials.
def test_greetings(client): """ A test method to test view hello_greetings""" resp = client.get("/hello/greetings", follow_redirects=True) assert b"Good Morning" in resp.data
Run the tests using the below command from the project’s root directory.
pytest -v
Test run produces the test results in console, as shown below:
There are no failures yet. Let’s design one more test, as mentioned below.
def test_greetings2(client): """ A test method to test view hello_greetings2 """ resp = client.get("/hello/greetings2", follow_redirects=True) assert b"Good Morning" in resp.data
This test will fail as we did not define any message attribute in the HelloWorld class in the views.py file.
Once you run tests using pytest -v then again the results similar to the below-shown image will be displayed on the console.
The section below explains the steps that we need to perform while running the tests in a CI/CD platform. We use Git Actions for the same project.
CI/CD With Git Actions
We now save all the changes in the files and create a commit by giving the message for this tutorial. After committing on the local repository, we pull changes from the remote origin with the –rebase flag to see if there are any conflicts with the new changes on the remote. We rebase to keep the history consistent.
Use the below command to pull and merge the changes from the remote origin. However, commit your changes before pulling the changes from remote.
git pull origin master --rebase
Now checkout the local master branch and merge with the tutorial-2 branch. Once the merge is successful, publish those changes to the origin’s master. This action will invoke the builds on target platforms. We are testing this code on Python3.7 and Python 3.8 on Ubuntu latest.
Conclusion
In this tutorial, we saw how templates work in the Flask framework. We outlined the steps of creating and rendering flask templates with user-defined values using variables and expressions.
We also saw examples of a pre-defined view BaseView of the Flask Appbuilder plugin. This view can be subclassed readily by Flask developers to create custom views.
Concepts covered so far help the readers to quickly create static and dynamic websites using the Flask without a database backend. We will explain how to read and write data from and to the databases with ModelView in the next tutorial when we go through the concept of using Databases with Flask.
=> Read Through The Easy Flask Training Series