Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django templates and whitespace

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

like image 446
jlh Avatar asked Jun 15 '16 14:06

jlh


2 Answers

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.

like image 105
Martin Hallén Avatar answered Oct 24 '22 14:10

Martin Hallén


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>
like image 27
jlh Avatar answered Oct 24 '22 15:10

jlh