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 & 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)
|
---|