Code

AutoEscape alternative: filtertags.py

File filtertags.py, 4.8 KB (added by SmileyChris, 8 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)