Implementing a Django templatetag aware of instances of itself in extended templates

I have been using Django for some time now and I am trying to extend its functionality due to the latest requeriments of my project. I’ve found myself lost when trying to implement a new templatetag and posted a question on stack overflow without much luck for the time being. My situation is as follows:

My Django templatetag that takes a set of parameters and builds a list. It sets context variables to output that list after some tweaking. I would like to output that list in the base template of my project.

base.html

{% load mytemplatetag %}
{% mytemplatetag 'foo' 'bar' %}
<!DOCTYPE html>
<html>
<head>
</head>
<body>
{{ mytemplatetag_output }}
</body>
</html>

After rendering the base view the output should be something like:

foo
bar

I would like my templatetag to be aware when used in derived templates:

view.html

{% extends 'private/base.html' %}
{% load mytemplatetag %}
{% mytemplatetag 'foo' 'baz' %}

After rendering the new view the output should be something like:

foo
bar
baz

At the moment my implementation is somewhat similar to the following code:

mytemplatetag.py

from django import template


register = template.Library()

@register.tag
def mytemplatetag(parser, token):
    paramters = token.split_contents()
    tag_name = paramters[0]
    items = paramters[1:]
    for item in items:
        if not (item[0] == item[-1] and item[0] in ('"', "'")):
            raise template.TemplateSyntaxError(
                "%r tag's argument should be in quotes" % tag_name
            )
    items = [m[1:-1] for m in items]
    return MyTemlpateTagNode(items)


class MyTemlpateTagNode(template.Node):
    def __init__(self, items):
        self.items = unique(sort(items))

    def render(self, context):
        context['mytemplatetag_output'] = '\n'.join(self.items)
        return ''

How could I modify my code to let my instance be aware of template extension to achieve the results I want?

Thanks in advance!

I’ve come up with another idea, which is less efficient and that I have found it also doesn’t work. Can anyone explain why?

mytemplatetag.py

from django import template


register = template.Library()

@register.simple_tag(takes_context=True)
def mytemplatetag_add(context, item):
    if not '_mytemplatetag_items' in context:
        context['_mytemplatetag_items'] = []
    context['_mytemplatetag_items'].append(item)
    return ''

@register.simple_tag(takes_context=True)
def mytemplatetag_output(context):
    if not '_mytemplatetag_items' in context:
        items = []
    else:
        items = context['_mytemplatetag_items']
    return '\n'.join(unique(sort(items)))

Both my base and my extended view call mytemplatetag_add but while debugging I am unable to see the calls to the function with the parameters pased by the extended view. Why is this happening?

Thank you!

I was able to get my templatetag working but it seems that calls to my functions in the child template only are processed if they are inside a block.

Is there any way to circumvent this and allow my functions to be called anywhere in the child template code?

Thank you!

Not that I know of, template code outside of blocks doesn’t have anywhere to belong on the page?

I think what you want is basically this:

# base.html
{% block myblock %}
{{ mytemplatetag_output }}
{% endblock myblock %}
# sub.html
{% block myblock %}
{% mytemplatetag_add 'foo' %}
{{ block.super }}
{% endblock myblock %}

I’d question though the value of manipulating context variables in templates though - why not do this in the view, where you can use arbitrary python?

Thank you for your reply @adamchainz!

My code is now available in https://pypi.org/project/django-fe-manager/ in case you are interested in the idea.

My code manages JS and CSS dependencies for views. I am not very familiar with Django yet so there may be basic conceptual errors regarding how to structure things and I would love to improve and to get feedback.

A simple explanation for my code would be that I store all the possible front end components of the web in a settings structure. This structure contains dependencies for each module. Then, each view defines what components does it need (e.g. jquery and datatables). My code computes all the requeriments of a view and its templates, orders the includes for both JS and CSS and outputs them in the base template.

It’s like a python version of require that also manages CSS :slight_smile:

Thank you!

That’s a good idea - it’s very similar to Django forms’ Media handling. Also similar to a system used at Facebook (back in the day).

Btw the the separation of CSS in <head> and JS in <body> isn’t required so much these days. If you write it correctly, you can put all your JS in <head> too using async and defer attributes: https://flaviocopes.com/javascript-async-defer/ .