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:
- duplicate cornershop/templates/cart.html in widgetshop/templates and hack it,
- include cornershop/templates/cart.html and hack around it, then copy modifications in both sites layout templates,
- include cornershop/templates/cart.html from widgetshop/templates/cart.html and hack it,
- 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
Comments
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
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»