--- /dev/null
+#
+# templatetags/macros.py - Support for macros in Django templates
+#
+# Author: Michal Ludvig <michal@logix.cz>
+# http://www.logix.cz/michal
+#
+
+"""
+Tag library that provides support for "macros" in
+Django templates.
+
+Usage example:
+
+0) Save this file as
+ <yourapp>/taglibrary/macros.py
+
+1) In your template load the library:
+ {% load macros %}
+
+2) Define a new macro called 'my_macro' with
+ parameter 'arg1':
+ {% macro my_macro arg1 %}
+ Parameter: {{ arg1 }} <br/>
+ {% endmacro %}
+
+3) Use the macro with a String parameter:
+ {% usemacro my_macro "String parameter" %}
+
+ or with a variable parameter (provided the
+ context defines 'somearg' variable, e.g. with
+ value "Variable parameter"):
+ {% usemacro my_macro somearg %}
+
+ The output of the above code would be:
+ Parameter: String parameter <br/>
+ Parameter: Variable parameter <br/>
+
+4) Alternatively save your macros in a separate
+ file, e.g. "mymacros.html" and load it to the
+ current template with:
+ {% loadmacros "mymacros.html" %}
+ Then use these loaded macros in {% usemacro %}
+ as described above.
+
+Macros can take zero or more arguments and both
+context variables and macro arguments are resolved
+in macro body when used in {% usemacro ... %} tag.
+
+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 resolve_variable, FilterExpression
+from django.template.loader import get_template, get_template_from_string, find_template_source
+from django.conf import settings
+import re
+
+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 = args
+
+ 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:
+ raise template.TemplateSyntaxError, "'%s' tag requires at least one argument (macro name)" % token.contents.split()[0]
+ # TODO: check that 'args' are all simple strings ([a-zA-Z0-9_]+)
+ r_valid_arg_name = re.compile(r'^[a-zA-Z0-9_]+$')
+ for arg in args:
+ if not r_valid_arg_name.match(arg):
+ raise template.TemplateSyntaxError, "Argument '%s' to macro '%s' contains illegal characters. Only alphanumeric characters and '_' are allowed." % (arg, macro_name)
+ 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:
+ raise template.TemplateSyntaxError, "'%s' tag requires at least one argument (macro name)" % token.contents.split()[0]
+ 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, filter_expressions):
+ self.nodelist = macro.nodelist
+ self.args = macro.args
+ self.filter_expressions = filter_expressions
+ def render(self, context):
+ for (arg, fe) in [(self.args[i], self.filter_expressions[i]) for i in range(len(self.args))]:
+ context[arg] = fe.resolve(context)
+ return self.nodelist.render(context)
+
+@register.tag(name="usemacro")
+def do_usemacro(parser, token):
+ try:
+ args = token.split_contents()
+ tag_name, macro_name, values = args[0], args[1], args[2:]
+ except IndexError:
+ raise template.TemplateSyntaxError, "'%s' tag requires at least one argument (macro name)" % token.contents.split()[0]
+ try:
+ macro = parser._macros[macro_name]
+ except (AttributeError, KeyError):
+ raise template.TemplateSyntaxError, "Macro '%s' is not defined" % macro_name
+
+ if (len(values) != len(macro.args)):
+ raise template.TemplateSyntaxError, "Macro '%s' was declared with %d parameters and used with %d parameter" % (
+ macro_name,
+ len(macro.args),
+ len(values))
+ filter_expressions = []
+ for val in values:
+ if (val[0] == "'" or val[0] == '"') and (val[0] != val[-1]):
+ raise template.TemplateSyntaxError, "Non-terminated string argument: %s" % val[1:]
+ filter_expressions.append(FilterExpression(val, parser))
+ return UseMacroNode(macro, filter_expressions)
\ No newline at end of file