Powerful Page Templates in Flask With Jinja

Work with markup that writes itself using Flask's built-in Jinja library.

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 oh, so much more than that.

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

    Create a Simple 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">
        <!-- Google Analytics -->
        <script>
        window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
        ga('create', 'UA-XXXXX-Y', 'auto');
        ga('send', 'pageview');
        </script>
        <script async src='https://www.google-analytics.com/analytics.js'></script>
        <!-- End Google Analytics -->
    </head>
    <body>
      {% include 'nav.html' %}
      {% block content %}{% endblock %}
      {% include 'footer.html' %}
    </body>
    </html>
    

    Note the bracketed values in our otherwise-HTML! 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 reserved to be replaced with a variable named 'title' when we serve this view in Flask. We pass variables to templates when we render them in the 'routes' part of our app.
    • Includes such as {% include 'nav.html' %} are saying "load the entirety of a separate HTML file named nav.html into this spot right here." This is called a partial. Partials are encapsulated, standalone components intended to be frequently reused throughout our app.
    • Things get real interesting with blocks, as seen in {% block content %}{% endblock %}. This statement is reserving a chunk of our template to receive a corresponding chunk of another template when such a template is loaded into layout.hml. Remember: layouts are just barebones commonalities between pages. To build meaningful pages, we must combine the parts of unique pages (such as index.html) with layouts 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 %}
    

    Small But Powerful

    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
    New York City Website
    Product manager turned engineer with an ongoing identity crisis. Breaks everything before learning best practices. Completely normal and emotionally stable.

    Product manager turned engineer with an ongoing identity crisis. Breaks everything before learning best practices. Completely normal and emotionally stable.