summaryrefslogtreecommitdiff
path: root/odoo
diff options
context:
space:
mode:
authorVictor Feyens <vfe@odoo.com>2019-10-31 14:57:06 +0000
committerfw-bot <otis@odoo.com>2019-11-04 11:16:12 +0000
commitae4855fa1e0156cefdb1c31945252f2b7517a6f4 (patch)
tree324979856bbf49ed5ddae92f5fa5e1e5d631e48c /odoo
parentb2f3f092b726719159a16b4362c05592ba71360c (diff)
[IMP] base, doc: orm page refactoring.
closes odoo/odoo#39725 X-original-commit: 7ce7c58592c7c7202a37c73767c477be5a835752 Signed-off-by: Victor Feyens (vfe) <vfe@odoo.com>
Diffstat (limited to 'odoo')
-rw-r--r--odoo/api.py166
-rw-r--r--odoo/exceptions.py71
-rw-r--r--odoo/fields.py391
-rw-r--r--odoo/models.py271
4 files changed, 462 insertions, 437 deletions
diff --git a/odoo/api.py b/odoo/api.py
index 931dc5ab149..f90bed82585 100644
--- a/odoo/api.py
+++ b/odoo/api.py
@@ -1,34 +1,9 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-""" This module provides the elements for managing two different API styles,
- namely the "traditional" and "record" styles.
-
- In the "traditional" style, parameters like the database cursor, user id,
- context dictionary and record ids (usually denoted as ``cr``, ``uid``,
- ``context``, ``ids``) are passed explicitly to all methods. In the "record"
- style, those parameters are hidden into model instances, which gives it a
- more object-oriented feel.
-
- For instance, the statements::
-
- model = self.pool.get(MODEL)
- ids = model.search(cr, uid, DOMAIN, context=context)
- for rec in model.browse(cr, uid, ids, context=context):
- print rec.name
- model.write(cr, uid, ids, VALUES, context=context)
-
- may also be written as::
-
- env = Environment(cr, uid, context) # cr, uid, context wrapped in env
- model = env[MODEL] # retrieve an instance of MODEL
- recs = model.search(DOMAIN) # search returns a recordset
- for rec in recs: # iterate over the records
- print rec.name
- recs.write(VALUES) # update all records in recs
-
- Methods written in the "traditional" style are automatically decorated,
- following some heuristics based on parameter names.
+"""The Odoo API module defines Odoo Environments and method decorators.
+
+.. todo:: Document this module
"""
__all__ = [
@@ -124,8 +99,9 @@ def propagate(method1, method2):
def constrains(*args):
- """ Decorates a constraint checker. Each argument must be a field name
- used in the check::
+ """Decorate a constraint checker.
+
+ Each argument must be a field name used in the check::
@api.constrains('name', 'description')
def _check_description(self):
@@ -135,14 +111,14 @@ def constrains(*args):
Invoked on the records on which one of the named fields has been modified.
- Should raise :class:`~odoo.exceptions.ValidationError` if the
+ Should raise :exc:`~odoo.exceptions.ValidationError` if the
validation failed.
.. warning::
``@constrains`` only supports simple field names, dotted names
(fields of relational fields e.g. ``partner_id.customer``) are not
- supported and will be ignored
+ supported and will be ignored.
``@constrains`` will be triggered only if the declared fields in the
decorated method are included in the ``create`` or ``write`` call.
@@ -156,44 +132,51 @@ def constrains(*args):
def onchange(*args):
- """ Return a decorator to decorate an onchange method for given fields.
- Each argument must be a field name::
+ """Return a decorator to decorate an onchange method for given fields.
- @api.onchange('partner_id')
- def _onchange_partner(self):
- self.message = "Dear %s" % (self.partner_id.name or "")
+ In the form views where the field appears, the method will be called
+ when one of the given fields is modified. The method is invoked on a
+ pseudo-record that contains the values present in the form. Field
+ assignments on that record are automatically sent back to the client.
- In the form views where the field appears, the method will be called
- when one of the given fields is modified. The method is invoked on a
- pseudo-record that contains the values present in the form. Field
- assignments on that record are automatically sent back to the client.
+ Each argument must be a field name::
- The method may return a dictionary for changing field domains and pop up
- a warning message, like in the old API::
+ @api.onchange('partner_id')
+ def _onchange_partner(self):
+ self.message = "Dear %s" % (self.partner_id.name or "")
- return {
- 'domain': {'other_id': [('partner_id', '=', partner_id)]},
- 'warning': {'title': "Warning", 'message': "What is this?", 'type': 'notification'},
- }
- If the type is set to notification, the warning will be displayed in a notification.
- Otherwise it will be displayed in a dialog as default.
+ .. code-block:: python
- .. danger::
+ return {
+ 'domain': {'other_id': [('partner_id', '=', partner_id)]},
+ 'warning': {'title': "Warning", 'message': "What is this?", 'type': 'notification'},
+ }
- Since ``@onchange`` returns a recordset of pseudo-records,
- calling any one of the CRUD methods
- (:meth:`create`, :meth:`read`, :meth:`write`, :meth:`unlink`)
- on the aforementioned recordset is undefined behaviour,
- as they potentially do not exist in the database yet.
+ If the type is set to notification, the warning will be displayed in a notification.
+ Otherwise it will be displayed in a dialog as default.
- Instead, simply set the record's field like shown in the example
- above or call the :meth:`update` method.
+ .. warning::
- .. warning::
+ ``@onchange`` only supports simple field names, dotted names
+ (fields of relational fields e.g. ``partner_id.tz``) are not
+ supported and will be ignored
+
+ .. danger::
+
+ Since ``@onchange`` returns a recordset of pseudo-records,
+ calling any one of the CRUD methods
+ (:meth:`create`, :meth:`read`, :meth:`write`, :meth:`unlink`)
+ on the aforementioned recordset is undefined behaviour,
+ as they potentially do not exist in the database yet.
+
+ Instead, simply set the record's field like shown in the example
+ above or call the :meth:`update` method.
+
+ .. warning::
+
+ It is not possible for a ``one2many`` or ``many2many`` field to modify
+ itself via onchange. This is a webclient limitation - see `#2693 <https://github.com/odoo/odoo/issues/2693>`_.
- ``@onchange`` only supports simple field names, dotted names
- (fields of relational fields e.g. ``partner_id.tz``) are not
- supported and will be ignored
"""
return attrsetter('_onchange', args)
@@ -225,25 +208,26 @@ def depends(*args):
def depends_context(*args):
""" Return a decorator that specifies the context dependencies of a
- non-stored "compute" method. Each argument is a key in the context's
- dictionary::
+ non-stored "compute" method. Each argument is a key in the context's
+ dictionary::
- price = fields.Float(compute='_compute_product_price')
+ price = fields.Float(compute='_compute_product_price')
- @api.depends_context('pricelist')
- def _compute_product_price(self):
- for product in self:
- if product.env.context.get('pricelist'):
- pricelist = self.env['product.pricelist'].browse(product.env.context['pricelist'])
- else:
- pricelist = self.env['product.pricelist'].get_default_pricelist()
- product.price = pricelist.get_products_price(product).get(product.id, 0.0)
-
- All dependencies must be hashable. The following keys have special
- support:
- - 'force_company' (value in context or current company id),
- - 'uid' (current user id and superuser flag),
- - 'active_test' (value in env.context or value in field.context).
+ @api.depends_context('pricelist')
+ def _compute_product_price(self):
+ for product in self:
+ if product.env.context.get('pricelist'):
+ pricelist = self.env['product.pricelist'].browse(product.env.context['pricelist'])
+ else:
+ pricelist = self.env['product.pricelist'].get_default_pricelist()
+ product.price = pricelist.get_products_price(product).get(product.id, 0.0)
+
+ All dependencies must be hashable. The following keys have special
+ support:
+
+ * `force_company` (value in context or current company id),
+ * `uid` (current user id and superuser flag),
+ * `active_test` (value in env.context or value in field.context).
"""
return attrsetter('_depends_context', args)
@@ -510,6 +494,9 @@ class Environment(Mapping):
:param user: optional user/user id to change the current user
:param context: optional context dictionary to change the current context
:param su: optional boolean to change the superuser mode
+ :type context: dict
+ :type user: int or :class:`~odoo.addons.base.models.res_users`
+ :type su: bool
"""
cr = self.cr if cr is None else cr
uid = self.uid if user is None else int(user)
@@ -518,7 +505,7 @@ class Environment(Mapping):
return Environment(cr, uid, context, su)
def ref(self, xml_id, raise_if_not_found=True):
- """ return the record corresponding to the given ``xml_id`` """
+ """Return the record corresponding to the given ``xml_id``."""
return self['ir.model.data'].xmlid_to_object(xml_id, raise_if_not_found=raise_if_not_found)
def is_superuser(self):
@@ -537,17 +524,19 @@ class Environment(Mapping):
@lazy_property
def user(self):
- """ return the current user (as an instance) """
+ """Return the current user (as an instance).
+
+ :rtype: :class:`~odoo.addons.base.models.res_users`"""
return self(su=True)['res.users'].browse(self.uid)
@lazy_property
def company(self):
"""Return the current company (as an instance).
- If not specified in the context ('allowed_company_ids'),
- fallback on current user main company.
+ If not specified in the context (`allowed_company_ids`),
+ fallback on current user main company.
- :raise AccessError: invalid or unauthorized 'allowed_company_ids' context key content.
+ :raise AccessError: invalid or unauthorized `allowed_company_ids` context key content.
:return: current company (default=`self.user.company_id`)
:rtype: res.company
@@ -574,10 +563,10 @@ class Environment(Mapping):
def companies(self):
"""Return a recordset of the enabled companies by the user.
- If not specified in the context('allowed_company_ids'),
- fallback on current user companies.
+ If not specified in the context(`allowed_company_ids`),
+ fallback on current user companies.
- :raise AccessError: invalid or unauthorized 'allowed_company_ids' context key content.
+ :raise AccessError: invalid or unauthorized `allowed_company_ids` context key content.
:return: current companies (default=`self.user.company_ids`)
:rtype: res.company
@@ -612,7 +601,10 @@ class Environment(Mapping):
@property
def lang(self):
- """ return the current language code """
+ """Return the current language code.
+
+ :rtype: str
+ """
return self.context.get('lang')
def clear(self):
diff --git a/odoo/exceptions.py b/odoo/exceptions.py
index 75e8749b028..b3cc6b48f1e 100644
--- a/odoo/exceptions.py
+++ b/odoo/exceptions.py
@@ -1,13 +1,15 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-""" OpenERP core exceptions.
+"""The Odoo Exceptions module defines a few core exception types.
-This module defines a few exception types. Those types are understood by the
-RPC layer. Any other exception type bubbling until the RPC layer will be
+Those types are understood by the RPC layer.
+Any other exception type bubbling until the RPC layer will be
treated as a 'Server error'.
-If you consider introducing new exceptions, check out the test_exceptions addon.
+.. note::
+ If you consider introducing new exceptions,
+ check out the :mod:`odoo.addons.test_exceptions` module.
"""
import logging
@@ -29,6 +31,11 @@ class except_orm(Exception):
class UserError(except_orm):
+ """Generic error managed by the client.
+
+ Typically when the user tries to do something that has no sense given the current
+ state of a record.
+ """
def __init__(self, msg):
super(UserError, self).__init__(msg, value='')
@@ -41,10 +48,9 @@ class RedirectWarning(Exception):
""" Warning with a possibility to redirect the user instead of simply
displaying the warning message.
- Should receive as parameters:
- :param int action_id: id of the action where to perform the redirection
- :param string button_text: text to put on the button that will trigger
- the redirection.
+ :param int action_id: id of the action where to perform the redirection
+ :param str button_text: text to put on the button that will trigger
+ the redirection.
"""
# using this RedirectWarning won't crash if used as an except_orm
@property
@@ -53,8 +59,17 @@ class RedirectWarning(Exception):
class AccessDenied(Exception):
- """ Login/password error. no traceback.
- Example: When you try to log with a wrong password."""
+ """Login/password error.
+
+ .. note::
+
+ No traceback.
+
+ .. admonition:: Example
+
+ When you try to log with a wrong password.
+ """
+
def __init__(self, message='Access denied'):
super(AccessDenied, self).__init__(message)
self.with_traceback(None)
@@ -63,29 +78,49 @@ class AccessDenied(Exception):
class AccessError(except_orm):
- """ Access rights error.
- Example: When you try to read a record that you are not allowed to."""
+ """Access rights error.
+
+ .. admonition:: Example
+
+ When you try to read a record that you are not allowed to.
+ """
+
def __init__(self, msg):
super(AccessError, self).__init__(msg)
class CacheMiss(except_orm, KeyError):
- """ Missing value(s) in cache.
- Example: When you try to read a value in a flushed cache."""
+ """Missing value(s) in cache.
+
+ .. admonition:: Example
+
+ When you try to read a value in a flushed cache.
+ """
+
def __init__(self, record, field):
super(CacheMiss, self).__init__("%s.%s" % (str(record), field.name))
class MissingError(except_orm):
- """ Missing record(s).
- Example: When you try to write on a deleted record."""
+ """Missing record(s).
+
+ .. admonition:: Example
+
+ When you try to write on a deleted record.
+ """
+
def __init__(self, msg):
super(MissingError, self).__init__(msg)
class ValidationError(except_orm):
- """ Violation of python constraints
- Example: When you try to create a new user with a login which already exist in the db."""
+ """Violation of python constraints.
+
+ .. admonition:: Example
+
+ When you try to create a new user with a login which already exist in the db.
+ """
+
def __init__(self, msg):
super(ValidationError, self).__init__(msg)
diff --git a/odoo/fields.py b/odoo/fields.py
index 70dadd84850..3ada0e914ab 100644
--- a/odoo/fields.py
+++ b/odoo/fields.py
@@ -99,169 +99,80 @@ class MetaField(type):
_global_seq = iter(itertools.count())
class Field(MetaField('DummyField', (object,), {})):
- """ The field descriptor contains the field definition, and manages accesses
- and assignments of the corresponding field on records. The following
- attributes may be provided when instanciating a field:
+ """The field descriptor contains the field definition, and manages accesses
+ and assignments of the corresponding field on records. The following
+ attributes may be provided when instanciating a field:
- :param string: the label of the field seen by users (string); if not
- set, the ORM takes the field name in the class (capitalized).
+ :param str string: the label of the field seen by users; if not
+ set, the ORM takes the field name in the class (capitalized).
- :param help: the tooltip of the field seen by users (string)
+ :param str help: the tooltip of the field seen by users
- :param readonly: whether the field is readonly (boolean, by default ``False``)
+ :param bool readonly: whether the field is readonly (default: ``False``)
- :param required: whether the value of the field is required (boolean, by
- default ``False``)
+ This only has an impact on the UI. Any field assignation in code will work
+ (if the field is a stored field or an inversable one).
- :param index: whether the field is indexed in database. Note: no effect
- on non-stored and virtual fields. (boolean, by default ``False``)
+ :param bool required: whether the value of the field is required (default: ``False``)
- :param default: the default value for the field; this is either a static
- value, or a function taking a recordset and returning a value; use
- ``default=None`` to discard default values for the field
+ :param bool index: whether the field is indexed in database. Note: no effect
+ on non-stored and virtual fields. (default: ``False``)
- :param states: a dictionary mapping state values to lists of UI attribute-value
- pairs; possible attributes are: 'readonly', 'required', 'invisible'.
- Note: Any state-based condition requires the ``state`` field value to be
+ :param default: the default value for the field; this is either a static
+ value, or a function taking a recordset and returning a value; use
+ ``default=None`` to discard default values for the field
+ :type default: value or callable
+
+ :param dict states: a dictionary mapping state values to lists of UI attribute-value
+ pairs; possible attributes are: ``readonly``, ``required``, ``invisible``.
+
+ .. warning:: Any state-based condition requires the ``state`` field value to be
available on the client-side UI. This is typically done by including it in
the relevant views, possibly made invisible if not relevant for the
end-user.
- :param groups: comma-separated list of group xml ids (string); this
- restricts the field access to the users of the given groups only
-
- :param bool copy: whether the field value should be copied when the record
- is duplicated (default: ``True`` for normal fields, ``False`` for
- ``one2many`` and computed fields, including property fields and
- related fields)
-
- .. _field-computed:
-
- .. rubric:: Computed fields
-
- One can define a field whose value is computed instead of simply being
- read from the database. The attributes that are specific to computed
- fields are given below. To define such a field, simply provide a value
- for the attribute ``compute``.
-
- :param compute: name of a method that computes the field
-
- :param inverse: name of a method that inverses the field (optional)
-
- :param search: name of a method that implement search on the field (optional)
-
- :param store: whether the field is stored in database (boolean, by
- default ``False`` on computed fields)
-
- :param compute_sudo: whether the field should be computed in superuser
- mode to bypass access rights (boolean, defaults to ``True`` for
- stored fields and ``False`` for non-stored fields)
-
- The methods given for ``compute``, ``inverse`` and ``search`` are model
- methods. Their signature is shown in the following example::
-
- upper = fields.Char(compute='_compute_upper',
- inverse='_inverse_upper',
- search='_search_upper')
-
- @api.depends('name')
- def _compute_upper(self):
- for rec in self:
- rec.upper = rec.name.upper() if rec.name else False
-
- def _inverse_upper(self):
- for rec in self:
- rec.name = rec.upper.lower() if rec.upper else False
+ :param str groups: comma-separated list of group xml ids (string); this
+ restricts the field access to the users of the given groups only
- def _search_upper(self, operator, value):
- if operator == 'like':
- operator = 'ilike'
- return [('name', operator, value)]
+ :param bool company_dependent: whether the field value is dependent of the current company;
- The compute method has to assign the field on all records of the invoked
- recordset. The decorator :meth:`odoo.api.depends` must be applied on
- the compute method to specify the field dependencies; those dependencies
- are used to determine when to recompute the field; recomputation is
- automatic and guarantees cache/database consistency. Note that the same
- method can be used for several fields, you simply have to assign all the
- given fields in the method; the method will be invoked once for all
- those fields.
+ The value isn't stored on the model table. It is registered as `ir.property`.
+ When the value of the company_dependent field is needed, an `ir.property`
+ is searched, linked to the current company (and current record if one property
+ exists).
- By default, a computed field is not stored to the database, and is
- computed on-the-fly. Adding the attribute ``store=True`` will store the
- field's values in the database. The advantage of a stored field is that
- searching on that field is done by the database itself. The disadvantage
- is that it requires database updates when the field must be recomputed.
+ If the value is changed on the record, it either modifies the existing property
+ for the current record (if one exists), or creates a new one for the current company
+ and res_id.
- The inverse method, as its name says, does the inverse of the compute
- method: the invoked records have a value for the field, and you must
- apply the necessary changes on the field dependencies such that the
- computation gives the expected value. Note that a computed field without
- an inverse method is readonly by default.
+ If the value is changed on the company side, it will impact all records on which
+ the value hasn't been changed.
- The search method is invoked when processing domains before doing an
- actual search on the model. It must return a domain equivalent to the
- condition: ``field operator value``.
+ :param bool copy: whether the field value should be copied when the record
+ is duplicated (default: ``True`` for normal fields, ``False`` for
+ ``one2many`` and computed fields, including property fields and
+ related fields)
- .. _field-related:
+ :param bool store: whether the field is stored in database
+ (default:``True``, ``False`` for computed fields)
- .. rubric:: Related fields
+ .. rubric:: Computed Fields
- The value of a related field is given by following a sequence of
- relational fields and reading a field on the reached model. The complete
- sequence of fields to traverse is specified by the attribute
+ :param str compute: name of a method that computes the field
- :param related: sequence of field names
+ .. seealso:: :ref:`Advanced Fields/Compute fields <reference/fields/compute>`
- Some field attributes are automatically copied from the source field if
- they are not redefined: ``string``, ``help``, ``readonly``, ``required`` (only
- if all fields in the sequence are required), ``groups``, ``digits``, ``size``,
- ``translate``, ``sanitize``, ``selection``, ``comodel_name``, ``domain``,
- ``context``. All semantic-free attributes are copied from the source
- field.
+ :param bool compute_sudo: whether the field should be recomputed as superuser
+ to bypass access rights (by default ``True`` for stored fields, ``False``
+ for non stored fields)
- By default, the values of related fields are not stored to the database.
- Add the attribute ``store=True`` to make it stored, just like computed
- fields. Related fields are automatically recomputed when their
- dependencies are modified.
+ :param str inverse: name of a method that inverses the field (optional)
- .. _field-company-dependent:
+ :param str search: name of a method that implement search on the field (optional)
- .. rubric:: Company-dependent fields
-
- Formerly known as 'property' fields, the value of those fields depends
- on the company. In other words, users that belong to different companies
- may see different values for the field on a given record.
-
- .. warning::
-
- Company-dependent fields aren't stored in the table of the model they're defined on,
- instead, they are stored in the ``ir.property`` model's table.
-
- :param company_dependent: whether the field is company-dependent (boolean)
-
- .. _field-incremental-definition:
-
- .. rubric:: Incremental definition
-
- A field is defined as class attribute on a model class. If the model
- is extended (see :class:`~odoo.models.Model`), one can also extend
- the field definition by redefining a field with the same name and same
- type on the subclass. In that case, the attributes of the field are
- taken from the parent class and overridden by the ones given in
- subclasses.
-
- For instance, the second class below only adds a tooltip on the field
- ``state``::
-
- class First(models.Model):
- _name = 'foo'
- state = fields.Selection([...], required=True)
-
- class Second(models.Model):
- _inherit = 'foo'
- state = fields.Selection(help="Blah blah blah")
+ :param str related: sequence of field names
+ .. seealso:: :ref:`Advanced fields/Related fields <reference/fields/related>`
"""
type = None # type of the field (string)
@@ -1225,11 +1136,13 @@ class Integer(Field):
class Float(Field):
- """ The precision digits are given by the attribute
+ """The precision digits are given by the attribute
- :param digits: a pair (total, decimal), or a function taking a database
- cursor and returning a pair (total, decimal)
+ :param digits: a pair (total, decimal) or a
+ string referencing a `decimal.precision` record.
+ :type digits: tuple(int,int) or str
"""
+
type = 'float'
column_cast_from = ('int4', 'numeric', 'float8')
_slots = {
@@ -1290,8 +1203,8 @@ class Float(Field):
class Monetary(Field):
""" The decimal precision and currency symbol are taken from the attribute
- :param currency_field: name of the field holding the currency this monetary
- field is expressed in (default: `currency_id`)
+ :param str currency_field: name of the field holding the currency
+ this monetary field is expressed in (default: `\'currency_id\'`)
"""
type = 'monetary'
column_type = ('numeric', 'numeric')
@@ -1516,6 +1429,7 @@ class Char(_String):
may also be a callable such that ``translate(callback, value)``
translates ``value`` by using ``callback(term)`` to retrieve the
translation of terms.
+ :type translate: bool or callable
"""
type = 'char'
column_cast_from = ('text',)
@@ -1569,6 +1483,7 @@ class Text(_String):
may also be a callable such that ``translate(callback, value)``
translates ``value`` by using ``callback(term)`` to retrieve the
translation of terms.
+ :type translate: bool or callable
"""
type = 'text'
column_type = ('text', 'text')
@@ -1640,6 +1555,9 @@ class Html(_String):
class Date(Field):
+ """ This field type encapsulates a python date object.
+ :type date:
+ """
type = 'date'
column_type = ('date', 'date')
column_cast_from = ('timestamp',)
@@ -1651,17 +1569,18 @@ class Date(Field):
@staticmethod
def today(*args):
- """ Return the current day in the format expected by the ORM.
- This function may be used to compute default values.
+ """Return the current day in the format expected by the ORM.
+
+ .. note:: This function may be used to compute default values.
"""
return date.today()
@staticmethod
def context_today(record, timestamp=None):
- """
- Return the current date as seen in the client's timezone in a format
- fit for date fields. This method may be used to compute default
- values.
+ """Return the current date as seen in the client's timezone in a format
+ fit for date fields.
+
+ .. note:: This method may be used to compute default values.
:param record: recordset from which the timezone will be obtained.
:param datetime timestamp: optional datetime value to use instead of
@@ -1683,19 +1602,18 @@ class Date(Field):
@staticmethod
def to_date(value):
- """
- Attempt to convert ``value`` to a :class:`date` object.
+ """Attempt to convert ``value`` to a :class:`date` object.
+
+ .. warning::
- This function can take as input different kinds of types:
- * A falsy object, in which case None will be returned.
- * A string representing a date or datetime.
- * A date object, in which case the object will be returned as-is.
- * A datetime object, in which case it will be converted to a date object and all\
- datetime-specific information will be lost (HMS, TZ, ...).
+ If a datetime object is given as value,
+ it will be converted to a date object and all
+ datetime-specific information will be lost (HMS, TZ, ...).
:param value: value to convert.
+ :type value: str or date or datetime
:return: an object representing ``value``.
- :rtype: date
+ :rtype: date or None
"""
if not value:
return None
@@ -1738,6 +1656,9 @@ class Date(Field):
class Datetime(Field):
+ """ This field type encapsulates a python datetime object.
+ :type datetime:
+ """
type = 'datetime'
column_type = ('timestamp', 'timestamp')
column_cast_from = ('date',)
@@ -1749,33 +1670,32 @@ class Datetime(Field):
@staticmethod
def now(*args):
- """ Return the current day and time in the format expected by the ORM.
- This function may be used to compute default values.
+ """Return the current day and time in the format expected by the ORM.
+
+ .. note:: This function may be used to compute default values.
"""
# microseconds must be annihilated as they don't comply with the server datetime format
return datetime.now().replace(microsecond=0)
@staticmethod
def today(*args):
- """
- Return the current day, at midnight (00:00:00).
- """
+ """Return the current day, at midnight (00:00:00)."""
return Datetime.now().replace(hour=0, minute=0, second=0)
@staticmethod
def context_timestamp(record, timestamp):
- """
- Returns the given timestamp converted to the client's timezone.
- This method is *not* meant for use as a default initializer,
- because datetime fields are automatically converted upon
- display on client side. For default values, :meth:`fields.Datetime.now`
- should be used instead.
+ """Return the given timestamp converted to the client's timezone.
+
+ .. note:: This method is *not* meant for use as a default initializer,
+ because datetime fields are automatically converted upon
+ display on client side. For default values, :meth:`now`
+ should be used instead.
:param record: recordset from which the timezone will be obtained.
:param datetime timestamp: naive datetime value (expressed in UTC)
to be converted to the client timezone.
- :rtype: datetime
:return: timestamp converted to timezone-aware datetime in context timezone.
+ :rtype: datetime
"""
assert isinstance(timestamp, datetime), 'Datetime instance expected'
tz_name = record._context.get('tz') or record.env.user.tz
@@ -1792,18 +1712,12 @@ class Datetime(Field):
@staticmethod
def to_datetime(value):
- """
- Convert an ORM ``value`` into a :class:`datetime` value.
-
- This function can take as input different kinds of types:
- * A falsy object, in which case None will be returned.
- * A string representing a date or datetime.
- * A datetime object, in which case the object will be returned as-is.
- * A date object, in which case it will be converted to a datetime object.
+ """Convert an ORM ``value`` into a :class:`datetime` value.
:param value: value to convert.
+ :type value: str or date or datetime
:return: an object representing ``value``.
- :rtype: datetime
+ :rtype: datetime or None
"""
if not value:
return None
@@ -1823,12 +1737,13 @@ class Datetime(Field):
@staticmethod
def to_string(value):
- """
- Convert a :class:`datetime` or :class:`date` object to a string.
+ """Convert a :class:`datetime` or :class:`date` object to a string.
:param value: value to convert.
- :return: a string representing ``value`` in the server's datetime format, if ``value`` is
- of type :class:`date`, the time portion will be midnight (00:00:00).
+ :type value: datetime or date
+ :return: a string representing ``value`` in the server's datetime format,
+ if ``value`` is of type :class:`date`,
+ the time portion will be midnight (00:00:00).
:rtype: str
"""
return value.strftime(DATETIME_FORMAT) if value else False
@@ -2071,6 +1986,7 @@ class Selection(Field):
:param selection: specifies the possible values for this field.
It is given as either a list of pairs ``(value, label)``, or a model
method, or a method name.
+ :type selection: list(tuple(str,str)) or callable or str
:param selection_add: provides an extension of the selection in the case
of an overridden field. It is a list of pairs ``(value, label)`` or
@@ -2081,10 +1997,10 @@ class Selection(Field):
selection = [('a', 'A'), ('b', 'B')]
selection_add = [('c', 'C'), ('b',)]
> result = [('a', 'A'), ('c', 'C'), ('b', 'B')]
+ :type selection_add: list(tuple(str,str))
The attribute ``selection`` is mandatory except in the case of
- :ref:`related fields <field-related>` or :ref:`field extensions
- <field-incremental-definition>`.
+ ``related`` or extended fields.
"""
type = 'selection'
column_type = ('varchar', pg_varchar())
@@ -2181,7 +2097,7 @@ class Selection(Field):
return selection
def get_values(self, env):
- """ return a list of the possible values """
+ """Return a list of the possible values."""
selection = self.selection
if isinstance(selection, str):
selection = getattr(env[self.model_name], selection)()
@@ -2315,29 +2231,27 @@ class Many2one(_Relational):
""" The value of such a field is a recordset of size 0 (no
record) or 1 (a single record).
- :param comodel_name: name of the target model (string)
+ :param str comodel_name: name of the target model
+ ``Mandatory`` except for related or extended fields.
:param domain: an optional domain to set on candidate values on the
client side (domain or string)
- :param context: an optional context to use on the client side when
- handling that field (dictionary)
+ :param dict context: an optional context to use on the client side when
+ handling that field
- :param ondelete: what to do when the referred record is deleted;
+ :param str ondelete: what to do when the referred record is deleted;
possible values are: ``'set null'``, ``'restrict'``, ``'cascade'``
- :param auto_join: whether JOINs are generated upon search through that
- field (boolean, by default ``False``)
+ :param bool auto_join: whether JOINs are generated upon search through that
+ field (default: ``False``)
- :param delegate: set it to ``True`` to make fields of the target model
+ :param bool delegate: set it to ``True`` to make fields of the target model
accessible from the current model (corresponds to ``_inherits``)
:param check_company: add default domain ``['|', ('company_id', '=', False),
('company_id', '=', company_id)]``. Mark the field to be verified in
``_check_company``.
-
- The attribute ``comodel_name`` is mandatory except in the case of related
- fields or field extensions.
"""
type = 'many2one'
column_type = ('int4', 'int4')
@@ -2802,28 +2716,28 @@ class _RelationalMulti(_Relational):
class One2many(_RelationalMulti):
- """ One2many field; the value of such a field is the recordset of all the
- records in ``comodel_name`` such that the field ``inverse_name`` is equal to
- the current record.
+ """One2many field; the value of such a field is the recordset of all the
+ records in ``comodel_name`` such that the field ``inverse_name`` is equal to
+ the current record.
- :param comodel_name: name of the target model (string)
+ :param str comodel_name: name of the target model
- :param inverse_name: name of the inverse ``Many2one`` field in
- ``comodel_name`` (string)
+ :param str inverse_name: name of the inverse ``Many2one`` field in
+ ``comodel_name``
- :param domain: an optional domain to set on candidate values on the
- client side (domain or string)
+ :param domain: an optional domain to set on candidate values on the
+ client side (domain or string)
- :param context: an optional context to use on the client side when
- handling that field (dictionary)
+ :param dict context: an optional context to use on the client side when
+ handling that field
- :param auto_join: whether JOINs are generated upon search through that
- field (boolean, by default ``False``)
+ :param bool auto_join: whether JOINs are generated upon search through that
+ field (default: ``False``)
- :param limit: optional limit to use upon read (integer)
+ :param int limit: optional limit to use upon read
- The attributes ``comodel_name`` and ``inverse_name`` are mandatory except in
- the case of related fields or field extensions.
+ The attributes ``comodel_name`` and ``inverse_name`` are mandatory except in
+ the case of related fields or field extensions.
"""
type = 'one2many'
_slots = {
@@ -3065,46 +2979,43 @@ class One2many(_RelationalMulti):
class Many2many(_RelationalMulti):
""" Many2many field; the value of such a field is the recordset.
- :param comodel_name: name of the target model (string)
-
- The attribute ``comodel_name`` is mandatory except in the case of related
- fields or field extensions.
-
- :param relation: optional name of the table that stores the relation in
- the database (string)
+ :param comodel_name: name of the target model (string)
+ mandatory except in the case of related or extended fields
- :param column1: optional name of the column referring to "these" records
- in the table ``relation`` (string)
+ :param str relation: optional name of the table that stores the relation in
+ the database
- :param column2: optional name of the column referring to "those" records
- in the table ``relation`` (string)
+ :param str column1: optional name of the column referring to "these" records
+ in the table ``relation``
- The attributes ``relation``, ``column1`` and ``column2`` are optional.
- If not given, names are automatically generated from model names,
- provided ``model_name`` and ``comodel_name`` are different!
+ :param str column2: optional name of the column referring to "those" records
+ in the table ``relation``
- Note that having several fields with implicit relation parameters on a
- given model with the same comodel is not accepted by the ORM, since
- those field would use the same table. The ORM prevents two many2many
- fields to use the same relation parameters, except if
+ The attributes ``relation``, ``column1`` and ``column2`` are optional.
+ If not given, names are automatically generated from model names,
+ provided ``model_name`` and ``comodel_name`` are different!
- - both fields use the same model, comodel, and relation parameters are
- explicit; or
+ Note that having several fields with implicit relation parameters on a
+ given model with the same comodel is not accepted by the ORM, since
+ those field would use the same table. The ORM prevents two many2many
+ fields to use the same relation parameters, except if
- - at least one field belongs to a model with ``_auto = False``.
+ - both fields use the same model, comodel, and relation parameters are
+ explicit; or
- :param domain: an optional domain to set on candidate values on the
- client side (domain or string)
+ - at least one field belongs to a model with ``_auto = False``.
- :param context: an optional context to use on the client side when
- handling that field (dictionary)
+ :param domain: an optional domain to set on candidate values on the
+ client side (domain or string)
- :param limit: optional limit to use upon read (integer)
+ :param dict context: an optional context to use on the client side when
+ handling that field
- :param check_company: add default domain ``['|', ('company_id', '=', False),
- ('company_id', '=', company_id)]``. Mark the field to be verified in
- ``_check_company``.
+ :param check_company: add default domain ``['|', ('company_id', '=', False),
+ ('company_id', '=', company_id)]``. Mark the field to be verified in
+ ``_check_company``.
+ :param int limit: optional limit to use upon read
"""
type = 'many2many'
_slots = {
diff --git a/odoo/models.py b/odoo/models.py
index b9201de446b..be614498fe4 100644
--- a/odoo/models.py
+++ b/odoo/models.py
@@ -237,9 +237,9 @@ VALID_AGGREGATE_FUNCTIONS = {
class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
- """ Base class for Odoo models.
+ """Base class for Odoo models.
- Odoo models are created by inheriting:
+ Odoo models are created by inheriting one of the following:
* :class:`Model` for regular database-persisted models
@@ -261,35 +261,80 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
explicit representation: a record is represented as a recordset of one
record.
- To create a class that should not be instantiated, the _register class
- attribute may be set to False.
+ To create a class that should not be instantiated,
+ the :attr:`~odoo.models.BaseModel._register` attribute may be set to False.
"""
- _auto = False # don't create any database backend
- _register = False # not visible in ORM registry
- _abstract = True # whether model is abstract
- _transient = False # whether model is transient
-
- _name = None # the model name
- _description = None # the model's informal name
- _custom = False # should be True for custom models only
-
- _inherit = None # Python-inherited models ('model' or ['model'])
- _inherits = {} # inherited models {'parent_model': 'm2o_field'}
-
- _table = None # SQL table name used by model
- _sequence = None # SQL sequence to use for ID field
- _sql_constraints = [] # SQL constraints [(name, sql_def, message)]
-
- _rec_name = None # field to use for labeling records
- _order = 'id' # default order for searching results
- _parent_name = 'parent_id' # the many2one field used as parent field
- _parent_store = False # set to True to compute parent_path field
- _date_name = 'date' # field to use for default calendar view
- _fold_name = 'fold' # field to determine folded groups in kanban views
-
- _needaction = False # whether the model supports "need actions" (see mail)
- _translate = True # False disables translations export for this model
+
+ _auto = False
+ """Whether a database table should be created (default: ``True``).
+ If set to ``False``, override :meth:`~odoo.models.BaseModel.init`
+ to create the database table.
+
+ .. tip:: To create a model without any table, inherit
+ from :class:`~odoo.models.AbstractModel`.
+ """
+ _register = False #: not visible in ORM registry
+ _abstract = True #: whether model is abstract
+ _transient = False #: whether model is transient
+
+ _name = None #: the model name (in dot-notation, module namespace)
+ _description = None #: the model's informal name
+ _custom = False #: should be True for custom models only
+
+ _inherit = None
+ """Python-inherited models:
+
+ :type: str or list(str)
+
+ .. note::
+
+ * If :attr:`._name` is set, name(s) of parent models to inherit from
+ * If :attr:`._name` is unset, name of a single model to extend in-place
+ """
+ _inherits = {}
+ """dictionary {'parent_model': 'm2o_field'} mapping the _name of the parent business
+ objects to the names of the corresponding foreign key fields to use::
+
+ _inherits = {
+ 'a.model': 'a_field_id',
+ 'b.model': 'b_field_id'
+ }
+
+ implements composition-based inheritance: the new model exposes all
+ the fields of the inherited models but stores none of them:
+ the values themselves remain stored on the linked record.
+
+ .. warning::
+
+ if multiple fields with the same name are defined in the
+ :attr:`~odoo.models.Model._inherits`-ed models, the inherited field will
+ correspond to the last one (in the inherits list order).
+ """
+ _table = None #: SQL table name used by model if :attr:`_auto`
+ _sequence = None #: SQL sequence to use for ID field
+ _sql_constraints = [] #: SQL constraints [(name, sql_def, message)]
+
+ _rec_name = None #: field to use for labeling records, default: ``name``
+ _order = 'id' #: default order field for searching results
+ _parent_name = 'parent_id' #: the many2one field used as parent field
+ _parent_store = False
+ """set to True to compute parent_path field.
+
+ Alongside a :attr:`~.parent_path` field, sets up an indexed storage
+ of the tree structure of records, to enable faster hierarchical queries
+ on the records of the current model using the ``child_of`` and
+ ``parent_of`` domain operators.
+ """
+ _date_name = 'date' #: field to use for default calendar view
+ _fold_name = 'fold' #: field to determine folded groups in kanban views
+
+ _needaction = False # whether the model supports "need actions" (Old API)
+ _translate = True # False disables translations export for this model (Old API)
_check_company_auto = False
+ """On write and create, call ``_check_company`` to ensure companies
+ consistency on the relational fields having ``check_company=True``
+ as attribute.
+ """
# default values for _transient_vacuum()
_transient_check_count = 0
@@ -1439,14 +1484,15 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
Get the detailed composition of the requested view like fields, model, view architecture
- :param view_id: id of the view or None
- :param view_type: type of the view to return if view_id is None ('form', 'tree', ...)
- :param toolbar: true to include contextual actions
+ :param int view_id: id of the view or None
+ :param str view_type: type of the view to return if view_id is None ('form', 'tree', ...)
+ :param bool toolbar: true to include contextual actions
:param submenu: deprecated
- :return: dictionary describing the composition of the requested view (including inherited views and extensions)
+ :return: composition of the requested view (including inherited views and extensions)
+ :rtype: dict
:raise AttributeError:
- * if the inherited view has unknown position to work with other than 'before', 'after', 'inside', 'replace'
- * if some tag other than 'position' is found in parent view
+ * if the inherited view has unknown position to work with other than 'before', 'after', 'inside', 'replace'
+ * if some tag other than 'position' is found in parent view
:raise Invalid ArchitectureError: if there is view type other than form, tree, calendar, search etc defined on the structure
"""
View = self.env['ir.ui.view']
@@ -2038,10 +2084,10 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
@api.model
def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):
- """
- Get the list of records in list view grouped by the given ``groupby`` fields
+ """Get the list of records in list view grouped by the given ``groupby`` fields.
- :param domain: list specifying search criteria [['field_name', 'operator', 'value'], ...]
+ :param list domain: :ref:`A search domain <reference/orm/domains>`. Use an empty
+ list to match all records.
:param list fields: list of fields present in the list view specified on the object.
Each element is either 'field' (field name, using the default aggregation),
or 'field:agg' (aggregate field with aggregation function 'agg'),
@@ -2993,19 +3039,18 @@ Fields:
raise self.env['ir.rule']._make_access_error('read', forbidden)
def get_metadata(self):
- """
- Returns some metadata about the given records.
+ """Return some metadata about the given records.
:return: list of ownership dictionaries for each requested record
:rtype: list of dictionaries with the following keys:
- * id: object id
- * create_uid: user who created the record
- * create_date: date when the record was created
- * write_uid: last user who changed the record
- * write_date: date of the last change to the record
- * xmlid: XML ID to use to refer to this record (if there is one), in format ``module.name``
- * noupdate: A boolean telling if the record will be updated or not
+ * id: object id
+ * create_uid: user who created the record
+ * create_date: date when the record was created
+ * write_uid: last user who changed the record
+ * write_date: date of the last change to the record
+ * xmlid: XML ID to use to refer to this record (if there is one), in format ``module.name``
+ * noupdate: A boolean telling if the record will be updated or not
"""
IrModelData = self.env['ir.model.data'].sudo()
@@ -3320,7 +3365,7 @@ Record ids: %(records)s
:raise AccessError: * if user has no write rights on the requested object
* if user tries to bypass access rules for write on the requested object
- :raise ValidateError: if user tries to enter invalid value for a field that is not in selection
+ :raise ValidationError: if user tries to enter invalid value for a field that is not in selection
:raise UserError: if a loop would be created in a hierarchy of objects a result of the operation (such as setting an object as its own parent)
* For numeric fields (:class:`~odoo.fields.Integer`,
@@ -3355,31 +3400,28 @@ Record ids: %(records)s
triplet is a command to execute on the set of records. Not all
commands apply in all situations. Possible commands are:
- ``(0, _, values)``
+ ``(0, 0, values)``
adds a new record created from the provided ``value`` dict.
``(1, id, values)``
updates an existing record of id ``id`` with the values in
``values``. Can not be used in :meth:`~.create`.
- ``(2, id, _)``
+ ``(2, id, 0)``
removes the record of id ``id`` from the set, then deletes it
(from the database). Can not be used in :meth:`~.create`.
- ``(3, id, _)``
+ ``(3, id, 0)``
removes the record of id ``id`` from the set, but does not
delete it. Can not be used in
:meth:`~.create`.
- ``(4, id, _)``
+ ``(4, id, 0)``
adds an existing record of id ``id`` to the set.
- ``(5, _, _)``
+ ``(5, 0, 0)``
removes all records from the set, equivalent to using the
command ``3`` on every record explicitly. Can not be used in
:meth:`~.create`.
- ``(6, _, ids)``
+ ``(6, 0, ids)``
replaces all existing records in the set by the ``ids`` list,
equivalent to using the command ``5`` followed by a command
``4`` for each ``id`` in ``ids``.
-
- .. note:: Values marked as ``_`` in the list above are ignored and
- can be anything, generally ``0`` or ``False``.
"""
if not self:
return True
@@ -3583,7 +3625,7 @@ Record ids: %(records)s
:return: the created records
:raise AccessError: * if user has no create rights on the requested object
* if user tries to bypass access rules for create on the requested object
- :raise ValidateError: if user tries to enter invalid value for a field that is not in selection
+ :raise ValidationError: if user tries to enter invalid value for a field that is not in selection
:raise UserError: if a loop would be created in a hierarchy of objects a result of the operation (such as setting an object as its own parent)
"""
if not vals_list:
@@ -4731,17 +4773,20 @@ Record ids: %(records)s
@api.model
def search_read(self, domain=None, fields=None, offset=0, limit=None, order=None):
- """
- Performs a ``search()`` followed by a ``read()``.
-
- :param domain: Search domain, see ``args`` parameter in ``search()``. Defaults to an empty domain that will match all records.
- :param fields: List of fields to read, see ``fields`` parameter in ``read()``. Defaults to all fields.
- :param offset: Number of records to skip, see ``offset`` parameter in ``search()``. Defaults to 0.
- :param limit: Maximum number of records to return, see ``limit`` parameter in ``search()``. Defaults to no limit.
- :param order: Columns to sort result, see ``order`` parameter in ``search()``. Defaults to no sort.
+ """Perform a :meth:`search` followed by a :meth:`read`.
+
+ :param domain: Search domain, see ``args`` parameter in :meth:`search`.
+ Defaults to an empty domain that will match all records.
+ :param fields: List of fields to read, see ``fields`` parameter in :meth:`read`.
+ Defaults to all fields.
+ :param int offset: Number of records to skip, see ``offset`` parameter in :meth:`search`.
+ Defaults to 0.
+ :param int limit: Maximum number of records to return, see ``limit`` parameter in :meth:`search`.
+ Defaults to no limit.
+ :param order: Columns to sort result, see ``order`` parameter in :meth:`search`.
+ Defaults to no sort.
:return: List of dictionaries containing the asked fields.
- :rtype: List of dictionaries.
-
+ :rtype: list(dict).
"""
records = self.search(domain or [], offset=offset, limit=limit, order=order)
if not records:
@@ -4863,7 +4908,14 @@ Record ids: %(records)s
Returns a recordset for the ids provided as parameter in the current
environment.
- Can take no ids, a single id or an iterable of ids.
+ .. code-block:: python
+
+ self.browse([7, 18, 12])
+ res.partner(7, 18, 12)
+
+ :param ids: id(s)
+ :type ids: int or list(int) or None
+ :return: recordset
"""
if not ids:
ids = ()
@@ -4892,8 +4944,9 @@ Record ids: %(records)s
#
def ensure_one(self):
- """ Verifies that the current recorset holds a single record. Raises
- an exception otherwise.
+ """Verify that the current recorset holds a single record.
+
+ :raise odoo.exceptions.ValueError: ``len(self) != 1``
"""
try:
# unpack to ensure there is only one value is faster than len when true and
@@ -4904,16 +4957,16 @@ Record ids: %(records)s
raise ValueError("Expected singleton: %s" % self)
def with_env(self, env):
- """ Returns a new version of this recordset attached to the provided
- environment
+ """Return a new version of this recordset attached to the provided environment.
+
+ :param env:
+ :type env: :class:`~odoo.api.Environment`
.. warning::
The new environment will not benefit from the current
environment's data cache, so later data access may incur extra
delays while re-fetching from the database.
The returned recordset has the same prefetch object as ``self``.
-
- :type env: :class:`~odoo.api.Environment`
"""
return self._browse(env, self._ids, self._prefetch_ids)
@@ -4924,7 +4977,7 @@ Record ids: %(records)s
disabled, depending on `flag`. The superuser mode does not change the
current user, and simply bypasses access rights checks.
- .. note::
+ .. warning::
Using ``sudo`` could cause data access to cross the
boundaries of record rules, possibly mixing records that
@@ -5070,12 +5123,32 @@ Record ids: %(records)s
return vals if isinstance(vals, BaseModel) else []
def mapped(self, func):
- """ Apply ``func`` on all records in ``self``, and return the result as a
- list or a recordset (if ``func`` return recordsets). In the latter
- case, the order of the returned recordset is arbitrary.
+ """Apply ``func`` on all records in ``self``, and return the result as a
+ list or a recordset (if ``func`` return recordsets). In the latter
+ case, the order of the returned recordset is arbitrary.
- :param func: a function or a dot-separated sequence of field names
- (string); any falsy value simply returns the recordset ``self``
+ :param func: a function or a dot-separated sequence of field names
+ :type func: callable or str
+ :return: self if func is falsy, result of func applied to all ``self`` records.
+ :rtype: list or recordset
+
+ .. code-block:: python3
+
+ # returns a list of summing two fields for each record in the set
+ records.mapped(lambda r: r.field1 + r.field2)
+
+ The provided function can be a string to get field values:
+
+ .. code-block:: python3
+
+ # returns a list of names
+ records.mapped('name')
+
+ # returns a recordset of partners
+ record.mapped('partner_id')
+
+ # returns the union of all partner banks, with duplicates removed
+ record.mapped('partner_id.bank_ids')
"""
if not func:
return self # support for an empty path of fields
@@ -5102,10 +5175,19 @@ Record ids: %(records)s
return recs
def filtered(self, func):
- """ Select the records in ``self`` such that ``func(rec)`` is true, and
- return them as a recordset.
+ """Return the records in ``self`` satisfying ``func``.
+
+ :param func: a function or a dot-separated sequence of field names
+ :type func: callable or str
+ :return: recordset of records satisfying func, may be empty.
+
+ .. code-block:: python3
- :param func: a function or a dot-separated sequence of field names
+ # only keep records whose company is the current user's
+ records.filtered(lambda r: r.company_id == user.company_id)
+
+ # only keep records whose partner is a company
+ records.filtered("partner_id.is_company")
"""
if isinstance(func, str):
name = func
@@ -5209,13 +5291,18 @@ Record ids: %(records)s
def sorted(self, key=None, reverse=False):
- """ Return the recordset ``self`` ordered by ``key``.
+ """Return the recordset ``self`` ordered by ``key``.
+
+ :param key: either a function of one argument that returns a
+ comparison key for each record, or a field name, or ``None``, in
+ which case records are ordered according the default model's order
+ :type key: callable or str or None
+ :param bool reverse: if ``True``, return the result in reverse order
- :param key: either a function of one argument that returns a
- comparison key for each record, or a field name, or ``None``, in
- which case records are ordered according the default model's order
+ .. code-block:: python3
- :param reverse: if ``True``, return the result in reverse order
+ # sort records by name
+ records.sorted(key=lambda r: r.name)
"""
if key is None:
recs = self.search([('id', 'in', self.ids)])
@@ -6033,11 +6120,11 @@ class Model(AbstractModel):
class TransientModel(Model):
""" Model super-class for transient records, meant to be temporarily
- persisted, and regularly vacuum-cleaned.
+ persistent, and regularly vacuum-cleaned.
A TransientModel has a simplified access rights management, all users can
- create new records, and may only access the records they created. The super-
- user has unrestricted access to all TransientModel records.
+ create new records, and may only access the records they created. The
+ superuser has unrestricted access to all TransientModel records.
"""
_auto = True # automatically create database backend
_register = False # not visible in ORM registry, meant to be python-inherited only