AutoEscape alternative: filtertags.py

File filtertags.py, 4.8 KB (added by Chris Beaven, 18 years ago)

finalfilter template tag

Line 
1from django.template import Library, Node, TemplateSyntaxError
2from django.template import VariableNode, FILTER_SEPARATOR
3from django.template.loader_tags import BlockNode
4
5register = Library()
6
7FINALIZED_FILTER_NAME = 'finalized'
8
9class FinalFilterNode(Node):
10 def __init__(self, nodelist, filters):
11 self.nodelist = nodelist
12 self.filters = filters
13 self.parsed = False
14
15 def render(self, context):
16 self.parse_nodes()
17 return self.nodelist.render(context)
18
19 def parse_nodes(self):
20 if self.parsed:
21 # Don't ever parse twice.
22 return
23 nodelist = self.nodelist
24 # Parse inner FinalFilterNodes first (they may contain a
25 # finalize filter).
26 for node in nodelist.get_nodes_by_type(FinalFilterNode):
27 node.parse_nodes()
28 # Parse child nodes.
29 for node in nodelist.get_nodes_by_type(VariableNode):
30 node_filters = node.filter_expression.filters
31 filter_funcs = [filter[0] for filter in node_filters]
32 if finalized not in filter_funcs:
33 # Ignore the node if it has the finalized functions in
34 # its filters.
35 for filter in self.filters:
36 if filter[0] not in filter_funcs:
37 # Don't double-up on filters which have already
38 # been applied to this VariableNode.
39 node_filters.append(filter)
40 self.parsed = True
41
42class ListFilterNode(Node):
43 def __init__(self, context_list, filter_expr):
44 self.context_list = context_list
45 self.filter_expr = filter_expr
46
47 def render(self, context):
48 if context.get(self.context_list):
49 context[self.context_list] = self.recursive_filter(context[self.context_list])
50 return ''
51
52 def recursive_filter(self, item):
53 if hasattr(item, '__iter__'):
54 # If the item is iterable then recurse.
55 return map(self.recursive_filter, item)
56 return self.filter_expr.resolve({self.filter_expr.var: item}, ignore_failures=True)
57
58def finalfilter(parser, token):
59 """
60 Add common filters to all variable nodes within this block tag.
61 Use the `finalized` filter in a variable node to skip adding the filters
62 to that node. If a common filter has already been explicitly added to a
63 variable node, it will *not* be added again.
64
65 Filters can also be piped through each other, and they can have
66 arguments -- just like in variable syntax.
67
68 Note: This tag adds the filter(s) at render time so this happens across
69 all extended block tags.
70
71 Example usage::
72
73 {% finalfilter escape %}
74 {{ html_menu|finalized }}
75 <h2>{{ object.company }}</h2>
76 <p>
77 <a href="{{ object.url }}">
78 {{ object.first_name }} {{ object.last_name }}
79 </a>
80 </p>
81 {% endfinalfilter %}
82
83 This example would add the escape filter to the end of each variable node
84 except for `html_menu`.
85 """
86 nodelist = parser.parse(('endfinalfilter',))
87 parser.delete_first_token()
88
89 try:
90 _, rest = token.contents.split(None, 1)
91 except ValueError:
92 raise TemplateSyntaxError, "'finalfilter' requires at least one filter"
93
94 # Build the final filters list.
95 filter_expression = parser.compile_filter('var%s%s' % (FILTER_SEPARATOR, rest))
96 filters = filter_expression.filters
97
98 return FinalFilterNode(nodelist, filters)
99finalfilter = register.tag(finalfilter)
100
101def listfilter(parser, token):
102 """
103 Replace a list in the context with one where each item has been processed
104 by the given filters. This also works with nested lists.
105
106 Filters can also be piped through each other, and they can have
107 arguments -- just like in variable syntax.
108
109 Example usage::
110
111 list_menu = ['People', [['Bob & Anne', []]]]
112
113 {% listfilter list_menu escape %}
114 <ul>{{ list_menu|unordered_list}}</ul>
115
116 This example would output::
117
118 <ul><li>People
119 <ul>
120 <li>Bob &amp; Anne</li>
121 </ul>
122 </li></ul>
123 """
124 try:
125 context_list, rest = token.contents.split(None, 2)[1:]
126 except TypeError:
127 raise TemplateSyntaxError, "'listfilter' requires at least one argument"
128
129 filter_expr = parser.compile_filter('var%s%s' % (FILTER_SEPARATOR, rest))
130
131 return ListFilterNode(context_list, filter_expr)
132listfilter = register.tag(listfilter)
133
134def finalized(value):
135 """
136 Filter used to cancel the effect of {% finalfilter %}, has no other effect.
137 """
138 return value
139register.filter(FINALIZED_FILTER_NAME, finalized)
Back to Top