Defining filters on record lists: Domain

We've already seen the first example of a domain in the first action, which was [('customer', '=', True)]. It is a very common use case when you need to display a subset of all available records from an action, or to allow only a subset of possible records to be the target of a many2one relation. The way to describe these filters in Odoo is called a domain. This recipe illustrates how to use such a domain to display a selection of partners.

How to do it...

To display a subset of partners from your action, you need to perform the following steps:

  1. Add an action for non-French speaking customers:
    <record id="action_my_customers" model="ir.actions.act_window">
        <field name="name">
            All customers who don't speak French
        </field>
        <field name="res_model">res.partner</field>
        <field name="domain">
            [('customer', '=', True), ('user_id', '=', uid), ('lang', '!=', 'fr_FR')]
        </field>
    </record>
  2. Add an action for the customers who are customers or suppliers:
    <record id="action_customers_or_suppliers"
            model="ir.actions.act_window">
        <field name="name">Customers or suppliers</field>
        <field name="res_model">res.partner</field>
        <field name="domain">
            ['|', ('customer', '=', True), ('supplier', '=', True)]
        </field>
    </record>
  3. Add menus that call this action. This is left as an exercise for the reader.

How it works...

The simplest form of a domain is a list of 3-tuples that contains a field name of the model in question as string in the first element, an operator as string in the second element, and the value the field is to be checked against as the third element. This is what we did in the first action, and this is interpreted as: All those conditions have to apply to the records we're interested in. This actually is a shortcut, because the domains know the two prefix operators & and |, where & is the default. So, in formal form, the first domain would be written as ['&', '&', ('customer', '=', True), ('user_id', '=', uid), ('lang', '!=', 'fr_FR')].

While a bit hard to read for bigger expressions, the advantage of prefix operators is that their scope is rigidly defined, which saves you having to worry about operator precedence and brackets. It's always the following two expressions: The first & applies to '&', ('customer', '=', True), ('user_id', '=', uid) as the first operand and ('lang', '!=', 'fr_FR') as the second. Then, the second & applies to ('customer', '=', True) as the first operand and ('user_id', '=', uid) as the second.

In the second action, we have to write out the full form because we need the | operator.

There is also a ! operator for negation, but, given logical equivalences and negated comparison operators such as != and not in, it is not really necessary. Note that this is a unary prefix operator, so it only applies to the following expression in the domain and not to everything that follows.

Note that the right operand doesn't need to be a fixed value when you write a domain for a window action or other client side domains. You can use the same minimal Python as described earlier for contexts, so you can write filters such as changed last week, my partners, and so on.

There's more...

The preceding domains work only on fields of the model itself, while we often need to filter based on the properties of linked records. To do this, you can use the notation also used in @api.depends definitions or related fields: create a dotted path from the current model to the model you want to filter for. To search partners which have a sales person that is a member of a group starting with the letter G, you would use the domain [('user_id.groups_id.name', '=like', 'G%')]. The path can be arbitrarily long, so you only have to take care that there are relation fields between the current model and the model you want to filter for.

Operators

The following table lists the available operators and their semantics:

Operator (equivalent)

Semantics

=, != (<>)

Exact match, not equal (deprecated notation of not equal)

in, not in

Checks if the value is one of the values named in a list in the right operand, given as a Python list: [('uid', 'in', [1, 2, 3])]

<, <=

Greater than, greater or equal

>, >=

Less than, less or equal

like, not like

Checks if the right operand is contained (substring) in the value

ilike, not ilike

The same as the preceding one but case insensitive

=like, =ilike

You can search for patterns here: % matches any string and _ matches one character. This is the equivalent of PostgreSQL's like.

child_of

For models with a parent_id field, this searches for children of the right operand, with the right operand included in the results.

=?

Evaluates to true if the right operand is false, otherwise it behaves like "= -" this is useful when you generate domains programmatically and want to filter for some value if it is set, but ignore it otherwise.

Pitfalls

This all works fine for traditional fields, but a notorious problem is searching for the value of a non-stored function field. It's a problem that people often omit the search function, while this is simple enough to fix by providing the search function in your own code as described in Chapter 4, Application Models.

Another issue that might baffle the developers is Odoo's behavior when searching through one2many or many2many fields with a negative operator. Imagine you have a partner with a tag A and you search for [('category_id.name', '!=', 'B')]. Your partner shows up in the result and this is what you expected. But if you add the tag B to this partner, it still shows up in your results, because for the search algorithm it is enough that there is one linked record (A in this case) that does not fulfill the criterion. Now if you remove the tag A so that B is the only tag, the partner will be filtered out. If you also remove the tag B so that the partner has no tags, it is still filtered out, because conditions on the linked records presuppose the existence of this record. In other situations though, this is the behavior you want, so it is not really an option to change the standard behavior. In case you need a different behavior here, provide a search function of your own that interprets the negation the way you need.

Note

A small gotcha is that people forget they are writing XML files when it is about domains. You need to escape the less-than operator. Searching for records that have been created before today would have to be [('create_date', '&lt;', current_date)] in XML.

See also

If you ever need to manipulate a domain you didn't create programmatically, use the utility functions provided in openerp.osv.expression. Especially is_leaf, normalize_domain, AND, and OR will allow you to combine domains exactly the way Odoo does it. Don't do this yourself, because there are many corner cases you have to take into account and it's very probable you'll overlook one.

For the standard application of domains, see the recipe Search views.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.204.8