Showing posts with label template. Show all posts
Showing posts with label template. Show all posts

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.

Saturday, 2 May 2009

Custom Template Tags - Part 3: A Last Minute Inclusion

After finishing last week's tutorial on block style template tags I realised that there was one template tag creation tip that I had yet to mention and that's the inclusion tag. This is quite closely related to the simple template tags described previously in that it only requires you to write one function. This time however you must provide also provide a template name which will be rendered at the end of it all. Take a look at the following example (a reworking of the simple tag example)


#File: list_formatters.py
from django import template

register = template.Library()

@register.inclusion_tag('format_list_tag.html')
def format_list(input_list):
return { 'unformatted_list':input_list, }

This file is fairly straightforward, we decorate the format_list function to say that it's an inclusion style tag and that it should use the template format_list_tag.html we then return a dictionary which will be used as the context for the template when it is processed. All that's left to include is the following template file:


{# File: format_list_tag.html #}
<ul>
{% for item in unformatted_list %}
<li>{{ item }}</li>
{% endfor %}
<ul>

This file simply provides a snippet of html describing how to display the list. Pop list_formatters.py into your templatetags directory and format_list_tag.html into your templates directory and pointing a url towards the following example (repeated from the previous tutorial) you can see the tag in action.


from django.template import Template,Context
from django.http import HttpResponse
def tag_test(request):
test_list=['Item 1','Item 2','Third time lucky']
t = Template("""{% load list_formatters %}
<html>
<head><title>Tag Test</title></head>
<body>
{% format_list test_list %}
</body>
</html>""")
return HttpResponse(t.render(Context({'test_list':test_list})))

There is one other useful feature of the inclusion tag and that is the ability to access the context. This can be done as follows (changes from the original file have been highlighted):


#File: list_formatters.py
from django import template

register = template.Library()

@register.inclusion_tag('format_list_tag.html', takes_context=True)
def format_list(context):
return { 'unformatted_list':context.get(test_list,[]), }

Whilst this example isn't really the best way to do things it should illustrate the power of what can be done by the inclusion tag and should enable you to use it in your own django sites.


Next time things will be back on track with the promised tutorial on custom filters