Adding a new template filter in Django 1.9, and a template tag annoyance

February 12, 2016

As the result of my discovery about Django's timesince introducing nonbreaking spaces, I wanted to fix this. Fixing this requires coding up a new template filter and then wiring it into Django, which took me a little bit of flailing around. I specifically picked Django 1.9 as my target, because 1.9 supports making your new template filters and tags available by default without a '{% load ... %}' statement and this matters to us.

When you are load'ing new template widgets, your files have to go in a specific and somewhat annoying place in your Django app. Since I wasn't doing this, I was free to shove my code into a normal .py file. My minimal filter is:

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

register = template.Library()

@register.filter
@stringfilter
def denonbreak(value):
   """Replace non-breaking spaces with plain spaces."""
   return value.replace(u"\xa0", u" ")

The resulting filter is called denonbreak. Although the documentation doesn't say so explicitly, you are specifically handed a Unicode string and so interacting with it using plain strings may not be reliable (or work at all). I suppose this is not surprising (and people using Python 3 expect that anyways).

To add your filter(s) and tag(s) as builtins, you make use of a new Django 1.9 feature in the normal template backend when setting things up in settings.py. This is easiest to show:

TEMPLATES = [
  {
    'BACKEND': 'django.template.backends.django.DjangoTemplates',
    [...]
    'OPTIONS': {
       'builtins': ['accounts.tmplfilters'],
       [...]

(Do not get diverted to 'libraries'; it is for something else.)

At this point you might ask why I care about not needing to {% load %} my filter. The answer is one of the features of Django templates, which is that there is no good way to suppress newlines at the end of template directives.

Suppose you have a template where you want to use your new tag:

{% load something %}
The following pending account requests haven't been
handled for at least {{cutoff|timesince|denonbreak}}:
[...]

Django will remove the {% load %}, but it won't remove the newline after it. Thus your rendered template will wind up starting with a blank line. In HTML this is no problem; surplus blank lines silently disappear when the browser renders the page. But in plain text it's another story, because now that newline is sticking around, clearly visible and often ugly. To fix it you must stick the {% load %} at the start of the first real line of text, which looks ugly in the actual template.

({% if %} is another template tag that will bite you in plaintext because of this. Basically any structuring tag will. I really wish Django had an option to suppress the trailing newline in these cases, but as far as I know it doesn't.)

This issue is why I was willing to jump to Django 1.9 and use the 'builtins' feature, despite what everyone generally says about making custom things be builtins. I just hate what happens to plaintext templates otherwise. Ours are ugly enough as it is because of other tags with this issue.


Comments on this page:

If Django templates allow newlines inside their code tags, you can make it just a little less ugly with a trick I sometimes use to make my HTML less ugly:

   {% load something
   %}The following pending account requests haven't been
   handled for at least {{cutoff|timesince|denonbreak}}:

What i do in HTML is this:

   <pre><code
   >blah
   blah
   blah</code></pre>

This is arguably uglier in another way and therefore takes some getting used to, but I still find it to be an improvement overall.

By Ewen McNeill at 2016-02-12 14:54:13:

FWiW, it looks like djnago explicitly rejected adding jinga2 (and Perl templating language, etc) like handling for suppressing newlines. Apparently the core team was not interested in the feature (eg, not "needed" for HTML) and do not provide an alternative. (The only suggested answer Google turns up is "spaceless", but that suppresses all inter-tag whtieapace, and leaves the new line after the closing spaceless tag...)

Unfortunate, really, as in jinga2, etc, all you do is add a "-" sign immediately before the closing tag, Or immediately after the opening tag to suppress the previous newline. (And apparently djnago plans to eventually replace its template system with jinga2 anyway.)

Ewen (who is even fussy about rendering out tidy HTML without misplaced whitespace)

By cks at 2016-02-12 16:50:28:

Unfortunately it turns out that the Django template language has an undocumented requirement that the whole {% ... %} tag has to be on a single line (this probably true for variable expansions too). If you have an internal newline, the tag is not recognized and you get literal text.

Written on 12 February 2016.
« My current views on using OpenSSH with CA-based host and user authentication
We need to deploy anti-spam precautions even if they're a bit imperfect »

Page tools: View Source, View Normal, Add Comment.
Search:
Login: Password:
Atom Syndication: Recent Comments.

Last modified: Fri Feb 12 01:19:34 2016
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.