How to correctly filter One2Many records

Hi, I have two One2Many fields referring to the same table . I’m trying to display gender=Male and gender=Female records separately in two tabs.

I tried using domain filtering but it would hide the gender column for all records, instead of showing only records where gender=Male or gender=Female

female_users = fields.One2Many(
‘table.users’, ‘users’, ‘Female Users’, readonly=True, domain=[(‘gender’, ‘=’, ‘Female’)]))) #Female Tab

male_users = fields.One2Many(
‘table.users’, ‘users’, ‘Male Users’, readonly=True, domain=[(‘gender’, ‘=’, ‘Male’)]))) #Male Tab

May I know how can I achieve this?

Thanks

It does both things. If you use domain=[(‘gender’, ‘=’, ‘Female’)] it will show only the registers that meet the condition and also it will hide the column because there is no sense in showing a column where all records have the same value.

Okay understood on the column will be hidden to reduce redundancy.

But I tried using this domain=[(‘gender’, ‘=’, ‘Female’)] and it shows all records instead of just Female records. As in I have total 11 records, and both my Female tab and Male tab shows all 11 records, instead of for example 6 records and 5 records respectively.

Indeed, you are right. I think that you have to do something like it’s done in stock.shipment.in where there is one field of type one2many without domain and then there are 2 additional functional fields (one for inventory moves and another for incoming moves). Another simpler option that can work for you is to use a filter instead of a domain, but I’m not sure if it is the best way to accomplish it.
Would be nice if anyone else that has it more clear can confirm how to solve it…

@athelia Here is a custom field (initially written by @ced for us) :slight_smile:

class One2ManyDomain(One2Many):
'''
Behaves exaclty like a O2M field, but the domain on the field is also used
when reading it.

The 'bypass_active_test' attribute can be used to disable active checks
when reading the field.
'''
def __init__(self, *args, bypass_active_test=False, **kwargs):
    super().__init__(*args, **kwargs)
    self.bypass_active_test = bypass_active_test

@classmethod
def init_from_One2Many(cls, source):
    return cls(source.model_name, source.field, source.string,
        source.add_remove, source.order, source.datetime_field,
        source.size, source.help, source.required, source.readonly,
        source.domain, source.states, source.on_change,
        source.on_change_with, source.depends, source.context,
        source.loading, delete_missing=source._delete_missing,
        target_not_required=source._target_not_required,
        target_not_indexed=source._target_not_indexed)

def get(self, ids, model, name, values=None):
    '''
    Return target records ordered.
    '''
    pool = Pool()
    Relation = pool.get(self.model_name)
    if self.field in Relation._fields:
        field = Relation._fields[self.field]
    else:
        field = Relation._inherit_fields[self.field][2]
    res = {}
    for i in ids:
        res[i] = set(), []

    targets = []
    in_max = Transaction().database.IN_MAX
    for i in range(0, len(ids), in_max):
        sub_ids = ids[i:i + in_max]
        if field._type == 'reference':
            references = ['%s,%s' % (model.__name__, x) for x in sub_ids]
            clause = [(self.field, 'in', references)]
        else:
            clause = [(self.field, 'in', sub_ids)]

        def clean_domain(the_domain):
            if not the_domain:
                return []
            if the_domain[0] in ('OR', 'AND'):
                final_domain = [the_domain[0]]
                for elem in the_domain[1:]:
                    good_domain = clean_domain(elem)
                    if not good_domain:
                        final_domain.append(())
                    else:
                        final_domain.append(good_domain)
            elif isinstance(the_domain[0], (tuple, list)):
                final_domain = []
                for elem in the_domain:
                    good_domain = clean_domain(elem)
                    if not good_domain:
                        final_domain.append(())
                    else:
                        final_domain.append(good_domain)
            else:
                has_pyson = False
                for value in the_domain:
                    if isinstance(value, PYSON):
                        has_pyson = True
                        break
                if not has_pyson:
                    final_domain = the_domain
                else:
                    final_domain = None
            return final_domain

        clause.append(clean_domain(self.domain))
        with Transaction().set_context(
                active_test=not self.bypass_active_test):
            targets.append(Relation.search(clause, order=self.order))
    targets = list(chain(*targets))

    for target in targets:
        origin_id = getattr(target, self.field).id
        if target.id not in res[origin_id][0]:
            # Use set / list combination to manage order
            res[origin_id][0].add(target.id)
            res[origin_id][1].append(target.id)
    return dict((key, tuple(value[1])) for key, value in res.items())

We use this to solve the exact problem you have here.

Slight disclaimer : there are some caveats when using it, mostly you should not have “overlapping” One2Many / One2Many domain fields, else when they are set averything might go awry :slight_smile:

domain attribute is a constraint so you can not use it to filter the result because if you have infringing record in the target, this will raise an error. Instead you must use the filter attribute to actually filter the result.

This is a function field because it is more complex than just filtering. So I do not think it is a good example to follow in most cases.

filter attribute was created especially for such use case.

This is a more complex use case because it support dynamic domain as filter. In standard the filter supports only static domain.

1 Like