diff --git a/content/developer/reference/backend/orm.rst b/content/developer/reference/backend/orm.rst index 638cd9d72..b3259d31a 100644 --- a/content/developer/reference/backend/orm.rst +++ b/content/developer/reference/backend/orm.rst @@ -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 ------ diff --git a/content/developer/reference/backend/orm/changelog.rst b/content/developer/reference/backend/orm/changelog.rst index 661454070..d6deeeed8 100644 --- a/content/developer/reference/backend/orm/changelog.rst +++ b/content/developer/reference/backend/orm/changelog.rst @@ -7,6 +7,8 @@ Changelog Odoo Online version 18.1 ======================== +- New `odoo.domain` and `odoo.Domain` API for domain manipulation. + See `#170009 `_. - Declare constraints and indexes as model attributes with `#175783 `_. - The `json` controllers have been renamed to `jsonrpc`. They are called the same, only the `type` in the python files changed. See `#183636 `_. @@ -21,7 +23,6 @@ Odoo version 18.0 See `#179148 `_. - Translations are made available from the `Environment` with `#174844 `_. - Odoo Online version 17.4 ======================== diff --git a/content/developer/reference/backend/security.rst b/content/developer/reference/backend/security.rst index 120b8f2f2..0fea6bfa1 100644 --- a/content/developer/reference/backend/security.rst +++ b/content/developer/reference/backend/security.rst @@ -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 ` 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 `_ - `Psycopg documentation `_ +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 -----------------------