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