AutoEscape alternative: filtertags.2.py

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

Newer, simpler implementation

Line 
1from django.template import Library, Node, TemplateSyntaxError
2from django.template import VariableNode, FILTER_SEPARATOR
3from django.utils.html import escape
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
42def finalfilter(parser, token):
43 """
44 Add common filters to all variable nodes within this block tag.
45 Use the `finalized` filter in a variable node to skip adding the filters
46 to that node. If a common filter has already been explicitly added to a
47 variable node, it will *not* be added again.
48
49 Filters can also be piped through each other, and they can have
50 arguments -- just like in variable syntax.
51
52 Note: This tag adds the filter(s) at render time so this happens across
53 all extended block tags.
54
55 Example usage::
56
57 {% finalfilter escape %}
58 {{ html_menu|finalized }}
59 <h2>{{ object.company }}</h2>
60 <p>
61 <a href="{{ object.url }}">
62 {{ object.first_name }} {{ object.last_name }}
63 </a>
64 </p>
65 {% endfinalfilter %}
66
67 This example would add the escape filter to the end of each variable node
68 except for `html_menu`.
69 """
70 nodelist = parser.parse(('endfinalfilter',))
71 parser.delete_first_token()
72
73 try:
74 _, rest = token.contents.split(None, 1)
75 except ValueError:
76 raise TemplateSyntaxError, "'finalfilter' requires at least one filter"
77
78 # Build the final filters list.
79 filter_expression = parser.compile_filter('var%s%s' % (FILTER_SEPARATOR, rest))
80 filters = filter_expression.filters
81
82 return FinalFilterNode(nodelist, filters)
83finalfilter = register.tag(finalfilter)
84
85def finalized(value):
86 """
87 Used to cancel the effect of {% finalfilter %}. Has no other effect.
88 """
89 return value
90register.filter(FINALIZED_FILTER_NAME, finalized)
91
92def better_escape(value):
93 """
94 HTML escape the given text with ampersands, quotes and carets encoded.
95 Alternately, if a list is given, each item of the list is html escaped
96 (lists are escaped recursively).
97
98 Useful for this sort of thing::
99
100 {{ names_list|escape|join:'<br />' }}
101 """
102 if isinstance(value, (list, tuple)):
103 return map(better_escape, value)
104 else:
105 return escape(value)
106# It'd be nice to override the default escape filter, but that causes
107# problems with double-escaping when escape is used in templates which
108# use {% extends %} without {% load filtertags %}.
109# Best solution is if this is implemented in core :)
110register.filter('better_escape', better_escape)
Back to Top