Template inheritance explained

Template inheritance, as implemented by Django in Python and Dwoo in php, allow this ways of managing view construction:

  • grouping includes in a “child” template,
  • allow an include to not override the zone it belongs.

Features

Grouping includes

Instead of this base.html:

<html>

    <head>
        {% include head_template %}
    </head>

    <body>
        {% include body_template %}
    </body>

</html>

It is possible to have this base.html:

<html>

    <head>
        {% block head %}{% endblock %}
    </head>

    <body>
        {% block body %}{% endblock %}
    </body>

</html>

And in the request specific template:

{% extends "main.html" %}

{% block head %}
    <title>hello world</title>
{% endblock head %}

{% block body %}
    <h1>Hello world!</h1>
{% endblock body %}

That’s fairly useless.

Non-overriding included templates

Instead of this base.html:

<html>

    <head>
        <title>{{ title }}</title>
    </head>

    <body>
        {% include body_template %}
    </body>

</html>

It is possible to have this base.html:

<html>

    <head>
        {% block head %}
            <title>{{ title }}</title>
        {% endblock %}
    </head>

    <body>
        {% block body %}
        {% endblock %}
    </body>

</html>

And in the request specific template:

{% extends "main.html" %}

{% block head %}
    {{ block.super }}
    <link rel="stylesheet" href="{{ MEDIA_ROOT }}/css/foopage.css />
{% endblock head %}

{% block body %}
    <h1>Hello world!</h1>
{% endblock body %}

That solve an interesting need, making it possible to overload a zone without having to override defaults.

It is very useful indeed when customising templates maintained by another developer.

Template inheritance issues

Page templates have {% extends “base.html” %} hard coded all around.

It is impossible to: — use conditionnal block declaration, — use variable in {% extend %} bloc,

Proof of concept

With this base.html:

<html>

    <head>...</head>

    <body>

        {% block menu %}{% endblock menu %}
        {% block content %}{% endblock content %}

    </body>
<html>

Suppose another site should be made, with the contents block before the menu block:

<html>
    <head>...</head>

    <body>

        {% block content %}{% endblock content %}
        {% block menu %}{% endblock menu %}

    </body>
<html>

That would just work if the child templates could have a dynamic parent template name, or if conditionnal block declaration would be allowed — which would be a bad idea.

Decorating include

Solving needs that template inheritance was meant to solve without running into template inheritance issues is as simple as allowing a decorator template name in the include block.

Proof of concept

Instead of this base.html:

{% block head %}
    <title>{{ title }}</title>
{% endblock %}

And child.html:

{% block head %}
    {{ block.super }}
    <link rel="stylesheet" href="{{ MEDIA_ROOT }}/css/foopage.css />
{% endblock head %}

It would be possible to have this base.html:

<head>
    {% include "this_head.html" decorator "common_head.html" %}
</head>

Where this_head.html is specific to the request unlike common_head.html:

<link rel="stylesheet" href="{{ MEDIA_ROOT }}/css/foopage.css />

And common_head.html:

<title>{{ title }}</title>

<!-- instead of {{ block.super }}: -->
{% do_included_template_here %}

Real world example

You’re reusing an e-commerce application called “cornershop” in you project called “widgetshop”. Widgetshop is multi site, it sales on both “bluewidgets.com” and “redwidgets.com”.

Suppose cornershop/templates/cart.html contains:

<!-- cornershop cart stuff -->

Suppose “widgetshop” requires some shopping cart block modifications, it is possible to:

  1. duplicate cornershop/templates/cart.html in widgetshop/templates and hack it,
  2. include cornershop/templates/cart.html and hack around it, then copy modifications in both sites layout templates,
  3. include cornershop/templates/cart.html from widgetshop/templates/cart.html and hack it,
  4. include cornershop/templates/cart.html with a decorator.

1. Duplicating

You copy cornershop/templates/cart.html into widgetshop/templates/cart.html.

Resulting widgetshop/templates/cart.html:

<!-- cornershop cart stuff -->
<!-- your extra stuff -->

Works, but will require extra work in case of upstream updates of cornershop.

2. Classic inclusion

Including cornershop/templates/cart.html in widgetshop/templates/bluewidgets.com/base.html:

<html>

    [snip]

    {% include "cornershop/cart.html" %}
    <!-- your extra stuff -->

    [snip]

</html>

Works, but will require extra work to maintain it in widgetshop/templates/redwidgets.com/base.html as well.

3. Craftman’s decorator

You create widgetshop/templates/cart.html:

{% include "cornershop/cart.html" %}

<!-- your extra stuff -->

That works, solutions will be possible when you’ll need to reuse your extra stuff:

  • duplicate it,
  • use a third included template,
  • invent not so simple variables to control a generic intermediary template,

4. Include tag supporting a decorator argument

Add to both widgetshop/templates/bluewidgets.com/layout.html and widgetshop/templates/redwidgets.com/layout.html:

{% include "cornershop/cart.html" decorator "widgetshop/decorator_extra_stuff.html" %}

In widgetshop/templates/decorator_extra_stuff.html:

{% do_included_template_here %}
<!-- your extra stuff -->

Obviously, this modification will not need propagation nor cause any of the issues demonstrated above.

Conclusion

Trying to get object-oriented convenience in views is a good idea, although the following was forgotten:

  • Inheritance was made for object oriented programming.
  • Decorators were made for object oriented views and user interfaces.

Credits

  • mpaolini for helping my investigations
  • Derick Rethans for reviewing
Add post to: Delicious Reddit Slashdot Digg Technorati Google
(already: 2) Comment post

Comments

29.07.2009 12:40 Gaetano
avatar

Very nice post. However I see the decorator operator proposed as little more than a simplified ‘use a common 3rd template’ method.

In the extra_stuff.html, I can add things below or above the %do_included_template part, but not modify its logic — which is, I’d say, most often the case. To overcome that part you’re still left to — copy the original template in your decorator, changing parts you need, and be left with maintenance hell — springle the original template with usage of variables — and there is no limit down this path — split the original templates in as many small blocks as you can — which makes coding unpractical There’s no free lunch, I guess

12.08.2009 7:15 James P.
avatar

Absolutely correct Gaetano!

The “include” PHP statement contributed an historical dimension to web development.

In the deepest catacombs of troll circles, template inheritance kills a kitten every update.

Comment form for «Template inheritance explained»

Required. 30 chars of fewer.

Required.

captcha image Please, enter symbols, which you see on the image

Comment post