The Flask Mega-Tutorial Part 2: Templates

Web Development THE FLASK MEGA TUTORIAL SERIES Image

    Till the last part, we had a fully working, yet simple web application that has the following file structure:

    miniblog/
      venv/
      app/
        __init__.py
        routes.py
      miniblog.py

    To run the application we had set the FLASK_APP=microblog.py in our terminal session, and then execute flask run. This starts a web server with the application, which we can open by typing the http://127.0.0.1:5000/ URL in our web browser's address bar.

    In this part, we will continue working on the same application. In particular, we will learn how to generate more elaborate web pages with a complex structure and many dynamic components. If anything about the application or the development workflow so far isn't clear, please review Part 1 again before continuing.

    What Are Templates?

    Suppose we want our home page to have a heading that welcomes the user. For the moment, we're going to ignore the fact that the application does not have the concept of users yet, as this is going to come later. Instead, we are going to use a mock user, which can be implemented as a Python dictionary, as follows:

    user = {'username': 'Ashutosh'}
    

    For now, our index view function has been returning a simple string only. What we'll do now is expand that returned string into a complete HTML page, maybe something like this:

    from app import app
    
    
    @app.route('/')
    @app.route('/index')
    def index():
        user = {'username': 'Ashutosh'}
        return '''
        <html>
        <head>
            <title>Home Page - Miniblog</title>
        </head>
        <body>
            <h1>Hello, ''' + user['username'] + '''!</h1>
        </body>
    </html>'''
    
    Note: Python's triple quotes come to the rescue by allowing strings to span multiple lines, including verbatim NEWLINEs, TABs, and any other special characters. Learn More.

    If you are not familiar with HTML, I recommend that you read about HTML on MDN Web Docs for a brief introduction.

    After we have updated our view function as above and run the application, we'll get the following output on our browser:

    Now consider a situation when our website grows, will it be possible to write complex HTML codes within the view function? Even if we write for one view function, but will our project contain only one view function? Obviously no, the application is also going to have more view functions that are going to be associated with other URLs, so imagine if one day we decide to change the layout of this application, and have to update the HTML in every view function. This is clearly not an option that will scale as the application grows.

    What could be the solution to this problem? If the logic of the application is separated from the layout or presentation of the web pages, then things would be much more organized. And this is what we do in real-world projects where Frontend Developers design the web pages and Backend Developers work on the logic.

    Templates help achieve this separation between presentation and business logic. In Flask, templates are written as separate files, stored in a templates folder that is inside the application package. So, within the app package, create a sub-directory called templates.

    mkdir app/templates
    

    Within the templates folder, create an HTML file called index.html and add the HTML code into that file as :

    <html>
      <head>
        <title>{{ title }} - Miniblog</title>
      </head>
      <body>
        <h1>Hello, {{ username }}!</h1>
      </body>
    </html>
    

    This is a mostly standard, very simple HTML page. The only interesting thing about this page is that there are a couple of placeholders for the dynamic content, enclosed in {{ ... }} sections. These placeholders represent the parts of the page that are variable and will only be known at runtime.

    Now that the presentation of the page has been separated from the view function, it can be simplified as:

    from flask import render_template
    from app import app
    
    
    @app.route('/')
    @app.route('/index')
    def index():
        user = {'username': 'Ashutosh'}
        return render_template('index.html', title='Home', user=user)
    

    Doesn't this look much simpler and better now? Let us now run this new version of our application and see the output:

    The output looks exactly the same as before. We can also view the source code of the web page by using the Inspect or View Page Source option in our web browser :

    We can see that the source code also looks exactly the same as our HTML code that we'd used before. The operation that converts a template into a complete HTML page is called rendering. To render the template we had to import a function that comes with the Flask framework called render_template(). This function takes a template filename and a variable list of template arguments and returns the same template, but with all the placeholders in it replaced with actual values. The render_template() function invokes the Jinja2 template engine that comes bundled with the Flask framework. Jinja2 substitutes {{ ... }} blocks with the corresponding values, given by the arguments provided in the render_template() call.

    Now that we've seen how Jinja2 replaces placeholders with actual values during rendering, we can explore few other operations that Jinja2 supports and might be very useful while working on real-world projects.

    Conditional Statements in Jinja2

    Suppose, we come across a situation where we need to show different options for Authenticated(logged in)and Unauthenticated users. The first thing that comes to our mind is Conditional statements or if-else conditions. And Jinja2 comes to our rescue. Yes, Jinja2 supports control statements, given inside {% ... %} blocks. Let us see how we can use this in our template file :

    <html>
      <head>
        {% if title %}
        <title>{{ title }} - Miniblog</title>
        {% else %}
        <title>Welcome to Miniblog!</title>
        {% endif %}
      </head>
      <body>
        <h1>Hello, {{ user.username }}!</h1>
      </body>
    </html>
    

    Now the template is a bit smarter. If the view function forgets to pass a value for the title placeholder variable, then instead of showing an empty title, the template will provide a default one. We can try how this conditional works by removing the title argument in the render_template() call of the view function.

    from flask import render_template
    from app import app
    
    
    @app.route('/')
    @app.route('/index')
    def index():
        user = {'username': 'Ashutosh'}
        return render_template('index.html', user=user)
    

    Notice the title of the page when title is not passed from the view function. It has used the default title that we had given in the template file using the if-else condition.

    Loops in Jinja2

    Consider a situation where we have to show 15 blog posts in a web page. Each blog post will have a thumbnail, title and description. Are we going to repeat the same code for each of the blog post? Can't we create one blog post with placeholders and re-use it for all the blog posts? Yes, we can do this with the help of our friend, Jinja2. Here also, we are going to use the same {% ... %} blocks. Let us see how :

    from flask import render_template
    from app import app
    
    
    @app.route('/')
    @app.route('/index')
    def index():
        user = {'username': 'Ashutosh'}
        posts = [
            {
                'title': 'Hello World in Flask',
                'author': {'username': 'Ashutosh'},
                'body': 'Learn how to create a minimal application in Flask.'
            },
            {
                'title': 'Templates in Flask',
                'author': {'username': 'Krishna'},
                'body': 'Learn how to use external templates in Flask'
            }
        ]
        return render_template('index.html', title='Home', user=user, posts=posts)
    

    We'll be using dummy data for now, since we haven't implemented database stuffs yet. So, to represent user posts we're using a list, where each element is a dictionary that has title , author and body fields. We've passed the posts as posts to the template. 

    On the template side we have to solve a new problem. The list of posts can have any number of elements, it is up to the view function to decide how many posts are going to be presented in the page. The template cannot make any assumptions about how many posts there are, so it needs to be prepared to render as many posts as the view sends in a generic way.

    For this type of problem, Jinja2 offers a for control structure:

    <html>
      <head>
        {% if title %}
        <title>{{ title }} - Miniblog</title>
        {% else %}
        <title>Welcome to Miniblog</title>
        {% endif %}
      </head>
      <body>
        <h1>Hi, {{ user.username }}!</h1>
        {% for post in posts %}
        <div>
          <h2>{{ post.title }} | Written By {{ post.author.username }}</h2>
          <p>{{ post.body }}</p>
        </div>
        {% endfor %}
      </body>
    </html>
    

    When we run our application now, our output looks like this:

    Simple, isn't it? Give this new version of the application a try, and be sure to play with adding more content to the posts list to see how the template adapts and always renders all the posts the view function sends.
     

    Template Inheritance in Jinja2

    You would have noticed, most of the applications these days have a navigation bar at the top of the page with a few frequently used links, such as a link to edit your profile, to login, logout, etc as well as footer whith some information. You can notice this in this blog website as well. There is a navigation bar, side bar and footer in every page you visit on this website. We can too add these our web app. But as our application grows, the number of pages increases. Do you think, we are going to write the same navigation bar and all the common components in each of the page explicitly? Of course not, this is going to decrease the reusability and maintainibility of the code

    If you are aware of Object Oriented Programming(OOP) in any language, you might be knowing about Inheritance. Even if you've not learnt about OOP, you might have heard this word. Using Jinja2, we can use the concept of Template Inheritance. In essence, what we can do is move the parts of the page layout that are common to all templates to a base template, from which all other templates are derived. 

    So, let us create a base template called base.html that includes a simple navigation bar and also the title logic I implemented earlier. We need to write the following template in file app/templates/base.html:

    <html>
      <head>
        {% if title %}
        <title>{{ title }} - Miniblog</title>
        {% else %}
        <title>Welcome to Miniblog</title>
        {% endif %}
      </head>
      <body>
        <div>Go Back to <a href="/">Homepage</a></div>
        <hr />
        {% block content %} {% endblock content %}
      </body>
    </html>
    

    In this template we used the block statement to define the place where the derived templates can insert themselves. Blocks are given a unique name(content  in this case), which derived templates can reference when they provide their content.

    With the base template in place, we can now simplify index.html by making it inherit from base.html:

    {% extends "base.html" %} 
    
    {% block content %}
    
        <h1>Hi, {{ user.username }}!</h1>
        {% for post in posts %}
        <div>
        <h2>{{ post.title }} | Written By {{ post.author.username }}</h2>
        <p>{{ post.body }}</p>
        </div>
        {% endfor %} 
    
    {% endblock content %}
    

    Since the base.html template will now take care of the general page structure, we have removed all those elements from index.html and left only the content part. The extends statement establishes the inheritance link between the two templates, so that Jinja2 knows that when it is asked to render index.html it needs to embed it inside base.html. The two templates have matching block statements with name content, and this is how Jinja2 knows how to combine the two templates into one. Now if we need to create additional pages for the application, we can create them as derived templates from the same base.html template, and that is how we can have all the pages of the application sharing the same look and feel without duplication.


     

    Conclusion

    Let us end this part right here. In the upcoming blogs, we'll use few more Jinja2 operations that will be discussed there itself.
    Thanks for reading!

    0 Comments

    To add a comment, please Signup or Login