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
28 from types import NoneType
33 from types import NoneType
35 from openerp.tools import float_round, ustr, html_sanitize
36 from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as DATE_FORMAT
37 from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT as DATETIME_FORMAT
39 DATE_LENGTH = len(date.today().strftime(DATE_FORMAT))
40 DATETIME_LENGTH = len(datetime.now().strftime(DATETIME_FORMAT))
42 _logger = logging.getLogger(__name__)
44 class SpecialValue(object):
45 """ Encapsulates a value in the cache in place of a normal value. """
46 def __init__(self, value):
51 class FailedValue(SpecialValue):
52 """ Special value that encapsulates an exception instead of a value. """
53 def __init__(self, exception):
54 self.exception = exception
58 def _check_value(value):
59 """ Return `value`, or call its getter if `value` is a :class:`SpecialValue`. """
60 return value.get() if isinstance(value, SpecialValue) else value
63 def resolve_all_mro(cls, name, reverse=False):
64 """ Return the (successively overridden) values of attribute `name` in `cls`
65 in mro order, or inverse mro order if `reverse` is true.
67 klasses = reversed(cls.__mro__) if reverse else cls.__mro__
69 if name in klass.__dict__:
70 yield klass.__dict__[name]
73 class MetaField(type):
74 """ Metaclass for field classes. """
77 def __init__(cls, name, bases, attrs):
78 super(MetaField, cls).__init__(name, bases, attrs)
80 cls.by_type[cls.type] = cls
82 # compute class attributes to avoid calling dir() on fields
84 cls.related_attrs = []
85 cls.description_attrs = []
87 if attr.startswith('_column_'):
88 cls.column_attrs.append((attr[8:], attr))
89 elif attr.startswith('_related_'):
90 cls.related_attrs.append((attr[9:], attr))
91 elif attr.startswith('_description_'):
92 cls.description_attrs.append((attr[13:], attr))
96 """ The field descriptor contains the field definition, and manages accesses
97 and assignments of the corresponding field on records. The following
98 attributes may be provided when instanciating a field:
100 :param string: the label of the field seen by users (string); if not
101 set, the ORM takes the field name in the class (capitalized).
103 :param help: the tooltip of the field seen by users (string)
105 :param readonly: whether the field is readonly (boolean, by default ``False``)
107 :param required: whether the value of the field is required (boolean, by
110 :param index: whether the field is indexed in database (boolean, by
113 :param default: the default value for the field; this is either a static
114 value, or a function taking a recordset and returning a value
116 :param states: a dictionary mapping state values to lists of UI attribute-value
117 pairs; possible attributes are: 'readonly', 'required', 'invisible'.
118 Note: Any state-based condition requires the ``state`` field value to be
119 available on the client-side UI. This is typically done by including it in
120 the relevant views, possibly made invisible if not relevant for the
123 :param groups: comma-separated list of group xml ids (string); this
124 restricts the field access to the users of the given groups only
126 :param bool copy: whether the field value should be copied when the record
127 is duplicated (default: ``True`` for normal fields, ``False`` for
128 ``one2many`` and computed fields, including property fields and
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 interfaced by 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 for attr, value in attrs.iteritems():
321 if not hasattr(self, attr):
322 self._free_attrs.append(attr)
323 setattr(self, attr, value)
326 self.string = name.replace('_', ' ').capitalize()
328 # determine self.default and cls._defaults in a consistent way
329 self._determine_default(cls, name)
333 def _determine_default(self, cls, name):
334 """ Retrieve the default value for `self` in the hierarchy of `cls`, and
335 determine `self.default` and `cls._defaults` accordingly.
339 # traverse the class hierarchy upwards, and take the first field
340 # definition with a default or _defaults for self
341 for klass in cls.__mro__:
342 field = klass.__dict__.get(name, self)
343 if not isinstance(field, type(self)):
344 return # klass contains another value overridden by self
346 if 'default' in field._attrs:
347 # take the default in field, and adapt it for cls._defaults
348 value = field._attrs['default']
351 cls._defaults[name] = lambda model, cr, uid, context: \
352 self.convert_to_write(value(model.browse(cr, uid, [], context)))
354 self.default = lambda recs: value
355 cls._defaults[name] = value
358 defaults = klass.__dict__.get('_defaults') or {}
360 # take the value from _defaults, and adapt it for self.default
361 value = defaults[name]
362 value_func = value if callable(value) else lambda *args: value
363 self.default = lambda recs: self.convert_to_cache(
364 value_func(recs._model, recs._cr, recs._uid, recs._context),
365 recs, validate=False,
367 cls._defaults[name] = value
371 return "%s.%s" % (self.model_name, self.name)
374 return "%s.%s" % (self.model_name, self.name)
376 ############################################################################
382 """ Prepare `self` for a new setup. """
383 self.setup_done = False
384 # self._triggers is a set of pairs (field, path) that represents the
385 # computed fields that depend on `self`. When `self` is modified, it
386 # invalidates the cache of each `field`, and registers the records to
387 # recompute based on `path`. See method `modified` below for details.
388 self._triggers = set()
389 self.inverse_fields = []
391 def setup(self, env):
392 """ Complete the setup of `self` (dependencies, recomputation triggers,
393 and other properties). This method is idempotent: it has no effect
394 if `self` has already been set up.
396 if not self.setup_done:
398 self.setup_done = True
400 def _setup(self, env):
401 """ Do the actual setup of `self`. """
403 self._setup_related(env)
405 self._setup_regular(env)
407 # put invalidation/recomputation triggers on field dependencies
408 model = env[self.model_name]
409 for path in self.depends:
410 self._setup_dependency([], model, path.split('.'))
412 # put invalidation triggers on model dependencies
413 for dep_model_name, field_names in model._depends.iteritems():
414 dep_model = env[dep_model_name]
415 for field_name in field_names:
416 field = dep_model._fields[field_name]
417 field._triggers.add((self, None))
420 # Setup of related fields
423 def _setup_related(self, env):
424 """ Setup the attributes of a related field. """
425 # fix the type of self.related if necessary
426 if isinstance(self.related, basestring):
427 self.related = tuple(self.related.split('.'))
429 # determine the chain of fields, and make sure they are all set up
430 recs = env[self.model_name]
431 for name in self.related:
432 field = recs._fields[name]
436 self.related_field = field
438 # check type consistency
439 if self.type != field.type:
440 raise Warning("Type of related field %s is inconsistent with %s" % (self, field))
442 # determine dependencies, compute, inverse, and search
443 self.depends = ('.'.join(self.related),)
444 self.compute = self._compute_related
445 self.inverse = self._inverse_related
446 if field._description_searchable(env):
447 # allow searching on self only if the related field is searchable
448 self.search = self._search_related
450 # copy attributes from field to self (string, help, etc.)
451 for attr, prop in self.related_attrs:
452 if not getattr(self, attr):
453 setattr(self, attr, getattr(field, prop))
455 def _compute_related(self, records):
456 """ Compute the related field `self` on `records`. """
457 # when related_sudo, bypass access rights checks when reading values
458 others = records.sudo() if self.related_sudo else records
459 for record, other in zip(records, others):
461 # draft record, do not switch to another environment
463 # traverse the intermediate fields; follow the first record at each step
464 for name in self.related[:-1]:
465 other = other[name][:1]
466 record[self.name] = other[self.related[-1]]
468 def _inverse_related(self, records):
469 """ Inverse the related field `self` on `records`. """
470 for record in records:
472 # traverse the intermediate fields, and keep at most one record
473 for name in self.related[:-1]:
474 other = other[name][:1]
476 other[self.related[-1]] = record[self.name]
478 def _search_related(self, records, operator, value):
479 """ Determine the domain to search on field `self`. """
480 return [('.'.join(self.related), operator, value)]
482 # properties used by _setup_related() to copy values from related field
483 _related_comodel_name = property(attrgetter('comodel_name'))
484 _related_string = property(attrgetter('string'))
485 _related_help = property(attrgetter('help'))
486 _related_readonly = property(attrgetter('readonly'))
487 _related_groups = property(attrgetter('groups'))
490 # Setup of non-related fields
493 def _setup_regular(self, env):
494 """ Setup the attributes of a non-related field. """
495 recs = env[self.model_name]
497 def make_depends(deps):
498 return tuple(deps(recs) if callable(deps) else deps)
500 # convert compute into a callable and determine depends
501 if isinstance(self.compute, basestring):
502 # if the compute method has been overridden, concatenate all their _depends
504 for method in resolve_all_mro(type(recs), self.compute, reverse=True):
505 self.depends += make_depends(getattr(method, '_depends', ()))
506 self.compute = getattr(type(recs), self.compute)
508 self.depends = make_depends(getattr(self.compute, '_depends', ()))
510 # convert inverse and search into callables
511 if isinstance(self.inverse, basestring):
512 self.inverse = getattr(type(recs), self.inverse)
513 if isinstance(self.search, basestring):
514 self.search = getattr(type(recs), self.search)
516 def _setup_dependency(self, path0, model, path1):
517 """ Make `self` depend on `model`; `path0 + path1` is a dependency of
518 `self`, and `path0` is the sequence of field names from `self.model`
522 head, tail = path1[0], path1[1:]
525 # special case: add triggers on all fields of model (except self)
526 fields = set(model._fields.itervalues()) - set([self])
528 fields = [model._fields[head]]
532 _logger.debug("Field %s is recursively defined", self)
533 self.recursive = True
538 #_logger.debug("Add trigger on %s to recompute %s", field, self)
539 field._triggers.add((self, '.'.join(path0 or ['id'])))
541 # add trigger on inverse fields, too
542 for invf in field.inverse_fields:
543 #_logger.debug("Add trigger on %s to recompute %s", invf, self)
544 invf._triggers.add((self, '.'.join(path0 + [head])))
546 # recursively traverse the dependency
548 comodel = env[field.comodel_name]
549 self._setup_dependency(path0 + [head], comodel, tail)
552 def dependents(self):
553 """ Return the computed fields that depend on `self`. """
554 return (field for field, path in self._triggers)
556 ############################################################################
561 def get_description(self, env):
562 """ Return a dictionary that describes the field `self`. """
563 desc = {'type': self.type}
564 for attr, prop in self.description_attrs:
565 value = getattr(self, prop)
568 if value is not None:
573 # properties used by get_description()
575 def _description_store(self, env):
577 # if the corresponding column is a function field, check the column
578 column = env[self.model_name]._columns.get(self.name)
579 return bool(getattr(column, 'store', True))
582 def _description_searchable(self, env):
584 column = env[self.model_name]._columns.get(self.name)
585 return bool(getattr(column, 'store', True)) or \
586 bool(getattr(column, '_fnct_search', False))
587 return bool(self.search)
589 def _description_sortable(self, env):
591 column = env[self.model_name]._columns.get(self.name)
592 return bool(getattr(column, 'store', True))
594 # self is sortable if the inherited field is itself sortable
595 return self.related_field._description_sortable(env)
598 _description_manual = property(attrgetter('manual'))
599 _description_depends = property(attrgetter('depends'))
600 _description_related = property(attrgetter('related'))
601 _description_company_dependent = property(attrgetter('company_dependent'))
602 _description_readonly = property(attrgetter('readonly'))
603 _description_required = property(attrgetter('required'))
604 _description_states = property(attrgetter('states'))
605 _description_groups = property(attrgetter('groups'))
606 _description_change_default = property(attrgetter('change_default'))
607 _description_deprecated = property(attrgetter('deprecated'))
609 def _description_string(self, env):
610 if self.string and env.lang:
611 name = "%s,%s" % (self.model_name, self.name)
612 trans = env['ir.translation']._get_source(name, 'field', env.lang)
613 return trans or self.string
616 def _description_help(self, env):
617 if self.help and env.lang:
618 name = "%s,%s" % (self.model_name, self.name)
619 trans = env['ir.translation']._get_source(name, 'help', env.lang)
620 return trans or self.help
623 ############################################################################
625 # Conversion to column instance
629 """ return a low-level field object corresponding to `self` """
632 # some columns are registry-dependent, like float fields (digits);
633 # duplicate them to avoid sharing between registries
634 _logger.debug("Create fields._column for Field %s", self)
636 for attr, prop in self.column_attrs:
637 args[attr] = getattr(self, prop)
638 for attr in self._free_attrs:
639 args[attr] = getattr(self, attr)
641 if self.company_dependent:
642 # company-dependent fields are mapped to former property fields
643 args['type'] = self.type
644 args['relation'] = self.comodel_name
645 return fields.property(**args)
647 if isinstance(self.column, fields.function):
648 # it is too tricky to recreate a function field, so for that case,
649 # we make a stupid (and possibly incorrect) copy of the column
650 return copy(self.column)
652 return getattr(fields, self.type)(**args)
654 # properties used by to_column() to create a column instance
655 _column_copy = property(attrgetter('copy'))
656 _column_select = property(attrgetter('index'))
657 _column_manual = property(attrgetter('manual'))
658 _column_string = property(attrgetter('string'))
659 _column_help = property(attrgetter('help'))
660 _column_readonly = property(attrgetter('readonly'))
661 _column_required = property(attrgetter('required'))
662 _column_states = property(attrgetter('states'))
663 _column_groups = property(attrgetter('groups'))
664 _column_change_default = property(attrgetter('change_default'))
665 _column_deprecated = property(attrgetter('deprecated'))
667 ############################################################################
669 # Conversion of values
673 """ return the null value for this field in the given environment """
676 def convert_to_cache(self, value, record, validate=True):
677 """ convert `value` to the cache level in `env`; `value` may come from
678 an assignment, or have the format of methods :meth:`BaseModel.read`
679 or :meth:`BaseModel.write`
681 :param record: the target record for the assignment, or an empty recordset
683 :param bool validate: when True, field-specific validation of
684 `value` will be performed
688 def convert_to_read(self, value, use_name_get=True):
689 """ convert `value` from the cache to a value as returned by method
690 :meth:`BaseModel.read`
692 :param bool use_name_get: when True, value's diplay name will
693 be computed using :meth:`BaseModel.name_get`, if relevant
696 return False if value is None else value
698 def convert_to_write(self, value, target=None, fnames=None):
699 """ convert `value` from the cache to a valid value for method
700 :meth:`BaseModel.write`.
702 :param target: optional, the record to be modified with this value
703 :param fnames: for relational fields only, an optional collection of
704 field names to convert
706 return self.convert_to_read(value)
708 def convert_to_onchange(self, value):
709 """ convert `value` from the cache to a valid value for an onchange
712 return self.convert_to_write(value)
714 def convert_to_export(self, value, env):
715 """ convert `value` from the cache to a valid value for export. The
716 parameter `env` is given for managing translations.
718 if env.context.get('export_raw_data'):
720 return bool(value) and ustr(value)
722 def convert_to_display_name(self, value):
723 """ convert `value` from the cache to a suitable display name. """
726 ############################################################################
731 def __get__(self, record, owner):
732 """ return the value of field `self` on `record` """
734 return self # the field is accessed through the owner class
737 # null record -> return the null value for this field
738 return self.null(record.env)
740 # only a single record may be accessed
744 return record._cache[self]
748 # cache miss, retrieve value
750 # normal record -> read or compute value for this field
751 self.determine_value(record)
753 # draft record -> compute the value or let it be null
754 self.determine_draft_value(record)
756 # the result should be in cache now
757 return record._cache[self]
759 def __set__(self, record, value):
760 """ set the value of field `self` on `record` """
763 # only a single record may be updated
766 # adapt value to the cache level
767 value = self.convert_to_cache(value, record)
769 if env.in_draft or not record.id:
770 # determine dependent fields
771 spec = self.modified_draft(record)
773 # set value in cache, inverse field, and mark record as dirty
774 record._cache[self] = value
776 for invf in self.inverse_fields:
777 invf._update(value, record)
780 # determine more dependent fields, and invalidate them
782 spec += self.modified_draft(record)
786 # simply write to the database, and update cache
787 record.write({self.name: self.convert_to_write(value)})
788 record._cache[self] = value
790 ############################################################################
792 # Computation of field values
795 def _compute_value(self, records):
796 """ Invoke the compute method on `records`. """
797 # mark the computed fields failed in cache, so that access before
798 # computation raises an exception
799 exc = Warning("Field %s is accessed before being computed." % self)
800 for field in self.computed_fields:
801 records._cache[field] = FailedValue(exc)
802 records.env.computed[field].update(records._ids)
803 self.compute(records)
804 for field in self.computed_fields:
805 records.env.computed[field].difference_update(records._ids)
807 def compute_value(self, records):
808 """ Invoke the compute method on `records`; the results are in cache. """
809 with records.env.do_in_draft():
811 self._compute_value(records)
812 except (AccessError, MissingError):
813 # some record is forbidden or missing, retry record by record
814 for record in records:
816 self._compute_value(record)
817 except Exception as exc:
818 record._cache[self.name] = FailedValue(exc)
820 def determine_value(self, record):
821 """ Determine the value of `self` for `record`. """
824 if self.store and not (self.depends and env.in_draft):
825 # this is a stored field
827 # this is a stored computed field, check for recomputation
828 recs = record._recompute_check(self)
830 # recompute the value (only in cache)
831 self.compute_value(recs)
832 # HACK: if result is in the wrong cache, copy values
834 for source, target in zip(recs, recs.with_env(env)):
836 values = target._convert_to_cache({
837 f.name: source[f.name] for f in self.computed_fields
839 except MissingError as e:
840 values = FailedValue(e)
841 target._cache.update(values)
842 # the result is saved to database by BaseModel.recompute()
845 # read the field from database
846 record._prefetch_field(self)
849 # this is either a non-stored computed field, or a stored computed
850 # field in draft mode
852 self.compute_value(record)
854 recs = record._in_cache_without(self)
855 self.compute_value(recs)
858 # this is a non-stored non-computed field
859 record._cache[self] = self.null(env)
861 def determine_draft_value(self, record):
862 """ Determine the value of `self` for the given draft `record`. """
864 self._compute_value(record)
866 record._cache[self] = SpecialValue(self.null(record.env))
868 def determine_inverse(self, records):
869 """ Given the value of `self` on `records`, inverse the computation. """
871 self.inverse(records)
873 def determine_domain(self, records, operator, value):
874 """ Return a domain representing a condition on `self`. """
876 return self.search(records, operator, value)
878 return [(self.name, operator, value)]
880 ############################################################################
882 # Notification when fields are modified
885 def modified(self, records):
886 """ Notify that field `self` has been modified on `records`: prepare the
887 fields/records to recompute, and return a spec indicating what to
890 # invalidate the fields that depend on self, and prepare recomputation
891 spec = [(self, records._ids)]
892 for field, path in self._triggers:
893 if path and field.store:
894 # don't move this line to function top, see log
895 env = records.env(user=SUPERUSER_ID, context={'active_test': False})
896 target = env[field.model_name].search([(path, 'in', records.ids)])
898 spec.append((field, target._ids))
899 target.with_env(records.env)._recompute_todo(field)
901 spec.append((field, None))
905 def modified_draft(self, records):
906 """ Same as :meth:`modified`, but in draft mode. """
909 # invalidate the fields on the records in cache that depend on
910 # `records`, except fields currently being computed
912 for field, path in self._triggers:
913 target = env[field.model_name]
914 computed = target.browse(env.computed[field])
916 target = records - computed
918 target = (target.browse(env.cache[field]) - computed).filtered(
919 lambda rec: rec._mapped_cache(path) & records
922 target = target.browse(env.cache[field]) - computed
925 spec.append((field, target._ids))
930 class Boolean(Field):
933 def convert_to_cache(self, value, record, validate=True):
936 def convert_to_export(self, value, env):
937 if env.context.get('export_raw_data'):
942 class Integer(Field):
945 def convert_to_cache(self, value, record, validate=True):
946 if isinstance(value, dict):
947 # special case, when an integer field is used as inverse for a one2many
948 return value.get('id', False)
949 return int(value or 0)
951 def convert_to_read(self, value, use_name_get=True):
952 # Integer values greater than 2^31-1 are not supported in pure XMLRPC,
953 # so we have to pass them as floats :-(
954 if value and value > xmlrpclib.MAXINT:
958 def _update(self, records, value):
959 # special case, when an integer field is used as inverse for a one2many
960 records._cache[self] = value.id or 0
964 """ The precision digits are given by the attribute
966 :param digits: a pair (total, decimal), or a function taking a database
967 cursor and returning a pair (total, decimal)
970 _digits = None # digits argument passed to class initializer
971 digits = None # digits as computed by setup()
973 def __init__(self, string=None, digits=None, **kwargs):
974 super(Float, self).__init__(string=string, _digits=digits, **kwargs)
976 def _setup_digits(self, env):
977 """ Setup the digits for `self` and its corresponding column """
978 self.digits = self._digits(env.cr) if callable(self._digits) else self._digits
980 assert isinstance(self.digits, (tuple, list)) and len(self.digits) >= 2, \
981 "Float field %s with digits %r, expecting (total, decimal)" % (self, self.digits)
983 column = env[self.model_name]._columns[self.name]
984 column.digits_change(env.cr)
986 def _setup_regular(self, env):
987 super(Float, self)._setup_regular(env)
988 self._setup_digits(env)
990 _related_digits = property(attrgetter('digits'))
992 _description_digits = property(attrgetter('digits'))
994 _column_digits = property(lambda self: not callable(self._digits) and self._digits)
995 _column_digits_compute = property(lambda self: callable(self._digits) and self._digits)
997 def convert_to_cache(self, value, record, validate=True):
998 # apply rounding here, otherwise value in cache may be wrong!
1000 return float_round(float(value or 0.0), precision_digits=self.digits[1])
1002 return float(value or 0.0)
1005 class _String(Field):
1006 """ Abstract class for string fields. """
1009 _column_translate = property(attrgetter('translate'))
1010 _related_translate = property(attrgetter('translate'))
1011 _description_translate = property(attrgetter('translate'))
1014 class Char(_String):
1015 """ Basic string field, can be length-limited, usually displayed as a
1016 single-line string in clients
1018 :param int size: the maximum size of values stored for that field
1019 :param bool translate: whether the values of this field can be translated
1024 def _setup(self, env):
1025 super(Char, self)._setup(env)
1026 assert isinstance(self.size, (NoneType, int)), \
1027 "Char field %s with non-integer size %r" % (self, self.size)
1029 _column_size = property(attrgetter('size'))
1030 _related_size = property(attrgetter('size'))
1031 _description_size = property(attrgetter('size'))
1033 def convert_to_cache(self, value, record, validate=True):
1034 if value is None or value is False:
1036 return ustr(value)[:self.size]
1038 class Text(_String):
1039 """ Text field. Very similar to :class:`~.Char` but used for longer
1040 contents and displayed as a multiline text box
1042 :param translate: whether the value of this field can be translated
1046 def convert_to_cache(self, value, record, validate=True):
1047 if value is None or value is False:
1051 class Html(_String):
1053 sanitize = True # whether value must be sanitized
1055 _column_sanitize = property(attrgetter('sanitize'))
1056 _related_sanitize = property(attrgetter('sanitize'))
1057 _description_sanitize = property(attrgetter('sanitize'))
1059 def convert_to_cache(self, value, record, validate=True):
1060 if value is None or value is False:
1062 if validate and self.sanitize:
1063 return html_sanitize(value)
1072 """ Return the current day in the format expected by the ORM.
1073 This function may be used to compute default values.
1075 return date.today().strftime(DATE_FORMAT)
1078 def context_today(record, timestamp=None):
1079 """ Return the current date as seen in the client's timezone in a format
1080 fit for date fields. This method may be used to compute default
1083 :param datetime timestamp: optional datetime value to use instead of
1084 the current date and time (must be a datetime, regular dates
1085 can't be converted between timezones.)
1088 today = timestamp or datetime.now()
1089 context_today = None
1090 tz_name = record._context.get('tz') or record.env.user.tz
1093 today_utc = pytz.timezone('UTC').localize(today, is_dst=False) # UTC = no DST
1094 context_today = today_utc.astimezone(pytz.timezone(tz_name))
1096 _logger.debug("failed to compute context/client-specific today date, using UTC value for `today`",
1098 return (context_today or today).strftime(DATE_FORMAT)
1101 def from_string(value):
1102 """ Convert an ORM `value` into a :class:`date` value. """
1103 value = value[:DATE_LENGTH]
1104 return datetime.strptime(value, DATE_FORMAT).date()
1107 def to_string(value):
1108 """ Convert a :class:`date` value into the format expected by the ORM. """
1109 return value.strftime(DATE_FORMAT)
1111 def convert_to_cache(self, value, record, validate=True):
1114 if isinstance(value, basestring):
1116 # force parsing for validation
1117 self.from_string(value)
1118 return value[:DATE_LENGTH]
1119 return self.to_string(value)
1121 def convert_to_export(self, value, env):
1122 if value and env.context.get('export_raw_data'):
1123 return self.from_string(value)
1124 return bool(value) and ustr(value)
1127 class Datetime(Field):
1132 """ Return the current day and time in the format expected by the ORM.
1133 This function may be used to compute default values.
1135 return datetime.now().strftime(DATETIME_FORMAT)
1138 def context_timestamp(record, timestamp):
1139 """Returns the given timestamp converted to the client's timezone.
1140 This method is *not* meant for use as a _defaults initializer,
1141 because datetime fields are automatically converted upon
1142 display on client side. For _defaults you :meth:`fields.datetime.now`
1143 should be used instead.
1145 :param datetime timestamp: naive datetime value (expressed in UTC)
1146 to be converted to the client timezone
1148 :return: timestamp converted to timezone-aware datetime in context
1151 assert isinstance(timestamp, datetime), 'Datetime instance expected'
1152 tz_name = record._context.get('tz') or record.env.user.tz
1155 utc = pytz.timezone('UTC')
1156 context_tz = pytz.timezone(tz_name)
1157 utc_timestamp = utc.localize(timestamp, is_dst=False) # UTC = no DST
1158 return utc_timestamp.astimezone(context_tz)
1160 _logger.debug("failed to compute context/client-specific timestamp, "
1161 "using the UTC value",
1166 def from_string(value):
1167 """ Convert an ORM `value` into a :class:`datetime` value. """
1168 value = value[:DATETIME_LENGTH]
1169 if len(value) == DATE_LENGTH:
1170 value += " 00:00:00"
1171 return datetime.strptime(value, DATETIME_FORMAT)
1174 def to_string(value):
1175 """ Convert a :class:`datetime` value into the format expected by the ORM. """
1176 return value.strftime(DATETIME_FORMAT)
1178 def convert_to_cache(self, value, record, validate=True):
1181 if isinstance(value, basestring):
1183 # force parsing for validation
1184 self.from_string(value)
1185 value = value[:DATETIME_LENGTH]
1186 if len(value) == DATE_LENGTH:
1187 value += " 00:00:00"
1189 return self.to_string(value)
1191 def convert_to_export(self, value, env):
1192 if value and env.context.get('export_raw_data'):
1193 return self.from_string(value)
1194 return bool(value) and ustr(value)
1197 class Binary(Field):
1201 class Selection(Field):
1203 :param selection: specifies the possible values for this field.
1204 It is given as either a list of pairs (`value`, `string`), or a
1205 model method, or a method name.
1206 :param selection_add: provides an extension of the selection in the case
1207 of an overridden field. It is a list of pairs (`value`, `string`).
1209 The attribute `selection` is mandatory except in the case of
1210 :ref:`related fields <field-related>` or :ref:`field extensions
1211 <field-incremental-definition>`.
1214 selection = None # [(value, string), ...], function or method name
1215 selection_add = None # [(value, string), ...]
1217 def __init__(self, selection=None, string=None, **kwargs):
1218 if callable(selection):
1219 from openerp import api
1220 selection = api.expected(api.model, selection)
1221 super(Selection, self).__init__(selection=selection, string=string, **kwargs)
1223 def _setup(self, env):
1224 super(Selection, self)._setup(env)
1225 assert self.selection is not None, "Field %s without selection" % self
1227 def _setup_related(self, env):
1228 super(Selection, self)._setup_related(env)
1229 # selection must be computed on related field
1230 field = self.related_field
1231 self.selection = lambda model: field._description_selection(model.env)
1233 def set_class_name(self, cls, name):
1234 super(Selection, self).set_class_name(cls, name)
1235 # determine selection (applying 'selection_add' extensions)
1237 for field in resolve_all_mro(cls, name, reverse=True):
1238 if isinstance(field, type(self)):
1239 # We cannot use field.selection or field.selection_add here
1240 # because those attributes are overridden by `set_class_name`.
1241 if 'selection' in field._attrs:
1242 selection = field._attrs['selection']
1243 if 'selection_add' in field._attrs:
1244 selection = selection + field._attrs['selection_add']
1247 self.selection = selection
1249 def _description_selection(self, env):
1250 """ return the selection list (pairs (value, label)); labels are
1251 translated according to context language
1253 selection = self.selection
1254 if isinstance(selection, basestring):
1255 return getattr(env[self.model_name], selection)()
1256 if callable(selection):
1257 return selection(env[self.model_name])
1259 # translate selection labels
1261 name = "%s,%s" % (self.model_name, self.name)
1262 translate = partial(
1263 env['ir.translation']._get_source, name, 'selection', env.lang)
1264 return [(value, translate(label) if label else label) for value, label in selection]
1269 def _column_selection(self):
1270 if isinstance(self.selection, basestring):
1271 method = self.selection
1272 return lambda self, *a, **kw: getattr(self, method)(*a, **kw)
1274 return self.selection
1276 def get_values(self, env):
1277 """ return a list of the possible values """
1278 selection = self.selection
1279 if isinstance(selection, basestring):
1280 selection = getattr(env[self.model_name], selection)()
1281 elif callable(selection):
1282 selection = selection(env[self.model_name])
1283 return [value for value, _ in selection]
1285 def convert_to_cache(self, value, record, validate=True):
1287 return value or False
1288 if value in self.get_values(record.env):
1292 raise ValueError("Wrong value for %s: %r" % (self, value))
1294 def convert_to_export(self, value, env):
1295 if not isinstance(self.selection, list):
1296 # FIXME: this reproduces an existing buggy behavior!
1298 for item in self._description_selection(env):
1299 if item[0] == value:
1304 class Reference(Selection):
1308 def __init__(self, selection=None, string=None, **kwargs):
1309 super(Reference, self).__init__(selection=selection, string=string, **kwargs)
1311 def _setup(self, env):
1312 super(Reference, self)._setup(env)
1313 assert isinstance(self.size, int), \
1314 "Reference field %s with non-integer size %r" % (self, self.size)
1316 _related_size = property(attrgetter('size'))
1318 _column_size = property(attrgetter('size'))
1320 def convert_to_cache(self, value, record, validate=True):
1321 if isinstance(value, BaseModel):
1322 if ((not validate or value._name in self.get_values(record.env))
1323 and len(value) <= 1):
1324 return value.with_env(record.env) or False
1325 elif isinstance(value, basestring):
1326 res_model, res_id = value.split(',')
1327 return record.env[res_model].browse(int(res_id))
1330 raise ValueError("Wrong value for %s: %r" % (self, value))
1332 def convert_to_read(self, value, use_name_get=True):
1333 return "%s,%s" % (value._name, value.id) if value else False
1335 def convert_to_export(self, value, env):
1336 return bool(value) and value.name_get()[0][1]
1338 def convert_to_display_name(self, value):
1339 return ustr(value and value.display_name)
1342 class _Relational(Field):
1343 """ Abstract class for relational fields. """
1345 domain = None # domain for searching values
1346 context = None # context for searching values
1348 def _setup(self, env):
1349 super(_Relational, self)._setup(env)
1350 assert self.comodel_name in env.registry, \
1351 "Field %s with unknown comodel_name %r" % (self, self.comodel_name)
1354 def _related_domain(self):
1355 if callable(self.domain):
1356 # will be called with another model than self's
1357 return lambda recs: self.domain(recs.env[self.model_name])
1359 # maybe not correct if domain is a string...
1362 _related_context = property(attrgetter('context'))
1364 _description_relation = property(attrgetter('comodel_name'))
1365 _description_context = property(attrgetter('context'))
1367 def _description_domain(self, env):
1368 return self.domain(env[self.model_name]) if callable(self.domain) else self.domain
1370 _column_obj = property(attrgetter('comodel_name'))
1371 _column_domain = property(attrgetter('domain'))
1372 _column_context = property(attrgetter('context'))
1374 def null(self, env):
1375 return env[self.comodel_name]
1377 def modified(self, records):
1378 # Invalidate cache for self.inverse_fields, too. Note that recomputation
1379 # of fields that depend on self.inverse_fields is already covered by the
1380 # triggers (see above).
1381 spec = super(_Relational, self).modified(records)
1382 for invf in self.inverse_fields:
1383 spec.append((invf, None))
1387 class Many2one(_Relational):
1388 """ The value of such a field is a recordset of size 0 (no
1389 record) or 1 (a single record).
1391 :param comodel_name: name of the target model (string)
1393 :param domain: an optional domain to set on candidate values on the
1394 client side (domain or string)
1396 :param context: an optional context to use on the client side when
1397 handling that field (dictionary)
1399 :param ondelete: what to do when the referred record is deleted;
1400 possible values are: ``'set null'``, ``'restrict'``, ``'cascade'``
1402 :param auto_join: whether JOINs are generated upon search through that
1403 field (boolean, by default ``False``)
1405 :param delegate: set it to ``True`` to make fields of the target model
1406 accessible from the current model (corresponds to ``_inherits``)
1408 The attribute `comodel_name` is mandatory except in the case of related
1409 fields or field extensions.
1412 ondelete = 'set null' # what to do when value is deleted
1413 auto_join = False # whether joins are generated upon search
1414 delegate = False # whether self implements delegation
1416 def __init__(self, comodel_name=None, string=None, **kwargs):
1417 super(Many2one, self).__init__(comodel_name=comodel_name, string=string, **kwargs)
1419 def set_class_name(self, cls, name):
1420 super(Many2one, self).set_class_name(cls, name)
1421 # determine self.delegate
1422 if not self.delegate:
1423 self.delegate = name in cls._inherits.values()
1425 _column_ondelete = property(attrgetter('ondelete'))
1426 _column_auto_join = property(attrgetter('auto_join'))
1428 def _update(self, records, value):
1429 """ Update the cached value of `self` for `records` with `value`. """
1430 records._cache[self] = value
1432 def convert_to_cache(self, value, record, validate=True):
1433 if isinstance(value, (NoneType, int)):
1434 return record.env[self.comodel_name].browse(value)
1435 if isinstance(value, BaseModel):
1436 if value._name == self.comodel_name and len(value) <= 1:
1437 return value.with_env(record.env)
1438 raise ValueError("Wrong value for %s: %r" % (self, value))
1439 elif isinstance(value, tuple):
1440 return record.env[self.comodel_name].browse(value[0])
1441 elif isinstance(value, dict):
1442 return record.env[self.comodel_name].new(value)
1444 return record.env[self.comodel_name].browse(value)
1446 def convert_to_read(self, value, use_name_get=True):
1447 if use_name_get and value:
1448 # evaluate name_get() as superuser, because the visibility of a
1449 # many2one field value (id and name) depends on the current record's
1450 # access rights, and not the value's access rights.
1452 return value.sudo().name_get()[0]
1453 except MissingError:
1454 # Should not happen, unless the foreign key is missing.
1459 def convert_to_write(self, value, target=None, fnames=None):
1462 def convert_to_onchange(self, value):
1465 def convert_to_export(self, value, env):
1466 return bool(value) and value.name_get()[0][1]
1468 def convert_to_display_name(self, value):
1469 return ustr(value.display_name)
1472 class UnionUpdate(SpecialValue):
1473 """ Placeholder for a value update; when this value is taken from the cache,
1474 it returns ``record[field.name] | value`` and stores it in the cache.
1476 def __init__(self, field, record, value):
1477 self.args = (field, record, value)
1480 field, record, value = self.args
1481 # in order to read the current field's value, remove self from cache
1482 del record._cache[field]
1483 # read the current field's value, and update it in cache only
1484 record._cache[field] = new_value = record[field.name] | value
1488 class _RelationalMulti(_Relational):
1489 """ Abstract class for relational fields *2many. """
1491 def _update(self, records, value):
1492 """ Update the cached value of `self` for `records` with `value`. """
1493 for record in records:
1494 if self in record._cache:
1495 record._cache[self] = record[self.name] | value
1497 record._cache[self] = UnionUpdate(self, record, value)
1499 def convert_to_cache(self, value, record, validate=True):
1500 if isinstance(value, BaseModel):
1501 if value._name == self.comodel_name:
1502 return value.with_env(record.env)
1503 elif isinstance(value, list):
1504 # value is a list of record ids or commands
1506 record = record.browse() # new record has no value
1507 result = record[self.name]
1508 # modify result with the commands;
1509 # beware to not introduce duplicates in result
1510 for command in value:
1511 if isinstance(command, (tuple, list)):
1513 result += result.new(command[2])
1514 elif command[0] == 1:
1515 result.browse(command[1]).update(command[2])
1516 result += result.browse(command[1]) - result
1517 elif command[0] == 2:
1518 # note: the record will be deleted by write()
1519 result -= result.browse(command[1])
1520 elif command[0] == 3:
1521 result -= result.browse(command[1])
1522 elif command[0] == 4:
1523 result += result.browse(command[1]) - result
1524 elif command[0] == 5:
1525 result = result.browse()
1526 elif command[0] == 6:
1527 result = result.browse(command[2])
1528 elif isinstance(command, dict):
1529 result += result.new(command)
1531 result += result.browse(command) - result
1534 return self.null(record.env)
1535 raise ValueError("Wrong value for %s: %s" % (self, value))
1537 def convert_to_read(self, value, use_name_get=True):
1540 def convert_to_write(self, value, target=None, fnames=None):
1541 # remove/delete former records
1544 result = [(6, 0, set_ids)]
1545 add_existing = lambda id: set_ids.append(id)
1547 tag = 2 if self.type == 'one2many' else 3
1548 result = [(tag, record.id) for record in target[self.name] - value]
1549 add_existing = lambda id: result.append((4, id))
1552 # take all fields in cache, except the inverses of self
1553 fnames = set(value._fields) - set(MAGIC_COLUMNS)
1554 for invf in self.inverse_fields:
1555 fnames.discard(invf.name)
1557 # add new and existing records
1558 for record in value:
1559 if not record.id or record._dirty:
1560 values = dict((k, v) for k, v in record._cache.iteritems() if k in fnames)
1561 values = record._convert_to_write(values)
1563 result.append((0, 0, values))
1565 result.append((1, record.id, values))
1567 add_existing(record.id)
1571 def convert_to_export(self, value, env):
1572 return bool(value) and ','.join(name for id, name in value.name_get())
1574 def convert_to_display_name(self, value):
1575 raise NotImplementedError()
1577 def _compute_related(self, records):
1578 """ Compute the related field `self` on `records`. """
1579 for record in records:
1581 # traverse the intermediate fields, and keep at most one record
1582 for name in self.related[:-1]:
1583 value = value[name][:1]
1584 record[self.name] = value[self.related[-1]]
1587 class One2many(_RelationalMulti):
1588 """ One2many field; the value of such a field is the recordset of all the
1589 records in `comodel_name` such that the field `inverse_name` is equal to
1592 :param comodel_name: name of the target model (string)
1594 :param inverse_name: name of the inverse `Many2one` field in
1595 `comodel_name` (string)
1597 :param domain: an optional domain to set on candidate values on the
1598 client side (domain or string)
1600 :param context: an optional context to use on the client side when
1601 handling that field (dictionary)
1603 :param auto_join: whether JOINs are generated upon search through that
1604 field (boolean, by default ``False``)
1606 :param limit: optional limit to use upon read (integer)
1608 The attributes `comodel_name` and `inverse_name` are mandatory except in
1609 the case of related fields or field extensions.
1612 inverse_name = None # name of the inverse field
1613 auto_join = False # whether joins are generated upon search
1614 limit = None # optional limit to use upon read
1615 copy = False # o2m are not copied by default
1617 def __init__(self, comodel_name=None, inverse_name=None, string=None, **kwargs):
1618 super(One2many, self).__init__(
1619 comodel_name=comodel_name,
1620 inverse_name=inverse_name,
1625 def _setup_regular(self, env):
1626 super(One2many, self)._setup_regular(env)
1628 if self.inverse_name:
1629 # link self to its inverse field and vice-versa
1630 invf = env[self.comodel_name]._fields[self.inverse_name]
1631 # In some rare cases, a `One2many` field can link to `Int` field
1632 # (res_model/res_id pattern). Only inverse the field if this is
1633 # a `Many2one` field.
1634 if isinstance(invf, Many2one):
1635 self.inverse_fields.append(invf)
1636 invf.inverse_fields.append(self)
1638 _description_relation_field = property(attrgetter('inverse_name'))
1640 _column_fields_id = property(attrgetter('inverse_name'))
1641 _column_auto_join = property(attrgetter('auto_join'))
1642 _column_limit = property(attrgetter('limit'))
1645 class Many2many(_RelationalMulti):
1646 """ Many2many field; the value of such a field is the recordset.
1648 :param comodel_name: name of the target model (string)
1650 The attribute `comodel_name` is mandatory except in the case of related
1651 fields or field extensions.
1653 :param relation: optional name of the table that stores the relation in
1654 the database (string)
1656 :param column1: optional name of the column referring to "these" records
1657 in the table `relation` (string)
1659 :param column2: optional name of the column referring to "those" records
1660 in the table `relation` (string)
1662 The attributes `relation`, `column1` and `column2` are optional. If not
1663 given, names are automatically generated from model names, provided
1664 `model_name` and `comodel_name` are different!
1666 :param domain: an optional domain to set on candidate values on the
1667 client side (domain or string)
1669 :param context: an optional context to use on the client side when
1670 handling that field (dictionary)
1672 :param limit: optional limit to use upon read (integer)
1676 relation = None # name of table
1677 column1 = None # column of table referring to model
1678 column2 = None # column of table referring to comodel
1679 limit = None # optional limit to use upon read
1681 def __init__(self, comodel_name=None, relation=None, column1=None, column2=None,
1682 string=None, **kwargs):
1683 super(Many2many, self).__init__(
1684 comodel_name=comodel_name,
1692 def _setup_regular(self, env):
1693 super(Many2many, self)._setup_regular(env)
1695 if self.store and not self.relation:
1696 model = env[self.model_name]
1697 column = model._columns[self.name]
1698 if not isinstance(column, fields.function):
1699 self.relation, self.column1, self.column2 = column._sql_names(model)
1702 m2m = env.registry._m2m
1703 # if inverse field has already been setup, it is present in m2m
1704 invf = m2m.get((self.relation, self.column2, self.column1))
1706 self.inverse_fields.append(invf)
1707 invf.inverse_fields.append(self)
1709 # add self in m2m, so that its inverse field can find it
1710 m2m[(self.relation, self.column1, self.column2)] = self
1712 _column_rel = property(attrgetter('relation'))
1713 _column_id1 = property(attrgetter('column1'))
1714 _column_id2 = property(attrgetter('column2'))
1715 _column_limit = property(attrgetter('limit'))
1719 """ Special case for field 'id'. """
1721 #: Can't write this!
1724 def __init__(self, string=None, **kwargs):
1725 super(Id, self).__init__(type='integer', string=string, **kwargs)
1727 def to_column(self):
1728 """ to_column() -> fields._column
1732 return fields.integer('ID')
1734 def __get__(self, record, owner):
1736 return self # the field is accessed through the class owner
1739 return record.ensure_one()._ids[0]
1741 def __set__(self, record, value):
1742 raise TypeError("field 'id' cannot be assigned")
1745 # imported here to avoid dependency cycle issues
1746 from openerp import SUPERUSER_ID
1747 from .exceptions import Warning, AccessError, MissingError
1748 from .models import BaseModel, MAGIC_COLUMNS
1749 from .osv import fields