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