113

Here's my custom filter:

from django import template

register = template.Library()

@register.filter
def replace(value, cherche, remplacement):
    return value.replace(cherche, remplacement)

And, here are the ways I tried using it in my template file that resulted in an error:

{{ attr.name|replace:"_"," " }}
{{ attr.name|replace:"_" " " }}
{{ attr.name|replace:"_":" " }}
{{ attr.name|replace:"cherche='_', remplacement=' '" }}

I looked into django's docs and book but only found example using a single argument... is it even possible?

Super Kai - Kazuya Ito
  • 22,221
  • 10
  • 124
  • 129
bchhun
  • 18,116
  • 8
  • 28
  • 31

10 Answers10

119

It is possible and fairly simple.

Django only allows one argument to your filter, but there's no reason you can't put all your arguments into a single string using a comma to separate them.

So for example, if you want a filter that checks if variable X is in the list [1,2,3,4] you will want a template filter that looks like this:

{% if X|is_in:"1,2,3,4" %}

Now we can create your templatetag like this:

from django.template import Library

register = Library()

def is_in(var, args):
    if args is None:
        return False
    arg_list = [arg.strip() for arg in args.split(',')]
    return var in arg_list

register.filter(is_in)

The line that creates arg_list is a generator expression that splits the args string on all the commas and calls .strip() to remove any leading and trailing spaces.

If, for example, the 3rd argument is an int then just do:

arg_list[2] = int(arg_list[2])

Or if all of them are ints do:

arg_list = [int(arg) for arg in args.split(',')]

EDIT: now to specifically answer your question by using key,value pairs as parameters, you can use the same class Django uses to parse query strings out of URL's, which then also has the benefit of handling character encoding properly according to your settings.py.

So, as with query strings, each parameter is separated by '&':

{{ attr.name|replace:"cherche=_&remplacement= " }}

Then your replace function will now look like this:

from django import template
from django.http import QueryDict

register = template.Library()

@register.filter
def replace(value, args):
    qs = QueryDict(args)
    if qs.has_key('cherche') and qs.has_key('remplacement'):
        return value.replace(qs['cherche'], qs['remplacement'])
    else:
        return value

You could speed this up some at the risk of doing some incorrect replacements:

qs = QueryDict(args)
return value.replace(qs.get('cherche',''), qs.get('remplacement',''))
Van Gale
  • 43,536
  • 9
  • 71
  • 81
  • 1
    Ifthe values for these are in variables how to implement this...? – Anto Nov 13 '13 at 11:30
  • 2
    This seemed helpful, but I couldn't get it to work with variables being passed. To do so, I had use a `tag` or `simple_tag` - which allows for multiple variables, even named variables to be passed. – Furbeenator Jan 13 '15 at 19:10
  • 2
    I'm way from the future, but seems to me a tag would be a better solution than essentially writing a parser in the filter. – logicOnAbstractions Mar 25 '21 at 18:36
33

It is more simple than you think

You can use simple_tag for this.

from django import template
register = template.Library()

@register.simple_tag
def multiple_args_tag(a, b, c, d):
   #do your stuff
   return 

In Template:

{% multiple_args_tag 'arg1' 'arg2' 'arg3' 'arg4' %}

NOTE: Don't forget to re-run the server.

Sawan Chauhan
  • 621
  • 7
  • 9
  • 2
    Thanks! When `safe` is needed, you can use it as follows: `{% multiple_args_tag 'arg1' 'arg2' 'arg3' 'arg4' as my_var_name %}` followed by `{{ my_var_name|safe }}` – sitWolf Jul 05 '22 at 17:44
31

It's easy like this.

@register.filter(name='one_more')
def one_more(_1, _2):
    return _1, _2

def your_filter(_1_2, _3)
    _1, _2 = _1_2
    print "now you have three arguments, enjoy"

{{ _1|one_more:_2|your_filter:_3 }}
gshmu
  • 311
  • 3
  • 2
  • Really great thank you for this solution. I have upgraded it a little so you can chain different lengths of parameters. https://gist.github.com/BrnoPCmaniak/e9552294b3059461f940a47143f58811 – Filip Dobrovolný Apr 03 '17 at 21:24
  • 4
    This should be correct answer ! It's a beautiful python solution (maybe not the best django solution, see @dragonroot answer) – Antoine Draune Feb 10 '20 at 19:03
  • 2
    This is quite a solution. Thanks helped a lot !! :) – ashim888 Sep 29 '22 at 11:39
  • Is working, thank you. No need to add name to @register.filter() and your_filter needs register.filter() too. – user3486626 May 30 '23 at 18:22
21

Instead of a filter, register your tag as a simple tag. Those can take multiple arguments. The syntax for invoking it will be a little bit different, but that's just syntactic sugar changing.

dragonroot
  • 5,653
  • 3
  • 38
  • 63
  • 2
    This was the correct answer for my issue. In order to pass a template variable into this function, I had to use a `simple_tag`. – Furbeenator Jan 13 '15 at 19:11
  • 1
    This is a good solution. It is definitely worth checking out the django docs for the simple tag: https://docs.djangoproject.com/en/1.8/howto/custom-template-tags/#simple-tags – Gunther Oct 09 '15 at 15:32
  • This is what makes the most sense to me. Tags are more powerful and complex than filters, thus if your problem is more complex seems like a better approach than trying to fit a square peg into a round hole. – logicOnAbstractions Mar 25 '21 at 18:35
17

Not possible according to this section of the docs:

Custom filters are just Python functions that take one or two arguments:

  • The value of the variable (input) -- not necessarily a string.
  • The value of the argument -- this can have a default value, or be left out altogether.
Jeff Bauer
  • 13,890
  • 9
  • 51
  • 73
  • Van Gale's approach will work if you're using hard-coded strings. Django ticket [https://code.djangoproject.com/ticket/1199] supports multiple arguments in a custom filter and a patch has been accepted. – Jeff Bauer Oct 02 '11 at 16:13
3

<my-site>/globaltags/replace.py

from django.template import Library

import re

register = Library()

def search(value, search):
    return re.sub(search, '#f4x@SgXXmS', value)

def replace(value, replace):
    return re.sub('#f4x@SgXXmS', replace, value)

register.filter(search)
register.filter(replace)

In the template:

{{ "saniel"|search:"s"|replace:"d" }}
theosp
  • 7,439
  • 3
  • 23
  • 24
  • Would be nice if you explained `#f4x@SgXXmS` bit? – Dan Abramov Oct 11 '11 at 13:12
  • 1
    just a random string used as a placeholder. I've picked this string because I believed it won't be part of the input string. If for example I've used "{}" instead of '#f4x@SgXXmS' {{ "use {} instead off []"|search:"off"|replace:"of" }} would return: "use of instead of []" and not the expected result: "use {} instead of []" – theosp Oct 16 '11 at 11:11
  • 6
    Oh, that makes sense. Might be nice to declare it as `SUBSTRING_THAT_NEVER_OCCURS` thought. – Dan Abramov Oct 16 '11 at 13:11
3

This feature has been marked as WONTFIX in 2013 Django's Trac: http://code.djangoproject.com/ticket/1199

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
bchhun
  • 18,116
  • 8
  • 28
  • 31
  • That ticket was closed as WONTFIX last year (2013), their developer suggest using a custom tag if need multiple arguments. – Paul Lo Aug 28 '14 at 10:38
0

For example, you can use the separator , to pass multiple values to replace filter, then use the values by spliting with split() as shown below. *You can and should use other separator which values don't have:

from django import template

register = template.Library()

@register.filter
def replace(value, args):
    cherche, remplacement = args.split(',')
    return value.replace(cherche, remplacement)
{{ "2023-06-23"|replace:"-,/" }} {% 2023/06/23 %}
                  {% Here ↑ %}
Super Kai - Kazuya Ito
  • 22,221
  • 10
  • 124
  • 129
-1

You can just simply do this:

{% assign find_total_issued = dailysalesreport | find: "TotalIssued":"13" %}

public static List<object> Find(object collection, string column, string value)

And it will reach the destination as the abstraction of the function is sjare.

Yannis
  • 1,682
  • 7
  • 27
  • 45
-2

Heres a bad idea but works:

{{ xml|input_by_xpath:"{'type':'radio','xpath':'//result/value'}" }}

and

@register.filter
def input_by_xpath(device, args): 
    args = eval(args)
    ...
    result = "<input type=\"%s\" value=\"%s\" name=\"%s\"/>"%(args['type'],value,args['xpath'])
    return mark_safe(result)