[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
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``
parameter. The value is a method name returning a
: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):
if operator == 'like':
operator = 'ilike'
return [('name', operator, value)]
return Domain('name', operator, value)
The search method is invoked when processing domains before doing an
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 ?
* Computed fields are readonly by default. To allow *setting* values on a computed field, use the ``inverse``
parameter. It is the name of a function reversing the computation and
* computed fields are readonly by default. To allow *setting* values on a
computed field, use the ``inverse`` parameter.
It is the name of a function reversing the computation and
setting the relevant fields::
document = fields.Char(compute='_get_document', inverse='_set_document')
@ -854,7 +856,7 @@ Common ORM methods
.. currentmodule:: odoo.models
Create/update
Create/Update
-------------
.. todo:: api.model_create_multi information
@ -900,10 +902,11 @@ Fields
Search domains
~~~~~~~~~~~~~~
A domain is a list of criteria, each criterion being a triple (either a
``list`` or a ``tuple``) of ``(field_name, operator, value)`` where:
A :class:`~odoo.fields.Domain` is a first-order logical expression used for
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 :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
@ -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.
* ``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:
``=``
@ -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
``False``, otherwise behaves 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
percent sign ``%`` matches any string of zero or more characters.
``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
``not like``
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``
``in``
is equal to any of the items from ``value``, ``value`` should be a
list of items
collection of items
``not in``
is unequal to all of the items from ``value``
``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`).
``any``
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`)
satisfies the provided domain ``value``.
The ``field_expr`` should be a field name.
``not any``
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`)
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
field.
Domain criteria can be combined using logical operators in *prefix* form:
:class:`~odoo.fields.Domain` can be used as a builder for domains.
``'&'``
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.
.. code-block:: python
.. note:: Mostly to negate combinations of criteria
Individual criterion generally have a negative form (e.g. ``=`` ->
``!=``, ``<`` -> ``>=``) which is simpler than negating the positive.
# parse a domain (from list to Domain)
domain = Domain([('name', '=', 'abc'), ('phone', 'like', '7620')])
# 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::
@ -1013,6 +1040,7 @@ Domain criteria can be combined using logical operators in *prefix* form:
[('birthday.month_number', '=', 2)]
Unlink
------

View File

@ -7,6 +7,8 @@ Changelog
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>`_.
- 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>`_.
@ -21,7 +23,6 @@ Odoo version 18.0
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>`_.
Odoo Online version 17.4
========================

View File

@ -245,6 +245,12 @@ less secure.
'AND state=%s AND obj_price > 0', (tuple(ids), 'draft',))
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
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
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
# 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',
(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
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>`_
- `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
-----------------------