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.
To display a subset of partners from your action, you need to perform the following steps:
<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>
<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>
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.
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.
The following table lists the available operators and their semantics:
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.
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.