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
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/ .