No Surprises
No Surprises

Reputation: 4901

How to select/reduce a list of dictionaries in Flask/Jinja

I have a Jinja template with a list of dictionaries. Order matters. I'd like to reduce the list or lookup values based on the keys/values of the dictionaries. Here's an example:

{%
    set ordered_dicts = [
        {
            'id': 'foo',
            'name': 'My name is Foo'
        },
        {
            'id': 'bar',
            'name': 'My name is Bar'
        }
    ]
%}

If I have a variable some_id = 'foo', how do I get 'My name is Foo' out of ordered_dicts in my Jinja template?

I tried select() and selectattr() but couldn't figure them out based on the documentation. Here's what I tried:

{{ ordered_dicts|selectattr("id", "foo") }}

That outputs:

<generator object _select_or_reject at 0x10748d870>

I don't think I'm understanding the use of select() and selectattr() properly.

Do I need to iterate over the list and do the lookup manually?


Update:

As codegeek and gipi pointed out, I need to do something like this with the generator:

{{ ordered_dicts|selectattr("id", "foo")|list }}

The resulting error: TemplateRuntimeError: no test named 'foo', which clarifies how selectattr() works. The second argument has to be one of the builtin tests. As far as I can tell, none of these tests will let me check whether the value associated with a key matches another value. Here's what I'd like to do:

{{ ordered_dicts|selectattr("id", "sameas", "foo")|list }}

But that doesn't work, since the sameas test checks whether two objects are really the same object in memory, not whether two strings/numbers are equivalent.

So is it possible to pick an item based on a key/value comparison test?

Upvotes: 25

Views: 51454

Answers (6)

gipi
gipi

Reputation: 2515

select() and selectattr() act upon a list and return a list, so if you know that there is only one result take the first from the generator, i.e

{{ oredered_dicts|selectattr("id", "foo")|first }}

Note: code not tested

Upvotes: 4

Aikude
Aikude

Reputation: 627

For people who don't have selectattr (e.g. you're stuck with Jinja2.6), and don't want to make yet another custom filter, these 2 lines will solve your problem real quick.

{% set selection = [] %}
{% for x in biglist if x.criteria == 'pickme' %}{% do selection.append(x) %}{% endfor %}

Upvotes: 7

Sergey GRIZZLY
Sergey GRIZZLY

Reputation: 111

More natural way for Ansible is to create /etc/ansible/test_plugins/custom.py with content

from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

from ansible import errors

def equalto(value, other):
    return bool(value == other)

class TestModule(object):
    ''' Ansible file jinja2 tests '''

    def tests(self):
        return {
            'equalto' : equalto,
        }

Upvotes: 1

Michael Wyraz
Michael Wyraz

Reputation: 3818

Have a look to https://github.com/ansible/ansible/issues/8836 for this issue.

A solution/workaround is to create a file filter_plugins/core.py in your workbook directory with the following content:

def filter_list(list, key, value):
    return filter(lambda t: t[key] == value, list)

class FilterModule(object):
    def filters(self):
        return {
            'byattr': filter_list
        }

And use it so:

{{ ordered_dicts|byattr("id", "foo") }}

Upvotes: 2

artvolk
artvolk

Reputation: 9528

I've just backported equalto like this:

app.jinja_env.tests['equalto'] = lambda value, other : value == other

After that this example from 2.8 docs works:

{{ users|selectattr("email", "equalto", "[email protected]") }}

Update: Flask has a decorator for registering tests, slightly cleaner syntax: http://flask.pocoo.org/docs/api/#flask.Flask.template_test

Upvotes: 24

lawicko
lawicko

Reputation: 7344

It looks like the equalto filter is coming in Jinja2.8 (changelog) but it doesn't have any release date set yet (24 Feb 2014). As a workaround I'd suggest using groupby filter:

<ul>
{% for group in persons|groupby('gender') %}
    <li>{{ group.grouper }}<ul>
    {% for person in group.list %}
        <li>{{ person.first_name }} {{ person.last_name }}</li>
    {% endfor %}</ul></li>
{% endfor %}
</ul>

Upvotes: 0

Related Questions