diff options
| author | Victor Feyens <vfe@odoo.com> | 2019-10-31 14:57:06 +0000 |
|---|---|---|
| committer | fw-bot <otis@odoo.com> | 2019-11-04 11:16:12 +0000 |
| commit | ae4855fa1e0156cefdb1c31945252f2b7517a6f4 (patch) | |
| tree | 324979856bbf49ed5ddae92f5fa5e1e5d631e48c /doc/reference | |
| parent | b2f3f092b726719159a16b4362c05592ba71360c (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/reference')
| -rw-r--r-- | doc/reference/data.rst | 2 | ||||
| -rw-r--r-- | doc/reference/orm.rst | 1400 |
2 files changed, 554 insertions, 848 deletions
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 |
