Iterate with pyson?

I’m trying to add a visual to account.move to find unbalanced draft moves.
Is it possible to iterate with pyson?
This gives me an error TypeError: ‘Eval’ object is not iterable

@classmethod
def view_attributes(cls):
    return [
        ('/tree', 'visual',
         If(((Eval('state') == 'draft')
            & sum(l.debit - l.credit for l in Eval('lines')))),
            'warning', ''),
        ]

Any hint on how to maybe do this?

This is not possible, you should create a function field that return the sum of credit and debit of your lines.

Ok, I suspected as such.

So I added

balanced = fields.Function(fields.Boolean('Balanced'), 'get_balanced')

def get_balanced(self, name):
    'return balance state of move lines'
    if self.lines is not None:
        return sum(line.debit - line.credit for line in self.lines) == 0
    else:
        return False  # empty move unbalanced?

which I can confirm works okay via trytond-console

But the following still does not colour the unbalanced moves in the GTK client moves tree

@classmethod
def view_attributes(cls):
    return [
        ('/tree', 'visual',
            If(((Eval('state') == 'draft') & ~Bool(Eval('balanced'))),
               'danger', '')),
        ]

Is there something else I’m missing?

[edited: strange, after restarting server/client, now I get all lines in red even though
balanced is False for only one move in my base when running trytond-console… I added a
dependency to ‘lines’ but that doesn’t seem to change anything]

Perhaps there are problems with the pyson evaluation of the fields.Function balanced…
Adding a default value of True, the tree is no longer all red.

@classmethod
def view_attributes(cls):
    return [
        ('/tree', 'visual',
            If(((Eval('state') == 'draft') & ~Bool(Eval('balanced',True))),
               'danger', '')),
        ]

from the console I compare ‘state’ with ‘balanced’:

>>> i.fields_get('balanced')
{'balanced': {'context': '{}', 'loading': 'lazy', 'name': 'balanced', 'on_change': [], 'on_change_with': [], 'readonly': True, 'required': False, 'states': '{}', 'type': 'boolean', 'domain': '[]', 'searchable': False, 'sortable': False, 'string': 'Balanced', 'help': '', 'create': True, 'delete': True}}
>>> i.fields_get('state')
{'state': {'context': '{}', 'loading': 'eager', 'name': 'state', 'on_change': [], 'on_change_with': [], 'readonly': True, 'required': True, 'states': '{}', 'type': 'selection', 'domain': '[]', 'searchable': True, 'sortable': True, 'string': 'État', 'help': '', 'selection': [('draft', 'Brouillon'), ('posted', 'Posté')], 'selection_change_with': [], 'sort': True, 'create': True, 'delete': True}}

Seemingly the only pertinent differences is that ‘balanced’ has lazy ‘loading’ and no searcher.

The fields used in view_attributes PYSON expression must be present in the view to be actually evaluated otherwise it is the default value that is used.
We could have some helper to ensure to add some fields with the view_attributes: Issue 8862: Add depends fields on view_attributes - Tryton issue tracker

okay, in the mean time, I added <field name="balanced" tree_invisible="1"/> to move_tree.xml and now I see the ‘danger’ highlighting hoped for.