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. """
24 from datetime import date, datetime
25 from functools import partial
26 from operator import attrgetter
27 from types import NoneType
32 from openerp.tools import float_round, ustr, html_sanitize
33 from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as DATE_FORMAT
34 from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT as DATETIME_FORMAT
36 DATE_LENGTH = len(date.today().strftime(DATE_FORMAT))
37 DATETIME_LENGTH = len(datetime.now().strftime(DATETIME_FORMAT))
39 _logger = logging.getLogger(__name__)
41 class SpecialValue(object):
42 """ Encapsulates a value in the cache in place of a normal value. """
43 def __init__(self, value):
48 class FailedValue(SpecialValue):
49 """ Special value that encapsulates an exception instead of a value. """
50 def __init__(self, exception):
51 self.exception = exception
55 def _check_value(value):
56 """ Return `value`, or call its getter if `value` is a :class:`SpecialValue`. """
57 return value.get() if isinstance(value, SpecialValue) else value
60 def resolve_all_mro(cls, name, reverse=False):
61 """ Return the (successively overridden) values of attribute `name` in `cls`
62 in mro order, or inverse mro order if `reverse` is true.
64 klasses = reversed(cls.__mro__) if reverse else cls.__mro__
66 if name in klass.__dict__:
67 yield klass.__dict__[name]
70 class MetaField(type):
71 """ Metaclass for field classes. """
74 def __init__(cls, name, bases, attrs):
75 super(MetaField, cls).__init__(name, bases, attrs)
77 cls.by_type[cls.type] = cls
79 # compute class attributes to avoid calling dir() on fields
81 cls.related_attrs = []
82 cls.description_attrs = []
84 if attr.startswith('_column_'):
85 cls.column_attrs.append((attr[8:], attr))
86 elif attr.startswith('_related_'):
87 cls.related_attrs.append((attr[9:], attr))
88 elif attr.startswith('_description_'):
89 cls.description_attrs.append((attr[13:], attr))
93 """ The field descriptor contains the field definition, and manages accesses
94 and assignments of the corresponding field on records. The following
95 attributes may be provided when instanciating a field:
97 :param string: the label of the field seen by users (string); if not
98 set, the ORM takes the field name in the class (capitalized).
100 :param help: the tooltip of the field seen by users (string)
102 :param readonly: whether the field is readonly (boolean, by default ``False``)
104 :param required: whether the value of the field is required (boolean, by
107 :param index: whether the field is indexed in database (boolean, by
110 :param default: the default value for the field; this is either a static
111 value, or a function taking a recordset and returning a value
113 :param states: a dictionary mapping state values to lists of UI attribute-value
114 pairs; possible attributes are: 'readonly', 'required', 'invisible'.
115 Note: Any state-based condition requires the ``state`` field value to be
116 available on the client-side UI. This is typically done by including it in
117 the relevant views, possibly made invisible if not relevant for the
120 :param groups: comma-separated list of group xml ids (string); this
121 restricts the field access to the users of the given groups only
123 :param bool copy: whether the field value should be copied when the record
124 is duplicated (default: ``True`` for normal fields, ``False`` for
125 ``one2many`` and computed fields, including property fields and
128 :param string oldname: the previous name of this field, so that ORM can rename
129 it automatically at migration
133 .. rubric:: Computed fields
135 One can define a field whose value is computed instead of simply being
136 read from the database. The attributes that are specific to computed
137 fields are given below. To define such a field, simply provide a value
138 for the attribute `compute`.
140 :param compute: name of a method that computes the field
142 :param inverse: name of a method that inverses the field (optional)
144 :param search: name of a method that implement search on the field (optional)
146 :param store: whether the field is stored in database (boolean, by
147 default ``False`` on computed fields)
149 The methods given for `compute`, `inverse` and `search` are model
150 methods. Their signature is shown in the following example::
152 upper = fields.Char(compute='_compute_upper',
153 inverse='_inverse_upper',
154 search='_search_upper')
157 def _compute_upper(self):
159 self.upper = self.name.upper() if self.name else False
161 def _inverse_upper(self):
163 self.name = self.upper.lower() if self.upper else False
165 def _search_upper(self, operator, value):
166 if operator == 'like':
168 return [('name', operator, value)]
170 The compute method has to assign the field on all records of the invoked
171 recordset. The decorator :meth:`openerp.api.depends` must be applied on
172 the compute method to specify the field dependencies; those dependencies
173 are used to determine when to recompute the field; recomputation is
174 automatic and guarantees cache/database consistency. Note that the same
175 method can be used for several fields, you simply have to assign all the
176 given fields in the method; the method will be invoked once for all
179 By default, a computed field is not stored to the database, and is
180 computed on-the-fly. Adding the attribute ``store=True`` will store the
181 field's values in the database. The advantage of a stored field is that
182 searching on that field is done by the database itself. The disadvantage
183 is that it requires database updates when the field must be recomputed.
185 The inverse method, as its name says, does the inverse of the compute
186 method: the invoked records have a value for the field, and you must
187 apply the necessary changes on the field dependencies such that the
188 computation gives the expected value. Note that a computed field without
189 an inverse method is readonly by default.
191 The search method is invoked when processing domains before doing an
192 actual search on the model. It must return a domain equivalent to the
193 condition: `field operator value`.
197 .. rubric:: Related fields
199 The value of a related field is given by following a sequence of
200 relational fields and reading a field on the reached model. The complete
201 sequence of fields to traverse is specified by the attribute
203 :param related: sequence of field names
205 The value of some attributes from related fields are automatically taken
206 from the source field, when it makes sense. Examples are the attributes
207 `string` or `selection` on selection fields.
209 By default, the values of related fields are not stored to the database.
210 Add the attribute ``store=True`` to make it stored, just like computed
211 fields. Related fields are automatically recomputed when their
212 dependencies are modified.
214 .. _field-company-dependent:
216 .. rubric:: Company-dependent fields
218 Formerly known as 'property' fields, the value of those fields depends
219 on the company. In other words, users that belong to different companies
220 may see different values for the field on a given record.
222 :param company_dependent: whether the field is company-dependent (boolean)
224 .. _field-incremental-definition:
226 .. rubric:: Incremental definition
228 A field is defined as class attribute on a model class. If the model
229 is extended (see :class:`~openerp.models.Model`), one can also extend
230 the field definition by redefining a field with the same name and same
231 type on the subclass. In that case, the attributes of the field are
232 taken from the parent class and overridden by the ones given in
235 For instance, the second class below only adds a tooltip on the field
238 class First(models.Model):
240 state = fields.Selection([...], required=True)
242 class Second(models.Model):
244 state = fields.Selection(help="Blah blah blah")
247 __metaclass__ = MetaField
249 _attrs = None # dictionary with all field attributes
250 _free_attrs = None # list of semantic-free attribute names
252 automatic = False # whether the field is automatically created ("magic" field)
253 inherited = False # whether the field is inherited (_inherits)
254 column = None # the column corresponding to the field
255 setup_done = False # whether the field has been set up
257 name = None # name of the field
258 type = None # type of the field (string)
259 relational = False # whether the field is a relational one
260 model_name = None # name of the model of this field
261 comodel_name = None # name of the model of values (if relational)
262 inverse_fields = None # list of inverse fields (objects)
264 store = True # whether the field is stored in database
265 index = False # whether the field is indexed in database
266 manual = False # whether the field is a custom field
267 copy = True # whether the field is copied over by BaseModel.copy()
268 depends = () # collection of field dependencies
269 recursive = False # whether self depends on itself
270 compute = None # compute(recs) computes field on recs
271 inverse = None # inverse(recs) inverses field on recs
272 search = None # search(recs, operator, value) searches on self
273 related = None # sequence of field names, for related fields
274 related_sudo = True # whether related fields should be read as admin
275 company_dependent = False # whether `self` is company-dependent (property field)
276 default = None # default(recs) returns the default value
278 string = None # field label
279 help = None # field tooltip
283 groups = False # csv list of group xml ids
284 change_default = None # whether the field may trigger a "user-onchange"
285 deprecated = None # whether the field is ... deprecated
287 def __init__(self, string=None, **kwargs):
288 kwargs['string'] = string
289 self._attrs = {key: val for key, val in kwargs.iteritems() if val is not None}
290 self._free_attrs = []
292 def new(self, **kwargs):
293 """ Return a field of the same type as `self`, with its own parameters. """
294 return type(self)(**kwargs)
296 def set_class_name(self, cls, name):
297 """ Assign the model class and field name of `self`. """
298 self.model_name = cls._name
301 # determine all inherited field attributes
303 for field in resolve_all_mro(cls, name, reverse=True):
304 if isinstance(field, type(self)):
305 attrs.update(field._attrs)
308 attrs.update(self._attrs) # necessary in case self is not in cls
310 # initialize `self` with `attrs`
311 if attrs.get('compute'):
312 # by default, computed fields are not stored, not copied and readonly
313 attrs['store'] = attrs.get('store', False)
314 attrs['copy'] = attrs.get('copy', False)
315 attrs['readonly'] = attrs.get('readonly', not attrs.get('inverse'))
316 if attrs.get('related'):
317 # by default, related fields are not stored
318 attrs['store'] = attrs.get('store', False)
320 # fix for function fields overridden by regular columns
321 if not isinstance(attrs.get('column'), (NoneType, fields.function)):
322 attrs.pop('store', None)
324 for attr, value in attrs.iteritems():
325 if not hasattr(self, attr):
326 self._free_attrs.append(attr)
327 setattr(self, attr, value)
329 if not self.string and not self.related:
330 # related fields get their string from their parent field
331 self.string = name.replace('_', ' ').capitalize()
333 # determine self.default and cls._defaults in a consistent way
334 self._determine_default(cls, name)
338 def _determine_default(self, cls, name):
339 """ Retrieve the default value for `self` in the hierarchy of `cls`, and
340 determine `self.default` and `cls._defaults` accordingly.
344 # traverse the class hierarchy upwards, and take the first field
345 # definition with a default or _defaults for self
346 for klass in cls.__mro__:
347 field = klass.__dict__.get(name, self)
348 if not isinstance(field, type(self)):
349 return # klass contains another value overridden by self
351 if 'default' in field._attrs:
352 # take the default in field, and adapt it for cls._defaults
353 value = field._attrs['default']
356 cls._defaults[name] = lambda model, cr, uid, context: \
357 self.convert_to_write(value(model.browse(cr, uid, [], context)))
359 self.default = lambda recs: value
360 cls._defaults[name] = value
363 defaults = klass.__dict__.get('_defaults') or {}
365 # take the value from _defaults, and adapt it for self.default
366 value = defaults[name]
367 value_func = value if callable(value) else lambda *args: value
368 self.default = lambda recs: self.convert_to_cache(
369 value_func(recs._model, recs._cr, recs._uid, recs._context),
370 recs, validate=False,
372 cls._defaults[name] = value
376 return "%s.%s" % (self.model_name, self.name)
379 return "%s.%s" % (self.model_name, self.name)
381 ############################################################################
387 """ Prepare `self` for a new setup. """
388 self.setup_done = False
389 # self._triggers is a set of pairs (field, path) that represents the
390 # computed fields that depend on `self`. When `self` is modified, it
391 # invalidates the cache of each `field`, and registers the records to
392 # recompute based on `path`. See method `modified` below for details.
393 self._triggers = set()
394 self.inverse_fields = []
396 def setup(self, env):
397 """ Complete the setup of `self` (dependencies, recomputation triggers,
398 and other properties). This method is idempotent: it has no effect
399 if `self` has already been set up.
401 if not self.setup_done:
403 self.setup_done = True
405 def _setup(self, env):
406 """ Do the actual setup of `self`. """
408 self._setup_related(env)
410 self._setup_regular(env)
412 # put invalidation/recomputation triggers on field dependencies
413 model = env[self.model_name]
414 for path in self.depends:
415 self._setup_dependency([], model, path.split('.'))
417 # put invalidation triggers on model dependencies
418 for dep_model_name, field_names in model._depends.iteritems():
419 dep_model = env[dep_model_name]
420 for field_name in field_names:
421 field = dep_model._fields[field_name]
422 field._triggers.add((self, None))
425 # Setup of related fields
428 def _setup_related(self, env):
429 """ Setup the attributes of a related field. """
430 # fix the type of self.related if necessary
431 if isinstance(self.related, basestring):
432 self.related = tuple(self.related.split('.'))
434 # determine the chain of fields, and make sure they are all set up
435 recs = env[self.model_name]
437 for name in self.related:
438 field = recs._fields[name]
443 self.related_field = field
445 # check type consistency
446 if self.type != field.type:
447 raise Warning("Type of related field %s is inconsistent with %s" % (self, field))
449 # determine dependencies, compute, inverse, and search
450 self.depends = ('.'.join(self.related),)
451 self.compute = self._compute_related
452 self.inverse = self._inverse_related
453 if field._description_searchable:
454 # allow searching on self only if the related field is searchable
455 self.search = self._search_related
457 # copy attributes from field to self (string, help, etc.)
458 for attr, prop in self.related_attrs:
459 if not getattr(self, attr):
460 setattr(self, attr, getattr(field, prop))
462 # special case for required: check if all fields are required
463 if not self.store and not self.required:
464 self.required = all(field.required for field in fields)
466 def _compute_related(self, records):
467 """ Compute the related field `self` on `records`. """
468 # when related_sudo, bypass access rights checks when reading values
469 others = records.sudo() if self.related_sudo else records
470 for record, other in zip(records, others):
472 # draft record, do not switch to another environment
474 # traverse the intermediate fields; follow the first record at each step
475 for name in self.related[:-1]:
476 other = other[name][:1]
477 record[self.name] = other[self.related[-1]]
479 def _inverse_related(self, records):
480 """ Inverse the related field `self` on `records`. """
481 for record in records:
483 # traverse the intermediate fields, and keep at most one record
484 for name in self.related[:-1]:
485 other = other[name][:1]
487 other[self.related[-1]] = record[self.name]
489 def _search_related(self, records, operator, value):
490 """ Determine the domain to search on field `self`. """
491 return [('.'.join(self.related), operator, value)]
493 # properties used by _setup_related() to copy values from related field
494 _related_comodel_name = property(attrgetter('comodel_name'))
495 _related_string = property(attrgetter('string'))
496 _related_help = property(attrgetter('help'))
497 _related_readonly = property(attrgetter('readonly'))
498 _related_groups = property(attrgetter('groups'))
501 # Setup of non-related fields
504 def _setup_regular(self, env):
505 """ Setup the attributes of a non-related field. """
506 recs = env[self.model_name]
508 def make_depends(deps):
509 return tuple(deps(recs) if callable(deps) else deps)
511 # convert compute into a callable and determine depends
512 if isinstance(self.compute, basestring):
513 # if the compute method has been overridden, concatenate all their _depends
515 for method in resolve_all_mro(type(recs), self.compute, reverse=True):
516 self.depends += make_depends(getattr(method, '_depends', ()))
517 self.compute = getattr(type(recs), self.compute)
519 self.depends = make_depends(getattr(self.compute, '_depends', ()))
521 # convert inverse and search into callables
522 if isinstance(self.inverse, basestring):
523 self.inverse = getattr(type(recs), self.inverse)
524 if isinstance(self.search, basestring):
525 self.search = getattr(type(recs), self.search)
527 def _setup_dependency(self, path0, model, path1):
528 """ Make `self` depend on `model`; `path0 + path1` is a dependency of
529 `self`, and `path0` is the sequence of field names from `self.model`
533 head, tail = path1[0], path1[1:]
536 # special case: add triggers on all fields of model (except self)
537 fields = set(model._fields.itervalues()) - set([self])
539 fields = [model._fields[head]]
543 _logger.debug("Field %s is recursively defined", self)
544 self.recursive = True
549 #_logger.debug("Add trigger on %s to recompute %s", field, self)
550 field._triggers.add((self, '.'.join(path0 or ['id'])))
552 # add trigger on inverse fields, too
553 for invf in field.inverse_fields:
554 #_logger.debug("Add trigger on %s to recompute %s", invf, self)
555 invf._triggers.add((self, '.'.join(path0 + [head])))
557 # recursively traverse the dependency
559 comodel = env[field.comodel_name]
560 self._setup_dependency(path0 + [head], comodel, tail)
563 def dependents(self):
564 """ Return the computed fields that depend on `self`. """
565 return (field for field, path in self._triggers)
567 ############################################################################
572 def get_description(self, env):
573 """ Return a dictionary that describes the field `self`. """
574 desc = {'type': self.type}
575 for attr, prop in self.description_attrs:
576 value = getattr(self, prop)
579 if value is not None:
584 # properties used by get_description()
585 _description_store = property(attrgetter('store'))
586 _description_manual = property(attrgetter('manual'))
587 _description_depends = property(attrgetter('depends'))
588 _description_related = property(attrgetter('related'))
589 _description_company_dependent = property(attrgetter('company_dependent'))
590 _description_readonly = property(attrgetter('readonly'))
591 _description_required = property(attrgetter('required'))
592 _description_states = property(attrgetter('states'))
593 _description_groups = property(attrgetter('groups'))
594 _description_change_default = property(attrgetter('change_default'))
595 _description_deprecated = property(attrgetter('deprecated'))
598 def _description_searchable(self):
599 return bool(self.store or self.search or (self.column and self.column._fnct_search))
602 def _description_sortable(self):
603 return self.store or (self.inherited and self.related_field._description_sortable)
605 def _description_string(self, env):
606 if self.string and env.lang:
607 name = "%s,%s" % (self.model_name, self.name)
608 trans = env['ir.translation']._get_source(name, 'field', env.lang)
609 return trans or self.string
612 def _description_help(self, env):
613 if self.help and env.lang:
614 name = "%s,%s" % (self.model_name, self.name)
615 trans = env['ir.translation']._get_source(name, 'help', env.lang)
616 return trans or self.help
619 ############################################################################
621 # Conversion to column instance
625 """ return a low-level field object corresponding to `self` """
626 assert self.store or self.column
628 # determine column parameters
629 _logger.debug("Create fields._column for Field %s", self)
631 for attr, prop in self.column_attrs:
632 args[attr] = getattr(self, prop)
633 for attr in self._free_attrs:
634 args[attr] = getattr(self, attr)
636 if self.company_dependent:
637 # company-dependent fields are mapped to former property fields
638 args['type'] = self.type
639 args['relation'] = self.comodel_name
640 self.column = fields.property(**args)
642 # let the column provide a valid column for the given parameters
643 self.column = self.column.new(**args)
645 # create a fresh new column of the right type
646 self.column = getattr(fields, self.type)(**args)
650 # properties used by to_column() to create a column instance
651 _column_copy = property(attrgetter('copy'))
652 _column_select = property(attrgetter('index'))
653 _column_manual = property(attrgetter('manual'))
654 _column_string = property(attrgetter('string'))
655 _column_help = property(attrgetter('help'))
656 _column_readonly = property(attrgetter('readonly'))
657 _column_required = property(attrgetter('required'))
658 _column_states = property(attrgetter('states'))
659 _column_groups = property(attrgetter('groups'))
660 _column_change_default = property(attrgetter('change_default'))
661 _column_deprecated = property(attrgetter('deprecated'))
663 ############################################################################
665 # Conversion of values
669 """ return the null value for this field in the given environment """
672 def convert_to_cache(self, value, record, validate=True):
673 """ convert `value` to the cache level in `env`; `value` may come from
674 an assignment, or have the format of methods :meth:`BaseModel.read`
675 or :meth:`BaseModel.write`
677 :param record: the target record for the assignment, or an empty recordset
679 :param bool validate: when True, field-specific validation of
680 `value` will be performed
684 def convert_to_read(self, value, use_name_get=True):
685 """ convert `value` from the cache to a value as returned by method
686 :meth:`BaseModel.read`
688 :param bool use_name_get: when True, value's diplay name will
689 be computed using :meth:`BaseModel.name_get`, if relevant
692 return False if value is None else value
694 def convert_to_write(self, value, target=None, fnames=None):
695 """ convert `value` from the cache to a valid value for method
696 :meth:`BaseModel.write`.
698 :param target: optional, the record to be modified with this value
699 :param fnames: for relational fields only, an optional collection of
700 field names to convert
702 return self.convert_to_read(value)
704 def convert_to_onchange(self, value):
705 """ convert `value` from the cache to a valid value for an onchange
708 return self.convert_to_write(value)
710 def convert_to_export(self, value, env):
711 """ convert `value` from the cache to a valid value for export. The
712 parameter `env` is given for managing translations.
714 if env.context.get('export_raw_data'):
716 return bool(value) and ustr(value)
718 def convert_to_display_name(self, value):
719 """ convert `value` from the cache to a suitable display name. """
722 ############################################################################
727 def __get__(self, record, owner):
728 """ return the value of field `self` on `record` """
730 return self # the field is accessed through the owner class
733 # null record -> return the null value for this field
734 return self.null(record.env)
736 # only a single record may be accessed
740 return record._cache[self]
744 # cache miss, retrieve value
746 # normal record -> read or compute value for this field
747 self.determine_value(record)
749 # draft record -> compute the value or let it be null
750 self.determine_draft_value(record)
752 # the result should be in cache now
753 return record._cache[self]
755 def __set__(self, record, value):
756 """ set the value of field `self` on `record` """
759 # only a single record may be updated
762 # adapt value to the cache level
763 value = self.convert_to_cache(value, record)
765 if env.in_draft or not record.id:
766 # determine dependent fields
767 spec = self.modified_draft(record)
769 # set value in cache, inverse field, and mark record as dirty
770 record._cache[self] = value
772 for invf in self.inverse_fields:
773 invf._update(value, record)
776 # determine more dependent fields, and invalidate them
778 spec += self.modified_draft(record)
782 # simply write to the database, and update cache
783 record.write({self.name: self.convert_to_write(value)})
784 record._cache[self] = value
786 ############################################################################
788 # Computation of field values
791 def _compute_value(self, records):
792 """ Invoke the compute method on `records`. """
793 # initialize the fields to their corresponding null value in cache
794 for field in self.computed_fields:
795 records._cache[field] = field.null(records.env)
796 records.env.computed[field].update(records._ids)
797 self.compute(records)
798 for field in self.computed_fields:
799 records.env.computed[field].difference_update(records._ids)
801 def compute_value(self, records):
802 """ Invoke the compute method on `records`; the results are in cache. """
803 with records.env.do_in_draft():
805 self._compute_value(records)
806 except (AccessError, MissingError):
807 # some record is forbidden or missing, retry record by record
808 for record in records:
810 self._compute_value(record)
811 except Exception as exc:
812 record._cache[self.name] = FailedValue(exc)
814 def determine_value(self, record):
815 """ Determine the value of `self` for `record`. """
818 if self.column and not (self.depends and env.in_draft):
819 # this is a stored field or an old-style function field
821 # this is a stored computed field, check for recomputation
822 recs = record._recompute_check(self)
824 # recompute the value (only in cache)
825 self.compute_value(recs)
826 # HACK: if result is in the wrong cache, copy values
828 for source, target in zip(recs, recs.with_env(env)):
830 values = target._convert_to_cache({
831 f.name: source[f.name] for f in self.computed_fields
833 except MissingError as e:
834 values = FailedValue(e)
835 target._cache.update(values)
836 # the result is saved to database by BaseModel.recompute()
839 # read the field from database
840 record._prefetch_field(self)
843 # this is either a non-stored computed field, or a stored computed
844 # field in draft mode
846 self.compute_value(record)
848 recs = record._in_cache_without(self)
849 self.compute_value(recs)
852 # this is a non-stored non-computed field
853 record._cache[self] = self.null(env)
855 def determine_draft_value(self, record):
856 """ Determine the value of `self` for the given draft `record`. """
858 self._compute_value(record)
860 record._cache[self] = SpecialValue(self.null(record.env))
862 def determine_inverse(self, records):
863 """ Given the value of `self` on `records`, inverse the computation. """
865 self.inverse(records)
867 def determine_domain(self, records, operator, value):
868 """ Return a domain representing a condition on `self`. """
870 return self.search(records, operator, value)
872 return [(self.name, operator, value)]
874 ############################################################################
876 # Notification when fields are modified
879 def modified(self, records):
880 """ Notify that field `self` has been modified on `records`: prepare the
881 fields/records to recompute, and return a spec indicating what to
884 # invalidate the fields that depend on self, and prepare recomputation
885 spec = [(self, records._ids)]
886 for field, path in self._triggers:
887 if path and field.store:
888 # don't move this line to function top, see log
889 env = records.env(user=SUPERUSER_ID, context={'active_test': False})
890 target = env[field.model_name].search([(path, 'in', records.ids)])
892 spec.append((field, target._ids))
893 target.with_env(records.env)._recompute_todo(field)
895 spec.append((field, None))
899 def modified_draft(self, records):
900 """ Same as :meth:`modified`, but in draft mode. """
903 # invalidate the fields on the records in cache that depend on
904 # `records`, except fields currently being computed
906 for field, path in self._triggers:
907 target = env[field.model_name]
908 computed = target.browse(env.computed[field])
910 target = records - computed
912 target = (target.browse(env.cache[field]) - computed).filtered(
913 lambda rec: rec._mapped_cache(path) & records
916 target = target.browse(env.cache[field]) - computed
919 spec.append((field, target._ids))
924 class Boolean(Field):
927 def convert_to_cache(self, value, record, validate=True):
930 def convert_to_export(self, value, env):
931 if env.context.get('export_raw_data'):
936 class Integer(Field):
939 def convert_to_cache(self, value, record, validate=True):
940 if isinstance(value, dict):
941 # special case, when an integer field is used as inverse for a one2many
942 return value.get('id', False)
943 return int(value or 0)
945 def convert_to_read(self, value, use_name_get=True):
946 # Integer values greater than 2^31-1 are not supported in pure XMLRPC,
947 # so we have to pass them as floats :-(
948 if value and value > xmlrpclib.MAXINT:
952 def _update(self, records, value):
953 # special case, when an integer field is used as inverse for a one2many
954 records._cache[self] = value.id or 0
958 """ The precision digits are given by the attribute
960 :param digits: a pair (total, decimal), or a function taking a database
961 cursor and returning a pair (total, decimal)
964 _digits = None # digits argument passed to class initializer
965 digits = None # digits as computed by setup()
967 def __init__(self, string=None, digits=None, **kwargs):
968 super(Float, self).__init__(string=string, _digits=digits, **kwargs)
970 def _setup_digits(self, env):
971 """ Setup the digits for `self` and its corresponding column """
972 self.digits = self._digits(env.cr) if callable(self._digits) else self._digits
974 assert isinstance(self.digits, (tuple, list)) and len(self.digits) >= 2, \
975 "Float field %s with digits %r, expecting (total, decimal)" % (self, self.digits)
977 self.column.digits_change(env.cr)
979 def _setup_regular(self, env):
980 super(Float, self)._setup_regular(env)
981 self._setup_digits(env)
983 _related_digits = property(attrgetter('digits'))
985 _description_digits = property(attrgetter('digits'))
987 _column_digits = property(lambda self: not callable(self._digits) and self._digits)
988 _column_digits_compute = property(lambda self: callable(self._digits) and self._digits)
990 def convert_to_cache(self, value, record, validate=True):
991 # apply rounding here, otherwise value in cache may be wrong!
993 return float_round(float(value or 0.0), precision_digits=self.digits[1])
995 return float(value or 0.0)
998 class _String(Field):
999 """ Abstract class for string fields. """
1002 _column_translate = property(attrgetter('translate'))
1003 _related_translate = property(attrgetter('translate'))
1004 _description_translate = property(attrgetter('translate'))
1007 class Char(_String):
1008 """ Basic string field, can be length-limited, usually displayed as a
1009 single-line string in clients
1011 :param int size: the maximum size of values stored for that field
1012 :param bool translate: whether the values of this field can be translated
1017 def _setup(self, env):
1018 super(Char, self)._setup(env)
1019 assert isinstance(self.size, (NoneType, int)), \
1020 "Char field %s with non-integer size %r" % (self, self.size)
1022 _column_size = property(attrgetter('size'))
1023 _related_size = property(attrgetter('size'))
1024 _description_size = property(attrgetter('size'))
1026 def convert_to_cache(self, value, record, validate=True):
1027 if value is None or value is False:
1029 return ustr(value)[:self.size]
1031 class Text(_String):
1032 """ Text field. Very similar to :class:`~.Char` but used for longer
1033 contents and displayed as a multiline text box
1035 :param translate: whether the value of this field can be translated
1039 def convert_to_cache(self, value, record, validate=True):
1040 if value is None or value is False:
1044 class Html(_String):
1046 sanitize = True # whether value must be sanitized
1048 _column_sanitize = property(attrgetter('sanitize'))
1049 _related_sanitize = property(attrgetter('sanitize'))
1050 _description_sanitize = property(attrgetter('sanitize'))
1052 def convert_to_cache(self, value, record, validate=True):
1053 if value is None or value is False:
1055 if validate and self.sanitize:
1056 return html_sanitize(value)
1065 """ Return the current day in the format expected by the ORM.
1066 This function may be used to compute default values.
1068 return date.today().strftime(DATE_FORMAT)
1071 def context_today(record, timestamp=None):
1072 """ Return the current date as seen in the client's timezone in a format
1073 fit for date fields. This method may be used to compute default
1076 :param datetime timestamp: optional datetime value to use instead of
1077 the current date and time (must be a datetime, regular dates
1078 can't be converted between timezones.)
1081 today = timestamp or datetime.now()
1082 context_today = None
1083 tz_name = record._context.get('tz') or record.env.user.tz
1086 today_utc = pytz.timezone('UTC').localize(today, is_dst=False) # UTC = no DST
1087 context_today = today_utc.astimezone(pytz.timezone(tz_name))
1089 _logger.debug("failed to compute context/client-specific today date, using UTC value for `today`",
1091 return (context_today or today).strftime(DATE_FORMAT)
1094 def from_string(value):
1095 """ Convert an ORM `value` into a :class:`date` value. """
1096 value = value[:DATE_LENGTH]
1097 return datetime.strptime(value, DATE_FORMAT).date()
1100 def to_string(value):
1101 """ Convert a :class:`date` value into the format expected by the ORM. """
1102 return value.strftime(DATE_FORMAT)
1104 def convert_to_cache(self, value, record, validate=True):
1107 if isinstance(value, basestring):
1109 # force parsing for validation
1110 self.from_string(value)
1111 return value[:DATE_LENGTH]
1112 return self.to_string(value)
1114 def convert_to_export(self, value, env):
1115 if value and env.context.get('export_raw_data'):
1116 return self.from_string(value)
1117 return bool(value) and ustr(value)
1120 class Datetime(Field):
1125 """ Return the current day and time in the format expected by the ORM.
1126 This function may be used to compute default values.
1128 return datetime.now().strftime(DATETIME_FORMAT)
1131 def context_timestamp(record, timestamp):
1132 """Returns the given timestamp converted to the client's timezone.
1133 This method is *not* meant for use as a _defaults initializer,
1134 because datetime fields are automatically converted upon
1135 display on client side. For _defaults you :meth:`fields.datetime.now`
1136 should be used instead.
1138 :param datetime timestamp: naive datetime value (expressed in UTC)
1139 to be converted to the client timezone
1141 :return: timestamp converted to timezone-aware datetime in context
1144 assert isinstance(timestamp, datetime), 'Datetime instance expected'
1145 tz_name = record._context.get('tz') or record.env.user.tz
1148 utc = pytz.timezone('UTC')
1149 context_tz = pytz.timezone(tz_name)
1150 utc_timestamp = utc.localize(timestamp, is_dst=False) # UTC = no DST
1151 return utc_timestamp.astimezone(context_tz)
1153 _logger.debug("failed to compute context/client-specific timestamp, "
1154 "using the UTC value",
1159 def from_string(value):
1160 """ Convert an ORM `value` into a :class:`datetime` value. """
1161 value = value[:DATETIME_LENGTH]
1162 if len(value) == DATE_LENGTH:
1163 value += " 00:00:00"
1164 return datetime.strptime(value, DATETIME_FORMAT)
1167 def to_string(value):
1168 """ Convert a :class:`datetime` value into the format expected by the ORM. """
1169 return value.strftime(DATETIME_FORMAT)
1171 def convert_to_cache(self, value, record, validate=True):
1174 if isinstance(value, basestring):
1176 # force parsing for validation
1177 self.from_string(value)
1178 value = value[:DATETIME_LENGTH]
1179 if len(value) == DATE_LENGTH:
1180 value += " 00:00:00"
1182 return self.to_string(value)
1184 def convert_to_export(self, value, env):
1185 if value and env.context.get('export_raw_data'):
1186 return self.from_string(value)
1187 return bool(value) and ustr(value)
1190 class Binary(Field):
1194 class Selection(Field):
1196 :param selection: specifies the possible values for this field.
1197 It is given as either a list of pairs (`value`, `string`), or a
1198 model method, or a method name.
1199 :param selection_add: provides an extension of the selection in the case
1200 of an overridden field. It is a list of pairs (`value`, `string`).
1202 The attribute `selection` is mandatory except in the case of
1203 :ref:`related fields <field-related>` or :ref:`field extensions
1204 <field-incremental-definition>`.
1207 selection = None # [(value, string), ...], function or method name
1208 selection_add = None # [(value, string), ...]
1210 def __init__(self, selection=None, string=None, **kwargs):
1211 if callable(selection):
1212 from openerp import api
1213 selection = api.expected(api.model, selection)
1214 super(Selection, self).__init__(selection=selection, string=string, **kwargs)
1216 def _setup(self, env):
1217 super(Selection, self)._setup(env)
1218 assert self.selection is not None, "Field %s without selection" % self
1220 def _setup_related(self, env):
1221 super(Selection, self)._setup_related(env)
1222 # selection must be computed on related field
1223 field = self.related_field
1224 self.selection = lambda model: field._description_selection(model.env)
1226 def set_class_name(self, cls, name):
1227 super(Selection, self).set_class_name(cls, name)
1228 # determine selection (applying 'selection_add' extensions)
1230 for field in resolve_all_mro(cls, name, reverse=True):
1231 if isinstance(field, type(self)):
1232 # We cannot use field.selection or field.selection_add here
1233 # because those attributes are overridden by `set_class_name`.
1234 if 'selection' in field._attrs:
1235 selection = field._attrs['selection']
1236 if 'selection_add' in field._attrs:
1237 selection = selection + field._attrs['selection_add']
1240 self.selection = selection
1242 def _description_selection(self, env):
1243 """ return the selection list (pairs (value, label)); labels are
1244 translated according to context language
1246 selection = self.selection
1247 if isinstance(selection, basestring):
1248 return getattr(env[self.model_name], selection)()
1249 if callable(selection):
1250 return selection(env[self.model_name])
1252 # translate selection labels
1254 name = "%s,%s" % (self.model_name, self.name)
1255 translate = partial(
1256 env['ir.translation']._get_source, name, 'selection', env.lang)
1257 return [(value, translate(label) if label else label) for value, label in selection]
1262 def _column_selection(self):
1263 if isinstance(self.selection, basestring):
1264 method = self.selection
1265 return lambda self, *a, **kw: getattr(self, method)(*a, **kw)
1267 return self.selection
1269 def get_values(self, env):
1270 """ return a list of the possible values """
1271 selection = self.selection
1272 if isinstance(selection, basestring):
1273 selection = getattr(env[self.model_name], selection)()
1274 elif callable(selection):
1275 selection = selection(env[self.model_name])
1276 return [value for value, _ in selection]
1278 def convert_to_cache(self, value, record, validate=True):
1280 return value or False
1281 if value in self.get_values(record.env):
1285 raise ValueError("Wrong value for %s: %r" % (self, value))
1287 def convert_to_export(self, value, env):
1288 if not isinstance(self.selection, list):
1289 # FIXME: this reproduces an existing buggy behavior!
1291 for item in self._description_selection(env):
1292 if item[0] == value:
1297 class Reference(Selection):
1301 def __init__(self, selection=None, string=None, **kwargs):
1302 super(Reference, self).__init__(selection=selection, string=string, **kwargs)
1304 def _setup(self, env):
1305 super(Reference, self)._setup(env)
1306 assert isinstance(self.size, (NoneType, int)), \
1307 "Reference field %s with non-integer size %r" % (self, self.size)
1309 _related_size = property(attrgetter('size'))
1311 _column_size = property(attrgetter('size'))
1313 def convert_to_cache(self, value, record, validate=True):
1314 if isinstance(value, BaseModel):
1315 if ((not validate or value._name in self.get_values(record.env))
1316 and len(value) <= 1):
1317 return value.with_env(record.env) or False
1318 elif isinstance(value, basestring):
1319 res_model, res_id = value.split(',')
1320 return record.env[res_model].browse(int(res_id))
1323 raise ValueError("Wrong value for %s: %r" % (self, value))
1325 def convert_to_read(self, value, use_name_get=True):
1326 return "%s,%s" % (value._name, value.id) if value else False
1328 def convert_to_export(self, value, env):
1329 return bool(value) and value.name_get()[0][1]
1331 def convert_to_display_name(self, value):
1332 return ustr(value and value.display_name)
1335 class _Relational(Field):
1336 """ Abstract class for relational fields. """
1338 domain = None # domain for searching values
1339 context = None # context for searching values
1341 def _setup(self, env):
1342 super(_Relational, self)._setup(env)
1343 assert self.comodel_name in env.registry, \
1344 "Field %s with unknown comodel_name %r" % (self, self.comodel_name)
1347 def _related_domain(self):
1348 if callable(self.domain):
1349 # will be called with another model than self's
1350 return lambda recs: self.domain(recs.env[self.model_name])
1352 # maybe not correct if domain is a string...
1355 _related_context = property(attrgetter('context'))
1357 _description_relation = property(attrgetter('comodel_name'))
1358 _description_context = property(attrgetter('context'))
1360 def _description_domain(self, env):
1361 return self.domain(env[self.model_name]) if callable(self.domain) else self.domain
1363 _column_obj = property(attrgetter('comodel_name'))
1364 _column_domain = property(attrgetter('domain'))
1365 _column_context = property(attrgetter('context'))
1367 def null(self, env):
1368 return env[self.comodel_name]
1370 def modified(self, records):
1371 # Invalidate cache for self.inverse_fields, too. Note that recomputation
1372 # of fields that depend on self.inverse_fields is already covered by the
1373 # triggers (see above).
1374 spec = super(_Relational, self).modified(records)
1375 for invf in self.inverse_fields:
1376 spec.append((invf, None))
1380 class Many2one(_Relational):
1381 """ The value of such a field is a recordset of size 0 (no
1382 record) or 1 (a single record).
1384 :param comodel_name: name of the target model (string)
1386 :param domain: an optional domain to set on candidate values on the
1387 client side (domain or string)
1389 :param context: an optional context to use on the client side when
1390 handling that field (dictionary)
1392 :param ondelete: what to do when the referred record is deleted;
1393 possible values are: ``'set null'``, ``'restrict'``, ``'cascade'``
1395 :param auto_join: whether JOINs are generated upon search through that
1396 field (boolean, by default ``False``)
1398 :param delegate: set it to ``True`` to make fields of the target model
1399 accessible from the current model (corresponds to ``_inherits``)
1401 The attribute `comodel_name` is mandatory except in the case of related
1402 fields or field extensions.
1405 ondelete = 'set null' # what to do when value is deleted
1406 auto_join = False # whether joins are generated upon search
1407 delegate = False # whether self implements delegation
1409 def __init__(self, comodel_name=None, string=None, **kwargs):
1410 super(Many2one, self).__init__(comodel_name=comodel_name, string=string, **kwargs)
1412 def set_class_name(self, cls, name):
1413 super(Many2one, self).set_class_name(cls, name)
1414 # determine self.delegate
1415 if not self.delegate:
1416 self.delegate = name in cls._inherits.values()
1418 _column_ondelete = property(attrgetter('ondelete'))
1419 _column_auto_join = property(attrgetter('auto_join'))
1421 def _update(self, records, value):
1422 """ Update the cached value of `self` for `records` with `value`. """
1423 records._cache[self] = value
1425 def convert_to_cache(self, value, record, validate=True):
1426 if isinstance(value, (NoneType, int)):
1427 return record.env[self.comodel_name].browse(value)
1428 if isinstance(value, BaseModel):
1429 if value._name == self.comodel_name and len(value) <= 1:
1430 return value.with_env(record.env)
1431 raise ValueError("Wrong value for %s: %r" % (self, value))
1432 elif isinstance(value, tuple):
1433 return record.env[self.comodel_name].browse(value[0])
1434 elif isinstance(value, dict):
1435 return record.env[self.comodel_name].new(value)
1437 return record.env[self.comodel_name].browse(value)
1439 def convert_to_read(self, value, use_name_get=True):
1440 if use_name_get and value:
1441 # evaluate name_get() as superuser, because the visibility of a
1442 # many2one field value (id and name) depends on the current record's
1443 # access rights, and not the value's access rights.
1445 return value.sudo().name_get()[0]
1446 except MissingError:
1447 # Should not happen, unless the foreign key is missing.
1452 def convert_to_write(self, value, target=None, fnames=None):
1455 def convert_to_onchange(self, value):
1458 def convert_to_export(self, value, env):
1459 return bool(value) and value.name_get()[0][1]
1461 def convert_to_display_name(self, value):
1462 return ustr(value.display_name)
1465 class UnionUpdate(SpecialValue):
1466 """ Placeholder for a value update; when this value is taken from the cache,
1467 it returns ``record[field.name] | value`` and stores it in the cache.
1469 def __init__(self, field, record, value):
1470 self.args = (field, record, value)
1473 field, record, value = self.args
1474 # in order to read the current field's value, remove self from cache
1475 del record._cache[field]
1476 # read the current field's value, and update it in cache only
1477 record._cache[field] = new_value = record[field.name] | value
1481 class _RelationalMulti(_Relational):
1482 """ Abstract class for relational fields *2many. """
1484 def _update(self, records, value):
1485 """ Update the cached value of `self` for `records` with `value`. """
1486 for record in records:
1487 if self in record._cache:
1488 record._cache[self] = record[self.name] | value
1490 record._cache[self] = UnionUpdate(self, record, value)
1492 def convert_to_cache(self, value, record, validate=True):
1493 if isinstance(value, BaseModel):
1494 if value._name == self.comodel_name:
1495 return value.with_env(record.env)
1496 elif isinstance(value, list):
1497 # value is a list of record ids or commands
1499 record = record.browse() # new record has no value
1500 result = record[self.name]
1501 # modify result with the commands;
1502 # beware to not introduce duplicates in result
1503 for command in value:
1504 if isinstance(command, (tuple, list)):
1506 result += result.new(command[2])
1507 elif command[0] == 1:
1508 result.browse(command[1]).update(command[2])
1509 result += result.browse(command[1]) - result
1510 elif command[0] == 2:
1511 # note: the record will be deleted by write()
1512 result -= result.browse(command[1])
1513 elif command[0] == 3:
1514 result -= result.browse(command[1])
1515 elif command[0] == 4:
1516 result += result.browse(command[1]) - result
1517 elif command[0] == 5:
1518 result = result.browse()
1519 elif command[0] == 6:
1520 result = result.browse(command[2])
1521 elif isinstance(command, dict):
1522 result += result.new(command)
1524 result += result.browse(command) - result
1527 return self.null(record.env)
1528 raise ValueError("Wrong value for %s: %s" % (self, value))
1530 def convert_to_read(self, value, use_name_get=True):
1533 def convert_to_write(self, value, target=None, fnames=None):
1534 # remove/delete former records
1537 result = [(6, 0, set_ids)]
1538 add_existing = lambda id: set_ids.append(id)
1540 tag = 2 if self.type == 'one2many' else 3
1541 result = [(tag, record.id) for record in target[self.name] - value]
1542 add_existing = lambda id: result.append((4, id))
1545 # take all fields in cache, except the inverses of self
1546 fnames = set(value._fields) - set(MAGIC_COLUMNS)
1547 for invf in self.inverse_fields:
1548 fnames.discard(invf.name)
1550 # add new and existing records
1551 for record in value:
1552 if not record.id or record._dirty:
1553 values = dict((k, v) for k, v in record._cache.iteritems() if k in fnames)
1554 values = record._convert_to_write(values)
1556 result.append((0, 0, values))
1558 result.append((1, record.id, values))
1560 add_existing(record.id)
1564 def convert_to_export(self, value, env):
1565 return bool(value) and ','.join(name for id, name in value.name_get())
1567 def convert_to_display_name(self, value):
1568 raise NotImplementedError()
1570 def _compute_related(self, records):
1571 """ Compute the related field `self` on `records`. """
1572 for record in records:
1574 # traverse the intermediate fields, and keep at most one record
1575 for name in self.related[:-1]:
1576 value = value[name][:1]
1577 record[self.name] = value[self.related[-1]]
1580 class One2many(_RelationalMulti):
1581 """ One2many field; the value of such a field is the recordset of all the
1582 records in `comodel_name` such that the field `inverse_name` is equal to
1585 :param comodel_name: name of the target model (string)
1587 :param inverse_name: name of the inverse `Many2one` field in
1588 `comodel_name` (string)
1590 :param domain: an optional domain to set on candidate values on the
1591 client side (domain or string)
1593 :param context: an optional context to use on the client side when
1594 handling that field (dictionary)
1596 :param auto_join: whether JOINs are generated upon search through that
1597 field (boolean, by default ``False``)
1599 :param limit: optional limit to use upon read (integer)
1601 The attributes `comodel_name` and `inverse_name` are mandatory except in
1602 the case of related fields or field extensions.
1605 inverse_name = None # name of the inverse field
1606 auto_join = False # whether joins are generated upon search
1607 limit = None # optional limit to use upon read
1608 copy = False # o2m are not copied by default
1610 def __init__(self, comodel_name=None, inverse_name=None, string=None, **kwargs):
1611 super(One2many, self).__init__(
1612 comodel_name=comodel_name,
1613 inverse_name=inverse_name,
1618 def _setup_regular(self, env):
1619 super(One2many, self)._setup_regular(env)
1621 if self.inverse_name:
1622 # link self to its inverse field and vice-versa
1623 invf = env[self.comodel_name]._fields[self.inverse_name]
1624 # In some rare cases, a `One2many` field can link to `Int` field
1625 # (res_model/res_id pattern). Only inverse the field if this is
1626 # a `Many2one` field.
1627 if isinstance(invf, Many2one):
1628 self.inverse_fields.append(invf)
1629 invf.inverse_fields.append(self)
1631 _description_relation_field = property(attrgetter('inverse_name'))
1633 _column_fields_id = property(attrgetter('inverse_name'))
1634 _column_auto_join = property(attrgetter('auto_join'))
1635 _column_limit = property(attrgetter('limit'))
1638 class Many2many(_RelationalMulti):
1639 """ Many2many field; the value of such a field is the recordset.
1641 :param comodel_name: name of the target model (string)
1643 The attribute `comodel_name` is mandatory except in the case of related
1644 fields or field extensions.
1646 :param relation: optional name of the table that stores the relation in
1647 the database (string)
1649 :param column1: optional name of the column referring to "these" records
1650 in the table `relation` (string)
1652 :param column2: optional name of the column referring to "those" records
1653 in the table `relation` (string)
1655 The attributes `relation`, `column1` and `column2` are optional. If not
1656 given, names are automatically generated from model names, provided
1657 `model_name` and `comodel_name` are different!
1659 :param domain: an optional domain to set on candidate values on the
1660 client side (domain or string)
1662 :param context: an optional context to use on the client side when
1663 handling that field (dictionary)
1665 :param limit: optional limit to use upon read (integer)
1669 relation = None # name of table
1670 column1 = None # column of table referring to model
1671 column2 = None # column of table referring to comodel
1672 limit = None # optional limit to use upon read
1674 def __init__(self, comodel_name=None, relation=None, column1=None, column2=None,
1675 string=None, **kwargs):
1676 super(Many2many, self).__init__(
1677 comodel_name=comodel_name,
1685 def _setup_regular(self, env):
1686 super(Many2many, self)._setup_regular(env)
1688 if not self.relation:
1689 if isinstance(self.column, fields.many2many):
1690 self.relation, self.column1, self.column2 = \
1691 self.column._sql_names(env[self.model_name])
1694 m2m = env.registry._m2m
1695 # if inverse field has already been setup, it is present in m2m
1696 invf = m2m.get((self.relation, self.column2, self.column1))
1698 self.inverse_fields.append(invf)
1699 invf.inverse_fields.append(self)
1701 # add self in m2m, so that its inverse field can find it
1702 m2m[(self.relation, self.column1, self.column2)] = self
1704 _column_rel = property(attrgetter('relation'))
1705 _column_id1 = property(attrgetter('column1'))
1706 _column_id2 = property(attrgetter('column2'))
1707 _column_limit = property(attrgetter('limit'))
1711 """ Special case for field 'id'. """
1713 #: Can't write this!
1716 def __init__(self, string=None, **kwargs):
1717 super(Id, self).__init__(type='integer', string=string, **kwargs)
1719 def to_column(self):
1720 self.column = fields.integer('ID')
1723 def __get__(self, record, owner):
1725 return self # the field is accessed through the class owner
1728 return record.ensure_one()._ids[0]
1730 def __set__(self, record, value):
1731 raise TypeError("field 'id' cannot be assigned")
1734 # imported here to avoid dependency cycle issues
1735 from openerp import SUPERUSER_ID
1736 from .exceptions import Warning, AccessError, MissingError
1737 from .models import BaseModel, MAGIC_COLUMNS
1738 from .osv import fields