Django

Code

AutoEscape alternative: filtertags.py

File filtertags.py, 4.8 kB (added by SmileyChris, 2 years ago)

finalfilter template tag

Line 
1 from django.template import Library, Node, TemplateSyntaxError
2 from django.template import VariableNode, FILTER_SEPARATOR
3 from django.template.loader_tags import BlockNode
4
5 register = Library()
6
7 FINALIZED_FILTER_NAME = 'finalized'
8
9 class 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
42 class 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
58 def 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)
99 finalfilter = register.tag(finalfilter)
100
101 def 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)
132 listfilter = register.tag(listfilter)
133
134 def finalized(value):
135     """
136     Filter used to cancel the effect of {% finalfilter %}, has no other effect.
137     """
138     return value
139 register.filter(FINALIZED_FILTER_NAME, finalized)