Multiple Arguments in a Django Filter

Michael Shepanski
January 29, 2012
Jan
29

Custom Django filters are an awesome thing. One downside however is they don't easily allow multiple arguments passed through. I am finding in my time with Django I am wanting multiple arguments more and more. For example, lets look at the simple replace filter below.

@register.filter()
def replace(value, fromstr, tostr):
    return value.replace(fromstr, tostr)

The above code is not possible, because custom filters can only accept one argument. So we would need to do something similar to the following and called with something like {{myvariable|replace:"£,$"}}, which would replace the UK pound symbol with the US dollar sign.

@register.filter()
def replace(value, argstr):
    parts = argstr.split(",")
    fromstr = parts[0] if len(parts) >= 1 else ""
    tostr = parts[1] if len(parts) >= 2 else ""
    return value.replace(fromstr, tostr)

That's not too bad, but it starts to get more complicated if say you want the first argument to be an integer and the second argument to be a string. It essentially boils down to writing custom code for every filter to parse your arguments. So here is a little helper function I have been using that helps with exactly this.

Parse_Argstr(argsstr, argtypes, defaults, delim=",")

def parse_argstr(argsstr, argtypes, defaults, delim=","):
    rtnvals = []
    args = str(argsstr).split(delim)
    for i in range(len(argtypes)):
        argtype = argtypes[i]
        argstr = args[i] if len(args) > i else None
        default = defaults[i] if len(defaults) > i else None
        rtnvals.append(argtype(argstr) if argstr else default)
    return rtnvals

The parse_argstr function guarantees you will have a value for each argument in the argtypes list. If it is unable to parse an arg, or it is not specified, the default value will be returned. Now we can update out replace filter to look like the following:

@register.filter()
def replace(value, argstr=""):
    replacefrom, replaceto = parse_argstr(argstr, [str,str], ["",""])
    return value.replace(replacefrom, replaceto)

It doesn't do too much. But I find it extremely useful because it takes all the thinking out of creating filters with multiple arguments. When I don't have to think, I find I am more eager to write that one off filter I wanted, but hesitated because I didn't want to deal with parsing arguments yet again.


comments powered by Disqus