When working on a web application, you may have, on different screens, a variable number of similar components
with minor differences. A "card" component is a good example: every card has a wrapper section
element, a title, and an arbitrary content wrapped inside a div
element.
Consider the following simplified HTML example of an application :
<body>
<section class="card">
<h1>Stats</h1>
<div class="card-content">
<ul>
<li>A stat</li>
...
</ul>
</div>
</section>
<section class="card">
<h1>Activity</h1>
<div class="card-content">
<h2>Yesterday</h2>
<p>All my troubles seem so far away</p>
</div>
</section>
</body>
If you have dozens of such "cards" in your application, you will want to create a partial template for it in order to avoid code duplication. Here is how such card_section.html
partial would look like:
<section class="card">
<h1>{% block title %}Default title{% endblock %}</h1>
<div>{% block content %}Default content{% endblock %}</div>
</section>
Now, let's see how you can re-use this partial in your code.
Today, here is how you would do that using Django built-in tags:
- First, extends the
card_section.html
partial for every type of card:
In stats_card_section.html
:
{% extends "card_section.html" %}
{% block title %}Stats{% endblock %}
{% block content %}
<ul>
<li>A stat</li>
...
</ul>
{% endblock %}
In activity_card_section.html
:
{% extends "card_section.html" %}
{% block tititle %}Activity{% endblock %}
{% block content %}
<h2>Yesterday</h2>
<p>All my troubles seem so far away</p>
{% endblock %}
- Then, include the extended partials:
<body>
{% include "stats_card_section.html" %}
{% include "activity_card_section.html" %}
</body>
This will work, but it presents the following drawbacks:
- It requires the creation of a lot of "extended" partials to override the
title
andcontent
blocks - Often, these extended templates will only be used once (they re created to override the title and the content but are not meant to be re-used)
Now, imagine a new tag callled embed
, that allows you to include a template and override its slots (node similar to blocks) at the same time:
<body>
{% embed "section.html" %}
{% slot title %}Stats{% endslot %}
{% slot content %}
<ul>
<li>A stat</li>
...
</ul>
{% endslot %}
{% endembed %}
{% embed "section.html" %}
{% slot title %}Activity{% endslot %}
{% slot content %}
<h2>Yesterday</h2>
<p>All my troubles seem so far away</p>
{% endslot %}
{% endembed %}
</body>
This approach favors composition over inheritance: no need to create one "extended partial" by type of card.
Is is inspired by the embed
tag from the Twig template library (https://twig.symfony.com/doc/3.x/tags/embed.html).
A reasonable alternative would be to adapt include
so that it can be used in two fashions:
- Combined with
endinclude
, it would allow you to override blocks (similar behaviour to the proposedembed
tag - As a simple tag like today, without
endinclude
(current behaviour)
Given the similarities between this embed
tag and the existing include
built-in tag,
I have tried to reuse as much code as possible, by copying code from IncludeNode
and do_include
.
I tried using block
tags inside embed
tags, but block
nodes are very specific and have been designed with (possibly multiple) inheritance in mind, and reusing them would require a lot of hacky workarounds. Instead, I have implemented a slot
tag with a much simpler implementation.
Depending on the complexity, using block
tags is still an option but would require more research.
As mentioned above, a susbtantial amount of code has been copied from IncludeNode
and do_include
. If this shoud lead to a PR against Django core, copied code could be encapsulated in base classes / helper functions to avoid duplication.