Skip to content

Instantly share code, notes, and snippets.

@gabrielhurley
Created November 9, 2011 06:46

Revisions

  1. gabrielhurley revised this gist Nov 9, 2011. 1 changed file with 7 additions and 6 deletions.
    13 changes: 7 additions & 6 deletions assignment_tag.py
    Original file line number Diff line number Diff line change
    @@ -1,18 +1,19 @@
    """
    Adds the register.assignment_tag functionality from Django 1.4 to Django 1.3.
    Backports the ``register.assignment_tag`` functionality from Django 1.4 to
    Django 1.3.
    This code is almost entirely reproduced from
    https://code.djangoproject.com/browser/django/trunk/django/template/base.py
    and is the work of Django's authors. It is licensed under a BSD license as
    per Django's license available here:
    and is the work of Django's authors:
    https://code.djangoproject.com/browser/django/trunk/AUTHORS
    It is licensed under Django's BSD license, available here:
    https://code.djangoproject.com/browser/django/trunk/LICENSE
    To use, simply import this code in your project prior to using the
    assignment_tag decorator. In general, the top of your template tag
    ``register.assignment_tag``. In general, the top of your template tag
    python file would be a good place for the import.
    """
    from __future__ import absolute_import

    import re
    from inspect import getargspec

  2. gabrielhurley renamed this gist Nov 9, 2011. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  3. gabrielhurley revised this gist Nov 9, 2011. 1 changed file with 13 additions and 0 deletions.
    13 changes: 13 additions & 0 deletions utils.py
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,16 @@
    """
    Adds the register.assignment_tag functionality from Django 1.4 to Django 1.3.
    This code is almost entirely reproduced from
    https://code.djangoproject.com/browser/django/trunk/django/template/base.py
    and is the work of Django's authors. It is licensed under a BSD license as
    per Django's license available here:
    https://code.djangoproject.com/browser/django/trunk/LICENSE
    To use, simply import this code in your project prior to using the
    assignment_tag decorator. In general, the top of your template tag
    python file would be a good place for the import.
    """
    from __future__ import absolute_import

    import re
  4. gabrielhurley created this gist Nov 9, 2011.
    195 changes: 195 additions & 0 deletions utils.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,195 @@
    from __future__ import absolute_import

    import re
    from inspect import getargspec

    from django.template import TemplateSyntaxError
    from django.template.base import Node, Library


    if not hasattr(Library, 'assignment_tag'):
    # Regex for token keyword arguments
    kwarg_re = re.compile(r"(?:(\w+)=)?(.+)")

    def token_kwargs(bits, parser, support_legacy=False):
    """
    A utility method for parsing token keyword arguments.
    :param bits: A list containing remainder of the token (split by spaces)
    that is to be checked for arguments. Valid arguments will be removed
    from this list.
    :param support_legacy: If set to true ``True``, the legacy format
    ``1 as foo`` will be accepted. Otherwise, only the standard ``foo=1``
    format is allowed.
    :returns: A dictionary of the arguments retrieved from the ``bits`` token
    list.
    There is no requirement for all remaining token ``bits`` to be keyword
    arguments, so the dictionary will be returned as soon as an invalid
    argument format is reached.
    """
    if not bits:
    return {}
    match = kwarg_re.match(bits[0])
    kwarg_format = match and match.group(1)
    if not kwarg_format:
    if not support_legacy:
    return {}
    if len(bits) < 3 or bits[1] != 'as':
    return {}

    kwargs = {}
    while bits:
    if kwarg_format:
    match = kwarg_re.match(bits[0])
    if not match or not match.group(1):
    return kwargs
    key, value = match.groups()
    del bits[:1]
    else:
    if len(bits) < 3 or bits[1] != 'as':
    return kwargs
    key, value = bits[2], bits[0]
    del bits[:3]
    kwargs[key] = parser.compile_filter(value)
    if bits and not kwarg_format:
    if bits[0] != 'and':
    return kwargs
    del bits[:1]
    return kwargs

    def parse_bits(parser, bits, params, varargs, varkw, defaults,
    takes_context, name):
    """
    Parses bits for template tag helpers (simple_tag, include_tag and
    assignment_tag), in particular by detecting syntax errors and by
    extracting positional and keyword arguments.
    """
    if takes_context:
    if params[0] == 'context':
    params = params[1:]
    else:
    raise TemplateSyntaxError(
    "'%s' is decorated with takes_context=True so it must "
    "have a first argument of 'context'" % name)
    args = []
    kwargs = {}
    unhandled_params = list(params)
    for bit in bits:
    # First we try to extract a potential kwarg from the bit
    kwarg = token_kwargs([bit], parser)
    if kwarg:
    # The kwarg was successfully extracted
    param, value = kwarg.items()[0]
    if param not in params and varkw is None:
    # An unexpected keyword argument was supplied
    raise TemplateSyntaxError(
    "'%s' received unexpected keyword argument '%s'" %
    (name, param))
    elif param in kwargs:
    # The keyword argument has already been supplied once
    raise TemplateSyntaxError(
    "'%s' received multiple values for keyword argument '%s'" %
    (name, param))
    else:
    # All good, record the keyword argument
    kwargs[str(param)] = value
    if param in unhandled_params:
    # If using the keyword syntax for a positional arg, then
    # consume it.
    unhandled_params.remove(param)
    else:
    if kwargs:
    raise TemplateSyntaxError(
    "'%s' received some positional argument(s) after some "
    "keyword argument(s)" % name)
    else:
    # Record the positional argument
    args.append(parser.compile_filter(bit))
    try:
    # Consume from the list of expected positional arguments
    unhandled_params.pop(0)
    except IndexError:
    if varargs is None:
    raise TemplateSyntaxError(
    "'%s' received too many positional arguments" %
    name)
    if defaults is not None:
    # Consider the last n params handled, where n is the
    # number of defaults.
    unhandled_params = unhandled_params[:-len(defaults)]
    if unhandled_params:
    # Some positional arguments were not supplied
    raise TemplateSyntaxError(
    u"'%s' did not receive value(s) for the argument(s): %s" %
    (name, u", ".join([u"'%s'" % p for p in unhandled_params])))
    return args, kwargs


    class TagHelperNode(Node):
    """
    Base class for tag helper nodes such as SimpleNode, InclusionNode and
    AssignmentNode. Manages the positional and keyword arguments to be passed
    to the decorated function.
    """

    def __init__(self, takes_context, args, kwargs):
    self.takes_context = takes_context
    self.args = args
    self.kwargs = kwargs

    def get_resolved_arguments(self, context):
    resolved_args = [var.resolve(context) for var in self.args]
    if self.takes_context:
    resolved_args = [context] + resolved_args
    resolved_kwargs = dict((k, v.resolve(context))
    for k, v in self.kwargs.items())
    return resolved_args, resolved_kwargs


    def assignment_tag(self, func=None, takes_context=None, name=None):
    def dec(func):
    params, varargs, varkw, defaults = getargspec(func)

    class AssignmentNode(TagHelperNode):
    def __init__(self, takes_context, args, kwargs, target_var):
    super(AssignmentNode, self).__init__(takes_context, args, kwargs)
    self.target_var = target_var

    def render(self, context):
    resolved_args, resolved_kwargs = self.get_resolved_arguments(context)
    context[self.target_var] = func(*resolved_args, **resolved_kwargs)
    return ''

    function_name = (name or
    getattr(func, '_decorated_function', func).__name__)

    def compile_func(parser, token):
    bits = token.split_contents()[1:]
    if len(bits) < 2 or bits[-2] != 'as':
    raise TemplateSyntaxError(
    "'%s' tag takes at least 2 arguments and the "
    "second last argument must be 'as'" % function_name)
    target_var = bits[-1]
    bits = bits[:-2]
    args, kwargs = parse_bits(parser, bits, params,
    varargs, varkw, defaults, takes_context, function_name)
    return AssignmentNode(takes_context, args, kwargs, target_var)

    compile_func.__doc__ = func.__doc__
    self.tag(function_name, compile_func)
    return func

    if func is None:
    # @register.assignment_tag(...)
    return dec
    elif callable(func):
    # @register.assignment_tag
    return dec(func)
    else:
    raise TemplateSyntaxError("Invalid arguments provided to assignment_tag")


    Library.assignment_tag = assignment_tag