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.htmlpartial 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
titleandcontentblocks - 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 proposedembedtag - 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.