9

I started using custom inclusion tags within my django templates. For example I have a {% profilelink profile %} tag that inserts a link to a user profile together with a small version of the profile's picture, like so (profilelink.html):

<a href='{% url ... %}'><img src='{{ ... }}' alt='...'> {{ profile.name }}</a>

However, when I use it in the following snippet (sometemplate.html):

<p>Owned by {% profilelink owner %} (uploaded by {% profilelink uploader %})</p>

Then I get whitespace between the HTML produced by the second template tag and the closing parenthesis. This whitespace is unwanted. It comes from the final newline character in the file profilelink.html. This is a very common problem and searching Stackoverflow yields a lot of questions about whitespace in templates in general. Here's a summary of solutions found so far and why they don't work:

Some of these problems are solvable with the {% spaceless %} tag, but not all of them. This tag only removes whitespace between tags, which is not the case in the above example.

One possible solution is to not have a final EOL in profilelink.html but that's highly undesirable. Reasons: It is generally bad style; some editors (vim) silently add one back by default; that's how POSIX defines a line; it might make some SCMs unhappy; etc.

Another solution is switching to another template engine, like Jinja2, which may or may not solve this problem. It has support for constructs like {% ... -%} which eat the next EOL character. This is useful in some situations, but is also useless for my example above. But switching the templating backend for such a small annoyance seems a little overkill and adds another dependency. I'd like to stick to whatever is the standard "django" way of doing things. There are apparently plans to make Jinja2 the new Django default, though.

Some people suggested using a middleware class to remove redundant whitespace from the generated HTML before it's being sent to the browser. This is useful, but only for transforming the HTML in a way that is functionally equivalent, i.e. same semantics: it will then still be displayed the same way in the browser. That's not what I want, I want an actual change in semantics in order to have it display properly. This is impossible to implement in a generic middleware class. I need to have control over this on a case by case basis from within the template itself. I don't care about making the HTML prettier, I care about it being correct in the first place.

There's also bug #2594 which has been closed as WONTFIX with the argument (quote) "the Django template language is good enough for generating HTML, which isn't sensitive to whitespace". In my oponion this is just totally wrong. HTML is very much sensitive to whitespace, it just doesn't care how much there is of it. It cares a lot about whether there is some whitespace or none at all.

Some my question is: Is there any sane way to fix this problem in general? (One that always works, not just in some situations.)

(Any CSS-based fixes do not count. Copy/paste surprises are evil.)

3
  • 2
    Not the most elegant solution but you can consider using get_template instead of @register decorator then strip the newlines from the template string before registering your tag. On a second thought, it might also be possible to make this a decorator.
    – Selcuk
    Commented Jun 15, 2016 at 14:17
  • @Selcuk I tried to do this but didn't succeed. At least not without poking into the internals of the template system, which I'd like to avoid. If you or someone else can post a working solution, that would be great.
    – jlh
    Commented Jun 19, 2016 at 6:20
  • I didn't check, but jinja would supposedly solve your problem if you were to use macros. Django templates are pretty limiting to my taste. You can even use jinja for text emails, although that's still tricky. I wonder if there's a template engine (not necessarily in Python) that is good for that though...
    – x-yuri
    Commented Jul 24, 2019 at 20:37

2 Answers 2

3

I believe one solution is to use a simple_tag instead of an inclusion tag, hopefully without to much clutter.

I assume your tag is something like this:

@register.inclusion_tag('profilelink.html')
def profilelink(user):
    return {"profile": user}

Would it be possible to substitute this with

from django.template.loader import render_to_string

@register.simple_tag
def profilelink(user):
    t = render_to_string("profilelink.html", {"profile": user})
    return t.strip()

I don't have a Django-project in front of me now, so this is untested.

1
  • nice solution. For me, the html was showing up verbatim in the rendered template. So had to change the last line to return format_html(t.strip()) as mentioned here
    – Anupam
    Commented Jan 14, 2021 at 16:56
2

This is the best I came up with so far. I still hope for a better solution, but for now it will do.

I defined a custom filter like this in base/templatetags/basetags.yp (taken from this answer):

from django import template
from django.template.defaultfilters import stringfilter

register = template.Library()

@register.filter
@stringfilter
def trim(value):
    return value.strip()

And then use it as follows:

{% load basetags %}
<p>Owned by {% profilelink owner %} (uploaded by
{% filter trim %}{% profilelink uploader %}{% endfilter %})</p>

Not the answer you're looking for? Browse other questions tagged or ask your own question.