[IMP] core: odoo.domain

odoo/odoo#170009

closes odoo/documentation#10214

Related: odoo/enterprise#65013
Related: odoo/upgrade-util#99
Signed-off-by: Antoine Vandevenne (anv) <anv@odoo.com>
This commit is contained in:
Krzysztof Magusiak (krma) 2024-07-15 15:40:38 +00:00
parent a88d46164b
commit 87b489d36e
3 changed files with 96 additions and 26 deletions

View File

@ -257,7 +257,8 @@ it uses the values of other *fields*, it should specify those fields using
* computed fields are not stored by default, they are computed and * computed fields are not stored by default, they are computed and
returned when requested. Setting ``store=True`` will store them in the returned when requested. Setting ``store=True`` will store them in the
database and automatically enable searching. database and automatically enable searching and grouping.
Note that by default, ``compute_sudo=True`` is set on the field.
* searching on a computed field can also be enabled by setting the ``search`` * searching on a computed field can also be enabled by setting the ``search``
parameter. The value is a method name returning a parameter. The value is a method name returning a
:ref:`reference/orm/domains`. :: :ref:`reference/orm/domains`. ::
@ -267,7 +268,7 @@ it uses the values of other *fields*, it should specify those fields using
def _search_upper(self, operator, value): def _search_upper(self, operator, value):
if operator == 'like': if operator == 'like':
operator = 'ilike' operator = 'ilike'
return [('name', operator, value)] return Domain('name', operator, value)
The search method is invoked when processing domains before doing an The search method is invoked when processing domains before doing an
actual search on the model. It must return a domain equivalent to the actual search on the model. It must return a domain equivalent to the
@ -275,8 +276,9 @@ it uses the values of other *fields*, it should specify those fields using
.. TODO and/or by setting the store to True for search domains ? .. TODO and/or by setting the store to True for search domains ?
* Computed fields are readonly by default. To allow *setting* values on a computed field, use the ``inverse`` * computed fields are readonly by default. To allow *setting* values on a
parameter. It is the name of a function reversing the computation and computed field, use the ``inverse`` parameter.
It is the name of a function reversing the computation and
setting the relevant fields:: setting the relevant fields::
document = fields.Char(compute='_get_document', inverse='_set_document') document = fields.Char(compute='_get_document', inverse='_set_document')
@ -854,7 +856,7 @@ Common ORM methods
.. currentmodule:: odoo.models .. currentmodule:: odoo.models
Create/update Create/Update
------------- -------------
.. todo:: api.model_create_multi information .. todo:: api.model_create_multi information
@ -900,10 +902,11 @@ Fields
Search domains Search domains
~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~
A domain is a list of criteria, each criterion being a triple (either a A :class:`~odoo.fields.Domain` is a first-order logical expression used for
``list`` or a ``tuple``) of ``(field_name, operator, value)`` where: filtering and searching recordsets.
A domain can be a simple condition ``(field_expr, operator, value)`` where:
* ``field_name`` (``str``) * ``field_expr`` (``str``)
a field name of the current model, or a relationship traversal through a field name of the current model, or a relationship traversal through
a :class:`~odoo.fields.Many2one` using dot-notation e.g. ``'street'`` a :class:`~odoo.fields.Many2one` using dot-notation e.g. ``'street'``
or ``'partner_id.country'``. If the field is a date(time) field, you can also or ``'partner_id.country'``. If the field is a date(time) field, you can also
@ -914,7 +917,7 @@ A domain is a list of criteria, each criterion being a triple (either a
They all use an integer as value. They all use an integer as value.
* ``operator`` (``str``) * ``operator`` (``str``)
an operator used to compare the ``field_name`` with the ``value``. Valid an operator used to compare the ``field_expr`` with the ``value``. Valid
operators are: operators are:
``=`` ``=``
@ -933,11 +936,11 @@ A domain is a list of criteria, each criterion being a triple (either a
unset or equals to (returns true if ``value`` is either ``None`` or unset or equals to (returns true if ``value`` is either ``None`` or
``False``, otherwise behaves like ``=``) ``False``, otherwise behaves like ``=``)
``=like`` ``=like``
matches ``field_name`` against the ``value`` pattern. An underscore matches ``field_expr`` against the ``value`` pattern. An underscore
``_`` in the pattern stands for (matches) any single character; a ``_`` in the pattern stands for (matches) any single character; a
percent sign ``%`` matches any string of zero or more characters. percent sign ``%`` matches any string of zero or more characters.
``like`` ``like``
matches ``field_name`` against the ``%value%`` pattern. Similar to matches ``field_expr`` against the ``%value%`` pattern. Similar to
``=like`` but wraps ``value`` with '%' before matching ``=like`` but wraps ``value`` with '%' before matching
``not like`` ``not like``
doesn't match against the ``%value%`` pattern doesn't match against the ``%value%`` pattern
@ -949,7 +952,7 @@ A domain is a list of criteria, each criterion being a triple (either a
case insensitive ``=like`` case insensitive ``=like``
``in`` ``in``
is equal to any of the items from ``value``, ``value`` should be a is equal to any of the items from ``value``, ``value`` should be a
list of items collection of items
``not in`` ``not in``
is unequal to all of the items from ``value`` is unequal to all of the items from ``value``
``child_of`` ``child_of``
@ -968,12 +971,13 @@ A domain is a list of criteria, each criterion being a triple (either a
:attr:`~odoo.models.Model._parent_name`). :attr:`~odoo.models.Model._parent_name`).
``any`` ``any``
matches if any record in the relationship traversal through matches if any record in the relationship traversal through
``field_name`` (:class:`~odoo.fields.Many2one`, ``field_expr`` (:class:`~odoo.fields.Many2one`,
:class:`~odoo.fields.One2many`, or :class:`~odoo.fields.Many2many`) :class:`~odoo.fields.One2many`, or :class:`~odoo.fields.Many2many`)
satisfies the provided domain ``value``. satisfies the provided domain ``value``.
The ``field_expr`` should be a field name.
``not any`` ``not any``
matches if no record in the relationship traversal through matches if no record in the relationship traversal through
``field_name`` (:class:`~odoo.fields.Many2one`, ``field_expr`` (:class:`~odoo.fields.Many2one`,
:class:`~odoo.fields.One2many`, or :class:`~odoo.fields.Many2many`) :class:`~odoo.fields.One2many`, or :class:`~odoo.fields.Many2many`)
satisfies the provided domain ``value``. satisfies the provided domain ``value``.
@ -981,19 +985,42 @@ A domain is a list of criteria, each criterion being a triple (either a
variable type, must be comparable (through ``operator``) to the named variable type, must be comparable (through ``operator``) to the named
field. field.
Domain criteria can be combined using logical operators in *prefix* form: :class:`~odoo.fields.Domain` can be used as a builder for domains.
``'&'`` .. code-block:: python
logical *AND*, default operation to combine criteria following one
another. Arity 2 (uses the next 2 criteria or combinations).
``'|'``
logical *OR*, arity 2.
``'!'``
logical *NOT*, arity 1.
.. note:: Mostly to negate combinations of criteria # parse a domain (from list to Domain)
Individual criterion generally have a negative form (e.g. ``=`` -> domain = Domain([('name', '=', 'abc'), ('phone', 'like', '7620')])
``!=``, ``<`` -> ``>=``) which is simpler than negating the positive.
# serialize domain as a list (from Domain to list)
domain_list = list(domain)
# simple domains
d1 = Domain('name', '=', 'abc')
d2 = Domain('phone', 'like', '7620')
# combine domains
d3 = d1 & d2 # and
d4 = d1 | d2 # or
d5 = ~d1 # not
# combine and parse multiple domains (any iterable of domains)
Domain.AND([d1, d2, d3, ...])
Domain.OR([d4, d5, ...])
# constants
Domain.TRUE # true domain
Domain.FALSE # false domain
.. automethod:: odoo.fields.Domain.iter_conditions
.. automethod:: odoo.fields.Domain.map_conditions
A domain is serialized as a ``list`` of criteria, each criterion being a triple
(either a ``list`` or a ``tuple``) representing a simple condition.
Domain criteria can be combined using logical operators in a *prefix* notation.
You can combine 2 domains using ``'&'`` (AND), ``'|'`` (OR)
and you can negate 1 using ``'!'`` (NOT).
.. example:: .. example::
@ -1013,6 +1040,7 @@ Domain criteria can be combined using logical operators in *prefix* form:
[('birthday.month_number', '=', 2)] [('birthday.month_number', '=', 2)]
Unlink Unlink
------ ------

View File

@ -7,6 +7,8 @@ Changelog
Odoo Online version 18.1 Odoo Online version 18.1
======================== ========================
- New `odoo.domain` and `odoo.Domain` API for domain manipulation.
See `#170009 <https://github.com/odoo/odoo/pull/170009>`_.
- Declare constraints and indexes as model attributes with `#175783 <https://github.com/odoo/odoo/pull/175783>`_. - Declare constraints and indexes as model attributes with `#175783 <https://github.com/odoo/odoo/pull/175783>`_.
- The `json` controllers have been renamed to `jsonrpc`. They are called the same, only the - The `json` controllers have been renamed to `jsonrpc`. They are called the same, only the
`type` in the python files changed. See `#183636 <https://github.com/odoo/odoo/pull/183636>`_. `type` in the python files changed. See `#183636 <https://github.com/odoo/odoo/pull/183636>`_.
@ -21,7 +23,6 @@ Odoo version 18.0
See `#179148 <https://github.com/odoo/odoo/pull/179148>`_. See `#179148 <https://github.com/odoo/odoo/pull/179148>`_.
- Translations are made available from the `Environment` with `#174844 <https://github.com/odoo/odoo/pull/174844>`_. - Translations are made available from the `Environment` with `#174844 <https://github.com/odoo/odoo/pull/174844>`_.
Odoo Online version 17.4 Odoo Online version 17.4
======================== ========================

View File

@ -245,6 +245,12 @@ less secure.
'AND state=%s AND obj_price > 0', (tuple(ids), 'draft',)) 'AND state=%s AND obj_price > 0', (tuple(ids), 'draft',))
auction_lots_ids = [x[0] for x in self.env.cr.fetchall()] auction_lots_ids = [x[0] for x in self.env.cr.fetchall()]
# nearly there
auction_lots_ids = [x[x] for x in self.env.execute_query(SQL("""
SELECT id FROM auction_lots
WHERE auction_id IN %s AND state = %s AND obj_price > 0
""", tuple(ids), 'draft'))
# better # better
auction_lots_ids = self.search([('auction_id','in',ids), ('state','=','draft'), ('obj_price','>',0)]) auction_lots_ids = self.search([('auction_id','in',ids), ('state','=','draft'), ('obj_price','>',0)])
@ -266,6 +272,10 @@ database abstraction layer (psycopg2) to decide how to format query parameters,
not your job! For example psycopg2 knows that when you pass a list of values not your job! For example psycopg2 knows that when you pass a list of values
it needs to format them as a comma-separated list, enclosed in parentheses ! it needs to format them as a comma-separated list, enclosed in parentheses !
Even better, there exists a :class:`~odoo.tools.SQL` wrapper to build queries
using templates that handles the formatting of inputs.
Check :ref:`SQL execution <reference/orm/sql>` for detailed usage.
.. code-block:: python .. code-block:: python
# the following is very bad: # the following is very bad:
@ -281,6 +291,13 @@ it needs to format them as a comma-separated list, enclosed in parentheses !
'WHERE parent_id IN %s', 'WHERE parent_id IN %s',
(tuple(ids),)) (tuple(ids),))
# more readable
self.env.cr.execute(SQL("""
SELECT DISTINCT child_id
FROM account_account_consol_rel
WHERE parent_id IN %s
""", tuple(ids)))
This is very important, so please be careful also when refactoring, and most This is very important, so please be careful also when refactoring, and most
importantly do not copy these patterns! importantly do not copy these patterns!
@ -293,6 +310,30 @@ online documentation of pyscopg2 to learn of to use it properly:
- `Advanced parameter types <http://initd.org/psycopg/docs/usage.html#adaptation-of-python-values-to-sql-types>`_ - `Advanced parameter types <http://initd.org/psycopg/docs/usage.html#adaptation-of-python-values-to-sql-types>`_
- `Psycopg documentation <https://www.psycopg.org/docs/sql.html>`_ - `Psycopg documentation <https://www.psycopg.org/docs/sql.html>`_
Building the domains
--------------------
Domains are represented as lists and are serializable.
You would be tented to manipulate these lists directly, however it can
introduce subtle issues where the user could inject a domain if the input is
not normalized.
Use :class:`~odoo.fields.Domain` to handle safely the manipulation
of domains.
.. code-block:: python
# bad
# the user can just pass ['|', ('id', '>', 0)] to access all
domain = ... # passed by the user
security_domain = [('user_id', '=', self.env.uid)]
domain += security_domain # can have a side-effect if this is a function argument
self.search(domain)
# better
domain = Domain(...)
domain &= Domain('user_id', '=', self.env.uid)
self.search(domain)
Unescaped field content Unescaped field content
----------------------- -----------------------