1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
|
from django.template import Library as DjangoLibrary, InvalidTemplateLibrary
from jinja2.ext import Extension as Jinja2Extension
from coffin.interop import (
DJANGO, JINJA2,
guess_filter_type, jinja2_filter_to_django, django_filter_to_jinja2)
__all__ = ['Library']
class Library(DjangoLibrary):
"""Version of the Django ``Library`` class that can handle both
Django template engine tags and filters, as well as Jinja2
extensions and filters.
Tries to present a common registration interface to the extension
author, but provides both template engines with only those
components they can support.
Since custom Django tags and Jinja2 extensions are two completely
different beasts, they are handled completely separately. You can
register custom Django tags as usual, for example:
register.tag('current_time', do_current_time)
Or register a Jinja2 extension like this:
register.tag(CurrentTimeNode)
Filters, on the other hand, work similarily in both engines, and
for the most one can't tell whether a filter function was written
for Django or Jinja2. A compatibility layer is used to make to
make the filters you register usuable with both engines:
register.filter('cut', cut)
However, some of the more powerful filters just won't work in
Django, for example if more than one argument is required, or if
context- or environmentfilters are used. If ``cut`` in the above
example where such an extended filter, it would only be registered
with Jinja.
See also the module documentation for ``coffin.interop`` for
information on some of the limitations of this conversion.
TODO: Jinja versions of the ``simple_tag`` and ``inclusion_tag``
helpers would be nice, though since custom tags are not needed as
often in Jinja, this is not urgent.
"""
def __init__(self):
super(Library, self).__init__()
self.jinja2_filters = {}
self.jinja2_extensions = []
self.jinja2_globals = {}
self.jinja2_tests = {}
@classmethod
def from_django(cls, django_library):
"""Create a Coffin library object from a Django library.
Specifically, this ensures that filters already registered
with the Django library are also made available to Jinja,
where applicable.
"""
from copy import copy
result = cls()
result.filters = copy(django_library.filters)
result.tags = copy(django_library.tags)
for name, func in result.filters.iteritems():
result._register_filter(name, func, jinja2_only=True)
return result
def test(self, name=None, func=None):
def inner(f):
name = getattr(f, "_decorated_function", f).__name__
self.jinja2_tests[name] = f
return f
if name == None and func == None:
# @register.test()
return inner
elif func == None:
if (callable(name)):
# register.test()
return inner(name)
else:
# @register.test('somename') or @register.test(name='somename')
def dec(func):
return self.test(name, func)
return dec
elif name != None and func != None:
# register.filter('somename', somefunc)
self.jinja2_tests[name] = func
return func
else:
raise InvalidTemplateLibrary("Unsupported arguments to "
"Library.test: (%r, %r)", (name, func))
def object(self, name=None, func=None):
def inner(f):
name = getattr(f, "_decorated_function", f).__name__
self.jinja2_globals[name] = f
return f
if name == None and func == None:
# @register.object()
return inner
elif func == None:
if (callable(name)):
# register.object()
return inner(name)
else:
# @register.object('somename') or @register.object(name='somename')
def dec(func):
return self.object(name, func)
return dec
elif name != None and func != None:
# register.object('somename', somefunc)
self.jinja2_globals[name] = func
return func
else:
raise InvalidTemplateLibrary("Unsupported arguments to "
"Library.object: (%r, %r)", (name, func))
def tag(self, name_or_node=None, compile_function=None):
"""Register a Django template tag (1) or Jinja 2 extension (2).
For (1), supports the same invocation syntax as the original
Django version, including use as a decorator.
For (2), since Jinja 2 extensions are classes (which can't be
decorated), and have the tag name effectively built in, only the
following syntax is supported:
register.tag(MyJinjaExtensionNode)
"""
if isinstance(name_or_node, Jinja2Extension):
if compile_function:
raise InvalidTemplateLibrary('"compile_function" argument not supported for Jinja2 extensions')
self.jinja2_extensions.append(name_or_node)
return name_or_node
else:
return super(Library, self).tag(name_or_node, compile_function)
def tag_function(self, func_or_node):
if issubclass(func_or_node, Jinja2Extension):
self.jinja2_extensions.append(func_or_node)
return func_or_node
else:
return super(Library, self).tag_function(func_or_node)
def filter(self, name=None, filter_func=None, jinja2_only=False):
"""Register a filter with both the Django and Jinja2 template
engines, if possible - or only Jinja2, if ``jinja2_only`` is
specified. ``jinja2_only`` does not affect conversion of the
filter if neccessary.
Implements a compatibility layer to handle the different
auto-escaping approaches transparently. Extended Jinja2 filter
features like environment- and contextfilters are however not
supported in Django. Such filters will only be registered with
Jinja.
Supports the same invocation syntax as the original Django
version, including use as a decorator.
If the function is supposed to return the registered filter
(by example of the superclass implementation), but has
registered multiple filters, a tuple of all filters is
returned.
"""
def filter_function(f):
return self._register_filter(
getattr(f, "_decorated_function", f).__name__,
f, jinja2_only=jinja2_only)
if name == None and filter_func == None:
# @register.filter()
return filter_function
elif filter_func == None:
if (callable(name)):
# @register.filter
return filter_function(name)
else:
# @register.filter('somename') or @register.filter(name='somename')
def dec(func):
return self.filter(name, func, jinja2_only=jinja2_only)
return dec
elif name != None and filter_func != None:
# register.filter('somename', somefunc)
return self._register_filter(name, filter_func,
jinja2_only=jinja2_only)
else:
raise InvalidTemplateLibrary("Unsupported arguments to "
"Library.filter: (%r, %r)", (name, filter_func))
def _register_filter(self, name, func, jinja2_only=None):
filter_type, can_be_ported = guess_filter_type(func)
if filter_type == JINJA2 and not can_be_ported:
self.jinja2_filters[name] = func
return func
elif filter_type == DJANGO and not can_be_ported:
if jinja2_only:
raise ValueError('This filter cannot be ported to Jinja2.')
self.filters[name] = func
return func
elif jinja2_only:
func = django_filter_to_jinja2(func)
self.jinja2_filters[name] = func
return func
else:
# register the filter with both engines
django_func = jinja2_filter_to_django(func)
jinja2_func = django_filter_to_jinja2(func)
self.filters[name] = django_func
self.jinja2_filters[name] = jinja2_func
return (django_func, jinja2_func)
|