Template Inheritance

What is Template Inheritance?

Before you can implement CRUD functionality in your Flask app, you need a user-friendly front end to interact with your data. Flask's template inheritance system, powered by Jinja2, allows you to build a clean, modular HTML structure with reusable components.

This means that you won't have to rewrite shared code across pages such as meta data or header/footer layouts, which will save a lot of time in larger projects.

Template inheritance like this allows you to create a base HTML template that contains common layout elements and then extend that base template in other pages. This keeps your code DRY, making maintenance easier.

Base.html

Inside your project_name folder, create a folder named templates. Inside here, make your base.html file.

Also in your project_name folder, create a folder named static and inside here create a css folder with style.css inside.

In your base.html file you want to create your website with all the things that will be reused on every page of your app. These may include:

  1. Meta data for your website
  2. External links to CDNs such as Bootstrap or Materialize
  3. External links to JQuery
  4. Links for your favicon
  5. Links to your fontawesome account
  6. The title of your website
  7. Any analytics
  8. The body element
  9. The navigation bar
  10. A main element
  11. A footer
  12. Any post load JavaScript
<--DOCTYPE-->
<!DOCTYPE html>

<--Language-->
<html lang="en">


<head>
    <!--Charset-->
    <meta charset="UTF-8">
    <!--Viewport-->
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <!--Keyword-->
    <meta name="keywords" content="">
    <!--Description-->
    <meta name="description" content="">
    <!--Author-->
    <meta name="author" content="Web Dev Grad">

    <!--Favicon with a favicon folder in the static folder-->
    <link rel="apple-touch-icon" sizes="180x180" href="{{ url_for('static', filename='favicon/apple-touch-icon.png') }}">
    <link rel="icon" type="image/png" sizes="32x32" href="{{ url_for('static', filename='favicon/favicon-32x32.png') }}">
    <link rel="icon" type="image/png" sizes="16x16" href="{{ url_for('static', filename='favicon/favicon-16x16.png') }}">
    <link rel="icon" type="image/png" sizes="16x16" href="{{ url_for('static', filename='favicon/favicon-16x16.png') }}">
    <link rel="icon" type="image/png" sizes="192x192" href="{{ url_for('static', filename='favicon/android-chrome-192x192.png') }}">
    <link rel="icon" type="image/png" sizes="512x512" href="{{ url_for('static', filename='favicon/android-chrome-512x512.png') }}">
    <link rel="manifest" href="{{ url_for('static', filename='favicon/site.webmanifest') }}">

    <!--Fontawesome Link-->
    <script src="yourlinkehere" crossorigin="anonymous"></script>

    <!--Materialize CSS CND Link-->
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">

    <!--Link to personal stylesheet-->
    <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}" type="text/css">

    <!--Title with block for expanding for each page-->
    <title>
        Project Name{% block title %}{% endblock %}
    </title>

    <!--Google Analytics Tag-->
    <script async src=""></script>
    <script>
        window.dataLayer = window.dataLayer || [];
        function gtag(){dataLayer.push(arguments);}
        gtag('js', new Date());
        
        gtag('config', '', { 'anonymize_ip': true });
    </script>

</head>


<body>
    <!--Navbar for logged in user-->
    <!--Check for a logged in user-->
    {% if 'user_id' in session %}
    <nav>
        <div class="nav-wrapper">
        <a href="{{ url_for('home')}}" class="" aria-label="Go to Project Name Homepage">
            <img src="{{ url_for('static', filename='') }}" class="site-logo" alt="Image description">
            <h1>
            <img src="{{ url_for('static', filename='') }}" class="site-logo" alt="Image description">
            </h1>
        </a>
        <a href="#" data-target="mobile-demo" class="sidenav-trigger burger-menu" aria-label="Navigation menu toggle"><i class="fas fa-bars"></i></a>
        <ul class="right hide-on-med-and-down open-sans">
            <li><a href="{{ url_for('') }}" aria-label= "">Link1</a></li>
            <li><a href="{{ url_for('') }}" aria-label = "">Link2</a></li>
            <li><a href="{{ url_for('') }}" aria-label = "">Link3</a></li>
            <li><a href="{{ url_for('') }}" aria-label="">Link4</a></li>
        </ul>
        </div>
    </nav>

    <!-- Burger menu navbar -->
    <ul class="sidenav open-sans" id="mobile-demo">
        <li><a href="{{ url_for('') }}" aria-label= "">Link1</a></li>
        <li><a href="{{ url_for('') }}" aria-label = "">Link2</a></li>
        <li><a href="{{ url_for('') }}" aria-label = "">Link3</a></li>
        <li><a href="{{ url_for('') }}" aria-label="">Link4</a></li>
    </ul>

    <!--Navbar for logged out user-->
    {% else %}
    <nav>
        <div class="nav-wrapper">
        <a href="{{ url_for('home')}}" class="brand-logo dangrek-regular" aria-label="">
            <img src="{{ url_for('static', filename='') }}" class="site-logo" alt="">
            <h1>
            <img src="{{ url_for('static', filename='') }}" class="site-logo" alt="">
            </h1>
        </a>
        <a href="#" data-target="mobile-demo" class="sidenav-trigger" aria-label="Navigation menu toggle"><i class="fas fa-bars"></i></a>
        <ul class="right hide-on-med-and-down open-sans">
            <li><a href="{{ url_for('') }}" aria-label="">Link1</a></li>
            <li><a href="{{ url_for('') }}" aria-label="">Link2</a></li>
        </ul>
        </div>
    </nav>

    <!--Burger menu navbar-->
    <ul class="sidenav open-sans" id="mobile-demo">
        <li><a href="{{ url_for('') }}" aria-label="">Link1</a></li>
        <li><a href="{{ url_for('') }}" aria-label="">Link2</a></li>
    </ul>
    {% endif %}

    <!--Main-->
    <main>
        {% block content %}
        {% endblock %}
    </main>

    <!--Footer-->
    <footer class="page-footer">
        <div class="container">
        <div class="row valign-wrapper">
            <!--Site logo-->
            <div class="col s6">
            <a href="{{ url_for('')}}" class="dangrek-regular footer-logo" aria-label="">
                <img src="{{ url_for('static', filename='') }}" class="site-logo" alt="">
                <img src="{{ url_for('static', filename='') }}" class="site-logo" alt="">
            </a>
            </div>
            <!--Developer details-->
            <div class="col s6 roboto-regular">
            <p id="developer">Website Developer: <br class="hide-on-large-only"><span>Web Dev Grad</span></p>
            <!--Developer contact links-->
            <ul id="contact-links">
                <li class="contact-link">
                <a href="" aria-label="">
                    <i class="fas fa-envelope-square"></i>
                </a>
                </li>
                <li class="contact-link">
                <a href="" target="_blank" rel="noopener"
                    aria-label="">
                    <i class="fab fa-linkedin"></i>
                </a>
                </li>
                <li class="contact-link">
                <a href="" target="_blank" rel="noopener"
                    aria-label="">
                    <i class="fab fa-github-square"></i>
                </a>
                </li>
            </ul>
            </div>
        </div>
        </div>
    </footer>

    <!--Post Load JavaScript>
    <!--Materialize CSS JavaScript CDN Link-->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
    <!--Personal JavaScript link-->
    <script src="{{ url_for('static', filename='js/script.js') }}"></script>
</body>
</html>

Jinja2

Jinja2 is the templating language used in Flask, so we use Jinja2 templates to generate dynamic HTML pages. Jinja lets us write HTML mixed with special syntax to add logic, reuse code, and keep your templates clean and maintainable.

{% extends %}

{% extends 'filename.html' %} allows html files to use another as a base template and to expand upon them. For instance any page that will have the head, header, and footer in will use {% extends 'base.html' %} at the top of the file.

{% includes %}

Whereas {% extends 'filename.html' %} uses a file as a base to expand upon, {% includes 'filename_2.html' %} draws another file into your code to use. This is phrased quite confusingly but a key example would to think about your pages in sections. In the base.html, for example it would be clearer (and more modular) if it had the head and main but for the header and footer just said "insert header/footer here". In this way we can make header.html or footer.html as separate files that just consist of the header or footer elements. Then in base.html we would have {% includes 'header.html' %} and {% includes 'footer.html' %} where we want them to be. This makes the code more readable and troubleshooting easier. Is there an issue with something in the header? Then check out header.html instead of scrolling through base.html.

{% block %}

It's all well and good extending from a base template, but how can we add to it in our new file. This is where {% block blockname %}{% endblock %} comes in. In the file we are extending from we can add in a block with a descriptive name such as {% block title %}{% endblock %} in the title element. Then in the page that extends from base.html we can wrap some code in {% block title %}{% endblock %} and Flask will know to insert this into that specific space in the base template.

{% if %}

{% if %}{% else %}{% endif %} blocks only render if a conditions is true.

{% for %}

{% for %}{% endfor %} blocks iterate over lists of other collections such as:

<ul>
{% for task in tasks %}
    <li>{{ task.name }} - Due: {{ task.due_date }}</li>
{% else %}
    <li>No tasks available.</li>
{% endfor %}
</ul>

{{ url_for() }}

Use {{ url_for('route_name') }} as the href in anchor tags in order to direct users to the routes defined in your routes.py file.

These routes should be updated in your routes.py file to your html files that are in your templates folder to look like:

@app.route("/")
def home():
    """ A function that directs users to the homepage."""
    return render_template("home.html")