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)