Jinja Templates

In this article, I show how a Flask backend can render and return Jinja templates. I will show how to use variables inside the Jinja templates, blocks and macros.

Content

  • SPA Frontend
  • Server Side Rendering Frontend
  • Jinja usage
  • Jinja statements
  • Extensions
  • Blocks

You can find more details in this explanatory video.

All the code is available in this repository.

SPA Frontend

I will use a restful Flask application which is stateless. This makes my application ready to be used with a SPA front-end, a Single-Page Action front-end, like React or Angular. And not ready to be used with a server-side rendering front-end, like Jinja. Nevertheless, Jinja may be helpful in some cases.

The SPAs are loaded in the web browser, at the beginning. Then, when navigating in the website, the SPA will just load single blocks of data from the backend, as working with AJAX requests. The SPA will contain the view to be displayed, and the backend will be requested to obtain the data to fill those views. When authenticating the user, those blocks will be loaded, will be requested, with the information of the authenticated user. This information is used to be sent via a token, a JWT, in the header of the request.

Server Side Rendering Frontend

On the other side, with a server-side rendering website, the browser will request the server to obtain both the view to be displayed and the data. The server will build the view with the data already included on it. As the requests that will be made to the server, to the backend, are mainly GET requests, I can hardly manage the headers that will be sent. That’s why, with the server side rendering, the best option is a stateful backend.

With the stateful backend, the server will create a session on the server side and create a cookie that will be returned to the browser. This cookie will identify the session and the connected user. This cookie will allow the server to return the requested data inside the templates. But i can also mix both concepts: have a stateless application with the server side rendering. Instead of storing in the cookie the session id, I can store the authentication token on it. This will require some adaptations which are out of the scope of this article.

Jinja usage

Today, I will just implement a single case: an email templating from an endpoint. I will return an email content with the summary of my account. Let’s create this endpoint.

from flask import render_template

@mails_bp.route("/mails/summary", methods=["GET"])
@token_auth.login_required
def summary_mail():

    current_user = token_auth.current_user()

    return render_template(
        "summary_mail.html.jinja",
        title="Summary",
        description="Summary of the last week",
        user=current_user,
    )

First of all, I will return a simple HTML page which contains the structure of my website and customized title and description. The additional parameters of the render_template method are values that will be accessible within my template. I’ve named my template summary_mail.html.jinja. I’ve added the suffix Jinja to all of my Jinja templates to allow PyCharm to easily format them. This template will be located by default in the templates folder.

<!doctype html>
<html>

    <head>
        <title>{{title}}</title>
        <meta charset="utf-8">
        <meta name="description" content={{description}}>
        <link href="http://localhost:5000/static/css/summary_mail.css" rel="stylesheet">
    </head>

    <body>
        <div class="regular-layout">
            <div class="regular-layout-body">
                <div class="regular-head">
                    <div class="regular-title">Social Network - {{ user.username }}</div>
                </div>
            </div>
        </div>
    </body>
</html>

This is a regular HTML file. To use the variables injected by Jinja, I use the double curly brackets. Inside those curly brackets, I use the dot notation to access the inner fields of my objects or dictionaries. Then, i have the CSS file. This is a static file. It won’t change after the server side rendering. All the static content will be located, by default, in the static folder.

Jinja statements

Let’s add some more information to this page. I will enrich the header with some navigation buttons and add some content to the body.

@mails_bp.route("/mails/summary", methods=["GET"])
@token_auth.login_required  # type: ignore
def summary_mail():

    navigation_items = [
        {"url": "localhost:5000/messages", "label": "Home"},
        {"url": "localhost:5000/groups", "label": "Groups"},
        {"url": "localhost:5000/users", "label": "Users"},
    ]
    current_user = token_auth.current_user()
    messages = db.session.scalars(
        select(Message).where(Message.user_id == current_user.id).order_by(Message.created)
    ).all()

    return render_template(
        "summary_mail.html.jinja",
        title="Summary",
        description="Summary of the last week",
        user=current_user,
        navigation_items=navigation_items,
        messages=message_schema.dump(messages, many=True),
    )

Let’s go now to the template to use these new variable.

<!doctype html>
<html>

    <head>
        <title>{{title}}</title>
        <meta charset="utf-8">
        <meta name="description" content={{description}}>
        <link href="http://localhost:5000/static/css/summary_mail.css" rel="stylesheet">
    </head>

    <body>
        <div class="regular-layout">
            <div class="regular-layout-body">
                <div class="regular-head">
                    <div class="regular-title">Social Network - {{ user.username }}</div>
                    <div class="tabs">
                        {% for item in navigation_items %}
                            <a href="{{ item.url }}" class="tab">{{ item.label }}</a>
                        {% endfor %}
                        <div class="tab">Logout</div>
                    </div>
                </div>
                <div class="regular-content">
                    <div>
                       {% for message in messages %}
                          <div class="box">
                             <div class="box-header">
                                <a href="{{ message.author.url }}" class="box-author">{{ message.author.label|e }}</a>
                                <div class="box-date">{{ message.date }}</div>
                             </div>
                             <div class="message-content">{{ message.content|e }}</div>
                          </div>
                       {% endfor %}
                    </div>
                </div>
            </div>
        </div>
    </body>
</html>

Here is the for loop. All the Jinja statements are surrounded with the curly bracket and the percentage symbol: the for loop, the if statement, the blocks, the macros, the includes and more. I have the list on the right side and the items of the current iteration on the left side. Then, inside the loop, as before, I can access the fields with the dot notation.

But in the second loop, the messages loop, I’ve used an escape character (|e). This will escape all the special characters before printing the content of the message. This is a security approach to prevent XSS attacks, the Cross Site Scripting attacks.

Extensions

Okay, my template starts to be fine. But what if I want to reuse the layout for other templates? I want to separate the header of my template into a separated Jinja template. I will create a layout file and include it into my summary mail file. And I will do the same for the CSS file.

{% extends 'layout.html.jinja' %}

{% block css %}
  <link href="http://localhost:5000/static/css/summary_mail.css" rel="stylesheet">
{% endblock %}

{% block content %}
    <div>
        {% for message in messages %}
           <div class="box">
              <div class="box-header">
                 <a href="{{ message.author.url }}" class="box-author">{{ message.author.label|e }}</a>
                 <div class="box-date">{{ message.date }}</div>
              </div>
              <div class="message-content">{{ message.content|e }}</div>
           </div>
        {% endfor %}
    </div>
{% endblock %}

Here is the summary_mail.html.jinja file. With the extends keyword, I indicate that my template, that my file, will begin with the layout template and include those blocks inside.

<!doctype html>
<html>

    <head>
        <title>{{title}}</title>
        <meta charset="utf-8">
        <meta name="description" content={{description}}>
        <link href="http://localhost:5000/static/css/layout.css" rel="stylesheet">
        {% block css %}{% endblock %}
    </head>

    <body>
        <div class="regular-layout">
            <div class="regular-layout-body">
                <div class="regular-head">
                    <div class="regular-title">Social Network - {{ user.username }}</div>
                    <div class="tabs">
                        {% for item in navigation_items %}
                            <a href="{{ item.url }}" class="tab">{{ item.label }}</a>
                        {% endfor %}
                        <div class="tab">Logout</div>
                    </div>
                </div>
                <div class="regular-content">
                    {% block content %}{% endblock %}
                </div>
            </div>
        </div>
    </body>
</html>

In the layout.html.jinja file, I’ve defined where we’ll be located the CSS block and the content block. Then in the summary file, I’ve defined what’s inside the CSS block and what’s inside the content block. Jinja will then build the final file taking those two pieces of code and injecting them inside the layout file.

Blocks

One last point, inside my mail summary file, I may need the box with the message and author information to be reusable somewhere else. So, I will create a macro with it. A macro is like a function. I define it at the beginning and call it when necessary.

{% macro box(author_url, author_name, date, content) -%}
    <div class="box">
        <div class="box-header">
            <a href="{{ author_url }}" class="box-author">{{ author_name|e }}</a>
            <div class="box-date">{{ date }}</div>
        </div>
        <div class="message-content">{{ content|e }}</div>
    </div>
{%- endmacro %}

{% block content %}
    <div>
        {% for message in messages %}
            {{ box(message.author.url, message.author.label, message.date, message.content) }}
        {% endfor %}
    </div>
{% endblock %}

In the macro definition, I name the function with the input parameters and use the input parameters name inside my macro. And to use the macro, just call the name of the function and put the values that will be used inside the macro.

Conclusion

  • Jinja is already included by default in a Flask application. So, i didn’t need to add any dependency.
  • I’ve used the method render_template to return a template with all the variables or object needed.
  • The templates are located in the templates folder and the CSS files in the static folder.
  • To use my variables inside the Jinja templates, I have to surround them with double curly brackets.
  • To use the Jinja statements, I surround them with a single curly bracket and the percentage symbol.
  • I’ve created a reusable layout and defined where we’ll be located some blocks.
  • Then, in my summary mail template, I’ve defined the content of the blocks and indicated that I extend the layout file.
  • Finally, I’ve defined a macro as a function, with a name and input parameters.

References

Repository


Never Miss Another Tech Innovation

Concrete insights and actionable resources delivered straight to your inbox to boost your developer career.

My New ebook, Best Practices To Create A Backend With Spring Boot 3, is available now.

Best practices to create a backend with Spring Boot 3

Leave a comment

Discover more from The Dev World - Sergio Lema

Subscribe now to keep reading and get access to the full archive.

Continue reading