Skip to content

Using Templates

Web frameworks free us from WSGI, allowing us to focus on developing web apps by writing functions associated with URLs. However, a web app involves not just business logic but also the pages presented to users. Returning a simple HTML string for basic pages might work, but imagine trying to write a complex HTML document like a 6000-line homepage for a major site. Doing that in a Python string is impractical.

Experienced web developers know that the most complex part of a web app is the HTML page. HTML not only needs to be correct, but also styled using CSS, and often includes complex JavaScript for interactions and animations. Generating HTML pages can be quite challenging.

To address this, template engines were introduced.

With templates, we prepare an HTML document that includes variables and instructions, then generate the final HTML by replacing variables based on provided data before sending it to the user:

tmpl-mvc.webp

This approach follows the MVC (Model-View-Controller) pattern:

  • Controller (C): The URL-handling function in Python acts as the Controller, which is responsible for business logic like verifying usernames or fetching user data.
  • View (V): The template containing variables like represents the View, handling the display logic by replacing variables and generating the HTML users see.

The Model (M) is used to pass data to the View, allowing the template to fetch the appropriate values when replacing variables. In the example, the Model is represented as a dictionary:

python
{ 'name': 'Michael' }

Many web frameworks in Python allow passing keyword arguments, which are assembled into a dictionary internally as the Model.

Implementing MVC in Flask

Let's rewrite a simple example from before using the MVC pattern:

python
from flask import Flask, request, render_template

app = Flask(__name__)

@app.route('/', methods=['GET', 'POST'])
def home():
    return render_template('home.html')

@app.route('/signin', methods=['GET'])
def signin_form():
    return render_template('form.html')

@app.route('/signin', methods=['POST'])
def signin():
    username = request.form['username']
    password = request.form['password']
    if username == 'admin' and password == 'password':
        return render_template('signin-ok.html', username=username)
    return render_template('form.html', message='Bad username or password', username=username)

if __name__ == '__main__':
    app.run()

Flask uses the render_template() function to render templates. The default template engine for Flask is Jinja2, so let's install it:

bash
$ pip install jinja2

Then, create the following Jinja2 templates:

home.html (Homepage template):

html
<html>
<head>
  <title>Home</title>
</head>
<body>
  <h1 style="font-style:italic">Home</h1>
</body>
</html>

form.html (Login form template):

html
<html>
<head>
  <title>Please Sign In</title>
</head>
<body>
  {% if message %}
  <p style="color:red">{{ message }}</p>
  {% endif %}
  <form action="/signin" method="post">
    <legend>Please sign in:</legend>
    <p><input name="username" placeholder="Username" value="{{ username }}"></p>
    <p><input name="password" placeholder="Password" type="password"></p>
    <p><button type="submit">Sign In</button></p>
  </form>
</body>
</html>

signin-ok.html (Login success template):

html
<html>
<head>
  <title>Welcome, {{ username }}</title>
</head>
<body>
  <p>Welcome, {{ username }}!</p>
</body>
</html>

The template for login failure is not needed separately; instead, we add a conditional check in form.html to reuse it as a failure template.

Make sure to place all templates in the correct templates directory at the same level as app.py:

tmpl-dir.png

Start the server by running:

bash
$ python app.py

Now, the template-based pages can be viewed in the browser:

tmpl-result.webp

With MVC, we handle the Model and Controller in Python code, while the View is managed via templates, effectively separating Python code from HTML code.

Benefits of Using Templates

Using templates makes it much easier to modify the HTML. After editing a template and saving it, you can refresh the browser to see the changes immediately, which is essential for debugging HTML, CSS, and JavaScript.

In Jinja2 templates, is used to represent a variable that needs to be replaced. Often, you might need to use control statements like loops and conditionals. In Jinja2, these are denoted by {% ... %}.

For example, looping through a list of pages:

html
{% for i in page_list %}
    <a href="/page/{{ i }}">{{ i }}</a>
{% endfor %}

If page_list is a list [1, 2, 3, 4, 5], the above template generates five hyperlinks.

Other Template Engines

In addition to Jinja2, other popular template engines include:

  • Mako: Uses <% ... %> and ${xxx} syntax.
  • Cheetah: Also uses <% ... %> and ${xxx} syntax.
  • Django: As part of the Django framework, it uses {% ... %} and .

Summary

The MVC pattern separates Python code from HTML, placing all HTML in templates for more efficient development. Templates make it easier to work with and modify the view layer without affecting the backend logic.

Using Templates has loaded