Login

Parse custom template tag's args or kwargs

Author:
t_rybik
Posted:
September 9, 2010
Language:
Python
Version:
1.2
Score:
1 (after 1 ratings)

Enhanced version of snippet 1113

Usage example (not self-contained):

@register.tag
def groupurl(parser, token):
    '''
    Syntax::

        {% groupurl view_name group [key=val, [key=val, ...]] [as varname] %}

    Example::

        <a href="{% groupurl blog_detail group slug=blog.slug %}">{{ blog.name }}</a>    
    '''
    bits = token.contents.split()
    tag_name = bits[0]

    if len(bits) < 3:
        raise template.TemplateSyntaxError("'%s' takes tag at least 2 arguments (path to a view and a group)" % (tag_name,)
    bits_iter = iter(bits[1:])    
    # view_name + group and url kwargs
    args, kwargs = parse_args_and_kwargs(parser, bits_iter, stop_test='as', tagname=tag_name)
    if len(args) != 2:
        raise template.TemplateSyntaxError("'%s' takes exactly two non-kwargs (path to a view and a group)" % (tag_name,))
    view_name, group = args       

    # as var
    asvar = None
    for bit in bits_iter:
        asvar = bit

    return GroupURLNode(view_name, group, kwargs, asvar)
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
import inspect
from functools import partial
from django.template import TemplateSyntaxError



def isiterable(obj):
    '''
    Check if arg is iterable. Object is iterable when implements metod __iter__.
    '''
    return hasattr(obj, '__iter__')

def isstring(obj):
    '''
    Check whether `obj` is a string instance, i.e. str or unicode instance.
    '''
    return isinstance(obj, basestring)



_ARGS_TYPES = (_BOTH, _ARGS_ONLY, _KWARGS_ONLY) = (0, 1, 2)
def _parse_args_and_kwargs(parser, bits_iter, sep=",", tagname=None, type=_BOTH,
                          stop_test=lambda bit: False, return_stop_bit=False):
    '''
    :param type: indicates to support _ARGS_ONLY, _KWARGS_ONLY or _BOTH.
    '''
    assert type in _ARGS_TYPES, "'type' argument must be one of: _BOTH, _ARGS_ONLY, _KWARGS_ONLY."
    args_only, kwargs_only = (type == _ARGS_ONLY), (type == _KWARGS_ONLY)
    args = []
    kwargs = {}
    tagname = " '%s'" % tagname if tagname else ""
    bit_test = stop_test if inspect.isfunction(stop_test) else\
               (lambda bit: bit in stop_test) if (isiterable(stop_test) and not isstring(stop_test)) else\
               (lambda bit: bit == stop_test)
    stop_bit = None
    for bit in bits_iter:
        if bit_test(bit):
            # if we would like to stop on the bit that passes the test:
            #bits_iter = itertools.chain((bit,), bits_iter)
            stop_bit = bit
            break
        for arg in bit.split(sep):
            if '=' in arg:
                if args_only:
                    raise TemplateSyntaxError("Tag%s does not support kwargs arguments." % tagname)
                k, v = arg.split('=', 1)
                k = str(k.strip())
                if k in kwargs:
                    raise TemplateSyntaxError("Duplicate key '%s' in%s tag kwargs." % (k, tagname))
                kwargs[k] = parser.compile_filter(v)
            elif arg:
                if kwargs_only:
                    raise TemplateSyntaxError("Tag%s does not support non-kwargs arguments." % tagname)
                args.append(parser.compile_filter(arg))
    ret = (args,) if args_only else\
          (kwargs,) if kwargs_only else\
          (args, kwargs)
    if return_stop_bit:
        ret += (stop_bit,)
    if len(ret) == 1:
        return ret[0]
    return ret

parse_args_and_kwargs = partial(_parse_args_and_kwargs, type=_BOTH)
parse_args_and_kwargs.__name__ = 'parse_args_and_kwargs'
parse_args_and_kwargs.__doc__ =\
'''
Parses bits created form token "arg1,key1=val1, arg2 , ..." after splitting
contents.

:param sep: Single bit separator; by default ",".
:rtype sep: str

:param stop_test: Optional test to stop parsing earlier. This can be one of:
 * single string with keyword to stop on
 * list of string keywords to stop on
 * function which takes only current bit of `bits_iter` as an argument and
   returns boolean value.
Attention: `bits_iter` will be stopped after the bit that passes the test.
           To obtain the stop bit use `return_stop_bit` flag and capture the
           additional return value.
:rtype stop_test: str or list or callable

:returns: List of args and dictionary of kwargs with values compiled with the
          parser.compile_filter() function. If `return_stop_bit` is True then
          also the the last bit at which parsing stopped (see `stop_test`).
:rtype: tuple(list, dict [, basestring or None])
'''
parse_args = partial(_parse_args_and_kwargs, type=_ARGS_ONLY)
parse_args.__name__ = 'parse_args'
parse_args.__doc__ =\
'''
Parses bits created form token "arg1,arg2 , ...", after splitting contents. See
`parse_args_and_kwargs` for params details.

:returns: List of args with values compiled with the parser.compile_filter()
          function. If `return_stop_bit` is True then also the the last bit at
          which parsing stopped (see `stop_test`).
:rtype: list or tuple(list, basestring or None)
'''
parse_kwargs = partial(_parse_args_and_kwargs, type=_KWARGS_ONLY)
parse_kwargs.__name__ = 'parse_kwargs'
parse_kwargs.__doc__ =\
'''
Parses bits created form token "key1=val1, key2=val2, ...", after splitting
contents. See parse_args_and_kwargs` for params details.

:returns: Dictionary of kwargs with values compiled with the
          parser.compile_filter() function. If `return_stop_bit` is True then
          also the the last bit at which parsing stopped (see `stop_test`).
:rtype: dict or tuple(dict, basestring or None)
'''

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 11 months, 2 weeks ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 11 months, 3 weeks ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 6 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 7 months ago
  5. Help text hyperlinks by sa2812 1 year, 8 months ago

Comments

ogai (on June 21, 2011):

Have a look how Django 1.3 does it in the url tag code: django/template/defaulttags.py

#

Please login first before commenting.