Proposal: The 'use' template tag, a cross between 'include' and 'extends'.

186 views
Skip to first unread message

Sam Willis

unread,
Sep 3, 2014, 12:10:33 PM9/3/14
to django-d...@googlegroups.com
Hi All,

I would like to propose a new template tag to be included in Django, it is sort of a cross between 'include' and 'extends'. You can find an implementation here: https://gist.github.com/samwillis/af3992f69c2597a16252

The main use case for this tag is being able to create reusable 'components' for rendering a website. Say for example a page header, a panel with headers and footers, or a modal window (as seen in the Bootstrap framework). Rather than needing to repeat the html everywhere you need it and having to search out all occurrences to make a change to the structure you can create a simple template and include it.

To some extent this can currently be done with either an included template using the '{% include "template.html" with var="value" %}' syntax or using a custom template tag. However, the former isn't suitable for changing whole blocks in the include template, and the latter can be overkill and may not be suitable for a designer with little knowledge of Python and the Django Template API.

The 'use' tag loads a template and renders it with the current context similar to the 'include' tag. You can pass additional context using keyword arguments as well as override blocks in the included template.

Example (simple) template:

<div class="page-heading {{ extra_class }}">
   
<h1>{% block heading %}{% endblock %}</h1>
</div>

Example 'use' tag use with the above template:

{% use "page_header.html" %}
   
{% block heading %}Some Title{% endblock %}
{% enduse %}

{% use "page_header.html" with extra_class="large" %}
   
{% block heading %}Some Title{% endblock %}
{% enduse %}

As with 'include' use the 'only' argument to exclude the current context when rendering the included template:

{% use "page_header.html" only %}
   
{% block heading %}Some Title{% endblock %}
{% enduse %}

{% use "page_header.html" with extra_class="large" only %}
   
{% block heading %}Some Title{% endblock %}
{% enduse %}

The included template receives an additional context variable called 'used_blocks' which is a Dict indicating which blocks were overridden in the 'use' tag. Using this you can conditionally show content around the block. For example, if you had this template for generating a page heading:

<div class="page-heading">
   
<h1>{% block heading %}{% endblock %}</h1>
    {% if used_blocks.sub_heading %}
       
<h2>{% block sub_heading %}{% endblock %}<h2>
    {% endif %}
</div>

and included it using:

{% use "page_header.html" %}
   
{% block heading %}My Page Title{% endblock %}
{% enduse %}

it would exclude the '<h2>' tags from the empty subheading.

Finally, as syntactic sugar if you are just overriding a single block you can express it as:

{% use "page_header.html" block heading %}
   
My Page Title
{% enduse %}

These example are a little simple, but this could be incredibly useful for more complex components such a modal windows.

I have made a first pass at an implementation here: https://gist.github.com/samwillis/af3992f69c2597a16252.

Although I have implemented this with the 'use' word, there may be a better word. I considered 'embed' but thought 'use' was a little cleaner

Thanks,
Sam

Ian Kelly

unread,
Sep 3, 2014, 12:24:17 PM9/3/14
to django-d...@googlegroups.com
On Wed, Sep 3, 2014 at 10:10 AM, Sam Willis <sam.w...@gmail.com> wrote:
> Although I have implemented this with the 'use' word, there may be a better
> word. I considered 'embed' but thought 'use' was a little cleaner

Since it's so similar to 'include', is there a reason not to just add
the new functionality to the existing tag?

Jonathan Slenders

unread,
Sep 3, 2014, 12:42:44 PM9/3/14
to django-d...@googlegroups.com
It's not similar. This implements the "decorator" pattern. Something that I've been proposing years ago.

Jonathan Slenders

unread,
Sep 3, 2014, 12:46:24 PM9/3/14
to django-d...@googlegroups.com
From 2011: https://github.com/vikingco/django-template-tags/blob/master/src/django_template_tags/templatetags/decorate.py

My proposal was refused back then, but I'll be very happy if something similar would make it. :)

Sam Willis

unread,
Sep 3, 2014, 12:56:14 PM9/3/14
to django-d...@googlegroups.com
If this was to be an addition to 'include' it would result in it having an optional closing tag, that seems a little confusing and you would need an argument to flag that there are blocks to override (and parse until the 'endinclude').

The advantage over Jonathans 'decorate' tag is that you can override any and multiple blocks in the included template.

Curtis Maloney

unread,
Sep 3, 2014, 9:23:57 PM9/3/14
to django-d...@googlegroups.com
Interesting idea... so much so I'd like to steal it :)  However, since it can be implemented as a 3rd party app, I suspect you'll get some push-back from trying to get it into core.

I've recently started work to reimagine my "sniplates" project [https://bitbucket.org/funkybob/django-sniplates], which overlaps some of these ideas.

My plan is to allow you to load "widget" sets from another template [widgets are simply blocks in the template], and use them within your other templates.

Since the widget sets are retained in the render_context, you can load them in inherited templates and access them all the way down.

I was also going to include the {% reuse %} tag from formulation, which lets you re-use any block tag from the current template context.

I'd be interested in including your idea into my app, if you're wiling to collaborate.

--
Curtis



--
You received this message because you are subscribed to the Google Groups "Django developers" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-develop...@googlegroups.com.
To post to this group, send email to django-d...@googlegroups.com.
Visit this group at http://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/0104008b-bb58-4730-a0dd-43c2875fa1b5%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Josh Smeaton

unread,
Sep 4, 2014, 7:09:06 AM9/4/14
to django-d...@googlegroups.com
I really like this idea, and have wanted something similar every time I start a new project and begin building out the main template. I think including this (or something like this) in core is a great idea. Just because it *can* be implemented in 3rd party code, doesn't mean it has to be.

Marc Tamlyn

unread,
Sep 4, 2014, 9:06:15 AM9/4/14
to django-d...@googlegroups.com
I would like to see someone do a review of the various third party implementations of concepts like this, I think there's a place in core for some variant, it's just working out which one the "right" one is.

The other issue with adding new tags to core (especially with "common" names) is name clashes with third party code. This shouldn't be too significant though if we have enough use.


--
You received this message because you are subscribed to the Google Groups "Django developers" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-develop...@googlegroups.com.
To post to this group, send email to django-d...@googlegroups.com.
Visit this group at http://groups.google.com/group/django-developers.

Sam Willis

unread,
Sep 4, 2014, 12:28:34 PM9/4/14
to django-d...@googlegroups.com
Curtis,

You are welcome to include it in your project and I'm happy to help out. Depending on what happens here I may still release it as a simple standalone app though.

I do think this is the kind of thing that would do best in core though as it would ensure that there was a standardised patten for creating these reusable template components.


Marc,

I am happy to have a look around (and have already to some extent when developing this) and put together a review of the various existing options. This is my first foray into contributing to Django and so I am keep to help in any way.

Does anyone know of anything particular I should be looking at?

Sam Willis

unread,
Sep 4, 2014, 1:11:57 PM9/4/14
to django-d...@googlegroups.com
Hi again,

One thing that I wasn't happy with in the implementation I posted yesterday was that you could not override templates included using 'use' in another child template. Effectively all blocks inside the 'use' tag and its included template where invisible outside of it. This needed to be the case so that you didn't get block names clashing with each other and could use the 'use' tag to include the same template with the same blocks overridden multiple times in one template

One potential solution to this is to enable blocks to have namespaces, so for example if you had the template:

# page_heading.html
<div class="page-heading">
   
<h1>{% block heading %}My Heading{% endblock %}</h1>
   
<h2>{% block sub_heading %}My Subheading{% endblock %}<h2>
</div>

and included it a template such as (note the 'ns page_heading'):

# base_page.html
...
{% use 'page_heading.html' ns page_heading %}
   
<h1>{% block heading %}Basic Page Heading{% endblock %}</h1>
{% enduse %}
...

You could then override the 'heading' block in a child template of 'base_page.html' by doing:

# actual_page.html
{% extends 'base_page.html' %}
{% block page_heading.heading %}Basic Page Heading{% endblock %}

I have updated my implementation to support this here: https://gist.github.com/samwillis/af3992f69c2597a16252

It requires that the BlockContext supports namespaces, in my implementation there is NsBlockContext which is patched in when required but if this was accepted into core then NsBlockContext would replace BlockContext.

This is (almost) fully backwards compatible with existing code as blocks already named with periods will just end up in a namespace but continue to function fully. The only exception is if you have blocks named both 'global.block_name' and 'block_name' as these would now address the same block. We could give the global namespace a more obscure name (such as '__global__') to prevent this but it would make the trick below more clunky.

One interesting use for the 'use' tag with this addition is breaking up more complex templates into multiple files. While researching various options before developing this I found allot of people discovering that you couldn't override a block inside an 'include'ed template. With this 'use' tag you would be able to do this, for example you could have base templates like this:

# base.html
<html>
<head>
   
{% use 'head.html' ns global %}{% enduse %}
</head>
<body>
 ...
</
body>
</html>

# head.html
<title>{% block title %}{% endblock %}</title>

and the page template:

# actual_page.html
{% extends 'base.html' %}
{% block title %}My Page Title{% endblock %}

If we switched the 'use' tag to use a namespace other than global:

{% use 'head.html' ns html_head %}{% enduse %}

The page template would then be:

# actual_page.html
{% extends 'base.html' %}
{% block html_head.title %}My Page Title{% endblock %}

I think this will provide very useful in structuring complex templates.

Maybe with this functionality the tag should be called 'embed'?

What do you think?


Sam

Jonathan Slenders

unread,
Sep 5, 2014, 9:55:28 AM9/5/14
to django-d...@googlegroups.com
There I think you are going one step too far.

I think people should not be able to override blocks inside an included template in a "use"-block. That's one level too deep. It's confusing and you have other solutions for that.
In the template that you "use", you could put a {% block %} around the {% include %} if you want to be able to replace that. It's a little more work, but in my opinion, it's not worth introducing the concept of name spaces.



Aside from that, there's one example that I would like to share, why we would need something like this.
Ofter we end up writing code like this:

{% include "start_table.html" %}
{% include "content.html" %}
{% endclude "end_table.html" %}


"start_table.html" would contain:
<table><thead><tr><td>...</td><td>...</td></tr></thead>"

"end_table.html" would contain:
</table>

Now open any of the "start/end_table.html" files in an editor, and the editor will not be able to find the matching tags.
On the other hand, if you can have:

{% use "table.html" %}
    {% include "content.html" %}
{% enduse %}


Then we have the HTLM open and close tags in the same file.
(I agree that in this specific example, there is an alternative: use extends in the included template. But there are many cases where you don't want that, because you need e.g. the decorated and non-decorated version of the include.)

Now, whether or not to allow overriding multiple blocks of the "used" template. I don't know. It's technically also possible to support both cases. (E.g. replace the content of the "content" block if no blocks are defined between "use" and "enduse". Otherwise, find matching block namess, like with {% extends %}.)

Sam Willis

unread,
Sep 5, 2014, 10:52:06 AM9/5/14
to django-d...@googlegroups.com
Hi Jonathan,

I agree it may be one step to far but I thought it may be worth discussing as it opens up an interesting option for overcoming some of the shortcomings of the current 'include' tag. It actually isn't to hard to implement, I think my code should be pretty close to what would end up in use if it is accepted. The alternative to the namespaces could be adding a 'public' option to the tag that makes the blocks inside available to overwrite?

 I am particularly keen on being able to override multiple blocks, I think this is a good example of what could be constructed with this tag. Bootstrap has a Modal component and the code below uses its markup as an example::

# modal.html
<div class="modal fade {{ extra_class }}"
  id
="{{ modal_id }}"
  tabindex
="-1"
  role
="dialog"
 
{% if used_block.header %}aria-labelledby="{{ modal_id }}_label"{% endif %}
  aria
-hidden="true"
>
 
<div class="modal-dialog">
   
<div class="modal-content">
     
{% if used_block.header %}
       
<div class="modal-header">
         
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button>
         
<h4 class="modal-title" id="{{ modal_id }}_label">{% block title %}{% endblock %}</h4>
       
</div>
      {% endif %}
      {% if used_block.body %}
        <div class="modal-body">
          {% block body %}{% endblock %}
        </
div>
     
{% endif %}
     
{% if used_block.footer %}
       
<div class="modal-footer">
         
{% block footer %}{% endblock %}
       
</div>
      {% endif %}
    </
div>
 
</div>
</
div>

This could then be included in a page in the following ways:

# my_page.html

{% use 'modal.html' with modal_id='settings_modal' %}
 
{% block title %}
   
Your Settings
 
{% endblock %}
 
{% block body %}
   
... A settings form? ...
 
{% endblock %}
 
{% block footer %}
   
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
    <button type="button" class="btn btn-primary">Save changes</
button>

 
{% endblock %}
{% enduse %}


{% use 'modal.html' with extra_class='large' modal_id='annother_modal' %}
 
{% block title %}
   
Another Modal
 
{% endblock %}
 
{% block body %}
   
This one has no footer/

 
{% endblock %}
{% enduse %}


{% use 'modal.html' with extra_class='small' block body %}
 
This one doesn't even have a title, and as we are just overriding a single block we loose the block tags.
{% enduse %}

In the template we are using the 'used_blocks' context variable to decide when to include the div's that wrap the head, body and footer of the modal. We are also using the extra context variables assigned by the use tag to choose if its a normal, large or small modal and give it an id.

This can obviously already be implemented with a hierarchy of extended templates but I think this tag creates a very clean way of structuring these components that doesn't require making custom template tags or lots of template files.


Sam

Reply all
Reply to author
Forward
0 new messages