Sunday 10 May 2009

Custom Template Filters: Manipulating The Results

After last weeks brief distraction on the topic of inclusion tags, the time has finally come for the tutorial on template filters.


You've probably come across several of the standard filters when writing django templates, they're the things that are appended to variables though the use of the | character. Some common examples include |length which does as it says and returns the length of the variable (I.e. Number of characters if it's a string or number of elements if it's a list or dictionary), |safe which prevents any HTML within the variable being escaped and |date:"D dS M Y" which formats a date type variable using the format string provided.


The first example we're going to be looking at today will be a filter which can be used for sorting a list (by its values) or a dictionary (by its keys). The code is as follows (this should sit in the templatetags directory along with the files for custom template tags):


#File: custom_filters.py
from django import template
from django.utils.datastructures import SortedDict

register = template.Library()

@register.filter(name='sort')
def listsort(value):
if isinstance(value,dict):
new_dict = SortedDict()
key_list = value.keys()
key_list.sort()
for key in key_list:
new_dict[key] = value[key]
return new_dict
elif isinstance(value, list):
new_list = list(value)
new_list.sort()
return new_list
else:
return value
listsort.is_safe = True

If you've been following the tutorials on custom template tags over the last few weeks then most of this should look familiar. The key new points to notice are the decorator @register.filter(name='sort') which tells django that the upcoming function is a filter and that it should go by the name of sort when it is used within a template; SortedDict which is a useful data structure that is provided by django which remembers the order that you added items to it and the line listsort.is_safe = True which gives a hint to django that we've not done anything to the variable to cause it to need escaping (if the variable needed escaping beforehand however then it will still need escaping afterwards).


Pointing a url at the following view should allow you to see the filter in action. Two numbered lists should be displayed, the first being a sorted version of test_list and the second a (key) sorted version of test_dict.


#File: views.py
from django.template import Template,Context
from django.http import HttpResponse
def filter_test(request):
test_list=['Item 1','Item 2','Third', 'Initial element', 'Item 1.5']
test_dict={'a':'First', 't':'Second', 'b':'Third', 'alpha':'Fourth'}
t = Template("""{% load custom_filters %}
<html>
<head><title>Filter Test</title></head>
<body>
<ol>
{% for item in test_list %}
<li>{{ item }}</li>
{% endfor %}
</ol>
<ol>
{% with test_dict|sort as sorted_dict %}
{% for key, value in sorted_dict.items %}
<li>{{ key }} - {{ value }}</li>
{% endfor %}
{% endwith %}
</ol>
</body>
</html>""")
return HttpResponse(t.render(Context({'test_list':test_list,
'test_dict':test_dict,})))

That now shows the basics of creating custom filters, however it's also possible to create them to take extra arguments. To demonstrate this we'll look at a filter which allows you to prefix a string to the front of other string type variables. The code is as follows (append it to the existing file):


#File: custom_filters.py
from django import template
from django.template.defaultfilters import stringfilter

register = template.Library()

@register.filter
@stringfilter
def prefix(value, prefix):
return "%s%s" % (prefix, value)

In this example there's not been any arguments passed in to @register.filter, in this case django will take the name of the function being decorated to be the name of the filter (i.e. prefix). The @stringfilter decorator has been used on the function, which informs django that we only wish to operate on strings and so django will ensure that any variable passed in to the filter has been converted into a string first. Finally we do not specify that the output is safe, as there's no guarantee that the item being prefixed wont require escaping later on and so by not declaring it as safe it will force django to check that the string is escaped later on.


Now alter the view from before as follows (changes highlighted like this) and you should see the same result as before except the elements of test_list will have "Sorted! " prefixed to it.


#File: views.py
from django.template import Template,Context
from django.http import HttpResponse
def filter_test(request):
test_list=['Item 1','Item 2','Third', 'Initial element', 'Item 1.5']
test_dict={'a':'First', 't':'Second', 'b':'Third', 'alpha':'Fourth'}
t = Template("""{% load custom_filters %}
<html>
<head><title>Filter Test</title></head>
<body>
<ol>
{% for item in test_list %}
<li>{{ item|prefix:"Sorted! " }}</li>
{% endfor %}
</ol>
<ol>
{% with test_dict|sort as sorted_dict %}
{% for key, value in sorted_dict.items %}
<li>{{ key }} - {{ value }}</li>
{% endfor %}
{% endwith %}
</ol>
</body>
</html>""")
return HttpResponse(t.render(Context({'test_list':test_list,
'test_dict':test_dict,})))

That's all for the subject of custom filters. Next time we'll take a break from template related tutorials and have a look at Signals in django.

3 comments:

  1. Oh man, I spent agessss looking for the 'sort' tag, I don't know why Django devs haven't put this into the core!!!! <3

    ReplyDelete
  2. I have submitted a ticket to django devs to have this included into the core.

    http://code.djangoproject.com/ticket/15583

    ReplyDelete
  3. I'm glad this was able to help :-)

    I have a vague recollection of something similar being requested in core ages ago and it being rejected as it's logic that should be in the view rather than the template. Personally I think it would be useful to be in the template too though as sorting is generally useful for presentation.

    Also, it is technically possible to sort dictionaries using the existing filters although this is fairly hideous - my_dict.items|dictsort:"0" will give you a list of key,value pairs sorted by the key.

    ReplyDelete