[IMP] base, doc: orm page refactoring.
closes odoo/odoo#39725 X-original-commit: 7ce7c58592c7c7202a37c73767c477be5a835752 Signed-off-by: Victor Feyens (vfe) <vfe@odoo.com>
This commit is contained in:
@@ -367,9 +367,9 @@ Relations between models
|
||||
------------------------
|
||||
|
||||
We have seen a pair of "basic" fields stored directly in the record. There are
|
||||
:ref:`a number of basic fields <reference/orm/fields/basic>`. The second
|
||||
:ref:`a number of basic fields <reference/fields/basic>`. The second
|
||||
broad categories of fields are :ref:`relational
|
||||
<reference/orm/fields/relational>` and used to link records to one another
|
||||
<reference/fields/relational>` and used to link records to one another
|
||||
(within a model or across models).
|
||||
|
||||
For demonstration, let's create a *courses* model. Each course should have a
|
||||
|
||||
@@ -92,7 +92,7 @@ Nothing
|
||||
on the field. Can be used to clear a field, or avoid using a default value
|
||||
for the field.
|
||||
``search``
|
||||
for :ref:`relational fields <reference/orm/fields/relational>`, should be
|
||||
for :ref:`relational fields <reference/fields/relational>`, should be
|
||||
a :ref:`domain <reference/orm/domains>` on the field's model.
|
||||
|
||||
Will evaluate the domain, search the field's model using it and set the
|
||||
|
||||
+715
-1009
File diff suppressed because it is too large
Load Diff
+77
-85
@@ -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.
|
||||
"""The Odoo API module defines Odoo Environments and method decorators.
|
||||
|
||||
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.
|
||||
.. 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)
|
||||
@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).
|
||||
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):
|
||||
|
||||
+53
-18
@@ -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)
|
||||
|
||||
|
||||
+151
-240
@@ -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 str 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)
|
||||
:param bool company_dependent: whether the field value is dependent of the current company;
|
||||
|
||||
.. _field-computed:
|
||||
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).
|
||||
|
||||
.. rubric:: Computed fields
|
||||
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.
|
||||
|
||||
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``.
|
||||
If the value is changed on the company side, it will impact all records on which
|
||||
the value hasn't been changed.
|
||||
|
||||
:param compute: name of a method that computes the field
|
||||
: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)
|
||||
|
||||
:param inverse: name of a method that inverses the field (optional)
|
||||
:param bool store: whether the field is stored in database
|
||||
(default:``True``, ``False`` for computed fields)
|
||||
|
||||
:param search: name of a method that implement search on the field (optional)
|
||||
.. rubric:: Computed Fields
|
||||
|
||||
:param store: whether the field is stored in database (boolean, by
|
||||
default ``False`` on computed fields)
|
||||
:param str compute: name of a method that computes the field
|
||||
|
||||
: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)
|
||||
.. seealso:: :ref:`Advanced Fields/Compute fields <reference/fields/compute>`
|
||||
|
||||
The methods given for ``compute``, ``inverse`` and ``search`` are model
|
||||
methods. Their signature is shown in the following example::
|
||||
: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)
|
||||
|
||||
upper = fields.Char(compute='_compute_upper',
|
||||
inverse='_inverse_upper',
|
||||
search='_search_upper')
|
||||
:param str inverse: name of a method that inverses the field (optional)
|
||||
|
||||
@api.depends('name')
|
||||
def _compute_upper(self):
|
||||
for rec in self:
|
||||
rec.upper = rec.name.upper() if rec.name else False
|
||||
:param str search: name of a method that implement search on the field (optional)
|
||||
|
||||
def _inverse_upper(self):
|
||||
for rec in self:
|
||||
rec.name = rec.upper.lower() if rec.upper else False
|
||||
|
||||
def _search_upper(self, operator, value):
|
||||
if operator == 'like':
|
||||
operator = 'ilike'
|
||||
return [('name', operator, value)]
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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``.
|
||||
|
||||
.. _field-related:
|
||||
|
||||
.. rubric:: Related 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 related: sequence of field names
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
.. _field-company-dependent:
|
||||
|
||||
.. 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.
|
||||
|
||||
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, ...).
|
||||
.. warning::
|
||||
|
||||
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)
|
||||
:param comodel_name: name of the target model (string)
|
||||
mandatory except in the case of related or extended fields
|
||||
|
||||
The attribute ``comodel_name`` is mandatory except in the case of related
|
||||
fields or field extensions.
|
||||
:param str relation: optional name of the table that stores the relation in
|
||||
the database
|
||||
|
||||
:param relation: optional name of the table that stores the relation in
|
||||
the database (string)
|
||||
:param str column1: optional name of the column referring to "these" records
|
||||
in the table ``relation``
|
||||
|
||||
:param column1: optional name of the column referring to "these" records
|
||||
in the table ``relation`` (string)
|
||||
:param str column2: optional name of the column referring to "those" records
|
||||
in the table ``relation``
|
||||
|
||||
:param column2: optional name of the column referring to "those" records
|
||||
in the table ``relation`` (string)
|
||||
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!
|
||||
|
||||
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!
|
||||
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
|
||||
|
||||
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
|
||||
- both fields use the same model, comodel, and relation parameters are
|
||||
explicit; or
|
||||
|
||||
- both fields use the same model, comodel, and relation parameters are
|
||||
explicit; or
|
||||
- at least one field belongs to a model with ``_auto = False``.
|
||||
|
||||
- at least one field belongs to a model with ``_auto = False``.
|
||||
: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 dict context: an optional context to use on the client side when
|
||||
handling that field
|
||||
|
||||
:param context: an optional context to use on the client side when
|
||||
handling that field (dictionary)
|
||||
|
||||
:param limit: optional limit to use upon read (integer)
|
||||
|
||||
: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 = {
|
||||
|
||||
+173
-86
@@ -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
|
||||
_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.
|
||||
|
||||
_inherit = None # Python-inherited models ('model' or ['model'])
|
||||
_inherits = {} # inherited models {'parent_model': 'm2o_field'}
|
||||
.. 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
|
||||
|
||||
_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)]
|
||||
_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
|
||||
|
||||
_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
|
||||
_inherit = None
|
||||
"""Python-inherited models:
|
||||
|
||||
_needaction = False # whether the model supports "need actions" (see mail)
|
||||
_translate = True # False disables translations export for this model
|
||||
: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()``.
|
||||
"""Perform a :meth:`search` followed by a :meth:`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.
|
||||
: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
|
||||
: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
|
||||
|
||||
# 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
|
||||
: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 reverse: if ``True``, return the result in reverse order
|
||||
.. code-block:: python3
|
||||
|
||||
# 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
|
||||
|
||||
Reference in New Issue
Block a user