AutoEscape alternative: filtertags.2.py

File filtertags.2.py, 3.9 KB (added by Chris Beaven, 17 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