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 import resolve_variable, FilterExpression
56 from django.template.loader import get_template, get_template_from_string, find_template_source
57 from django.conf import settings
60 register = template.Library()
62 def _setup_macros_dict(parser):
63 ## Metadata of each macro are stored in a new attribute
64 ## of 'parser' class. That way we can access it later
65 ## in the template when processing 'usemacro' tags.
67 ## Only try to access it to eventually trigger an exception
69 except AttributeError:
72 class DefineMacroNode(template.Node):
73 def __init__(self, name, nodelist, args):
75 self.nodelist = nodelist
78 def render(self, context):
79 ## empty string - {% macro %} tag does no output
82 @register.tag(name="macro")
83 def do_macro(parser, token):
85 args = token.split_contents()
86 tag_name, macro_name, args = args[0], args[1], args[2:]
88 raise template.TemplateSyntaxError, "'%s' tag requires at least one argument (macro name)" % token.contents.split()[0]
89 # TODO: check that 'args' are all simple strings ([a-zA-Z0-9_]+)
90 r_valid_arg_name = re.compile(r'^[a-zA-Z0-9_]+$')
92 if not r_valid_arg_name.match(arg):
93 raise template.TemplateSyntaxError, "Argument '%s' to macro '%s' contains illegal characters. Only alphanumeric characters and '_' are allowed." % (arg, macro_name)
94 nodelist = parser.parse(('endmacro', ))
95 parser.delete_first_token()
97 ## Metadata of each macro are stored in a new attribute
98 ## of 'parser' class. That way we can access it later
99 ## in the template when processing 'usemacro' tags.
100 _setup_macros_dict(parser)
102 parser._macros[macro_name] = DefineMacroNode(macro_name, nodelist, args)
103 return parser._macros[macro_name]
105 class LoadMacrosNode(template.Node):
106 def render(self, context):
107 ## empty string - {% loadmacros %} tag does no output
110 @register.tag(name="loadmacros")
111 def do_loadmacros(parser, token):
113 tag_name, filename = token.split_contents()
115 raise template.TemplateSyntaxError, "'%s' tag requires at least one argument (macro name)" % token.contents.split()[0]
116 if filename[0] in ('"', "'") and filename[-1] == filename[0]:
117 filename = filename[1:-1]
118 t = get_template(filename)
119 macros = t.nodelist.get_nodes_by_type(DefineMacroNode)
120 ## Metadata of each macro are stored in a new attribute
121 ## of 'parser' class. That way we can access it later
122 ## in the template when processing 'usemacro' tags.
123 _setup_macros_dict(parser)
125 parser._macros[macro.name] = macro
126 return LoadMacrosNode()
128 class UseMacroNode(template.Node):
129 def __init__(self, macro, filter_expressions):
130 self.nodelist = macro.nodelist
131 self.args = macro.args
132 self.filter_expressions = filter_expressions
133 def render(self, context):
134 for (arg, fe) in [(self.args[i], self.filter_expressions[i]) for i in range(len(self.args))]:
135 context[arg] = fe.resolve(context)
136 return self.nodelist.render(context)
138 @register.tag(name="usemacro")
139 def do_usemacro(parser, token):
141 args = token.split_contents()
142 tag_name, macro_name, values = args[0], args[1], args[2:]
144 raise template.TemplateSyntaxError, "'%s' tag requires at least one argument (macro name)" % token.contents.split()[0]
146 macro = parser._macros[macro_name]
147 except (AttributeError, KeyError):
148 raise template.TemplateSyntaxError, "Macro '%s' is not defined" % macro_name
150 if (len(values) != len(macro.args)):
151 raise template.TemplateSyntaxError, "Macro '%s' was declared with %d parameters and used with %d parameter" % (
155 filter_expressions = []
157 if (val[0] == "'" or val[0] == '"') and (val[0] != val[-1]):
158 raise template.TemplateSyntaxError, "Non-terminated string argument: %s" % val[1:]
159 filter_expressions.append(FilterExpression(val, parser))
160 return UseMacroNode(macro, filter_expressions)