summaryrefslogtreecommitdiff
path: root/doc
diff options
context:
space:
mode:
authorVictor Feyens <vfe@odoo.com>2019-10-31 14:57:06 +0000
committerfw-bot <otis@odoo.com>2019-11-04 11:16:12 +0000
commitae4855fa1e0156cefdb1c31945252f2b7517a6f4 (patch)
tree324979856bbf49ed5ddae92f5fa5e1e5d631e48c /doc
parentb2f3f092b726719159a16b4362c05592ba71360c (diff)
[IMP] base, doc: orm page refactoring.
closes odoo/odoo#39725 X-original-commit: 7ce7c58592c7c7202a37c73767c477be5a835752 Signed-off-by: Victor Feyens (vfe) <vfe@odoo.com>
Diffstat (limited to 'doc')
-rw-r--r--doc/howtos/website.rst4
-rw-r--r--doc/reference/data.rst2
-rw-r--r--doc/reference/orm.rst1400
3 files changed, 556 insertions, 850 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