Search code examples
djangopython-3.xtemplatetags

Filter 2D list with template tag in Django


I'm trying to write a template tag that will filter a 2D list.

This is my template tag:

from django import template

register = template.Library()

@register.filter
def index_two(seq, position1, position2):
    return seq[position1][position2]

This is my list that I'm passing to my template:

summary = [[50, 0, 0], [50, 100, 100]]

I'm trying to reference the first element of the first list in summary, like so:

{{summary|index_two:0 0}}

However, I received a template syntax error: index_two requires 3 arguments, 2 provided.

I've tried to adjust my template tag to the answer here, but I can't get it to work for me.

Any suggestions?

Thanks


Solution

  • A Django template filter takes at most two parameters (the one that is passed with the "pipe" (|) character, and an optional extra one, like is specified in the documentation [Django-doc]:

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

    1. The value of the variable (input) – not necessarily a string.
    2. The value of the argument – this can have a default value, or be left out altogether

    But we can make the component more reusable, and thus obtain an single element each time, like:

    from django import template
    
    register = template.Library()
    
    @register.filter
    def index(seq, position):
        return seq[position]

    Then we can write it like:

    {{ summary|index:0|index:0 }}

    So now we can use index to obtain an element of a list, and by chaining, we can dive deeper into the list. We thus make the function more reusable.

    This technique is somewhat related to currying in functional programming where functions always take exactly one parameter.

    Alternative, you can use some sort of format that can be decoded, like for example a string containing comma-separated values:

    from django import template
    
    register = template.Library()
    
    @register.filter
    def index_two(seq, positions):
        p1, p2 = map(int, positions.split(','))
        return seq[p1][p2]

    and then use it like:

    {{ summary|index_two:'0,0' }}

    But personally I find this less elegant, and will probably cause more trouble, since it requires a well defined format, and it is always possible that it fails for some corner cases.