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