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 | #
# templatetags/macros.py - Support for macros in Django templates
#
# Based on snippet by
# Michal Ludvig <michal@logix.cz>
# http://www.logix.cz/michal
#
# Extended for args and kwargs into templatetags/kwacro.py by
# Skylar Saveland http://skyl.org
# https://gist.github.com/skyl/1715202
#
# Modified to support rendering into context by matt@peloquin.com
#
"""
Usage example:
0) Save this file as <yourapp>/templatetags/macros.py
1) In your template load the library:
{% load macros %}
2) Define a new macro called 'my_macro' that takes args and/or kwargs
All will be optional.
{% macro my_macro arg1 arg2 baz="Default baz" %}
{% firstof arg1 "default_arg1" %}
{% if arg2 %}{{ arg2 }}{% else %}default_arg2{% endif %}
{{ baz }}
{% endmacro %}
3) Use the macro with string parameters or context variables:
{% usemacro my_macro "foo" "bar" baz="KW" %}
<br>
{% usemacro my_macro num_pages "bar" %}
<br>
{% setmacro my_macro %} {{ my_macro }}
renders like
foo bar KW
77 bar Default baz
default_arg1 default_arg2 Default baz
4) Alternatively save your macros in a separate file, e.g. "mymacro.html"
and load it to the current template with:
{% loadmacros "mymacros.html" %}
Then use these loaded macros in as described above.
Bear in mind that defined and loaded macros are local to each template
file and are not inherited through {% extends ... %} tags.
"""
from django import template
from django.template import FilterExpression
from django.template.loader import get_template
register = template.Library()
def _setup_macros_dict(parser):
## Metadata of each macro are stored in a new attribute
## of 'parser' class. That way we can access it later
## in the template when processing 'usemacro' tags.
try:
## Only try to access it to eventually trigger an exception
parser._macros
except AttributeError:
parser._macros = {}
class DefineMacroNode(template.Node):
def __init__(self, name, nodelist, args):
self.name = name
self.nodelist = nodelist
self.args = []
self.kwargs = {}
for a in args:
if "=" not in a:
self.args.append(a)
else:
name, value = a.split("=")
self.kwargs[name] = value
def render(self, context):
## empty string - {% macro %} tag does no output
return ''
@register.tag(name="macro")
def do_macro(parser, token):
try:
args = token.split_contents()
tag_name, macro_name, args = args[0], args[1], args[2:]
except IndexError:
m = ("'%s' tag requires at least one argument (macro name)"
% token.contents.split()[0])
raise template.TemplateSyntaxError, m
# TODO: could do some validations here,
# for now, "blow your head clean off"
nodelist = parser.parse(('endmacro', ))
parser.delete_first_token()
## Metadata of each macro are stored in a new attribute
## of 'parser' class. That way we can access it later
## in the template when processing 'usemacro' tags.
_setup_macros_dict(parser)
parser._macros[macro_name] = DefineMacroNode(macro_name, nodelist, args)
return parser._macros[macro_name]
class LoadMacrosNode(template.Node):
def render(self, context):
## empty string - {% loadmacros %} tag does no output
return ''
@register.tag(name="loadmacros")
def do_loadmacros(parser, token):
try:
tag_name, filename = token.split_contents()
except IndexError:
m = ("'%s' tag requires at least one argument (macro name)"
% token.contents.split()[0])
raise template.TemplateSyntaxError, m
if filename[0] in ('"', "'") and filename[-1] == filename[0]:
filename = filename[1:-1]
t = get_template(filename)
macros = t.nodelist.get_nodes_by_type(DefineMacroNode)
## Metadata of each macro are stored in a new attribute
## of 'parser' class. That way we can access it later
## in the template when processing 'usemacro' tags.
_setup_macros_dict(parser)
for macro in macros:
parser._macros[macro.name] = macro
return LoadMacrosNode()
class UseMacroNode(template.Node):
def __init__(self, macro, fe_args, fe_kwargs, context_only):
self.macro = macro
self.fe_args = fe_args
self.fe_kwargs = fe_kwargs
self.context_only = context_only
def render(self, context):
for i, arg in enumerate(self.macro.args):
try:
fe = self.fe_args[i]
context[arg] = fe.resolve(context)
except IndexError:
context[arg] = ""
for name, default in self.macro.kwargs.iteritems():
if name in self.fe_kwargs:
context[name] = self.fe_kwargs[name].resolve(context)
else:
context[name] = FilterExpression(default,
self.macro.parser
).resolve(context)
# Place output into context variable
context[self.macro.name] = self.macro.nodelist.render(context)
return '' if self.context_only else context[self.macro.name]
def parse_usemacro(parser, token):
try:
args = token.split_contents()
tag_name, macro_name, values = args[0], args[1], args[2:]
except IndexError:
m = ("'%s' tag requires at least one argument (macro name)"
% token.contents.split()[0])
raise template.TemplateSyntaxError, m
try:
macro = parser._macros[macro_name]
except (AttributeError, KeyError):
m = "Macro '%s' is not defined" % macro_name
raise template.TemplateSyntaxError, m
fe_kwargs = {}
fe_args = []
for val in values:
if "=" in val:
# kwarg
name, value = val.split("=")
fe_kwargs[name] = FilterExpression(value, parser)
else: # arg
# no validation, go for it ...
fe_args.append(FilterExpression(val, parser))
macro.name = macro_name
macro.parser = parser
return macro, fe_args, fe_kwargs
@register.tag(name="usemacro")
def do_usemacro(parser, token):
return UseMacroNode(*parse_usemacro(parser, token), context_only=False)
@register.tag(name="setmacro")
def do_setmacro(parser, token):
return UseMacroNode(*parse_usemacro(parser, token), context_only=True)
|
Comments