Powerful Page Templates in Flask With Jinja

    So you want to build a web application in Python, eh? Hasn't anybody told you... Python is SLOW! Python is subject to CONTEXT SWITCHING! Oh, the HORROR!

    Chances are most of these people aren't well-versed in Python at all. I enjoy creating web applications in Flask more-so than I do in Node. Even if we were to put aside somehow the thousands of potentially malicious Javascript libraries needed to stand up a simple app, or the decades of legacy knowledge needed to tolerate the JavaScript ecosystem without going insane: the simplicity of Flask paired and its libraries get the job done faster in most cases. Oh yeah, and you can write backend logic in a language intended to do so at the drop of a dime.

    One of Flask's early surprises hits you when working with Jinja2. Jinja is Flask's default templating system, which processes templates into HTML markup to be served to users at runtime. In addition to the inheritance and partial inclusion we've come to expect from templating systems, Jinja is particularly well-equipped build pages out of raw data. You'll see what I mean.

    Layouts, Pages, and Partials

    Let's add three pages to our templates folder, which is a directory typically reserved for template files. We'll add 3 files: layout.html, index.html, and nav.html. Each of these templates represents one of three common template "types" when building in Jinja.

    Oh, and disregard the fact that these files retain an HTML file extension- they are so much more than that.

    myproject
    ├─ /templates
    │  ├─ layout.html
    │  ├─ index.html
    │  └─ nav.html
    └─ app.py
    

    Create a Simple Flask Route to Get Started

    We'll make a brief stop in app.py to set up the most basic logic an app can have: serving up a homepage:

    from flask import Flask, render_template
    
    app = Flask(__name__, template_folder="templates")
    
    @app.route('/')
    def home():
        """Landing page."""
        return render_template('/index.html', title="Lame Site")
    

    Note how we specify template_folder="templates" when instantiating our app object; this is critical to let Flask know where templates are going to be stored in the project directory. Be aware that this directory is almost always explicitly set as /templates. If you store your templates anywhere else, you have a mental health problem.

    The Bread and Butter of Template Inheritance

    Layout.html is going to be our base template. In other words, this barebones file will represent elements which should be common to all of our app's pages, such as metadata, analytics, etc. It is the 'page we load other pages into.'

    If you're familiar with Handlebars, Jinja's templating concepts are the same deal with slightly different syntax. Here's a decent layout.html example:

    <!-- layout.html -->
    <!doctype html>
    <html>
    <head>
      <title>{{title}}</title>
      <meta http-equiv="content-type" content="text/html; charset=utf-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
    </head>
    <body>
      {% include 'nav.html' %}
      {% block content %}{% endblock %}
      {% include 'footer.html' %}
    </body>
    </html>
    

    Note the curly brackets in our markdown. Each of these represents a reservation of "stuff to come." That "stuff" could come in the form of a variable, a partial, or code from another template.

    We've just utilized three templating concepts in the above example:

    • The double-bracket {{title}} is a reserved variable name. When we render this template, Jinja will look for a variable named title to drop in here (if title doesnt exist, this renders as nothing). In Flask we're able to "pass" variables to templates when we render them in the 'routes' part of our app, like we did in our home() route.
    • "Includes" (such as {% include 'nav.html' %}) are saying "load a separate HTML file named nav.html into this spot right here." Standalone templates which make up larger templates like this are called partials. Partials are usually bits of frequently used markdown, like navigation elements, scripts, or meta data.
    • Things get real interesting with blocks, which we see being used with {% block content %}{% endblock %}. This statement is reserving a chunk of space in our template which is expecting to receive a corresponding chunk from another template. Entire templates can be "loaded" into other templates, such as when a template extends another. Create a base template like layout.hml allows us to set a common structure between pages which extend them. To build meaningful pages, we must combine the parts of unique pages (such as index.html) with layouts (like layout.hml) to create full pages.

    Let's look at index.html: the file we're about to shove into layout.html:

    <!-- index.html -->
    {% extends 'layout.html' %}
    
    {% block content %}
    <div class="container">
    <h1>My Lame site</h1>
        <p>Hello, and welcome to my lame site! I'm so glad you're here. I'm so lonely.</p>
    </div>
    {% endblock %}
    

    Remember in the route we created in app.py: our function states "load index.html when users visit /." We never mention layout.html at all. That is because by including {% extends 'layout.html' %} in index.html, we're stating that index.html should extend layout.html. index.html is our unique snowflake of a page, and layout is our boring skeleton.

    Back to talking about blocks:  templates can contain multiple "blocks" of code. Each named block (such as {% block content %}) in a page such as index.html should correspond to an empty block we reserved in layout.html. When we serve the final page, it's like we're plugging plugs into their respective sockets. We can reserve as many "blocks" in our layout as we want, and thus serve multiple blocks within the same template.

    To help illustrate this, let's say you're building a horrible clickbait site where your requirements dictate that horrible, invasive ads should appear between pieces of content of every page of your site. If we keep that page structure to layout.html, then we can retrofit our layout to have multiple slots for incoming content from index.html: both before and after the horrible monstrosity of an idea I already regret imagining. If index.html contained both {% block content1 %}...{% endblock %} and {% block content2 %}...{% endblock %}, those blocks are now independent entities which can be loaded to their respective slots.

    Passing Data to Templates

    When we passed title="Lame Site" to index.html in our route, we were passing a simple variable to replace {{title}}. Check out this example of what we can do when we pass JSON objects to templates:

    <form action="/submitted" method=post>
      {% if error %}
       <p class=error><strong>Error:</strong> {{ error }}</p>
      {% endif %}
      {% for field in request.fields.requestTypeFields %}
         {% if field.name in ('Category', 'Product', 'Dashboard Name') %}
              <select id="{{request.name}} {{field.name}}" 
                      name="{{field.name}}" 
                      label="{{field.name}}" 
                      class="input-field">
               <option value="Choose your option" 
                       disabled 
                       selected>
                   {{field.description}}
                </option>
                {% for option in field.validValues %}
                    <option value="{{option.value}}">
                        {{option.label}}
                    </option>
                    {% endfor %}
                </select>
                <label>{{field.name}}</label>
           {% elif field.name == 'Description' %}
                <label for="{{request.name}} {{field.name}}">
                    {{field.name}}
                </label>
                <textarea id="{{field.name}}" 
                          class="materialize-textarea input-field" 
                          placeholder="{{field.description}}" 
                          name="{{field.name}}">
                 </textarea>
             {% else %}
             <label for="{{request.name}} {{field.name}}">
                 {{field.name}}
             </label>
             <input placeholder="{{field.description}}" 
                    id="{{request.name}} {{field.name}}" 
                    type="text" 
                    class="input-field validate" 
                    name="{{field.name}}">
             {% endif %}
        {% endfor %}
        <input type="submit" value="Submit" class="btn cyan formsubmit">
    </form>
    

    Those are for loops and if statements all working against a single JSON object. This specific example demonstrates building a form based on a JSON object; one we've happened to fetch from JIRA. Just by passing this JSON to a Jinja template, we can recreate an entire enterprise system's form logic in a simple template block.

    Here's another example. This one returns feedback to a user who presumably filled out a form incorrectly, thus must be chastised with popup error messages:

    {% with messages = get_flashed_messages() %}
      {% if messages %}
        <ul class=flashes>
        {% for message in messages %}
          <li>{{ message }}</li>
        {% endfor %}
        </ul>
      {% endif %}
    {% endwith %}
    

    Powerful Loops

    Sometimes we're faced with situations where we need to create templates with very complicated loops. I recently ran into a scenario where I had to dynamically create a multiple-choice question-and-answer quiz, with a catch: each answer had its own tooltip!

    Using a simple loop like {% for choice in choices %} isn't enough here, because my tooltips are stored in another array called tooltips. I also can't loop twice here, because the nature of the result HTML determines that we need to have all this happen in a single loop. This is where we can use loop.index.

    loop.index allows us to get the current index of a loop we're in. This way, we can create markup for our choices and our tooltips in the same loop:

    <div class="answer-background">
            <p>Choose your answer</p>
            {% for choice in choices %}
            <div class="answer-container">
              <div class="answer-tooltip">{{ tooltips[loop.index] }}</div>
              <button class="answer-option" id="{{loop.index}}">{{choice}}</button>
            </div>
            {% endfor %}
          </div>

    Become a Jinja Ninja

    Templates are one small part of Flask, but they demonstrate a greater philosophy generally consistent throughout the framework: small libraries can do big things. Sometimes it may take some courage to maneuver with the tools at hand cleverly, but the power of the Triforce is with you. You are Hyrule's last hope, great warrior... now you must make haste in your duties to build little apps. Or whatever.

    Todd Birchard's' avatar
    Todd Birchard
    New York City Website
    Engineer with an ongoing identity crisis. Breaks everything before learning best practices. Completely normal and emotionally stable.
    Todd Birchard's' avatar
    Todd Birchard

    Engineer with an ongoing identity crisis. Breaks everything before learning best practices. Completely normal and emotionally stable.