Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?

Fields

from openerp import models, fields, api, _

class MyModel(models.Model):
    _name = 'mymodule.mymodel'
    # Fields are declared as class attributes:
    char = fields.Char('Char', 64)        # name, size
    text = fields.Text('Text')
    intg = fields.Integer('Integer')
    flot = fields.Float('Float', (16,4))  # name, digits
    flag = fields.Boolean('Boolean')
    date = fields.Date('Date')
    time = fields.Datetime('Date and Time')
    html = fields.HTML('Hypertext')
    biny = fields.Binary('Binary')

A field is defined as class attribute on a model class. If the model is extended, it's fields can also be extended, the field attributes are taken from the parent class and can then be overridden in the subclass.

Common parameters:

  • string: field label on views. Can be passed as first parameter without using a keyword (string).
  • size on Char: the maximum size of values stored for that field (integer, optional).
  • translate on Char,Text: values are translatable (boolean).
  • digits on Float : a pair (total, decimal), or a function taking a database cursor and returning a pair (total, decimal).
  • help: tooltip seen by users (string).
  • readonly: set field as readonly (boolean).
  • required: set field as mandatory (boolean).
  • index: make the field indexed in the database (boolean).
  • default: default value; either a static value or a function taking a recordset and returning a value.
  • states: dictionary mapping state values to lists of attribute-value pairs ('readonly', 'required' or 'invisible').
  • groups: comma-separated list of group xml ids (string); restricts field access to those user groups.
  • company_dependent: value depends on the company (boolean). Maps to v7 property field.

Relational fields

    selc = fields.Selection([('code','Desc'),...], 'Label')
    refr = fields.Reference([('a.model', 'A Model'),...], 'Label')
    m2o  = fields.Many2one('amodule.amodel', 'Related Value')  # comodel, string
    o2m  = fields.One2many('amodule.amodel', 'inverse_id', 'Related List')
  • comodel_name: identifier name of the target model (string).
  • inverse_name on One2many: the inverse Many2one field in comodel_name (string).
  • limit on One2many: optional limit to use upon read (integer).
  • relation on Many2many:optional name of the table that stores the relation in the database (string).
  • column1 on Many2many: optional name of the column referring to "these" records in the relation table (string).
  • column2 on Many2many: optional name of the column referring to "those" records in the relationtable (string).
  • domain: optional domain to filter candidate values on client side (domain list or string).
  • context: optional context to use on the client side (dictionary).
  • ondelete: what to do when referred record is deleted: set null, restrict, cascade
  • auto_join: use JOINs when following relation, but skips security on related model (boolean).
  • delegate: make fields of the target model accessible; same as v7 _inherits (boolean).

Computed fields

Are defined providing a value for the attribute compute.

  • compute: name of a model method that computes the field
  • inverse: name of a model method that inverses the field (optional)
  • search: name of a model method that implement search on the field (optional)
  • store: whether the field is stored in database (boolean, by default False)

compute, inverse and search are model methods with signatures:

upper = fields.Char(compute='_compute_upper',
inverse='_inverse_upper',
search='_search_upper')

@api.depends('name')
def _compute_upper(self):
for rec in self:
self.upper = self.name.upper() if self.name else False

def _inverse_upper(self):
for rec in self:
self.name = self.upper.lower() if self.upper else False

def _search_upper(self, operator, value):
if operator == 'like':
operator = 'ilike'
return [('name', operator, value)]

The compute method has to assign the field on all records of the invoked recordset. The decorator :meth:openerp.api.depends must be applied on the compute method to specify the field dependencies; those dependencies are used to determine when to recompute the field; recomputation is automatic and guarantees cache/database consistency. Note that the same method can be used for several fields, you simply have to assign all the given fields in the method; the method will be invoked once for all those fields.

By default, a computed field is not stored to the database, and is computed on-the-fly. Adding the attribute store=True will store the field's values in the database. The advantage of a stored field is that searching on that field is done by the database itself. The disadvantage is that it requires database updates when the field must be recomputed.

The inverse method, as its name says, does the inverse of the compute method: the invoked records have a value for the field, and you must apply the necessary changes on the field dependencies such that the computation gives the expected value. Note that a computed field without an inverse method is readonly by default.

The search method is invoked when processing domains before doing an actual search on the model. It must return a domain equivalent to the condition: field operator value.

from openerp import models, fields, api, _

class MyModel(models.Model):
    _name = 'module.mymodel'

    name = fields.Char(required=True)
    parent = fields.Many2one('test_new_api.category')
    display_name = fields.Char(compute='_compute_display_name', inverse='_inverse_display_name')

    @api.one
    @api.depends('name', 'parent.display_name') # this definition is recursive
    def _compute_display_name(self):
        if self.parent:
            self.display_name = self.parent.display_name + ' / ' + self.name
        else:
            self.display_name = self.name

    @api.one
    def _inverse_display_name(self):
        names = self.display_name.split('/')
        # determine sequence of categories
        categories = []
        for name in names[:-1]:
            category = self.search([('name', 'ilike', name.strip())])
            categories.append(category[0])
        categories.append(self)
        # assign parents following sequence
        for parent, child in zip(categories, categories[1:]):
            if parent and child:
                child.parent = parent
        # assign name of last category, and reassign display_name (to normalize it)
        self.name = names[-1].strip()
    ```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment