1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2013-2014 OpenERP (<http://www.openerp.com>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
22 """ High-level objects for fields. """
25 from datetime import date, datetime
26 from functools import partial
27 from operator import attrgetter
32 from types import NoneType
34 from openerp.tools import float_round, ustr, html_sanitize
35 from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as DATE_FORMAT
36 from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT as DATETIME_FORMAT
38 DATE_LENGTH = len(date.today().strftime(DATE_FORMAT))
39 DATETIME_LENGTH = len(datetime.now().strftime(DATETIME_FORMAT))
41 _logger = logging.getLogger(__name__)
43 class SpecialValue(object):
44 """ Encapsulates a value in the cache in place of a normal value. """
45 def __init__(self, value):
50 class FailedValue(SpecialValue):
51 """ Special value that encapsulates an exception instead of a value. """
52 def __init__(self, exception):
53 self.exception = exception
57 def _check_value(value):
58 """ Return `value`, or call its getter if `value` is a :class:`SpecialValue`. """
59 return value.get() if isinstance(value, SpecialValue) else value
62 def resolve_all_mro(cls, name, reverse=False):
63 """ Return the (successively overridden) values of attribute `name` in `cls`
64 in mro order, or inverse mro order if `reverse` is true.
66 klasses = reversed(cls.__mro__) if reverse else cls.__mro__
68 if name in klass.__dict__:
69 yield klass.__dict__[name]
72 class MetaField(type):
73 """ Metaclass for field classes. """
76 def __init__(cls, name, bases, attrs):
77 super(MetaField, cls).__init__(name, bases, attrs)
79 cls.by_type[cls.type] = cls
81 # compute class attributes to avoid calling dir() on fields
83 cls.related_attrs = []
84 cls.description_attrs = []
86 if attr.startswith('_column_'):
87 cls.column_attrs.append((attr[8:], attr))
88 elif attr.startswith('_related_'):
89 cls.related_attrs.append((attr[9:], attr))
90 elif attr.startswith('_description_'):
91 cls.description_attrs.append((attr[13:], attr))
95 """ The field descriptor contains the field definition, and manages accesses
96 and assignments of the corresponding field on records. The following
97 attributes may be provided when instanciating a field:
99 :param string: the label of the field seen by users (string); if not
100 set, the ORM takes the field name in the class (capitalized).
102 :param help: the tooltip of the field seen by users (string)
104 :param readonly: whether the field is readonly (boolean, by default ``False``)
106 :param required: whether the value of the field is required (boolean, by
109 :param index: whether the field is indexed in database (boolean, by
112 :param default: the default value for the field; this is either a static
113 value, or a function taking a recordset and returning a value
115 :param states: a dictionary mapping state values to lists of UI attribute-value
116 pairs; possible attributes are: 'readonly', 'required', 'invisible'.
117 Note: Any state-based condition requires the ``state`` field value to be
118 available on the client-side UI. This is typically done by including it in
119 the relevant views, possibly made invisible if not relevant for the
122 :param groups: comma-separated list of group xml ids (string); this
123 restricts the field access to the users of the given groups only
125 :param bool copy: whether the field value should be copied when the record
126 is duplicated (default: ``True`` for normal fields, ``False`` for
127 ``one2many`` and computed fields, including property fields and
132 .. rubric:: Computed fields
134 One can define a field whose value is computed instead of simply being
135 read from the database. The attributes that are specific to computed
136 fields are given below. To define such a field, simply provide a value
137 for the attribute `compute`.
139 :param compute: name of a method that computes the field
141 :param inverse: name of a method that inverses the field (optional)
143 :param search: name of a method that implement search on the field (optional)
145 :param store: whether the field is stored in database (boolean, by
146 default ``False`` on computed fields)
148 The methods given for `compute`, `inverse` and `search` are model
149 methods. Their signature is shown in the following example::
151 upper = fields.Char(compute='_compute_upper',
152 inverse='_inverse_upper',
153 search='_search_upper')
156 def _compute_upper(self):
158 self.upper = self.name.upper() if self.name else False
160 def _inverse_upper(self):
162 self.name = self.upper.lower() if self.upper else False
164 def _search_upper(self, operator, value):
165 if operator == 'like':
167 return [('name', operator, value)]
169 The compute method has to assign the field on all records of the invoked
170 recordset. The decorator :meth:`openerp.api.depends` must be applied on
171 the compute method to specify the field dependencies; those dependencies
172 are used to determine when to recompute the field; recomputation is
173 automatic and guarantees cache/database consistency. Note that the same
174 method can be used for several fields, you simply have to assign all the
175 given fields in the method; the method will be invoked once for all
178 By default, a computed field is not stored to the database, and is
179 computed on-the-fly. Adding the attribute ``store=True`` will store the
180 field's values in the database. The advantage of a stored field is that
181 searching on that field is done by the database itself. The disadvantage
182 is that it requires database updates when the field must be recomputed.
184 The inverse method, as its name says, does the inverse of the compute
185 method: the invoked records have a value for the field, and you must
186 apply the necessary changes on the field dependencies such that the
187 computation gives the expected value. Note that a computed field without
188 an inverse method is readonly by default.
190 The search method is invoked when processing domains before doing an
191 actual search on the model. It must return a domain equivalent to the
192 condition: `field operator value`.
196 .. rubric:: Related fields
198 The value of a related field is given by following a sequence of
199 relational fields and reading a field on the reached model. The complete
200 sequence of fields to traverse is specified by the attribute
202 :param related: sequence of field names
204 The value of some attributes from related fields are automatically taken
205 from the source field, when it makes sense. Examples are the attributes
206 `string` or `selection` on selection fields.
208 By default, the values of related fields are not stored to the database.
209 Add the attribute ``store=True`` to make it stored, just like computed
210 fields. Related fields are automatically recomputed when their
211 dependencies are modified.
213 .. _field-company-dependent:
215 .. rubric:: Company-dependent fields
217 Formerly known as 'property' fields, the value of those fields depends
218 on the company. In other words, users that belong to different companies
219 may see different values for the field on a given record.
221 :param company_dependent: whether the field is company-dependent (boolean)
223 .. _field-incremental-definition:
225 .. rubric:: Incremental definition
227 A field is defined as class attribute on a model class. If the model
228 is extended (see :class:`~openerp.models.Model`), one can also extend
229 the field definition by redefining a field with the same name and same
230 type on the subclass. In that case, the attributes of the field are
231 taken from the parent class and overridden by the ones given in
234 For instance, the second class below only adds a tooltip on the field
237 class First(models.Model):
239 state = fields.Selection([...], required=True)
241 class Second(models.Model):
243 state = fields.Selection(help="Blah blah blah")
246 __metaclass__ = MetaField
248 _attrs = None # dictionary with all field attributes
249 _free_attrs = None # list of semantic-free attribute names
251 automatic = False # whether the field is automatically created ("magic" field)
252 inherited = False # whether the field is inherited (_inherits)
253 column = None # the column interfaced by the field
254 setup_done = False # whether the field has been set up
256 name = None # name of the field
257 type = None # type of the field (string)
258 relational = False # whether the field is a relational one
259 model_name = None # name of the model of this field
260 comodel_name = None # name of the model of values (if relational)
261 inverse_fields = None # list of inverse fields (objects)
263 store = True # whether the field is stored in database
264 index = False # whether the field is indexed in database
265 manual = False # whether the field is a custom field
266 copyable = True # whether the field is copied over by BaseModel.copy()
267 depends = () # collection of field dependencies
268 recursive = False # whether self depends on itself
269 compute = None # compute(recs) computes field on recs
270 inverse = None # inverse(recs) inverses field on recs
271 search = None # search(recs, operator, value) searches on self
272 related = None # sequence of field names, for related fields
273 related_sudo = True # whether related fields should be read as admin
274 company_dependent = False # whether `self` is company-dependent (property field)
275 default = None # default(recs) returns the default value
277 string = None # field label
278 help = None # field tooltip
282 groups = False # csv list of group xml ids
283 change_default = None # whether the field may trigger a "user-onchange"
284 deprecated = None # whether the field is ... deprecated
286 def __init__(self, string=None, **kwargs):
287 kwargs['string'] = string
288 self._attrs = {key: val for key, val in kwargs.iteritems() if val is not None}
289 self._free_attrs = []
291 def copy(self, **kwargs):
292 """ copy(item) -> test
294 make a copy of `self`, possibly modified with parameters `kwargs` """
296 field._attrs = {key: val for key, val in kwargs.iteritems() if val is not None}
297 field._free_attrs = list(self._free_attrs)
300 def set_class_name(self, cls, name):
301 """ Assign the model class and field name of `self`. """
302 self.model_name = cls._name
305 # determine all inherited field attributes
307 for field in resolve_all_mro(cls, name, reverse=True):
308 if isinstance(field, type(self)):
309 attrs.update(field._attrs)
312 attrs.update(self._attrs) # necessary in case self is not in cls
314 # initialize `self` with `attrs`
315 if attrs.get('compute'):
316 # by default, computed fields are not stored, not copied and readonly
317 attrs['store'] = attrs.get('store', False)
318 attrs['copy'] = attrs.get('copy', False)
319 attrs['readonly'] = attrs.get('readonly', not attrs.get('inverse'))
320 if attrs.get('related'):
321 # by default, related fields are not stored
322 attrs['store'] = attrs.get('store', False)
324 # attribute is copyable because there is also a copy() method
325 attrs['copyable'] = attrs.pop('copy')
327 for attr, value in attrs.iteritems():
328 if not hasattr(self, attr):
329 self._free_attrs.append(attr)
330 setattr(self, attr, value)
333 self.string = name.replace('_', ' ').capitalize()
335 # determine self.default and cls._defaults in a consistent way
336 self._determine_default(cls, name)
340 def _determine_default(self, cls, name):
341 """ Retrieve the default value for `self` in the hierarchy of `cls`, and
342 determine `self.default` and `cls._defaults` accordingly.
346 # traverse the class hierarchy upwards, and take the first field
347 # definition with a default or _defaults for self
348 for klass in cls.__mro__:
349 field = klass.__dict__.get(name, self)
350 if not isinstance(field, type(self)):
351 return # klass contains another value overridden by self
353 if 'default' in field._attrs:
354 # take the default in field, and adapt it for cls._defaults
355 value = field._attrs['default']
358 cls._defaults[name] = lambda model, cr, uid, context: \
359 self.convert_to_write(value(model.browse(cr, uid, [], context)))
361 self.default = lambda recs: value
362 cls._defaults[name] = value
365 defaults = klass.__dict__.get('_defaults') or {}
367 # take the value from _defaults, and adapt it for self.default
368 value = defaults[name]
369 value_func = value if callable(value) else lambda *args: value
370 self.default = lambda recs: self.convert_to_cache(
371 value_func(recs._model, recs._cr, recs._uid, recs._context),
372 recs, validate=False,
374 cls._defaults[name] = value
378 return "%s.%s" % (self.model_name, self.name)
381 return "%s.%s" % (self.model_name, self.name)
383 ############################################################################
389 """ Prepare `self` for a new setup. """
390 self.setup_done = False
391 # self._triggers is a set of pairs (field, path) that represents the
392 # computed fields that depend on `self`. When `self` is modified, it
393 # invalidates the cache of each `field`, and registers the records to
394 # recompute based on `path`. See method `modified` below for details.
395 self._triggers = set()
396 self.inverse_fields = []
398 def setup(self, env):
399 """ Complete the setup of `self` (dependencies, recomputation triggers,
400 and other properties). This method is idempotent: it has no effect
401 if `self` has already been set up.
403 if not self.setup_done:
404 self.setup_done = True
407 def _setup(self, env):
408 """ Do the actual setup of `self`. """
410 self._setup_related(env)
412 self._setup_regular(env)
414 # put invalidation/recomputation triggers on field dependencies
415 model = env[self.model_name]
416 for path in self.depends:
417 self._setup_dependency([], model, path.split('.'))
419 # put invalidation triggers on model dependencies
420 for dep_model_name, field_names in model._depends.iteritems():
421 dep_model = env[dep_model_name]
422 for field_name in field_names:
423 field = dep_model._fields[field_name]
424 field._triggers.add((self, None))
427 # Setup of related fields
430 def _setup_related(self, env):
431 """ Setup the attributes of a related field. """
432 # fix the type of self.related if necessary
433 if isinstance(self.related, basestring):
434 self.related = tuple(self.related.split('.'))
436 # determine the related field, and make sure it is set up
437 recs = env[self.model_name]
438 for name in self.related[:-1]:
440 field = self.related_field = recs._fields[self.related[-1]]
443 # check type consistency
444 if self.type != field.type:
445 raise Warning("Type of related field %s is inconsistent with %s" % (self, field))
447 # determine dependencies, compute, inverse, and search
448 self.depends = ('.'.join(self.related),)
449 self.compute = self._compute_related
450 self.inverse = self._inverse_related
451 if field._description_searchable(env):
452 # allow searching on self only if the related field is searchable
453 self.search = self._search_related
455 # copy attributes from field to self (string, help, etc.)
456 for attr, prop in self.related_attrs:
457 if not getattr(self, attr):
458 setattr(self, attr, getattr(field, prop))
460 def _compute_related(self, records):
461 """ Compute the related field `self` on `records`. """
462 # when related_sudo, bypass access rights checks when reading values
463 others = records.sudo() if self.related_sudo else records
464 for record, other in zip(records, others):
466 # draft record, do not switch to another environment
468 # traverse the intermediate fields; follow the first record at each step
469 for name in self.related[:-1]:
470 other = other[name][:1]
471 record[self.name] = other[self.related[-1]]
473 def _inverse_related(self, records):
474 """ Inverse the related field `self` on `records`. """
475 for record in records:
477 # traverse the intermediate fields, and keep at most one record
478 for name in self.related[:-1]:
479 other = other[name][:1]
481 other[self.related[-1]] = record[self.name]
483 def _search_related(self, records, operator, value):
484 """ Determine the domain to search on field `self`. """
485 return [('.'.join(self.related), operator, value)]
487 # properties used by _setup_related() to copy values from related field
488 _related_string = property(attrgetter('string'))
489 _related_help = property(attrgetter('help'))
490 _related_readonly = property(attrgetter('readonly'))
491 _related_groups = property(attrgetter('groups'))
494 # Setup of non-related fields
497 def _setup_regular(self, env):
498 """ Setup the attributes of a non-related field. """
499 recs = env[self.model_name]
501 def make_depends(deps):
502 return tuple(deps(recs) if callable(deps) else deps)
504 # convert compute into a callable and determine depends
505 if isinstance(self.compute, basestring):
506 # if the compute method has been overridden, concatenate all their _depends
508 for method in resolve_all_mro(type(recs), self.compute, reverse=True):
509 self.depends += make_depends(getattr(method, '_depends', ()))
510 self.compute = getattr(type(recs), self.compute)
512 self.depends = make_depends(getattr(self.compute, '_depends', ()))
514 # convert inverse and search into callables
515 if isinstance(self.inverse, basestring):
516 self.inverse = getattr(type(recs), self.inverse)
517 if isinstance(self.search, basestring):
518 self.search = getattr(type(recs), self.search)
520 def _setup_dependency(self, path0, model, path1):
521 """ Make `self` depend on `model`; `path0 + path1` is a dependency of
522 `self`, and `path0` is the sequence of field names from `self.model`
526 head, tail = path1[0], path1[1:]
529 # special case: add triggers on all fields of model (except self)
530 fields = set(model._fields.itervalues()) - set([self])
532 fields = [model._fields[head]]
536 _logger.debug("Field %s is recursively defined", self)
537 self.recursive = True
542 #_logger.debug("Add trigger on %s to recompute %s", field, self)
543 field._triggers.add((self, '.'.join(path0 or ['id'])))
545 # add trigger on inverse fields, too
546 for invf in field.inverse_fields:
547 #_logger.debug("Add trigger on %s to recompute %s", invf, self)
548 invf._triggers.add((self, '.'.join(path0 + [head])))
550 # recursively traverse the dependency
552 comodel = env[field.comodel_name]
553 self._setup_dependency(path0 + [head], comodel, tail)
556 def dependents(self):
557 """ Return the computed fields that depend on `self`. """
558 return (field for field, path in self._triggers)
560 ############################################################################
565 def get_description(self, env):
566 """ Return a dictionary that describes the field `self`. """
567 desc = {'type': self.type}
568 for attr, prop in self.description_attrs:
569 value = getattr(self, prop)
572 if value is not None:
577 # properties used by get_description()
579 def _description_store(self, env):
581 # if the corresponding column is a function field, check the column
582 column = env[self.model_name]._columns.get(self.name)
583 return bool(getattr(column, 'store', True))
586 def _description_searchable(self, env):
588 column = env[self.model_name]._columns.get(self.name)
589 return bool(getattr(column, 'store', True)) or \
590 bool(getattr(column, '_fnct_search', False))
591 return bool(self.search)
593 def _description_sortable(self, env):
595 column = env[self.model_name]._columns.get(self.name)
596 return bool(getattr(column, 'store', True))
598 # self is sortable if the inherited field is itself sortable
599 return self.related_field._description_sortable(env)
602 _description_manual = property(attrgetter('manual'))
603 _description_depends = property(attrgetter('depends'))
604 _description_related = property(attrgetter('related'))
605 _description_company_dependent = property(attrgetter('company_dependent'))
606 _description_readonly = property(attrgetter('readonly'))
607 _description_required = property(attrgetter('required'))
608 _description_states = property(attrgetter('states'))
609 _description_groups = property(attrgetter('groups'))
610 _description_change_default = property(attrgetter('change_default'))
611 _description_deprecated = property(attrgetter('deprecated'))
613 def _description_string(self, env):
614 if self.string and env.lang:
615 name = "%s,%s" % (self.model_name, self.name)
616 trans = env['ir.translation']._get_source(name, 'field', env.lang)
617 return trans or self.string
620 def _description_help(self, env):
621 if self.help and env.lang:
622 name = "%s,%s" % (self.model_name, self.name)
623 trans = env['ir.translation']._get_source(name, 'help', env.lang)
624 return trans or self.help
627 ############################################################################
629 # Conversion to column instance
633 """ return a low-level field object corresponding to `self` """
636 # some columns are registry-dependent, like float fields (digits);
637 # duplicate them to avoid sharing between registries
638 _logger.debug("Create fields._column for Field %s", self)
640 for attr, prop in self.column_attrs:
641 args[attr] = getattr(self, prop)
642 for attr in self._free_attrs:
643 args[attr] = getattr(self, attr)
645 if self.company_dependent:
646 # company-dependent fields are mapped to former property fields
647 args['type'] = self.type
648 args['relation'] = self.comodel_name
649 return fields.property(**args)
651 if isinstance(self.column, fields.function):
652 # it is too tricky to recreate a function field, so for that case,
653 # we make a stupid (and possibly incorrect) copy of the column
654 return copy(self.column)
656 return getattr(fields, self.type)(**args)
658 # properties used by to_column() to create a column instance
659 _column_copy = property(attrgetter('copyable'))
660 _column_select = property(attrgetter('index'))
661 _column_manual = property(attrgetter('manual'))
662 _column_string = property(attrgetter('string'))
663 _column_help = property(attrgetter('help'))
664 _column_readonly = property(attrgetter('readonly'))
665 _column_required = property(attrgetter('required'))
666 _column_states = property(attrgetter('states'))
667 _column_groups = property(attrgetter('groups'))
668 _column_change_default = property(attrgetter('change_default'))
669 _column_deprecated = property(attrgetter('deprecated'))
671 ############################################################################
673 # Conversion of values
677 """ return the null value for this field in the given environment """
680 def convert_to_cache(self, value, record, validate=True):
681 """ convert `value` to the cache level in `env`; `value` may come from
682 an assignment, or have the format of methods :meth:`BaseModel.read`
683 or :meth:`BaseModel.write`
685 :param record: the target record for the assignment, or an empty recordset
687 :param bool validate: when True, field-specific validation of
688 `value` will be performed
692 def convert_to_read(self, value, use_name_get=True):
693 """ convert `value` from the cache to a value as returned by method
694 :meth:`BaseModel.read`
696 :param bool use_name_get: when True, value's diplay name will
697 be computed using :meth:`BaseModel.name_get`, if relevant
700 return False if value is None else value
702 def convert_to_write(self, value, target=None, fnames=None):
703 """ convert `value` from the cache to a valid value for method
704 :meth:`BaseModel.write`.
706 :param target: optional, the record to be modified with this value
707 :param fnames: for relational fields only, an optional collection of
708 field names to convert
710 return self.convert_to_read(value)
712 def convert_to_onchange(self, value):
713 """ convert `value` from the cache to a valid value for an onchange
716 return self.convert_to_write(value)
718 def convert_to_export(self, value, env):
719 """ convert `value` from the cache to a valid value for export. The
720 parameter `env` is given for managing translations.
722 if env.context.get('export_raw_data'):
724 return bool(value) and ustr(value)
726 def convert_to_display_name(self, value):
727 """ convert `value` from the cache to a suitable display name. """
730 ############################################################################
735 def __get__(self, record, owner):
736 """ return the value of field `self` on `record` """
738 return self # the field is accessed through the owner class
741 # null record -> return the null value for this field
742 return self.null(record.env)
744 # only a single record may be accessed
748 return record._cache[self]
752 # cache miss, retrieve value
754 # normal record -> read or compute value for this field
755 self.determine_value(record)
757 # new record -> compute default value for this field
758 record.add_default_value(self)
760 # the result should be in cache now
761 return record._cache[self]
763 def __set__(self, record, value):
764 """ set the value of field `self` on `record` """
767 # only a single record may be updated
770 # adapt value to the cache level
771 value = self.convert_to_cache(value, record)
773 if env.in_draft or not record.id:
774 # determine dependent fields
775 spec = self.modified_draft(record)
777 # set value in cache, inverse field, and mark record as dirty
778 record._cache[self] = value
780 for invf in self.inverse_fields:
781 invf._update(value, record)
784 # determine more dependent fields, and invalidate them
786 spec += self.modified_draft(record)
790 # simply write to the database, and update cache
791 record.write({self.name: self.convert_to_write(value)})
792 record._cache[self] = value
794 ############################################################################
796 # Computation of field values
799 def _compute_value(self, records):
800 """ Invoke the compute method on `records`. """
801 # mark the computed fields failed in cache, so that access before
802 # computation raises an exception
803 exc = Warning("Field %s is accessed before being computed." % self)
804 for field in self.computed_fields:
805 records._cache[field] = FailedValue(exc)
806 records.env.computed[field].update(records._ids)
807 self.compute(records)
808 for field in self.computed_fields:
809 records.env.computed[field].difference_update(records._ids)
811 def compute_value(self, records):
812 """ Invoke the compute method on `records`; the results are in cache. """
813 with records.env.do_in_draft():
815 self._compute_value(records)
816 except (AccessError, MissingError):
817 # some record is forbidden or missing, retry record by record
818 for record in records:
820 self._compute_value(record)
821 except Exception as exc:
822 record._cache[self.name] = FailedValue(exc)
824 def determine_value(self, record):
825 """ Determine the value of `self` for `record`. """
828 if self.store and not (self.depends and env.in_draft):
829 # this is a stored field
831 # this is a stored computed field, check for recomputation
832 recs = record._recompute_check(self)
834 # recompute the value (only in cache)
835 self.compute_value(recs)
836 # HACK: if result is in the wrong cache, copy values
838 for source, target in zip(recs, recs.with_env(env)):
840 values = target._convert_to_cache({
841 f.name: source[f.name] for f in self.computed_fields
843 except MissingError as e:
844 values = FailedValue(e)
845 target._cache.update(values)
846 # the result is saved to database by BaseModel.recompute()
849 # read the field from database
850 record._prefetch_field(self)
853 # this is either a non-stored computed field, or a stored computed
854 # field in draft mode
856 self.compute_value(record)
858 recs = record._in_cache_without(self)
859 self.compute_value(recs)
862 # this is a non-stored non-computed field
863 record._cache[self] = self.null(env)
865 def determine_default(self, record):
866 """ determine the default value of field `self` on `record` """
868 value = self.default(record)
869 record._cache[self] = self.convert_to_cache(value, record)
871 self._compute_value(record)
873 record._cache[self] = SpecialValue(self.null(record.env))
875 def determine_inverse(self, records):
876 """ Given the value of `self` on `records`, inverse the computation. """
878 self.inverse(records)
880 def determine_domain(self, records, operator, value):
881 """ Return a domain representing a condition on `self`. """
883 return self.search(records, operator, value)
885 return [(self.name, operator, value)]
887 ############################################################################
889 # Notification when fields are modified
892 def modified(self, records):
893 """ Notify that field `self` has been modified on `records`: prepare the
894 fields/records to recompute, and return a spec indicating what to
897 # invalidate the fields that depend on self, and prepare recomputation
898 spec = [(self, records._ids)]
899 for field, path in self._triggers:
900 if path and field.store:
901 # don't move this line to function top, see log
902 env = records.env(user=SUPERUSER_ID, context={'active_test': False})
903 target = env[field.model_name].search([(path, 'in', records.ids)])
905 spec.append((field, target._ids))
906 target.with_env(records.env)._recompute_todo(field)
908 spec.append((field, None))
912 def modified_draft(self, records):
913 """ Same as :meth:`modified`, but in draft mode. """
916 # invalidate the fields on the records in cache that depend on
917 # `records`, except fields currently being computed
919 for field, path in self._triggers:
920 target = env[field.model_name]
921 computed = target.browse(env.computed[field])
923 target = records - computed
925 target = (target.browse(env.cache[field]) - computed).filtered(
926 lambda rec: rec._mapped_cache(path) & records
929 target = target.browse(env.cache[field]) - computed
932 spec.append((field, target._ids))
937 class Boolean(Field):
940 def convert_to_cache(self, value, record, validate=True):
943 def convert_to_export(self, value, env):
944 if env.context.get('export_raw_data'):
949 class Integer(Field):
952 def convert_to_cache(self, value, record, validate=True):
953 if isinstance(value, dict):
954 # special case, when an integer field is used as inverse for a one2many
955 return value.get('id', False)
956 return int(value or 0)
958 def convert_to_read(self, value, use_name_get=True):
959 # Integer values greater than 2^31-1 are not supported in pure XMLRPC,
960 # so we have to pass them as floats :-(
961 if value and value > xmlrpclib.MAXINT:
965 def _update(self, records, value):
966 # special case, when an integer field is used as inverse for a one2many
967 records._cache[self] = value.id or 0
971 """ The precision digits are given by the attribute
973 :param digits: a pair (total, decimal), or a function taking a database
974 cursor and returning a pair (total, decimal)
977 _digits = None # digits argument passed to class initializer
978 digits = None # digits as computed by setup()
980 def __init__(self, string=None, digits=None, **kwargs):
981 super(Float, self).__init__(string=string, _digits=digits, **kwargs)
983 def _setup_digits(self, env):
984 """ Setup the digits for `self` and its corresponding column """
985 self.digits = self._digits(env.cr) if callable(self._digits) else self._digits
987 column = env[self.model_name]._columns[self.name]
988 column.digits_change(env.cr)
990 def _setup_regular(self, env):
991 super(Float, self)._setup_regular(env)
992 self._setup_digits(env)
994 _related_digits = property(attrgetter('digits'))
996 _description_digits = property(attrgetter('digits'))
998 _column_digits = property(lambda self: not callable(self._digits) and self._digits)
999 _column_digits_compute = property(lambda self: callable(self._digits) and self._digits)
1001 def convert_to_cache(self, value, record, validate=True):
1002 # apply rounding here, otherwise value in cache may be wrong!
1004 return float_round(float(value or 0.0), precision_digits=self.digits[1])
1006 return float(value or 0.0)
1009 class _String(Field):
1010 """ Abstract class for string fields. """
1013 _column_translate = property(attrgetter('translate'))
1014 _related_translate = property(attrgetter('translate'))
1015 _description_translate = property(attrgetter('translate'))
1018 class Char(_String):
1019 """ Basic string field, can be length-limited, usually displayed as a
1020 single-line string in clients
1022 :param int size: the maximum size of values stored for that field
1023 :param bool translate: whether the values of this field can be translated
1028 _column_size = property(attrgetter('size'))
1029 _related_size = property(attrgetter('size'))
1030 _description_size = property(attrgetter('size'))
1032 def convert_to_cache(self, value, record, validate=True):
1033 if value is None or value is False:
1035 return ustr(value)[:self.size]
1037 class Text(_String):
1038 """ Text field. Very similar to :class:`~.Char` but used for longer
1039 contents and displayed as a multiline text box
1041 :param translate: whether the value of this field can be translated
1045 def convert_to_cache(self, value, record, validate=True):
1046 if value is None or value is False:
1050 class Html(_String):
1052 sanitize = True # whether value must be sanitized
1054 _column_sanitize = property(attrgetter('sanitize'))
1055 _related_sanitize = property(attrgetter('sanitize'))
1056 _description_sanitize = property(attrgetter('sanitize'))
1058 def convert_to_cache(self, value, record, validate=True):
1059 if value is None or value is False:
1061 if validate and self.sanitize:
1062 return html_sanitize(value)
1071 """ Return the current day in the format expected by the ORM.
1072 This function may be used to compute default values.
1074 return date.today().strftime(DATE_FORMAT)
1077 def context_today(record, timestamp=None):
1078 """ Return the current date as seen in the client's timezone in a format
1079 fit for date fields. This method may be used to compute default
1082 :param datetime timestamp: optional datetime value to use instead of
1083 the current date and time (must be a datetime, regular dates
1084 can't be converted between timezones.)
1087 today = timestamp or datetime.now()
1088 context_today = None
1089 tz_name = record._context.get('tz') or record.env.user.tz
1092 today_utc = pytz.timezone('UTC').localize(today, is_dst=False) # UTC = no DST
1093 context_today = today_utc.astimezone(pytz.timezone(tz_name))
1095 _logger.debug("failed to compute context/client-specific today date, using UTC value for `today`",
1097 return (context_today or today).strftime(DATE_FORMAT)
1100 def from_string(value):
1101 """ Convert an ORM `value` into a :class:`date` value. """
1102 value = value[:DATE_LENGTH]
1103 return datetime.strptime(value, DATE_FORMAT).date()
1106 def to_string(value):
1107 """ Convert a :class:`date` value into the format expected by the ORM. """
1108 return value.strftime(DATE_FORMAT)
1110 def convert_to_cache(self, value, record, validate=True):
1113 if isinstance(value, basestring):
1115 # force parsing for validation
1116 self.from_string(value)
1117 return value[:DATE_LENGTH]
1118 return self.to_string(value)
1120 def convert_to_export(self, value, env):
1121 if value and env.context.get('export_raw_data'):
1122 return self.from_string(value)
1123 return bool(value) and ustr(value)
1126 class Datetime(Field):
1131 """ Return the current day and time in the format expected by the ORM.
1132 This function may be used to compute default values.
1134 return datetime.now().strftime(DATETIME_FORMAT)
1137 def context_timestamp(record, timestamp):
1138 """Returns the given timestamp converted to the client's timezone.
1139 This method is *not* meant for use as a _defaults initializer,
1140 because datetime fields are automatically converted upon
1141 display on client side. For _defaults you :meth:`fields.datetime.now`
1142 should be used instead.
1144 :param datetime timestamp: naive datetime value (expressed in UTC)
1145 to be converted to the client timezone
1147 :return: timestamp converted to timezone-aware datetime in context
1150 assert isinstance(timestamp, datetime), 'Datetime instance expected'
1151 tz_name = record._context.get('tz') or record.env.user.tz
1154 utc = pytz.timezone('UTC')
1155 context_tz = pytz.timezone(tz_name)
1156 utc_timestamp = utc.localize(timestamp, is_dst=False) # UTC = no DST
1157 return utc_timestamp.astimezone(context_tz)
1159 _logger.debug("failed to compute context/client-specific timestamp, "
1160 "using the UTC value",
1165 def from_string(value):
1166 """ Convert an ORM `value` into a :class:`datetime` value. """
1167 value = value[:DATETIME_LENGTH]
1168 if len(value) == DATE_LENGTH:
1169 value += " 00:00:00"
1170 return datetime.strptime(value, DATETIME_FORMAT)
1173 def to_string(value):
1174 """ Convert a :class:`datetime` value into the format expected by the ORM. """
1175 return value.strftime(DATETIME_FORMAT)
1177 def convert_to_cache(self, value, record, validate=True):
1180 if isinstance(value, basestring):
1182 # force parsing for validation
1183 self.from_string(value)
1184 value = value[:DATETIME_LENGTH]
1185 if len(value) == DATE_LENGTH:
1186 value += " 00:00:00"
1188 return self.to_string(value)
1190 def convert_to_export(self, value, env):
1191 if value and env.context.get('export_raw_data'):
1192 return self.from_string(value)
1193 return bool(value) and ustr(value)
1196 class Binary(Field):
1200 class Selection(Field):
1202 :param selection: specifies the possible values for this field.
1203 It is given as either a list of pairs (`value`, `string`), or a
1204 model method, or a method name.
1205 :param selection_add: provides an extension of the selection in the case
1206 of an overridden field. It is a list of pairs (`value`, `string`).
1208 The attribute `selection` is mandatory except in the case of
1209 :ref:`related fields <field-related>` or :ref:`field extensions
1210 <field-incremental-definition>`.
1213 selection = None # [(value, string), ...], function or method name
1214 selection_add = None # [(value, string), ...]
1216 def __init__(self, selection=None, string=None, **kwargs):
1217 if callable(selection):
1218 from openerp import api
1219 selection = api.expected(api.model, selection)
1220 super(Selection, self).__init__(selection=selection, string=string, **kwargs)
1222 def _setup_related(self, env):
1223 super(Selection, self)._setup_related(env)
1224 # selection must be computed on related field
1225 field = self.related_field
1226 self.selection = lambda model: field._description_selection(model.env)
1228 def set_class_name(self, cls, name):
1229 super(Selection, self).set_class_name(cls, name)
1230 # determine selection (applying 'selection_add' extensions)
1232 for field in resolve_all_mro(cls, name, reverse=True):
1233 if isinstance(field, type(self)):
1234 # We cannot use field.selection or field.selection_add here
1235 # because those attributes are overridden by `set_class_name`.
1236 if 'selection' in field._attrs:
1237 selection = field._attrs['selection']
1238 if 'selection_add' in field._attrs:
1239 selection = selection + field._attrs['selection_add']
1242 self.selection = selection
1244 def _description_selection(self, env):
1245 """ return the selection list (pairs (value, label)); labels are
1246 translated according to context language
1248 selection = self.selection
1249 if isinstance(selection, basestring):
1250 return getattr(env[self.model_name], selection)()
1251 if callable(selection):
1252 return selection(env[self.model_name])
1254 # translate selection labels
1256 name = "%s,%s" % (self.model_name, self.name)
1257 translate = partial(
1258 env['ir.translation']._get_source, name, 'selection', env.lang)
1259 return [(value, translate(label)) for value, label in selection]
1264 def _column_selection(self):
1265 if isinstance(self.selection, basestring):
1266 method = self.selection
1267 return lambda self, *a, **kw: getattr(self, method)(*a, **kw)
1269 return self.selection
1271 def get_values(self, env):
1272 """ return a list of the possible values """
1273 selection = self.selection
1274 if isinstance(selection, basestring):
1275 selection = getattr(env[self.model_name], selection)()
1276 elif callable(selection):
1277 selection = selection(env[self.model_name])
1278 return [value for value, _ in selection]
1280 def convert_to_cache(self, value, record, validate=True):
1282 return value or False
1283 if value in self.get_values(record.env):
1287 raise ValueError("Wrong value for %s: %r" % (self, value))
1289 def convert_to_export(self, value, env):
1290 if not isinstance(self.selection, list):
1291 # FIXME: this reproduces an existing buggy behavior!
1293 for item in self._description_selection(env):
1294 if item[0] == value:
1299 class Reference(Selection):
1303 def __init__(self, selection=None, string=None, **kwargs):
1304 super(Reference, self).__init__(selection=selection, string=string, **kwargs)
1306 _related_size = property(attrgetter('size'))
1308 _column_size = property(attrgetter('size'))
1310 def convert_to_cache(self, value, record, validate=True):
1311 if isinstance(value, BaseModel):
1312 if ((not validate or value._name in self.get_values(record.env))
1313 and len(value) <= 1):
1314 return value.with_env(record.env) or False
1315 elif isinstance(value, basestring):
1316 res_model, res_id = value.split(',')
1317 return record.env[res_model].browse(int(res_id))
1320 raise ValueError("Wrong value for %s: %r" % (self, value))
1322 def convert_to_read(self, value, use_name_get=True):
1323 return "%s,%s" % (value._name, value.id) if value else False
1325 def convert_to_export(self, value, env):
1326 return bool(value) and value.name_get()[0][1]
1328 def convert_to_display_name(self, value):
1329 return ustr(value and value.display_name)
1332 class _Relational(Field):
1333 """ Abstract class for relational fields. """
1335 domain = None # domain for searching values
1336 context = None # context for searching values
1338 _description_relation = property(attrgetter('comodel_name'))
1339 _description_context = property(attrgetter('context'))
1341 def _description_domain(self, env):
1342 return self.domain(env[self.model_name]) if callable(self.domain) else self.domain
1344 _column_obj = property(attrgetter('comodel_name'))
1345 _column_domain = property(attrgetter('domain'))
1346 _column_context = property(attrgetter('context'))
1348 def null(self, env):
1349 return env[self.comodel_name]
1351 def modified(self, records):
1352 # Invalidate cache for self.inverse_fields, too. Note that recomputation
1353 # of fields that depend on self.inverse_fields is already covered by the
1354 # triggers (see above).
1355 spec = super(_Relational, self).modified(records)
1356 for invf in self.inverse_fields:
1357 spec.append((invf, None))
1361 class Many2one(_Relational):
1362 """ The value of such a field is a recordset of size 0 (no
1363 record) or 1 (a single record).
1365 :param comodel_name: name of the target model (string)
1367 :param domain: an optional domain to set on candidate values on the
1368 client side (domain or string)
1370 :param context: an optional context to use on the client side when
1371 handling that field (dictionary)
1373 :param ondelete: what to do when the referred record is deleted;
1374 possible values are: ``'set null'``, ``'restrict'``, ``'cascade'``
1376 :param auto_join: whether JOINs are generated upon search through that
1377 field (boolean, by default ``False``)
1379 :param delegate: set it to ``True`` to make fields of the target model
1380 accessible from the current model (corresponds to ``_inherits``)
1382 The attribute `comodel_name` is mandatory except in the case of related
1383 fields or field extensions.
1386 ondelete = 'set null' # what to do when value is deleted
1387 auto_join = False # whether joins are generated upon search
1388 delegate = False # whether self implements delegation
1390 def __init__(self, comodel_name=None, string=None, **kwargs):
1391 super(Many2one, self).__init__(comodel_name=comodel_name, string=string, **kwargs)
1393 def set_class_name(self, cls, name):
1394 super(Many2one, self).set_class_name(cls, name)
1395 # determine self.delegate
1396 if not self.delegate:
1397 self.delegate = name in cls._inherits.values()
1399 _column_ondelete = property(attrgetter('ondelete'))
1400 _column_auto_join = property(attrgetter('auto_join'))
1402 def _update(self, records, value):
1403 """ Update the cached value of `self` for `records` with `value`. """
1404 records._cache[self] = value
1406 def convert_to_cache(self, value, record, validate=True):
1407 if isinstance(value, (NoneType, int)):
1408 return record.env[self.comodel_name].browse(value)
1409 if isinstance(value, BaseModel):
1410 if value._name == self.comodel_name and len(value) <= 1:
1411 return value.with_env(record.env)
1412 raise ValueError("Wrong value for %s: %r" % (self, value))
1413 elif isinstance(value, tuple):
1414 return record.env[self.comodel_name].browse(value[0])
1415 elif isinstance(value, dict):
1416 return record.env[self.comodel_name].new(value)
1418 return record.env[self.comodel_name].browse(value)
1420 def convert_to_read(self, value, use_name_get=True):
1421 if use_name_get and value:
1422 # evaluate name_get() as superuser, because the visibility of a
1423 # many2one field value (id and name) depends on the current record's
1424 # access rights, and not the value's access rights.
1426 return value.sudo().name_get()[0]
1427 except MissingError:
1428 # Should not happen, unless the foreign key is missing.
1433 def convert_to_write(self, value, target=None, fnames=None):
1436 def convert_to_onchange(self, value):
1439 def convert_to_export(self, value, env):
1440 return bool(value) and value.name_get()[0][1]
1442 def convert_to_display_name(self, value):
1443 return ustr(value.display_name)
1445 def determine_default(self, record):
1446 super(Many2one, self).determine_default(record)
1448 # special case: fields that implement inheritance between models
1449 value = record[self.name]
1451 # the default value cannot be null, use a new record instead
1452 record[self.name] = record.env[self.comodel_name].new()
1455 class UnionUpdate(SpecialValue):
1456 """ Placeholder for a value update; when this value is taken from the cache,
1457 it returns ``record[field.name] | value`` and stores it in the cache.
1459 def __init__(self, field, record, value):
1460 self.args = (field, record, value)
1463 field, record, value = self.args
1464 # in order to read the current field's value, remove self from cache
1465 del record._cache[field]
1466 # read the current field's value, and update it in cache only
1467 record._cache[field] = new_value = record[field.name] | value
1471 class _RelationalMulti(_Relational):
1472 """ Abstract class for relational fields *2many. """
1474 def _update(self, records, value):
1475 """ Update the cached value of `self` for `records` with `value`. """
1476 for record in records:
1477 if self in record._cache:
1478 record._cache[self] = record[self.name] | value
1480 record._cache[self] = UnionUpdate(self, record, value)
1482 def convert_to_cache(self, value, record, validate=True):
1483 if isinstance(value, BaseModel):
1484 if value._name == self.comodel_name:
1485 return value.with_env(record.env)
1486 elif isinstance(value, list):
1487 # value is a list of record ids or commands
1489 record = record.browse() # new record has no value
1490 result = record[self.name]
1491 # modify result with the commands;
1492 # beware to not introduce duplicates in result
1493 for command in value:
1494 if isinstance(command, (tuple, list)):
1496 result += result.new(command[2])
1497 elif command[0] == 1:
1498 result.browse(command[1]).update(command[2])
1499 result += result.browse(command[1]) - result
1500 elif command[0] == 2:
1501 # note: the record will be deleted by write()
1502 result -= result.browse(command[1])
1503 elif command[0] == 3:
1504 result -= result.browse(command[1])
1505 elif command[0] == 4:
1506 result += result.browse(command[1]) - result
1507 elif command[0] == 5:
1508 result = result.browse()
1509 elif command[0] == 6:
1510 result = result.browse(command[2])
1511 elif isinstance(command, dict):
1512 result += result.new(command)
1514 result += result.browse(command) - result
1517 return self.null(record.env)
1518 raise ValueError("Wrong value for %s: %s" % (self, value))
1520 def convert_to_read(self, value, use_name_get=True):
1523 def convert_to_write(self, value, target=None, fnames=None):
1524 # remove/delete former records
1527 result = [(6, 0, set_ids)]
1528 add_existing = lambda id: set_ids.append(id)
1530 tag = 2 if self.type == 'one2many' else 3
1531 result = [(tag, record.id) for record in target[self.name] - value]
1532 add_existing = lambda id: result.append((4, id))
1535 # take all fields in cache, except the inverses of self
1536 fnames = set(value._fields) - set(MAGIC_COLUMNS)
1537 for invf in self.inverse_fields:
1538 fnames.discard(invf.name)
1540 # add new and existing records
1541 for record in value:
1542 if not record.id or record._dirty:
1543 values = dict((k, v) for k, v in record._cache.iteritems() if k in fnames)
1544 values = record._convert_to_write(values)
1546 result.append((0, 0, values))
1548 result.append((1, record.id, values))
1550 add_existing(record.id)
1554 def convert_to_export(self, value, env):
1555 return bool(value) and ','.join(name for id, name in value.name_get())
1557 def convert_to_display_name(self, value):
1558 raise NotImplementedError()
1560 def _compute_related(self, records):
1561 """ Compute the related field `self` on `records`. """
1562 for record in records:
1564 # traverse the intermediate fields, and keep at most one record
1565 for name in self.related[:-1]:
1566 value = value[name][:1]
1567 record[self.name] = value[self.related[-1]]
1570 class One2many(_RelationalMulti):
1571 """ One2many field; the value of such a field is the recordset of all the
1572 records in `comodel_name` such that the field `inverse_name` is equal to
1575 :param comodel_name: name of the target model (string)
1577 :param inverse_name: name of the inverse `Many2one` field in
1578 `comodel_name` (string)
1580 :param domain: an optional domain to set on candidate values on the
1581 client side (domain or string)
1583 :param context: an optional context to use on the client side when
1584 handling that field (dictionary)
1586 :param auto_join: whether JOINs are generated upon search through that
1587 field (boolean, by default ``False``)
1589 :param limit: optional limit to use upon read (integer)
1591 The attributes `comodel_name` and `inverse_name` are mandatory except in
1592 the case of related fields or field extensions.
1595 inverse_name = None # name of the inverse field
1596 auto_join = False # whether joins are generated upon search
1597 limit = None # optional limit to use upon read
1598 copyable = False # o2m are not copied by default
1600 def __init__(self, comodel_name=None, inverse_name=None, string=None, **kwargs):
1601 super(One2many, self).__init__(
1602 comodel_name=comodel_name,
1603 inverse_name=inverse_name,
1608 def _setup_regular(self, env):
1609 super(One2many, self)._setup_regular(env)
1611 if self.inverse_name:
1612 # link self to its inverse field and vice-versa
1613 invf = env[self.comodel_name]._fields[self.inverse_name]
1614 # In some rare cases, a `One2many` field can link to `Int` field
1615 # (res_model/res_id pattern). Only inverse the field if this is
1616 # a `Many2one` field.
1617 if isinstance(invf, Many2one):
1618 self.inverse_fields.append(invf)
1619 invf.inverse_fields.append(self)
1621 _description_relation_field = property(attrgetter('inverse_name'))
1623 _column_fields_id = property(attrgetter('inverse_name'))
1624 _column_auto_join = property(attrgetter('auto_join'))
1625 _column_limit = property(attrgetter('limit'))
1628 class Many2many(_RelationalMulti):
1629 """ Many2many field; the value of such a field is the recordset.
1631 :param comodel_name: name of the target model (string)
1633 The attribute `comodel_name` is mandatory except in the case of related
1634 fields or field extensions.
1636 :param relation: optional name of the table that stores the relation in
1637 the database (string)
1639 :param column1: optional name of the column referring to "these" records
1640 in the table `relation` (string)
1642 :param column2: optional name of the column referring to "those" records
1643 in the table `relation` (string)
1645 The attributes `relation`, `column1` and `column2` are optional. If not
1646 given, names are automatically generated from model names, provided
1647 `model_name` and `comodel_name` are different!
1649 :param domain: an optional domain to set on candidate values on the
1650 client side (domain or string)
1652 :param context: an optional context to use on the client side when
1653 handling that field (dictionary)
1655 :param limit: optional limit to use upon read (integer)
1659 relation = None # name of table
1660 column1 = None # column of table referring to model
1661 column2 = None # column of table referring to comodel
1662 limit = None # optional limit to use upon read
1664 def __init__(self, comodel_name=None, relation=None, column1=None, column2=None,
1665 string=None, **kwargs):
1666 super(Many2many, self).__init__(
1667 comodel_name=comodel_name,
1675 def _setup_regular(self, env):
1676 super(Many2many, self)._setup_regular(env)
1678 if self.store and not self.relation:
1679 model = env[self.model_name]
1680 column = model._columns[self.name]
1681 if not isinstance(column, fields.function):
1682 self.relation, self.column1, self.column2 = column._sql_names(model)
1685 m2m = env.registry._m2m
1686 # if inverse field has already been setup, it is present in m2m
1687 invf = m2m.get((self.relation, self.column2, self.column1))
1689 self.inverse_fields.append(invf)
1690 invf.inverse_fields.append(self)
1692 # add self in m2m, so that its inverse field can find it
1693 m2m[(self.relation, self.column1, self.column2)] = self
1695 _column_rel = property(attrgetter('relation'))
1696 _column_id1 = property(attrgetter('column1'))
1697 _column_id2 = property(attrgetter('column2'))
1698 _column_limit = property(attrgetter('limit'))
1702 """ Special case for field 'id'. """
1704 #: Can't write this!
1707 def __init__(self, string=None, **kwargs):
1708 super(Id, self).__init__(type='integer', string=string, **kwargs)
1710 def to_column(self):
1711 """ to_column() -> fields._column
1715 return fields.integer('ID')
1717 def __get__(self, record, owner):
1719 return self # the field is accessed through the class owner
1722 return record.ensure_one()._ids[0]
1724 def __set__(self, record, value):
1725 raise TypeError("field 'id' cannot be assigned")
1728 # imported here to avoid dependency cycle issues
1729 from openerp import SUPERUSER_ID
1730 from .exceptions import Warning, AccessError, MissingError
1731 from .models import BaseModel, MAGIC_COLUMNS
1732 from .osv import fields