Django

Code

AutoEscape alternative: filtertags.2.py

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

Newer, simpler implementation

Line 
1 from django.template import Library, Node, TemplateSyntaxError
2 from django.template import VariableNode, FILTER_SEPARATOR
3 from django.utils.html import escape
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 def 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)
83 finalfilter = register.tag(finalfilter)
84
85 def finalized(value):
86     """
87     Used to cancel the effect of {% finalfilter %}. Has no other effect.
88     """
89     return value
90 register.filter(FINALIZED_FILTER_NAME, finalized)
91
92 def 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 :)
110 register.filter('better_escape', better_escape)