2 # templatetags/macros.py - Support for macros in Django templates
4 # Author: Michal Ludvig <michal@logix.cz>
5 # http://www.logix.cz/michal
9 Tag library that provides support for "macros" in
15 <yourapp>/taglibrary/macros.py
17 1) In your template load the library:
20 2) Define a new macro called 'my_macro' with
22 {% macro my_macro arg1 %}
23 Parameter: {{ arg1 }} <br/>
26 3) Use the macro with a String parameter:
27 {% usemacro my_macro "String parameter" %}
29 or with a variable parameter (provided the
30 context defines 'somearg' variable, e.g. with
31 value "Variable parameter"):
32 {% usemacro my_macro somearg %}
34 The output of the above code would be:
35 Parameter: String parameter <br/>
36 Parameter: Variable parameter <br/>
38 4) Alternatively save your macros in a separate
39 file, e.g. "mymacros.html" and load it to the
40 current template with:
41 {% loadmacros "mymacros.html" %}
42 Then use these loaded macros in {% usemacro %}
45 Macros can take zero or more arguments and both
46 context variables and macro arguments are resolved
47 in macro body when used in {% usemacro ... %} tag.
49 Bear in mind that defined and loaded Macros are local
50 to each template file and are not inherited
51 through {% extends ... %} tags.
54 from django import template
55 from django.template.base import FilterExpression
56 from django.template.loader import get_template
59 register = template.Library()
61 def _setup_macros_dict(parser):
62 ## Metadata of each macro are stored in a new attribute
63 ## of 'parser' class. That way we can access it later
64 ## in the template when processing 'usemacro' tags.
66 ## Only try to access it to eventually trigger an exception
68 except AttributeError:
71 class DefineMacroNode(template.Node):
72 def __init__(self, name, nodelist, args):
74 self.nodelist = nodelist
77 def render(self, context):
78 ## empty string - {% macro %} tag does no output
81 @register.tag(name="macro")
82 def do_macro(parser, token):
84 args = token.split_contents()
85 tag_name, macro_name, args = args[0], args[1], args[2:]
87 raise template.TemplateSyntaxError("'%s' tag requires at least one argument (macro name)" % token.contents.split()[0])
88 # TODO: check that 'args' are all simple strings ([a-zA-Z0-9_]+)
89 r_valid_arg_name = re.compile(r'^[a-zA-Z0-9_]+$')
91 if not r_valid_arg_name.match(arg):
92 raise template.TemplateSyntaxError("Argument '%s' to macro '%s' contains illegal characters. Only alphanumeric characters and '_' are allowed." % (arg, macro_name))
93 nodelist = parser.parse(('endmacro', ))
94 parser.delete_first_token()
96 ## Metadata of each macro are stored in a new attribute
97 ## of 'parser' class. That way we can access it later
98 ## in the template when processing 'usemacro' tags.
99 _setup_macros_dict(parser)
101 parser._macros[macro_name] = DefineMacroNode(macro_name, nodelist, args)
102 return parser._macros[macro_name]
104 class LoadMacrosNode(template.Node):
105 def render(self, context):
106 ## empty string - {% loadmacros %} tag does no output
109 @register.tag(name="loadmacros")
110 def do_loadmacros(parser, token):
112 tag_name, filename = token.split_contents()
114 raise template.TemplateSyntaxError("'%s' tag requires at least one argument (macro name)" % token.contents.split()[0])
115 if filename[0] in ('"', "'") and filename[-1] == filename[0]:
116 filename = filename[1:-1]
117 t = get_template(filename)
118 macros = t.nodelist.get_nodes_by_type(DefineMacroNode)
119 ## Metadata of each macro are stored in a new attribute
120 ## of 'parser' class. That way we can access it later
121 ## in the template when processing 'usemacro' tags.
122 _setup_macros_dict(parser)
124 parser._macros[macro.name] = macro
125 return LoadMacrosNode()
127 class UseMacroNode(template.Node):
128 def __init__(self, macro, filter_expressions):
129 self.nodelist = macro.nodelist
130 self.args = macro.args
131 self.filter_expressions = filter_expressions
132 def render(self, context):
133 for (arg, fe) in [(self.args[i], self.filter_expressions[i]) for i in range(len(self.args))]:
134 context[arg] = fe.resolve(context)
135 return self.nodelist.render(context)
137 @register.tag(name="usemacro")
138 def do_usemacro(parser, token):
140 args = token.split_contents()
141 tag_name, macro_name, values = args[0], args[1], args[2:]
143 raise template.TemplateSyntaxError("'%s' tag requires at least one argument (macro name)" % token.contents.split()[0])
145 macro = parser._macros[macro_name]
146 except (AttributeError, KeyError):
147 raise template.TemplateSyntaxError("Macro '%s' is not defined" % macro_name)
149 if (len(values) != len(macro.args)):
150 raise template.TemplateSyntaxError("Macro '%s' was declared with %d parameters and used with %d parameter" % (
154 filter_expressions = []
156 if (val[0] == "'" or val[0] == '"') and (val[0] != val[-1]):
157 raise template.TemplateSyntaxError("Non-terminated string argument: %s" % val[1:])
158 filter_expressions.append(FilterExpression(val, parser))
159 return UseMacroNode(macro, filter_expressions)