Block composition and layout inheritance

Usually, any web application will have a number of web pages that will be different from each other. Code blocks such as headers and footers will be the same in almost all the pages throughout the site. Likewise, the menu also remains the same. In fact, usually, just the center container block changes, and the rest usually remains the same. For this, Jinja2 provides a great way of inheritance among templates.

It's a good practice to have a base template where we can structure the basic layout of the site along with the header and footer.

Getting ready

In this recipe, we will try to create a small application where we will have a home page and a product page (such as the ones we see on e-commerce stores). We will use the Bootstrap framework to give a minimalistic design to our templates. Bootstrap can be downloaded from http://getbootstrap.com/.

Here, we have a hardcoded data store for a few products placed in the models.py file. These are read in views.py and sent over to the template as template context variables via the render_template() method. The rest of the parsing and display is handled by the templating language, which, in our case, is Jinja2.

How to do it…

Have a look at the following layout:

flask_app/
    - run.py
    my_app/
        – __init__.py
        - product/
            - __init__.py
            - views.py
           - models.py
        - templates/
            - base.html
            - home.html
            - product.html
        - static/
            - js/
                - bootstrap.min.js
            - css/
                - bootstrap.min.css
                - main.css

In the preceding layout, static/css/bootstrap.min.css and static/js/bootstrap.min.js are standard files and can be downloaded from the Bootstrap website mentioned in the Getting ready section. The run.py file remains the same as always. The rest of the application is explained here. First, we will define our models, my_app/product/models.py. In this chapter, we will work on a simple non-persistent key-value store. We will start with a few hardcoded product records made well in advance:

PRODUCTS = {
    'iphone': {
        'name': 'iPhone 5S',
        'category': 'Phones',
        'price': 699,
    }, 
    'galaxy': {
        'name': 'Samsung Galaxy 5',
        'category': 'Phones',
        'price': 649,
    },
    'ipad-air': {
        'name': 'iPad Air',
        'category': 'Tablets',
        'price': 649,
    },
    'ipad-mini': {
        'name': 'iPad Mini',
        'category': 'Tablets',
        'price': 549
    }
}

Next comes the views, that is, my_app/product/views.py. Here, we will follow the blueprint style to write the application:

from werkzeug import abort
from flask import render_template
from flask import Blueprint
from my_app.product.models import PRODUCTS

product_blueprint = Blueprint('product', __name__)

@product_blueprint.route('/')
@product_blueprint.route('/home')
def home():
    return render_template('home.html', products=PRODUCTS)

@product_blueprint.route('/product/<key>')
def product(key):
    product = PRODUCTS.get(key)
    if not product:
        abort(404)
    return render_template('product.html', product=product)

The name of the blueprint, product, that is passed in the Blueprint constructor will be appended to the endpoints defined in this blueprint. Have a look at the base.html code for clarity.

Note

The abort() method comes in handy when you want to abort a request with a specific error message. Flask provides basic error message pages that can be customized as needed. We will see them in the Creating custom 404 and 500 handlers recipe in Chapter 4, Working with Views.

The application's configuration file, my_app/__init__.py, will now look like the following lines of code:

from flask import Flask
from my_app.product.views import product_blueprint

app = Flask(__name__)
app.register_blueprint(product_blueprint)

Apart from the CSS code provided by Bootstrap, we have a bit of custom CSS code in my_app/static/css/main.css:

body {
  padding-top: 50px;
}
.top-pad {
  padding: 40px 15px;
  text-align: center;
}

Coming down to templates, the first template acts as the base for all templates. This can aptly be named as base.html and placed at my_app/templates/base.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Flask Framework Cookbook</title>
    <link href="{{ url_for('static', filename='css/bootstrap.min.css') }}" rel="stylesheet">
    <link href="{{ url_for('static', filename='css/main.css') }}" rel="stylesheet">
  </head>
  <body>
    <div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
      <div class="container">
        <div class="navbar-header">
          <a class="navbar-brand" href="{{ url_for('product.home') }}">Flask Cookbook</a>
        </div>
      </div>
    </div>
    <div class="container">
      {% block container %}{% endblock %}
    </div>

    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
    <script src="{{ url_for('static', filename='js/bootstrap.min.js') }}"></script>
  </body>
</html>

Most of the preceding code is normal HTML and Jinja2 evaluation placeholders, which were introduced in the previous chapter. An important point to note is how the url_for() method is used for blueprint URLs. The blueprint name is appended to all the endpoints. This becomes very useful when we have multiple blueprints inside one application, and some of them can have similar-looking URLs.

In the home page, my_app/templates/home.html, we iterate over all the products and show them:

{% extends 'base.html' %}

{% block container %}
  <div class="top-pad">
    {% for id, product in products.iteritems() %}
      <div class="well">
        <h2>
          <a href="{{ url_for('product.product', key=id) }}">{{ product['name'] }}</a>
          <small>$ {{ product['price'] }}</small>
        </h2>
      </div>
    {% endfor %}
  </div>
{% endblock %}

The individual product page, my_app/templates/product.html, looks like the following lines of code:

{% extends 'home.html' %}

{% block container %}
  <div class="top-pad">
    <h1>{{ product['name'] }}
      <small>{{ product['category'] }}</small>
    </h1>
    <h3>$ {{ product['price'] }}</h3>
  </div>
{% endblock %}

How it works…

In the preceding template structure, we saw that there is an inheritance pattern being followed. The base.html file acted as the base template for all other templates. The home.html file inherited from base.html, and product.html inherited from home.html. In product.html, we also saw that we overwrote the container block, which was first populated in home.html. On running this app, we will see the output as shown in the following screenshots:

The preceding screenshot shows how the home page will look. Note the URL in the browser. This is how the product page will look:

See also

  • Check out the Creating a custom context processor and Creating a custom Jinja2 filter recipes, which extend this application