summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/howtos/website.rst4
-rw-r--r--doc/reference/data.rst2
-rw-r--r--doc/reference/orm.rst1400
-rw-r--r--odoo/api.py166
-rw-r--r--odoo/exceptions.py71
-rw-r--r--odoo/fields.py391
-rw-r--r--odoo/models.py271
7 files changed, 1018 insertions, 1287 deletions
diff --git a/doc/howtos/website.rst b/doc/howtos/website.rst
index 48b0345e5a9..5d3ab9f0b84 100644
--- a/doc/howtos/website.rst
+++ b/doc/howtos/website.rst
@@ -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
diff --git a/doc/reference/data.rst b/doc/reference/data.rst
index 416121b8255..3ab39f57484 100644
--- a/doc/reference/data.rst
+++ b/doc/reference/data.rst
@@ -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
diff --git a/doc/reference/orm.rst b/doc/reference/orm.rst
index 66f5b27f99a..d6ada51b4d4 100644
--- a/doc/reference/orm.rst
+++ b/doc/reference/orm.rst
@@ -6,375 +6,218 @@
ORM API
=======
-Recordsets
-==========
-
-.. versionadded:: 8.0
-
- This page documents the New API added in Odoo 8.0 which should be the
- primary development API going forward. It also provides information about
- porting from or bridging with the "old API" of versions 7 and earlier, but
- does not explicitly document that API. See the old documentation for that.
+.. automodule:: odoo.models
-Interaction with models and records is performed through recordsets, a sorted
-set of records of the same model.
+.. _reference/orm/models:
+.. _reference/orm/model:
-.. warning:: contrary to what the name implies, it is currently possible for
- recordsets to contain duplicates. This may change in the future.
+Models
+======
-Methods defined on a model are executed on a recordset, and their ``self`` is
-a recordset::
+Model fields are defined as attributes on the model itself::
+ from odoo import models, fields
class AModel(models.Model):
- _name = 'a.model'
- def a_method(self):
- # self can be anywhere between 0 records and all records in the
- # database
- self.do_operation()
-
-Iterating on a recordset will yield new sets of *a single record*
-("singletons"), much like iterating on a Python string yields strings of a
-single characters::
-
- def do_operation(self):
- print self # => a.model(1, 2, 3, 4, 5)
- for record in self:
- print record # => a.model(1), then a.model(2), then a.model(3), ...
-
-Field access
-------------
-
-Recordsets provide an "Active Record" interface: model fields can be read and
-written directly from the record as attributes, but only on singletons
-(single-record recordsets).
-Field values can also be accessed like dict items, which is more elegant and
-safer than ``getattr()`` for dynamic field names.
-Setting a field's value triggers an update to the database::
-
- >>> record.name
- Example Name
- >>> record.company_id.name
- Company Name
- >>> record.name = "Bob"
- >>> field = "name"
- >>> record[field]
- Bob
-
-Trying to read or write a field on multiple records will raise an error.
-
-Accessing a relational field (:class:`~odoo.fields.Many2one`,
-:class:`~odoo.fields.One2many`, :class:`~odoo.fields.Many2many`)
-*always* returns a recordset, empty if the field is not set.
-
-.. danger::
-
- each assignment to a field triggers a database update, when setting
- multiple fields at the same time or setting fields on multiple records
- (to the same value), use :meth:`~odoo.models.Model.write`::
-
- # 3 * len(records) database updates
- for record in records:
- record.a = 1
- record.b = 2
- record.c = 3
-
- # len(records) database updates
- for record in records:
- record.write({'a': 1, 'b': 2, 'c': 3})
-
- # 1 database update
- records.write({'a': 1, 'b': 2, 'c': 3})
+ _name = 'a.model.name'
-Record cache and prefetching
-----------------------------
+ field1 = fields.Char()
-Odoo maintains a cache for the fields of the records, so that not every field
-access issues a database request, which would be terrible for performance. The
-following example queries the database only for the first statement::
+.. warning:: this means you cannot define a field and a method with the same
+ name, the last one will silently overwrite the former ones.
- record.name # first access reads value from database
- record.name # second access gets value from cache
+By default, the field's label (user-visible name) is a capitalized version of
+the field name, this can be overridden with the ``string`` parameter. ::
-To avoid reading one field on one record at a time, Odoo *prefetches* records
-and fields following some heuristics to get good performance. Once a field must
-be read on a given record, the ORM actually reads that field on a larger
-recordset, and stores the returned values in cache for later use. The prefetched
-recordset is usually the recordset from which the record comes by iteration.
-Moreover, all simple stored fields (boolean, integer, float, char, text, date,
-datetime, selection, many2one) are fetched altogether; they correspond to the
-columns of the model's table, and are fetched efficiently in the same query.
+ field2 = fields.Integer(string="Field Label")
-Consider the following example, where ``partners`` is a recordset of 1000
-records. Without prefetching, the loop would make 2000 queries to the database.
-With prefetching, only one query is made::
+For the list of field types and parameters, see :ref:`the fields reference
+<reference/fields>`.
- for partner in partners:
- print partner.name # first pass prefetches 'name' and 'lang'
- # (and other fields) on all 'partners'
- print partner.lang
+Default values are defined as parameters on fields, either as a value::
-The prefetching also works on *secondary records*: when relational fields are
-read, their values (which are records) are subscribed for future prefetching.
-Accessing one of those secondary records prefetches all secondary records from
-the same model. This makes the following example generate only two queries, one
-for partners and one for countries::
+ name = fields.Char(default="a value")
- countries = set()
- for partner in partners:
- country = partner.country_id # first pass prefetches all partners
- countries.add(country.name) # first pass prefetches all countries
+or as a function called to compute the default value, which should return that
+value::
-Set operations
---------------
+ def _default_name(self):
+ return self.get_value()
-Recordsets are immutable, but sets of the same model can be combined using
-various set operations, returning new recordsets. Set operations do *not*
-preserve order.
+ name = fields.Char(default=lambda self: self._default_name())
-.. addition preserves order but can introduce duplicates
+.. rubric:: API
-* ``record in set`` returns whether ``record`` (which must be a 1-element
- recordset) is present in ``set``. ``record not in set`` is the inverse
- operation
-* ``set1 <= set2`` and ``set1 < set2`` return whether ``set1`` is a subset
- of ``set2`` (resp. strict)
-* ``set1 >= set2`` and ``set1 > set2`` return whether ``set1`` is a superset
- of ``set2`` (resp. strict)
-* ``set1 | set2`` returns the union of the two recordsets, a new recordset
- containing all records present in either source
-* ``set1 & set2`` returns the intersection of two recordsets, a new recordset
- containing only records present in both sources
-* ``set1 - set2`` returns a new recordset containing only records of ``set1``
- which are *not* in ``set2``
+.. autoclass:: odoo.models.BaseModel()
-Other recordset operations
---------------------------
+ .. autoattribute:: _auto
+ .. autoattribute:: _table
+ .. autoattribute:: _sequence
+ .. autoattribute:: _sql_constraints
-Recordsets are iterable so the usual Python tools are available for
-transformation (:func:`python:map`, :func:`python:sorted`,
-:func:`~python:itertools.ifilter`, ...) however these return either a
-:class:`python:list` or an :term:`python:iterator`, removing the ability to
-call methods on their result, or to use set operations.
+ .. autoattribute:: _register
-Recordsets therefore provide these operations returning recordsets themselves
-(when possible):
+ .. autoattribute:: _name
+ .. autoattribute:: _description
-:meth:`~odoo.models.Model.filtered`
- returns a recordset containing only records satisfying the provided
- predicate function. The predicate can also be a string to filter by a
- field being true or false::
+ .. autoattribute:: _inherit
+ .. autoattribute:: _inherits
- # only keep records whose company is the current user's
- records.filtered(lambda r: r.company_id == user.company_id)
+ .. autoattribute:: _rec_name
+ .. autoattribute:: _order
- # only keep records whose partner is a company
- records.filtered("partner_id.is_company")
+ .. autoattribute:: _check_company_auto
-:meth:`~odoo.models.Model.sorted`
- returns a recordset sorted by the provided key function. If no key
- is provided, use the model's default sort order::
+ .. autoattribute:: _parent_name
+ .. autoattribute:: _parent_store
- # sort records by name
- records.sorted(key=lambda r: r.name)
+ .. autoattribute:: _abstract
-:meth:`~odoo.models.Model.mapped`
- applies the provided function to each record in the recordset, returns
- a recordset if the results are recordsets::
+ .. seealso:: :class:`odoo.models.AbstractModel`
- # returns a list of summing two fields for each record in the set
- records.mapped(lambda r: r.field1 + r.field2)
+ .. autoattribute:: _transient
- The provided function can be a string to get field values::
+ .. seealso:: :class:`odoo.models.TransientModel`
- # returns a list of names
- records.mapped('name')
+ .. autoattribute:: _date_name
+ .. autoattribute:: _fold_name
- # returns a recordset of partners
- record.mapped('partner_id')
+AbstractModel
+-------------
- # returns the union of all partner banks, with duplicates removed
- record.mapped('partner_id.bank_ids')
+.. autoclass:: odoo.models.AbstractModel()
-Environment
-===========
+Model
+-----
-The :class:`~odoo.api.Environment` stores various contextual data used by
-the ORM: the database cursor (for database queries), the current user
-(for access rights checking) and the current context (storing arbitrary
-metadata). The environment also stores caches.
+.. autoclass:: odoo.models.Model()
-All recordsets have an environment, which is immutable, can be accessed
-using :attr:`~odoo.models.Model.env` and gives access to the current user
-(:attr:`~odoo.api.Environment.user`), the cursor
-(:attr:`~odoo.api.Environment.cr`) or the context
-(:attr:`~odoo.api.Environment.context`)::
+TransientModel
+--------------
- >>> records.env
- <Environment object ...>
- >>> records.env.user
- res.user(3)
- >>> records.env.cr
- <Cursor object ...)
+.. autoclass:: odoo.models.TransientModel()
-When creating a recordset from an other recordset, the environment is
-inherited. The environment can be used to get an empty recordset in an
-other model, and query that model::
+.. _reference/fields:
+.. _reference/orm/fields:
- >>> self.env['res.partner']
- res.partner
- >>> self.env['res.partner'].search([['is_company', '=', True], ['customer', '=', True]])
- res.partner(7, 18, 12, 14, 17, 19, 8, 31, 26, 16, 13, 20, 30, 22, 29, 15, 23, 28, 74)
+Fields
+======
-Altering the environment
-------------------------
+.. currentmodule:: odoo.fields
-The environment can be customized from a recordset. This returns a new
-version of the recordset using the altered environment.
+.. autoclass:: Field()
-:meth:`~odoo.models.Model.sudo`
- creates a new environment with the provided user set, uses the
- administrator if none is provided (to bypass access rights/rules in safe
- contexts), returns a copy of the recordset it is called on using the
- new environment::
+.. .. autoattribute:: Field._slots
+ :annotation:
- # create partner object as administrator
- env['res.partner'].sudo().create({'name': "A Partner"})
+.. _reference/fields/basic:
- # list partners visible by the "public" user
- public = env.ref('base.public_user')
- env['res.partner'].sudo(public).search([])
+Basic Fields
+------------
-:meth:`~odoo.models.Model.with_context`
- #. can take a single positional parameter, which replaces the current
- environment's context
- #. can take any number of parameters by keyword, which are added to either
- the current environment's context or the context set during step 1
+.. autoclass:: Boolean()
- ::
+.. autoclass:: Char()
- # look for partner, or create one with specified timezone if none is
- # found
- env['res.partner'].with_context(tz=a_tz).find_or_create(email_address)
+.. autoclass:: Float()
-:meth:`~odoo.models.Model.with_env`
- replaces the existing environment entirely
+.. autoclass:: Integer()
-Common ORM methods
-==================
+.. _reference/fields/advanced:
-.. maybe these clarifications/examples should be in the APIDoc?
+Advanced Fields
+---------------
-:meth:`~odoo.models.Model.search`
- Takes a :ref:`search domain <reference/orm/domains>`, returns a recordset
- of matching records. Can return a subset of matching records (``offset``
- and ``limit`` parameters) and be ordered (``order`` parameter)::
+.. autoclass:: Html()
- >>> # searches the current model
- >>> self.search([('is_company', '=', True), ('customer', '=', True)])
- res.partner(7, 18, 12, 14, 17, 19, 8, 31, 26, 16, 13, 20, 30, 22, 29, 15, 23, 28, 74)
- >>> self.search([('is_company', '=', True)], limit=1).name
- 'Agrolait'
+.. autoclass:: Monetary()
- .. tip:: to just check if any record matches a domain, or count the number
- of records which do, use
- :meth:`~odoo.models.Model.search_count`
+.. autoclass:: Selection()
-:meth:`~odoo.models.Model.create`
- Takes a dictionary of field values, or a list of such dictionaries, and
- returns a recordset containing the records created::
+.. autoclass:: Text()
- >>> self.create({'name': "Joe"})
- res.partner(78)
- >>> self.create([{'name': "Jack"}, {'name': "William"}, {'name': "Averell"}])
- res.partner(79, 80, 81)
+.. _reference/fields/date:
- See :ref:`how to define method \`create\` with one API or the other
- <reference/orm/oldapi>`.
+Date(time) Fields
+'''''''''''''''''
-:meth:`~odoo.models.Model.write`
- Takes a number of field values, writes them to all the records in its
- recordset. Does not return anything::
+Dates and Datetimes are very important fields in any kind of business
+application, they are heavily used in many popular Odoo applications such as
+logistics or accounting and their misuse can create invisible yet painful
+bugs, this excerpt aims to provide Odoo developers with the knowledge required
+to avoid misusing these fields.
- self.write({'name': "Newer Name"})
+When assigning a value to a Date/Datetime field, the following options are valid:
-:meth:`~odoo.models.Model.browse`
- Takes a database id or a list of ids and returns a recordset, useful when
- record ids are obtained from outside Odoo (e.g. round-trip through
- external system) or :ref:`when calling methods in the old API
- <reference/orm/oldapi>`::
+* A `date` or `datetime` object.
+* A string in the proper server format *(YYYY-MM-DD)* for Date fields,
+ *(YYYY-MM-DD HH:MM:SS)* for Datetime fields.
+* `False` or `None`.
- >>> self.browse([7, 18, 12])
- res.partner(7, 18, 12)
+The Date and Datetime fields class have helper methods to attempt conversion
+into a compatible type: :func:`~odoo.fields.Date.to_date` will convert to a `datetime.date`
+object while :func:`~odoo.fields.Datetime.to_datetime` will convert to a `datetime.datetime`.
-:meth:`~odoo.models.Model.exists`
- Returns a new recordset containing only the records which exist in the
- database. Can be used to check whether a record (e.g. obtained externally)
- still exists::
+.. admonition:: Example
- if not record.exists():
- raise Exception("The record has been deleted")
+ To parse date/datetimes coming from external sources::
- or after calling a method which could have removed some records::
+ fields.Date.to_date(self._context.get('date_from'))
- records.may_remove_some()
- # only keep records which were not deleted
- records = records.exists()
+Date / Datetime comparison best practices:
-:meth:`~odoo.api.Environment.ref`
- Environment method returning the record matching a provided
- :term:`external id`::
+* Date fields can **only** be compared to date objects.
+* Datetime fields can **only** be compared to datetime objects.
- >>> env.ref('base.group_public')
- res.groups(2)
+.. warning:: Strings representing dates and datetimes can be compared
+ between each other, however the result may not be the expected
+ result, as a datetime string will always be greater than a
+ date string, therefore this practice is **heavily**
+ discouraged.
-:meth:`~odoo.models.Model.ensure_one`
- checks that the recordset is a singleton (only contains a single record),
- raises an error otherwise::
+Common operations with dates and datetimes such as addition, substraction or
+fetching the start/end of a period are exposed through both
+:class:`~odoo.fields.Date` and :class:`~odoo.fields.Datetime`.
+These helpers are also available by importing `odoo.tools.date_utils`.
- records.ensure_one()
- # is equivalent to but clearer than:
- assert len(records) == 1, "Expected singleton"
+.. note:: Timezones
-Creating Models
-===============
+ Datetime fields are stored as `timestamp without timezone` columns in the database and are stored
+ in the UTC timezone. This is by design, as it makes the Odoo database independent from the timezone
+ of the hosting server system. Timezone conversion is managed entirely by the client side.
-Model fields are defined as attributes on the model itself::
+.. autoclass:: Date()
+ :members: today, context_today, to_date, to_string, start_of, end_of, add, subtract
- from odoo import models, fields
- class AModel(models.Model):
- _name = 'a.model.name'
+.. autoclass:: Datetime()
+ :members: now, today, context_timestamp, to_datetime, to_string, start_of, end_of, add, subtract
- field1 = fields.Char()
+.. _reference/fields/relational:
-.. warning:: this means you can not define a field and a method with the same
- name, they will conflict
+Relational Fields
+'''''''''''''''''
-By default, the field's label (user-visible name) is a capitalized version of
-the field name, this can be overridden with the ``string`` parameter::
+.. autoclass:: Many2one()
- field2 = fields.Integer(string="an other field")
+.. autoclass:: One2many()
-For the various field types and parameters, see :ref:`the fields reference
-<reference/orm/fields>`.
+.. autoclass:: Many2many()
-Default values are defined as parameters on fields, either a value::
+Pseudo-relational fields
+''''''''''''''''''''''''
- a_field = fields.Char(default="a value")
+.. autoclass:: Reference()
-or a function called to compute the default value, which should return that
-value::
+.. autoclass:: Many2oneReference()
- def compute_default_value(self):
- return self.get_value()
- a_field = fields.Char(default=compute_default_value)
+.. _reference/fields/compute:
-Computed fields
----------------
+Computed Fields
+'''''''''''''''
Fields can be computed (instead of read straight from the database) using the
``compute`` parameter. **It must assign the computed value to the field**. If
it uses the values of other *fields*, it should specify those fields using
-:func:`~odoo.api.depends`::
+:func:`~odoo.api.depends`. ::
from odoo import api
total = fields.Float(compute='_compute_total')
@@ -393,10 +236,10 @@ it uses the values of other *fields*, it should specify those fields using
* computed fields are not stored by default, they are computed and
returned when requested. Setting ``store=True`` will store them in the
- database and automatically enable searching
+ database and automatically enable searching.
* searching on a computed field can also be enabled by setting the ``search``
parameter. The value is a method name returning a
- :ref:`reference/orm/domains`::
+ :ref:`reference/orm/domains`. ::
upper_name = field.Char(compute='_compute_upper', search='_search_upper')
@@ -405,7 +248,13 @@ it uses the values of other *fields*, it should specify those fields using
operator = 'ilike'
return [('name', operator, value)]
-* to allow *setting* values on a computed field, use the ``inverse``
+ 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``.
+
+.. TODO and/or by setting the store to True for search domains ?
+
+* Computed fields are readonly by default. To allow *setting* values on a computed field, use the ``inverse``
parameter. It is the name of a function reversing the computation and
setting the relevant fields::
@@ -427,7 +276,7 @@ it uses the values of other *fields*, it should specify those fields using
discount_value = fields.Float(compute='_apply_discount')
total = fields.Float(compute='_apply_discount')
- @depends('value', 'discount')
+ @api.depends('value', 'discount')
def _apply_discount(self):
for record in self:
# compute actual discount from discount percentage
@@ -435,6 +284,8 @@ it uses the values of other *fields*, it should specify those fields using
record.discount_value = discount
record.total = record.value - discount
+.. _reference/fields/related:
+
Related fields
''''''''''''''
@@ -445,538 +296,563 @@ stored::
nickname = fields.Char(related='user_id.partner_id.name', store=True)
-onchange: updating UI on the fly
---------------------------------
-
-When a user changes a field's value in a form (but hasn't saved the form yet),
-it can be useful to automatically update other fields based on that value
-e.g. updating a final total when the tax is changed or a new invoice line is
-added.
-
-* computed fields are automatically checked and recomputed, they do not need
- an ``onchange``
-* for non-computed fields, the :func:`~odoo.api.onchange` decorator is used
- to provide new field values::
-
- @api.onchange('field1', 'field2') # if these fields are changed, call method
- def check_change(self):
- if self.field1 < self.field2:
- self.field3 = True
+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 ``related`` attribute.
- the changes performed during the method are then sent to the client program
- and become visible to the user
+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.
-* Both computed fields and new-API onchanges are automatically called by the
- client without having to add them in views
-* It is possible to suppress the trigger from a specific field by adding
- ``on_change="0"`` in a view::
-
- <field name="name" on_change="0"/>
-
- will not trigger any interface update when the field is edited by the user,
- even if there are function fields or explicit onchange depending on that
- 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.
-.. note::
+.. note:: The related fields are computed in sudo mode.
- ``onchange`` methods work on virtual records assignment on these records
- is not written to the database, just used to know which value to send back
- to the client
-
-.. warning::
+.. _reference/fields/automatic:
- 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>`_.
+Automatic fields
+----------------
-Low-level SQL
--------------
+.. Documented
-The :attr:`~odoo.api.Environment.cr` attribute on environments is the
-cursor for the current database transaction and allows executing SQL directly,
-either for queries which are difficult to express using the ORM (e.g. complex
-joins) or for performance reasons::
+.. attribute:: id
- self.env.cr.execute("some_sql", param1, param2, param3)
+ Identifier :class:`field <odoo.fields.Field>`
-Because models use the same cursor and the :class:`~odoo.api.Environment`
-holds various caches, these caches must be invalidated when *altering* the
-database in raw SQL, or further uses of models may become incoherent. It is
-necessary to clear caches when using ``CREATE``, ``UPDATE`` or ``DELETE`` in
-SQL, but not ``SELECT`` (which simply reads the database).
+ If length of current recordset is 1, return id of unique record in it.
-Clearing caches can be performed using the
-:meth:`~odoo.models.BaseModel.invalidate_cache` method of the
-:class:`~odoo.models.BaseModel` object.
+ Raise an Error otherwise.
+.. todo:: _log_access info
-.. _reference/orm/oldapi:
+.. attribute:: create_date
-Compatibility between new API and old API
-=========================================
+ :class:`~odoo.fields.Datetime`
-Odoo is currently transitioning from an older (less regular) API, it can be
-necessary to manually bridge from one to the other manually:
+.. attribute:: create_uid
-* RPC layers (both XML-RPC and JSON-RPC) are expressed in terms of the old
- API, methods expressed purely in the new API are not available over RPC
-* overridable methods may be called from older pieces of code still written
- in the old API style
+ :class:`~odoo.fields.Many2one`
-The big differences between the old and new APIs are:
+.. attribute:: write_date
-* values of the :class:`~odoo.api.Environment` (cursor, user id and
- context) are passed explicitly to methods instead
-* record data (:attr:`~odoo.models.Model.ids`) are passed explicitly to
- methods, and possibly not passed at all
-* methods tend to work on lists of ids instead of recordsets
+ :class:`~odoo.fields.Datetime`
-By default, methods are assumed to use the new API style and are not callable
-from the old API style.
+.. attribute:: write_uid
-.. tip:: calls from the new API to the old API are bridged
- :class: aphorism
+ :class:`~odoo.fields.Many2one`
- when using the new API style, calls to methods defined using the old API
- are automatically converted on-the-fly, there should be no need to do
- anything special::
+.. _reference/orm/fields/reserved:
- >>> # method in the old API style
- >>> def old_method(self, cr, uid, ids, context=None):
- ... print ids
+Reserved Field names
+--------------------
- >>> # method in the new API style
- >>> def new_method(self):
- ... # system automatically infers how to call the old-style
- ... # method from the new-style method
- ... self.old_method()
+A few field names are reserved for pre-defined behaviors beyond that of
+automated fields. They should be defined on a model when the related
+behavior is desired:
- >>> env[model].browse([1, 2, 3, 4]).new_method()
- [1, 2, 3, 4]
+.. attribute:: name
-Two decorators can expose a new-style method to the old API:
+ default value for :attr:`~odoo.models.BaseModel._rec_name`, used to
+ display records in context where a representative "naming" is
+ necessary.
-:func:`~odoo.api.model`
- the method is exposed as not using ids, its recordset will generally be
- empty. Its "old API" signature is ``cr, uid, *arguments, context``::
+ :class:`~odoo.fields.Char`
- @api.model
- def some_method(self, a_value):
- pass
- # can be called as
- old_style_model.some_method(cr, uid, a_value, context=context)
+.. attribute:: active
-Note that a method `create` decorated with :func:`~odoo.api.model` will always
-be called with a single dictionary. A method `create` decorated with the variant
-:func:`~odoo.api.model_create_multi` will always be called with a list of dicts.
-The decorators take care of converting the argument to one form or the other::
+ toggles the global visibility of the record, if ``active`` is set to
+ ``False`` the record is invisible in most searches and listing.
- @api.model
- def create(self, vals):
- ...
+ :class:`~odoo.fields.Boolean`
- @api.model_create_multi
- def create(self, vals_list):
- ...
+.. .. attribute:: sequence
+..
+.. Alterable ordering criteria, allows drag-and-drop reordering of models
+.. in list views.
+..
+.. :class:`~odoo.fields.Integer`
-Because new-style APIs tend to return recordsets and old-style APIs tend to
-return lists of ids, there is also a decorator managing this:
+.. attribute:: state
-:func:`~odoo.api.returns`
- the function is assumed to return a recordset, the first parameter should
- be the name of the recordset's model or ``self`` (for the current model).
+ lifecycle stages of the object, used by the ``states`` attribute on
+ :class:`fields <odoo.fields.Field>`.
- No effect if the method is called in new API style, but transforms the
- recordset into a list of ids when called from the old API style::
+ :class:`~odoo.fields.Selection`
- >>> @api.returns('self')
- ... def some_method(self):
- ... return self
- >>> new_style_model = env['a.model'].browse(1, 2, 3)
- >>> new_style_model.some_method()
- a.model(1, 2, 3)
- >>> old_style_model = pool['a.model']
- >>> old_style_model.some_method(cr, uid, [1, 2, 3], context=context)
- [1, 2, 3]
+.. attribute:: parent_id
-.. _reference/orm/model:
+ default_value of :attr:`~._parent_name`, used to organize
+ records in a tree structure and enables the ``child_of``
+ and ``parent_of`` operators in domains.
-Model Reference
-===============
+ :class:`~odoo.fields.Many2one`
-.. - can't get autoattribute to import docstrings, so use regular attribute
- - no autoclassmethod
+.. attribute:: parent_path
-.. currentmodule:: odoo.models
+ When :attr:`~._parent_store` is set to True, used to store a value reflecting
+ the tree structure of :attr:`~._parent_name`, and to optimize the operators
+ ``child_of`` and ``parent_of`` in search domains.
+ It must be declared with ``index=True`` for proper operation.
-.. autoclass:: Model
+ :class:`~odoo.fields.Char`
- .. rubric:: Structural attributes
- .. attribute:: _name
+Recordsets
+==========
- business object name, in dot-notation (in module namespace)
+Interactions with models and records are performed through recordsets, an ordered
+collection of records of the same model.
- .. attribute:: _rec_name
+.. warning:: Contrary to what the name implies, it is currently possible for
+ recordsets to contain duplicates. This may change in the future.
- Alternative field to use as name, used by osv’s name_get()
- (default: ``'name'``)
+Methods defined on a model are executed on a recordset, and their ``self`` is
+a recordset::
- .. attribute:: _inherit
+ class AModel(models.Model):
+ _name = 'a.model'
+ def a_method(self):
+ # self can be anything between 0 records and all records in the
+ # database
+ self.do_operation()
- * If :attr:`._name` is set, names of parent models to inherit from.
- Can be a ``str`` if inheriting from a single parent
- * If :attr:`._name` is unset, name of a single model to extend
- in-place
+Iterating on a recordset will yield new sets of *a single record*
+("singletons"), much like iterating on a Python string yields strings of a
+single characters::
- See :ref:`reference/orm/inheritance`.
+ def do_operation(self):
+ print(self) # => a.model(1, 2, 3, 4, 5)
+ for record in self:
+ print(record) # => a.model(1), then a.model(2), then a.model(3), ...
- .. attribute:: _order
+Field access
+------------
- Ordering field when searching without an ordering specified (default:
- ``'id'``)
+Recordsets provide an "Active Record" interface: model fields can be read and
+written directly from the record as attributes.
- :type: str
+.. note::
- .. attribute:: _auto
+ When accessing non-relational fields on a recordset of potentially multiple
+ records, use :meth:`~odoo.models.BaseModel.mapped`::
- Whether a database table should be created (default: ``True``)
+ total_qty = sum(self.mapped('qty'))
- If set to ``False``, override :meth:`.init` to create the database
- table
-
- .. tip:: To create a model without any table, inherit
- from ``odoo.models.AbstractModel``
+Field values can also be accessed like dict items, which is more elegant and
+safer than ``getattr()`` for dynamic field names.
+Setting a field's value triggers an update to the database::
- .. attribute:: _table
+ >>> record.name
+ Example Name
+ >>> record.company_id.name
+ Company Name
+ >>> record.name = "Bob"
+ >>> field = "name"
+ >>> record[field]
+ Bob
- Name of the table backing the model created when
- :attr:`~odoo.models.Model._auto`, automatically generated by
- default.
+.. warning::
- .. attribute:: _inherits
+ Trying to read a field on multiple records will raise an error for non relational
+ fields.
- dictionary mapping the _name of the parent business objects to the
- names of the corresponding foreign key fields to use::
+Accessing a relational field (:class:`~odoo.fields.Many2one`,
+:class:`~odoo.fields.One2many`, :class:`~odoo.fields.Many2many`)
+*always* returns a recordset, empty if the field is not set.
- _inherits = {
- 'a.model': 'a_field_id',
- 'b.model': 'b_field_id'
- }
+Record cache and prefetching
+----------------------------
- implements composition-based inheritance: the new model exposes all
- the fields of the :attr:`~odoo.models.Model._inherits`-ed model but
- stores none of them: the values themselves remain stored on the linked
- record.
+Odoo maintains a cache for the fields of the records, so that not every field
+access issues a database request, which would be terrible for performance. The
+following example queries the database only for the first statement::
- .. warning::
+ record.name # first access reads value from database
+ record.name # second access gets value from cache
- if the same field is defined on multiple
- :attr:`~odoo.models.Model._inherits`-ed
+To avoid reading one field on one record at a time, Odoo *prefetches* records
+and fields following some heuristics to get good performance. Once a field must
+be read on a given record, the ORM actually reads that field on a larger
+recordset, and stores the returned values in cache for later use. The prefetched
+recordset is usually the recordset from which the record comes by iteration.
+Moreover, all simple stored fields (boolean, integer, float, char, text, date,
+datetime, selection, many2one) are fetched altogether; they correspond to the
+columns of the model's table, and are fetched efficiently in the same query.
- .. attribute:: _constraints
+Consider the following example, where ``partners`` is a recordset of 1000
+records. Without prefetching, the loop would make 2000 queries to the database.
+With prefetching, only one query is made::
- list of ``(constraint_function, message, fields)`` defining Python
- constraints. The fields list is indicative
+ for partner in partners:
+ print partner.name # first pass prefetches 'name' and 'lang'
+ # (and other fields) on all 'partners'
+ print partner.lang
- .. deprecated:: 8.0
+The prefetching also works on *secondary records*: when relational fields are
+read, their values (which are records) are subscribed for future prefetching.
+Accessing one of those secondary records prefetches all secondary records from
+the same model. This makes the following example generate only two queries, one
+for partners and one for countries::
- use :func:`~odoo.api.constrains`
+ countries = set()
+ for partner in partners:
+ country = partner.country_id # first pass prefetches all partners
+ countries.add(country.name) # first pass prefetches all countries
- .. attribute:: _sql_constraints
- list of ``(name, sql_definition, message)`` triples defining SQL
- constraints to execute when generating the backing table
+.. _reference/api/decorators:
- .. attribute:: _parent_store
+Method decorators
+=================
- 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.
- (default: ``False``)
+.. automodule:: odoo.api
+ :members: depends, depends_context, constrains, onchange, returns, model_create_multi
- :type: bool
+.. .. currentmodule:: odoo.api
+.. .. autodata:: model
+.. .. autodata:: depends
+.. .. autodata:: constrains
+.. .. autodata:: onchange
+.. .. autodata:: returns
- .. attribute:: _check_company_auto
+.. todo:: With sphinx 2.0 : autodecorator
- On write and create, call ``_check_company`` to ensure companies
- consistency on the relational fields having ``check_company=True``
- as attribute.
- (default: ``False``)
+.. todo:: Add in Views reference
+ * It is possible to suppress the trigger from a specific field by adding
+ ``on_change="0"`` in a view::
- .. attribute:: _parent_name
+ <field name="name" on_change="0"/>
- Alternative field to use as parent, used by indexed storage of the tree structure of records
- (default: ``'parent_id'``)
+ will not trigger any interface update when the field is edited by the user,
+ even if there are function fields or explicit onchange depending on that
+ field.
- :type: str
+.. _reference/orm/environment:
- .. attribute:: _date_name
+Environment
+===========
- Alternative field to use for default calendar view (default: ``'date'``)
+The :class:`~odoo.api.Environment` stores various contextual data used by
+the ORM: the database cursor (for database queries), the current user
+(for access rights checking) and the current context (storing arbitrary
+metadata). The environment also stores caches.
- :type: str
+All recordsets have an environment, which is immutable, can be accessed
+using :attr:`~odoo.models.Model.env` and gives access to:
- .. attribute:: _fold_name
+* the current user (:attr:`~odoo.api.Environment.user`)
+* the cursor (:attr:`~odoo.api.Environment.cr`)
+* the superuser flag (:attr:`~odoo.api.Environment.su`)
+* or the context (:attr:`~odoo.api.Environment.context`)
- Alternative field to determine folded groups in kanban views
- (default: ``'fold'``)
+.. code-block:: bash
- :type: str
+ >>> records.env
+ <Environment object ...>
+ >>> records.env.user
+ res.user(3)
+ >>> records.env.cr
+ <Cursor object ...)
- .. attribute:: _translate
+When creating a recordset from an other recordset, the environment is
+inherited. The environment can be used to get an empty recordset in an
+other model, and query that model::
- False disables translations export for this model
- (default: ``True``)
+ >>> self.env['res.partner']
+ res.partner()
+ >>> self.env['res.partner'].search([['is_company', '=', True], ['customer', '=', True]])
+ res.partner(7, 18, 12, 14, 17, 19, 8, 31, 26, 16, 13, 20, 30, 22, 29, 15, 23, 28, 74)
- :type: bool
+.. currentmodule:: odoo.api
- .. rubric:: CRUD
+.. automethod:: Environment.ref
- .. automethod:: create
- .. automethod:: browse
- .. automethod:: unlink
- .. automethod:: write
+.. autoattribute:: Environment.lang
- .. automethod:: read
- .. automethod:: read_group
+.. autoattribute:: Environment.user
- .. rubric:: Searching
+.. autoattribute:: Environment.company
- .. automethod:: search
- .. automethod:: search_count
- .. automethod:: name_search
+.. autoattribute:: Environment.companies
- .. rubric:: Recordset operations
+.. TODO cr, uid but not @property or methods of Environment class...
- .. autoattribute:: ids
- .. automethod:: ensure_one
- .. automethod:: exists
- .. automethod:: filtered
- .. automethod:: sorted
- .. automethod:: mapped
+Altering the environment
+------------------------
- .. rubric:: Environment swapping
+.. currentmodule:: odoo.models
- .. automethod:: sudo
- .. automethod:: with_context
- .. automethod:: with_env
+.. automethod:: Model.with_context
- .. rubric:: Fields and views querying
+.. automethod:: Model.with_user
- .. automethod:: fields_get
- .. automethod:: fields_view_get
+.. automethod:: Model.with_env
- .. rubric:: Miscellaneous methods
+.. automethod:: Model.sudo
- .. automethod:: default_get
- .. automethod:: copy
- .. automethod:: name_get
- .. automethod:: name_create
+.. _reference/orm/sql:
- .. _reference/orm/model/automatic:
+SQL Execution
+-------------
- .. rubric:: Automatic fields
+The :attr:`~odoo.api.Environment.cr` attribute on environments is the
+cursor for the current database transaction and allows executing SQL directly,
+either for queries which are difficult to express using the ORM (e.g. complex
+joins) or for performance reasons::
- .. attribute:: id
+ self.env.cr.execute("some_sql", param1, param2, param3)
- Identifier :class:`field <odoo.fields.Field>`
+Because models use the same cursor and the :class:`~odoo.api.Environment`
+holds various caches, these caches must be invalidated when *altering* the
+database in raw SQL, or further uses of models may become incoherent. It is
+necessary to clear caches when using ``CREATE``, ``UPDATE`` or ``DELETE`` in
+SQL, but not ``SELECT`` (which simply reads the database).
- .. attribute:: _log_access
+.. note::
+ Clearing caches can be performed using the
+ :meth:`~odoo.models.Model.invalidate_cache` method.
- Whether log access fields (``create_date``, ``write_uid``, ...) should
- be generated (default: ``True``)
+.. automethod:: Model.invalidate_cache
- .. attribute:: create_date
+.. warning::
+ Executing raw SQL bypasses the ORM, and by consequent, Odoo security rules.
+ Please make sure your queries are sanitized when using user input and prefer using
+ ORM utilities if you don't really need to use SQL queries.
- Date at which the record was created
- :type: :class:`~odoo.field.Datetime`
+.. _reference/orm/models/crud:
- .. attribute:: create_uid
+Common ORM methods
+==================
- Relational field to the user who created the record
+.. currentmodule:: odoo.models
- :type: ``res.users``
+Create/update
+-------------
- .. attribute:: write_date
+.. todo:: api.model_create_multi information
- Date at which the record was last modified
+.. automethod:: Model.create
- :type: :class:`~odoo.field.Datetime`
+.. automethod:: Model.copy
- .. attribute:: write_uid
+.. automethod:: Model.default_get
- Relational field to the last user who modified the record
+.. automethod:: Model.name_create
- :type: ``res.users``
+.. automethod:: Model.write
- .. rubric:: Reserved field names
+.. automethod:: Model.flush
- A few field names are reserved for pre-defined behaviors beyond that of
- automated fields. They should be defined on a model when the related
- behavior is desired:
+Search/Read
+-----------
- .. attribute:: name
+.. automethod:: Model.browse
- default value for :attr:`~._rec_name`, used to
- display records in context where a representative "naming" is
- necessary.
+.. automethod:: Model.search
- :type: :class:`~odoo.fields.Char`
+.. automethod:: Model.search_count
- .. attribute:: active
+.. automethod:: Model.name_search
- toggles the global visibility of the record, if ``active`` is set to
- ``False`` the record is invisible in most searches and listing
+.. automethod:: Model.read
- :type: :class:`~odoo.fields.Boolean`
+.. automethod:: Model.read_group
- .. attribute:: sequence
+Fields/Views
+''''''''''''
- Alterable ordering criteria, allows drag-and-drop reordering of models
- in list views
+.. automethod:: Model.fields_get
- :type: :class:`~odoo.fields.Integer`
+.. automethod:: Model.fields_view_get
- .. attribute:: state
+.. _reference/orm/domains:
- lifecycle stages of the object, used by the ``states`` attribute on
- :class:`fields <odoo.fields.Field>`
+Search domains
+''''''''''''''
- :type: :class:`~odoo.fields.Selection`
+A domain is a list of criteria, each criterion being a triple (either a
+``list`` or a ``tuple``) of ``(field_name, operator, value)`` where:
- .. attribute:: parent_id
+* ``field_name`` (``str``)
+ a field name of the current model, or a relationship traversal through
+ a :class:`~odoo.fields.Many2one` using dot-notation e.g. ``'street'``
+ or ``'partner_id.country'``
- used to order records in a tree structure and enables the ``child_of``
- and ``parent_of`` operators in domains
+* ``operator`` (``str``)
+ an operator used to compare the ``field_name`` with the ``value``. Valid
+ operators are:
- :type: :class:`~odoo.fields.Many2one`
+ ``=``
+ equals to
+ ``!=``
+ not equals to
+ ``>``
+ greater than
+ ``>=``
+ greater than or equal to
+ ``<``
+ less than
+ ``<=``
+ less than or equal to
+ ``=?``
+ unset or equals to (returns true if ``value`` is either ``None`` or
+ ``False``, otherwise behaves like ``=``)
+ ``=like``
+ matches ``field_name`` against the ``value`` pattern. An underscore
+ ``_`` in the pattern stands for (matches) any single character; a
+ percent sign ``%`` matches any string of zero or more characters.
+ ``like``
+ matches ``field_name`` against the ``%value%`` pattern. Similar to
+ ``=like`` but wraps ``value`` with '%' before matching
+ ``not like``
+ doesn't match against the ``%value%`` pattern
+ ``ilike``
+ case insensitive ``like``
+ ``not ilike``
+ case insensitive ``not like``
+ ``=ilike``
+ case insensitive ``=like``
+ ``in``
+ is equal to any of the items from ``value``, ``value`` should be a
+ list of items
+ ``not in``
+ is unequal to all of the items from ``value``
+ ``child_of``
+ is a child (descendant) of a ``value`` record (value can be either
+ one item or a list of items).
- .. attribute:: parent_path
+ Takes the semantics of the model into account (i.e following the
+ relationship field named by
+ :attr:`~odoo.models.Model._parent_name`).
+ ``parent_of``
+ is a parent (ascendant) of a ``value`` record (value can be either
+ one item or a list of items).
- used to store an index of the tree structure when :attr:`~._parent_store`
- is set to True - must be declared with ``index=True`` for proper operation.
+ Takes the semantics of the model into account (i.e following the
+ relationship field named by
+ :attr:`~odoo.models.Model._parent_name`).
- :type: :class:`~odoo.fields.Char`
+* ``value``
+ variable type, must be comparable (through ``operator``) to the named
+ field.
+Domain criteria can be combined using logical operators in *prefix* form:
-.. _reference/orm/decorators:
+``'&'``
+ logical *AND*, default operation to combine criteria following one
+ another. Arity 2 (uses the next 2 criteria or combinations).
+``'|'``
+ logical *OR*, arity 2.
+``'!'``
+ logical *NOT*, arity 1.
-Method decorators
-=================
+ .. note:: Mostly to negate combinations of criteria
+ Individual criterion generally have a negative form (e.g. ``=`` ->
+ ``!=``, ``<`` -> ``>=``) which is simpler than negating the positive.
-.. automodule:: odoo.api
- :members: model, depends, constrains, onchange, returns
+.. admonition:: Example
-.. _reference/orm/fields:
+ To search for partners named *ABC*, from belgium or germany, whose language
+ is not english::
-Fields
-======
+ [('name','=','ABC'),
+ ('language.code','!=','en_US'),
+ '|',('country_id.code','=','be'),
+ ('country_id.code','=','de')]
-.. _reference/orm/fields/basic:
+ This domain is interpreted as:
-Basic fields
-------------
+ .. code-block:: text
-.. autodoc documents descriptors as attributes, even for the *definition* of
- descriptors. As a result automodule:: odoo.fields lists all the field
- classes as attributes without providing inheritance info or methods (though
- we don't document methods as they're not useful for "external" devs)
- (because we don't support pluggable field types) (or do we?)
+ (name is 'ABC')
+ AND (language is NOT english)
+ AND (country is Belgium OR Germany)
-.. autoclass:: odoo.fields.Field
+Unlink
+------
-.. autoclass:: odoo.fields.Char
- :show-inheritance:
+.. automethod:: Model.unlink
-.. autoclass:: odoo.fields.Boolean
- :show-inheritance:
+.. _reference/orm/records/info:
-.. autoclass:: odoo.fields.Integer
- :show-inheritance:
+Record(set) information
+-----------------------
-.. autoclass:: odoo.fields.Float
- :show-inheritance:
+.. autoattribute:: Model.ids
-.. autoclass:: odoo.fields.Text
- :show-inheritance:
+.. attribute:: env
-.. autoclass:: odoo.fields.Selection
- :show-inheritance:
+ Returns the environment of the given recordset.
-.. autoclass:: odoo.fields.Html
- :show-inheritance:
+ :type: :class:`~odoo.api.Environment`
-.. _reference/orm/fields/date_datetime:
+.. todo:: Environment documentation
-Date and Datetime fields
-------------------------
+.. automethod:: Model.exists
-Dates and Datetimes are very important fields in any kind of business
-application, they are heavily used in many popular Odoo applications such as
-logistics or accounting and their misuse can create invisible yet painful
-bugs, this excerpt aims to provide Odoo developers with the knowledge required
-to avoid misusing these fields.
+.. automethod:: Model.ensure_one
-When assigning a value to a Date/Datetime field, the following options are valid:
- * A string in the proper server format **(YYYY-MM-DD)** for Date fields,
- **(YYYY-MM-DD HH:MM:SS)** for Datetime fields.
- * A `date` or `datetime` object.
- * `False` or `None`.
+.. automethod:: Model.name_get
-If not sure of the type of the value being assigned to a Date/Datetime object,
-the best course of action is to pass the value to
-:func:`~odoo.fields.Date.to_date` or :func:`~odoo.fields.Datetime.to_datetime`
-which will attempt to convert the value to a date or datetime object
-respectively, which can then be assigned to the field in question.
+.. automethod:: Model.get_metadata
-.. admonition:: Example
+.. _reference/orm/records/operations:
- To parse date/datetimes coming from external sources::
+Operations
+----------
- fields.Date.to_date(self._context.get('date_from'))
+Recordsets are immutable, but sets of the same model can be combined using
+various set operations, returning new recordsets.
-Date / Datetime comparison best practices:
- * Date fields can **only** be compared to date objects.
- * Datetime fields can **only** be compared to datetime objects.
+.. addition preserves order but can introduce duplicates
- .. warning:: Strings representing dates and datetimes can be compared
- between each other, however the result may not be the expected
- result, as a datetime string will always be greater than a
- date string, therefore this practice is **heavily**
- discouraged.
+* ``record in set`` returns whether ``record`` (which must be a 1-element
+ recordset) is present in ``set``. ``record not in set`` is the inverse
+ operation
+* ``set1 <= set2`` and ``set1 < set2`` return whether ``set1`` is a subset
+ of ``set2`` (resp. strict)
+* ``set1 >= set2`` and ``set1 > set2`` return whether ``set1`` is a superset
+ of ``set2`` (resp. strict)
+* ``set1 | set2`` returns the union of the two recordsets, a new recordset
+ containing all records present in either source
+* ``set1 & set2`` returns the intersection of two recordsets, a new recordset
+ containing only records present in both sources
+* ``set1 - set2`` returns a new recordset containing only records of ``set1``
+ which are *not* in ``set2``
-Common operations with dates and datetimes such as addition, substraction or
-fetching the start/end of a period are exposed through both
-:class:`~odoo.fields.Date` and :class:`~odoo.fields.Datetime`.
-These helpers are also available by importing `odoo.tools.date_utils`.
+Recordsets are iterable so the usual Python tools are available for
+transformation (:func:`python:map`, :func:`python:sorted`,
+:func:`~python:itertools.ifilter`, ...) however these return either a
+:class:`python:list` or an :term:`python:iterator`, removing the ability to
+call methods on their result, or to use set operations.
-.. autoclass:: odoo.fields.Date
- :show-inheritance:
- :members: today, context_today, to_date, to_string, start_of, end_of, add, subtract
+Recordsets therefore provide the following operations returning recordsets themselves
+(when possible):
-.. autoclass:: odoo.fields.Datetime
- :show-inheritance:
- :members: now, today, context_timestamp, to_datetime, to_string, start_of, end_of, add, subtract
+Filter
+''''''
-.. _reference/orm/fields/relational:
+.. automethod:: Model.filtered
-Relational fields
------------------
+.. automethod:: Model.filtered_domain
-.. autoclass:: odoo.fields.Many2one
- :show-inheritance:
+Map
+'''
-.. autoclass:: odoo.fields.One2many
- :show-inheritance:
+.. automethod:: Model.mapped
-.. autoclass:: odoo.fields.Many2many
- :show-inheritance:
+Sort
+''''
-.. autoclass:: odoo.fields.Reference
- :show-inheritance:
+.. automethod:: Model.sorted
.. _reference/orm/inheritance:
@@ -1005,7 +881,7 @@ fields, methods and meta-information (defaults & al) from its base.
.. literalinclude:: ../../odoo/addons/test_documentation_examples/inheritance.py
:language: python
- :lines: 5-
+ :lines: 6-
and using them:
@@ -1034,7 +910,7 @@ them (e.g. to change their default sort order):
.. literalinclude:: ../../odoo/addons/test_documentation_examples/extension.py
:language: python
- :lines: 7-
+ :lines: 6-
.. literalinclude:: ../../odoo/addons/test_documentation_examples/tests/test_extension.py
:language: python
@@ -1047,7 +923,7 @@ will yield:
:lines: 13
.. note:: it will also yield the various :ref:`automatic fields
- <reference/orm/model/automatic>` unless they've been disabled
+ <reference/fields/automatic>` unless they've been disabled
Delegation
----------
@@ -1057,7 +933,9 @@ at runtime) but less power: using the :attr:`~odoo.models.Model._inherits`
a model *delegates* the lookup of any field not found on the current model
to "children" models. The delegation is performed via
:class:`~odoo.fields.Reference` fields automatically set up on the parent
-model. The main difference is in the meaning. When using Delegation, the model
+model.
+
+The main difference is in the meaning. When using Delegation, the model
**has one** instead of **is one**, turning the relationship in a composition
instead of inheritance:
@@ -1084,202 +962,30 @@ and it's possible to write directly on the delegated field:
.. warning:: when using delegation inheritance, methods are *not* inherited,
only fields
-.. _reference/orm/domains:
+Fields Incremental Definition
+-----------------------------
-Domains
-=======
+A field is defined as class attribute on a model class. If the model
+is extended, 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.
-A domain is a list of criteria, each criterion being a triple (either a
-``list`` or a ``tuple``) of ``(field_name, operator, value)`` where:
+For instance, the second class below only adds a tooltip on the field
+``state``::
-``field_name`` (``str``)
- a field name of the current model, or a relationship traversal through
- a :class:`~odoo.fields.Many2one` using dot-notation e.g. ``'street'``
- or ``'partner_id.country'``
-``operator`` (``str``)
- an operator used to compare the ``field_name`` with the ``value``. Valid
- operators are:
+ class First(models.Model):
+ _name = 'foo'
+ state = fields.Selection([...], required=True)
- ``=``
- equals to
- ``!=``
- not equals to
- ``>``
- greater than
- ``>=``
- greater than or equal to
- ``<``
- less than
- ``<=``
- less than or equal to
- ``=?``
- unset or equals to (returns true if ``value`` is either ``None`` or
- ``False``, otherwise behaves like ``=``)
- ``=like``
- matches ``field_name`` against the ``value`` pattern. An underscore
- ``_`` in the pattern stands for (matches) any single character; a
- percent sign ``%`` matches any string of zero or more characters.
- ``like``
- matches ``field_name`` against the ``%value%`` pattern. Similar to
- ``=like`` but wraps ``value`` with '%' before matching
- ``not like``
- doesn't match against the ``%value%`` pattern
- ``ilike``
- case insensitive ``like``
- ``not ilike``
- case insensitive ``not like``
- ``=ilike``
- case insensitive ``=like``
- ``in``
- is equal to any of the items from ``value``, ``value`` should be a
- list of items
- ``not in``
- is unequal to all of the items from ``value``
- ``child_of``
- is a child (descendant) of a ``value`` record.
-
- Takes the semantics of the model into account (i.e following the
- relationship field named by
- :attr:`~odoo.models.Model._parent_name`).
- ``parent_of``
- is a parent (ascendant) of a ``value`` record.
-
- Takes the semantics of the model into account (i.e following the
- relationship field named by
- :attr:`~odoo.models.Model._parent_name`).
+ class Second(models.Model):
+ _inherit = 'foo'
+ state = fields.Selection(help="Blah blah blah")
-``value``
- variable type, must be comparable (through ``operator``) to the named
- field
+.. _reference/exceptions:
-Domain criteria can be combined using logical operators in *prefix* form:
-
-``'&'``
- logical *AND*, default operation to combine criteria following one
- another. Arity 2 (uses the next 2 criteria or combinations).
-``'|'``
- logical *OR*, arity 2.
-``'!'``
- logical *NOT*, arity 1.
-
- .. tip:: Mostly to negate combinations of criteria
- :class: aphorism
-
- Individual criterion generally have a negative form (e.g. ``=`` ->
- ``!=``, ``<`` -> ``>=``) which is simpler than negating the positive.
-
-.. admonition:: Example
-
- To search for partners named *ABC*, from belgium or germany, whose language
- is not english::
-
- [('name','=','ABC'),
- ('language.code','!=','en_US'),
- '|',('country_id.code','=','be'),
- ('country_id.code','=','de')]
-
- This domain is interpreted as:
-
- .. code-block:: text
-
- (name is 'ABC')
- AND (language is NOT english)
- AND (country is Belgium OR Germany)
+Error management
+================
-Porting from the old API to the new API
-=======================================
-
-* bare lists of ids are to be avoided in the new API, use recordsets instead
-* methods still written in the old API should be automatically bridged by the
- ORM, no need to switch to the old API, just call them as if they were a new
- API method. See :ref:`reference/orm/oldapi/bridging` for more details.
-* :meth:`~odoo.models.Model.search` returns a recordset, no point in e.g.
- browsing its result
-* ``fields.related`` and ``fields.function`` are replaced by using a normal
- field type with either a ``related=`` or a ``compute=`` parameter
-* :func:`~odoo.api.depends` on ``compute=`` methods **must be complete**,
- it must list **all** the fields and sub-fields which the compute method
- uses. It is better to have too many dependencies (will recompute the field
- in cases where that is not needed) than not enough (will forget to recompute
- the field and then values will be incorrect)
-* **remove** all ``onchange`` methods on computed fields. Computed fields are
- automatically re-computed when one of their dependencies is changed, and
- that is used to auto-generate ``onchange`` by the client
-* the decorator :func:`~odoo.api.model` is
- for bridging *when calling from the old API context*, for internal or pure
- new-api (e.g. compute) it is useless
-* remove :attr:`~odoo.models.Model._default`, replace by ``default=``
- parameter on corresponding fields
-* if a field's ``string=`` is the titlecased version of the field name::
-
- name = fields.Char(string="Name")
-
- it is useless and should be removed
-* the ``multi=`` parameter does not do anything on new API fields use the same
- ``compute=`` methods on all relevant fields for the same result
-* provide ``compute=``, ``inverse=`` and ``search=`` methods by name (as a
- string), this makes them overridable (removes the need for an intermediate
- "trampoline" function)
-* double check that all fields and methods have different names, there is no
- warning in case of collision (because Python handles it before Odoo sees
- anything)
-* the normal new-api import is ``from odoo import fields, models``. If
- compatibility decorators are necessary, use ``from odoo import api,
- fields, models``
-* remove explicit definition of :attr:`~odoo.models.Model.create_uid`,
- :attr:`~odoo.models.Model.create_date`,
- :attr:`~odoo.models.Model.write_uid` and
- :attr:`~odoo.models.Model.write_date` fields: they are now created as
- regular "legitimate" fields, and can be read and written like any other
- field out-of-the-box
-* uses of :attr:`~odoo.models.Model._columns` or
- :attr:`~odoo.models.Model._all_columns` should be replaced by
- :attr:`~odoo.models.Model._fields`, which provides access to instances of
- new-style :class:`odoo.fields.Field` instances (rather than old-style
- :class:`odoo.osv.fields._column`).
-
- Non-stored computed fields created using the new API style are *not*
- available in :attr:`~odoo.models.Model._columns` and can only be
- inspected through :attr:`~odoo.models.Model._fields`
-* reassigning ``self`` in a method is probably unnecessary and may break
- translation introspection
-* :class:`~odoo.api.Environment` objects rely on some threadlocal state,
- which has to be set up before using them. It is necessary to do so using the
- :meth:`odoo.api.Environment.manage` context manager when trying to use
- the new API in contexts where it hasn't been set up yet, such as new threads
- or a Python interactive environment::
-
- >>> from odoo import api, modules
- >>> r = modules.registry.RegistryManager.get('test')
- >>> cr = r.cursor()
- >>> env = api.Environment(cr, 1, {})
- Traceback (most recent call last):
- ...
- AttributeError: environments
- >>> with api.Environment.manage():
- ... env = api.Environment(cr, 1, {})
- ... print env['res.partner'].browse(1)
- ...
- res.partner(1,)
-
-.. _reference/orm/oldapi/bridging:
-
-Automatic bridging of old API methods
--------------------------------------
-
-When models are initialized, all methods are automatically scanned and bridged
-if they look like models declared in the old API style. This bridging makes
-them transparently callable from new-API-style methods.
-
-Methods are matched as "old-API style" if their second positional parameter
-(after ``self``) is called either ``cr`` or ``cursor``. The system also
-recognizes the third positional parameter being called ``uid`` or ``user`` and
-the fourth being called ``id`` or ``ids``. It also recognizes the presence of
-any parameter called ``context``.
-
-When calling such methods from a new API context, the system will
-automatically fill matched parameters from the current
-:class:`~odoo.api.Environment` (for :attr:`~odoo.api.Environment.cr`,
-:attr:`~odoo.api.Environment.user` and
-:attr:`~odoo.api.Environment.context`) or the current recordset (for ``id``
-and ``ids``).
+.. automodule:: odoo.exceptions
+ :members: AccessDenied, AccessError, CacheMiss, MissingError, RedirectWarning, UserError, ValidationError
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