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 Some field attributes are automatically copied from the source field if
206 they are not redefined: `string`, `help`, `readonly`, `required` (only
207 if all fields in the sequence are required), `groups`, `digits`, `size`,
208 `translate`, `sanitize`, `selection`, `comodel_name`, `domain`,
209 `context`. All semantic-free attributes are copied from the source
212 By default, the values of related fields are not stored to the database.
213 Add the attribute ``store=True`` to make it stored, just like computed
214 fields. Related fields are automatically recomputed when their
215 dependencies are modified.
217 .. _field-company-dependent:
219 .. rubric:: Company-dependent fields
221 Formerly known as 'property' fields, the value of those fields depends
222 on the company. In other words, users that belong to different companies
223 may see different values for the field on a given record.
225 :param company_dependent: whether the field is company-dependent (boolean)
227 .. _field-incremental-definition:
229 .. rubric:: Incremental definition
231 A field is defined as class attribute on a model class. If the model
232 is extended (see :class:`~openerp.models.Model`), one can also extend
233 the field definition by redefining a field with the same name and same
234 type on the subclass. In that case, the attributes of the field are
235 taken from the parent class and overridden by the ones given in
238 For instance, the second class below only adds a tooltip on the field
241 class First(models.Model):
243 state = fields.Selection([...], required=True)
245 class Second(models.Model):
247 state = fields.Selection(help="Blah blah blah")
250 __metaclass__ = MetaField
252 _attrs = None # dictionary with all field attributes
253 _free_attrs = None # list of semantic-free attribute names
255 automatic = False # whether the field is automatically created ("magic" field)
256 inherited = False # whether the field is inherited (_inherits)
257 column = None # the column corresponding to the field
258 setup_done = False # whether the field has been set up
260 name = None # name of the field
261 type = None # type of the field (string)
262 relational = False # whether the field is a relational one
263 model_name = None # name of the model of this field
264 comodel_name = None # name of the model of values (if relational)
265 inverse_fields = None # list of inverse fields (objects)
267 store = True # whether the field is stored in database
268 index = False # whether the field is indexed in database
269 manual = False # whether the field is a custom field
270 copy = True # whether the field is copied over by BaseModel.copy()
271 depends = () # collection of field dependencies
272 recursive = False # whether self depends on itself
273 compute = None # compute(recs) computes field on recs
274 inverse = None # inverse(recs) inverses field on recs
275 search = None # search(recs, operator, value) searches on self
276 related = None # sequence of field names, for related fields
277 related_sudo = True # whether related fields should be read as admin
278 company_dependent = False # whether `self` is company-dependent (property field)
279 default = None # default(recs) returns the default value
281 string = None # field label
282 help = None # field tooltip
286 groups = False # csv list of group xml ids
287 change_default = None # whether the field may trigger a "user-onchange"
288 deprecated = None # whether the field is ... deprecated
290 def __init__(self, string=None, **kwargs):
291 kwargs['string'] = string
292 self._attrs = {key: val for key, val in kwargs.iteritems() if val is not None}
293 self._free_attrs = []
295 def new(self, **kwargs):
296 """ Return a field of the same type as `self`, with its own parameters. """
297 return type(self)(**kwargs)
299 def set_class_name(self, cls, name):
300 """ Assign the model class and field name of `self`. """
301 self.model_name = cls._name
304 # determine all inherited field attributes
306 for field in resolve_all_mro(cls, name, reverse=True):
307 if isinstance(field, type(self)):
308 attrs.update(field._attrs)
311 attrs.update(self._attrs) # necessary in case self is not in cls
313 # initialize `self` with `attrs`
314 if attrs.get('compute'):
315 # by default, computed fields are not stored, not copied and readonly
316 attrs['store'] = attrs.get('store', False)
317 attrs['copy'] = attrs.get('copy', False)
318 attrs['readonly'] = attrs.get('readonly', not attrs.get('inverse'))
319 if attrs.get('related'):
320 # by default, related fields are not stored
321 attrs['store'] = attrs.get('store', False)
323 # fix for function fields overridden by regular columns
324 if not isinstance(attrs.get('column'), (NoneType, fields.function)):
325 attrs.pop('store', None)
327 for attr, value in attrs.iteritems():
328 if not hasattr(self, attr):
329 self._free_attrs.append(attr)
330 setattr(self, attr, value)
332 if not self.string and not self.related:
333 # related fields get their string from their parent field
334 self.string = name.replace('_', ' ').capitalize()
336 # determine self.default and cls._defaults in a consistent way
337 self._determine_default(cls, name)
341 def _determine_default(self, cls, name):
342 """ Retrieve the default value for `self` in the hierarchy of `cls`, and
343 determine `self.default` and `cls._defaults` accordingly.
347 # traverse the class hierarchy upwards, and take the first field
348 # definition with a default or _defaults for self
349 for klass in cls.__mro__:
350 field = klass.__dict__.get(name, self)
351 if not isinstance(field, type(self)):
352 return # klass contains another value overridden by self
354 if 'default' in field._attrs:
355 # take the default in field, and adapt it for cls._defaults
356 value = field._attrs['default']
359 cls._defaults[name] = lambda model, cr, uid, context: \
360 self.convert_to_write(value(model.browse(cr, uid, [], context)))
362 self.default = lambda recs: value
363 cls._defaults[name] = value
366 defaults = klass.__dict__.get('_defaults') or {}
368 # take the value from _defaults, and adapt it for self.default
369 value = defaults[name]
370 value_func = value if callable(value) else lambda *args: value
371 self.default = lambda recs: self.convert_to_cache(
372 value_func(recs._model, recs._cr, recs._uid, recs._context),
373 recs, validate=False,
375 cls._defaults[name] = value
379 return "%s.%s" % (self.model_name, self.name)
382 return "%s.%s" % (self.model_name, self.name)
384 ############################################################################
390 """ Prepare `self` for a new setup. """
391 self.setup_done = False
392 # self._triggers is a set of pairs (field, path) that represents the
393 # computed fields that depend on `self`. When `self` is modified, it
394 # invalidates the cache of each `field`, and registers the records to
395 # recompute based on `path`. See method `modified` below for details.
396 self._triggers = set()
397 self.inverse_fields = []
399 def setup(self, env):
400 """ Complete the setup of `self` (dependencies, recomputation triggers,
401 and other properties). This method is idempotent: it has no effect
402 if `self` has already been set up.
404 if not self.setup_done:
406 self.setup_done = True
408 def _setup(self, env):
409 """ Do the actual setup of `self`. """
411 self._setup_related(env)
413 self._setup_regular(env)
415 # put invalidation/recomputation triggers on field dependencies
416 model = env[self.model_name]
417 for path in self.depends:
418 self._setup_dependency([], model, path.split('.'))
420 # put invalidation triggers on model dependencies
421 for dep_model_name, field_names in model._depends.iteritems():
422 dep_model = env[dep_model_name]
423 for field_name in field_names:
424 field = dep_model._fields[field_name]
425 field._triggers.add((self, None))
428 # Setup of related fields
431 def _setup_related(self, env):
432 """ Setup the attributes of a related field. """
433 # fix the type of self.related if necessary
434 if isinstance(self.related, basestring):
435 self.related = tuple(self.related.split('.'))
437 # determine the chain of fields, and make sure they are all set up
438 recs = env[self.model_name]
440 for name in self.related:
441 field = recs._fields[name]
446 self.related_field = field
448 # check type consistency
449 if self.type != field.type:
450 raise Warning("Type of related field %s is inconsistent with %s" % (self, field))
452 # determine dependencies, compute, inverse, and search
453 self.depends = ('.'.join(self.related),)
454 self.compute = self._compute_related
455 self.inverse = self._inverse_related
456 if field._description_searchable:
457 # allow searching on self only if the related field is searchable
458 self.search = self._search_related
460 # copy attributes from field to self (string, help, etc.)
461 for attr, prop in self.related_attrs:
462 if not getattr(self, attr):
463 setattr(self, attr, getattr(field, prop))
465 for attr in field._free_attrs:
466 if attr not in self._free_attrs:
467 self._free_attrs.append(attr)
468 setattr(self, attr, getattr(field, attr))
470 # special case for required: check if all fields are required
471 if not self.store and not self.required:
472 self.required = all(field.required for field in fields)
474 def _compute_related(self, records):
475 """ Compute the related field `self` on `records`. """
476 # when related_sudo, bypass access rights checks when reading values
477 others = records.sudo() if self.related_sudo else records
478 for record, other in zip(records, others):
480 # draft record, do not switch to another environment
482 # traverse the intermediate fields; follow the first record at each step
483 for name in self.related[:-1]:
484 other = other[name][:1]
485 record[self.name] = other[self.related[-1]]
487 def _inverse_related(self, records):
488 """ Inverse the related field `self` on `records`. """
489 for record in records:
491 # traverse the intermediate fields, and keep at most one record
492 for name in self.related[:-1]:
493 other = other[name][:1]
495 other[self.related[-1]] = record[self.name]
497 def _search_related(self, records, operator, value):
498 """ Determine the domain to search on field `self`. """
499 return [('.'.join(self.related), operator, value)]
501 # properties used by _setup_related() to copy values from related field
502 _related_comodel_name = property(attrgetter('comodel_name'))
503 _related_string = property(attrgetter('string'))
504 _related_help = property(attrgetter('help'))
505 _related_readonly = property(attrgetter('readonly'))
506 _related_groups = property(attrgetter('groups'))
509 def base_field(self):
510 """ Return the base field of an inherited field, or `self`. """
511 return self.related_field if self.inherited else self
514 # Setup of non-related fields
517 def _setup_regular(self, env):
518 """ Setup the attributes of a non-related field. """
519 recs = env[self.model_name]
521 def make_depends(deps):
522 return tuple(deps(recs) if callable(deps) else deps)
524 # convert compute into a callable and determine depends
525 if isinstance(self.compute, basestring):
526 # if the compute method has been overridden, concatenate all their _depends
528 for method in resolve_all_mro(type(recs), self.compute, reverse=True):
529 self.depends += make_depends(getattr(method, '_depends', ()))
530 self.compute = getattr(type(recs), self.compute)
532 self.depends = make_depends(getattr(self.compute, '_depends', ()))
534 # convert inverse and search into callables
535 if isinstance(self.inverse, basestring):
536 self.inverse = getattr(type(recs), self.inverse)
537 if isinstance(self.search, basestring):
538 self.search = getattr(type(recs), self.search)
540 def _setup_dependency(self, path0, model, path1):
541 """ Make `self` depend on `model`; `path0 + path1` is a dependency of
542 `self`, and `path0` is the sequence of field names from `self.model`
546 head, tail = path1[0], path1[1:]
549 # special case: add triggers on all fields of model (except self)
550 fields = set(model._fields.itervalues()) - set([self])
552 fields = [model._fields[head]]
556 _logger.debug("Field %s is recursively defined", self)
557 self.recursive = True
562 #_logger.debug("Add trigger on %s to recompute %s", field, self)
563 field._triggers.add((self, '.'.join(path0 or ['id'])))
565 # add trigger on inverse fields, too
566 for invf in field.inverse_fields:
567 #_logger.debug("Add trigger on %s to recompute %s", invf, self)
568 invf._triggers.add((self, '.'.join(path0 + [head])))
570 # recursively traverse the dependency
572 comodel = env[field.comodel_name]
573 self._setup_dependency(path0 + [head], comodel, tail)
576 def dependents(self):
577 """ Return the computed fields that depend on `self`. """
578 return (field for field, path in self._triggers)
580 ############################################################################
585 def get_description(self, env):
586 """ Return a dictionary that describes the field `self`. """
587 desc = {'type': self.type}
588 for attr, prop in self.description_attrs:
589 value = getattr(self, prop)
592 if value is not None:
597 # properties used by get_description()
598 _description_store = property(attrgetter('store'))
599 _description_manual = property(attrgetter('manual'))
600 _description_depends = property(attrgetter('depends'))
601 _description_related = property(attrgetter('related'))
602 _description_company_dependent = property(attrgetter('company_dependent'))
603 _description_readonly = property(attrgetter('readonly'))
604 _description_required = property(attrgetter('required'))
605 _description_states = property(attrgetter('states'))
606 _description_groups = property(attrgetter('groups'))
607 _description_change_default = property(attrgetter('change_default'))
608 _description_deprecated = property(attrgetter('deprecated'))
611 def _description_searchable(self):
612 return bool(self.store or self.search or (self.column and self.column._fnct_search))
615 def _description_sortable(self):
616 return self.store or (self.inherited and self.related_field._description_sortable)
618 def _description_string(self, env):
619 if self.string and env.lang:
620 name = "%s,%s" % (self.model_name, self.name)
621 trans = env['ir.translation']._get_source(name, 'field', env.lang)
622 return trans or self.string
625 def _description_help(self, env):
626 if self.help and env.lang:
627 name = "%s,%s" % (self.model_name, self.name)
628 trans = env['ir.translation']._get_source(name, 'help', env.lang)
629 return trans or self.help
632 ############################################################################
634 # Conversion to column instance
638 """ return a low-level field object corresponding to `self` """
639 assert self.store or self.column
641 # determine column parameters
642 _logger.debug("Create fields._column for Field %s", self)
644 for attr, prop in self.column_attrs:
645 args[attr] = getattr(self, prop)
646 for attr in self._free_attrs:
647 args[attr] = getattr(self, attr)
649 if self.company_dependent:
650 # company-dependent fields are mapped to former property fields
651 args['type'] = self.type
652 args['relation'] = self.comodel_name
653 self.column = fields.property(**args)
655 # let the column provide a valid column for the given parameters
656 self.column = self.column.new(**args)
658 # create a fresh new column of the right type
659 self.column = getattr(fields, self.type)(**args)
663 # properties used by to_column() to create a column instance
664 _column_copy = property(attrgetter('copy'))
665 _column_select = property(attrgetter('index'))
666 _column_manual = property(attrgetter('manual'))
667 _column_string = property(attrgetter('string'))
668 _column_help = property(attrgetter('help'))
669 _column_readonly = property(attrgetter('readonly'))
670 _column_required = property(attrgetter('required'))
671 _column_states = property(attrgetter('states'))
672 _column_groups = property(attrgetter('groups'))
673 _column_change_default = property(attrgetter('change_default'))
674 _column_deprecated = property(attrgetter('deprecated'))
676 ############################################################################
678 # Conversion of values
682 """ return the null value for this field in the given environment """
685 def convert_to_cache(self, value, record, validate=True):
686 """ convert `value` to the cache level in `env`; `value` may come from
687 an assignment, or have the format of methods :meth:`BaseModel.read`
688 or :meth:`BaseModel.write`
690 :param record: the target record for the assignment, or an empty recordset
692 :param bool validate: when True, field-specific validation of
693 `value` will be performed
697 def convert_to_read(self, value, use_name_get=True):
698 """ convert `value` from the cache to a value as returned by method
699 :meth:`BaseModel.read`
701 :param bool use_name_get: when True, value's diplay name will
702 be computed using :meth:`BaseModel.name_get`, if relevant
705 return False if value is None else value
707 def convert_to_write(self, value, target=None, fnames=None):
708 """ convert `value` from the cache to a valid value for method
709 :meth:`BaseModel.write`.
711 :param target: optional, the record to be modified with this value
712 :param fnames: for relational fields only, an optional collection of
713 field names to convert
715 return self.convert_to_read(value)
717 def convert_to_onchange(self, value):
718 """ convert `value` from the cache to a valid value for an onchange
721 return self.convert_to_write(value)
723 def convert_to_export(self, value, env):
724 """ convert `value` from the cache to a valid value for export. The
725 parameter `env` is given for managing translations.
727 if env.context.get('export_raw_data'):
729 return bool(value) and ustr(value)
731 def convert_to_display_name(self, value):
732 """ convert `value` from the cache to a suitable display name. """
735 ############################################################################
740 def __get__(self, record, owner):
741 """ return the value of field `self` on `record` """
743 return self # the field is accessed through the owner class
746 # null record -> return the null value for this field
747 return self.null(record.env)
749 # only a single record may be accessed
753 return record._cache[self]
757 # cache miss, retrieve value
759 # normal record -> read or compute value for this field
760 self.determine_value(record)
762 # draft record -> compute the value or let it be null
763 self.determine_draft_value(record)
765 # the result should be in cache now
766 return record._cache[self]
768 def __set__(self, record, value):
769 """ set the value of field `self` on `record` """
772 # only a single record may be updated
775 # adapt value to the cache level
776 value = self.convert_to_cache(value, record)
778 if env.in_draft or not record.id:
779 # determine dependent fields
780 spec = self.modified_draft(record)
782 # set value in cache, inverse field, and mark record as dirty
783 record._cache[self] = value
785 for invf in self.inverse_fields:
786 invf._update(value, record)
789 # determine more dependent fields, and invalidate them
791 spec += self.modified_draft(record)
795 # simply write to the database, and update cache
796 record.write({self.name: self.convert_to_write(value)})
797 record._cache[self] = value
799 ############################################################################
801 # Computation of field values
804 def _compute_value(self, records):
805 """ Invoke the compute method on `records`. """
806 # initialize the fields to their corresponding null value in cache
807 for field in self.computed_fields:
808 records._cache[field] = field.null(records.env)
809 records.env.computed[field].update(records._ids)
810 self.compute(records)
811 for field in self.computed_fields:
812 records.env.computed[field].difference_update(records._ids)
814 def compute_value(self, records):
815 """ Invoke the compute method on `records`; the results are in cache. """
816 with records.env.do_in_draft():
818 self._compute_value(records)
819 except (AccessError, MissingError):
820 # some record is forbidden or missing, retry record by record
821 for record in records:
823 self._compute_value(record)
824 except Exception as exc:
825 record._cache[self.name] = FailedValue(exc)
827 def determine_value(self, record):
828 """ Determine the value of `self` for `record`. """
831 if self.column and not (self.depends and env.in_draft):
832 # this is a stored field or an old-style function field
834 # this is a stored computed field, check for recomputation
835 recs = record._recompute_check(self)
837 # recompute the value (only in cache)
838 self.compute_value(recs)
839 # HACK: if result is in the wrong cache, copy values
841 for source, target in zip(recs, recs.with_env(env)):
843 values = target._convert_to_cache({
844 f.name: source[f.name] for f in self.computed_fields
846 except MissingError as e:
847 values = FailedValue(e)
848 target._cache.update(values)
849 # the result is saved to database by BaseModel.recompute()
852 # read the field from database
853 record._prefetch_field(self)
856 # this is either a non-stored computed field, or a stored computed
857 # field in draft mode
859 self.compute_value(record)
861 recs = record._in_cache_without(self)
862 self.compute_value(recs)
865 # this is a non-stored non-computed field
866 record._cache[self] = self.null(env)
868 def determine_draft_value(self, record):
869 """ Determine the value of `self` for the given draft `record`. """
871 self._compute_value(record)
873 record._cache[self] = SpecialValue(self.null(record.env))
875 def determine_inverse(self, records):
876 """ Given the value of `self` on `records`, inverse the computation. """
878 self.inverse(records)
880 def determine_domain(self, records, operator, value):
881 """ Return a domain representing a condition on `self`. """
883 return self.search(records, operator, value)
885 return [(self.name, operator, value)]
887 ############################################################################
889 # Notification when fields are modified
892 def modified(self, records):
893 """ Notify that field `self` has been modified on `records`: prepare the
894 fields/records to recompute, and return a spec indicating what to
897 # invalidate the fields that depend on self, and prepare recomputation
898 spec = [(self, records._ids)]
899 for field, path in self._triggers:
900 if path and field.store:
901 # don't move this line to function top, see log
902 env = records.env(user=SUPERUSER_ID, context={'active_test': False})
903 target = env[field.model_name].search([(path, 'in', records.ids)])
905 spec.append((field, target._ids))
906 target.with_env(records.env)._recompute_todo(field)
908 spec.append((field, None))
912 def modified_draft(self, records):
913 """ Same as :meth:`modified`, but in draft mode. """
916 # invalidate the fields on the records in cache that depend on
917 # `records`, except fields currently being computed
919 for field, path in self._triggers:
920 target = env[field.model_name]
921 computed = target.browse(env.computed[field])
923 target = records - computed
925 target = (target.browse(env.cache[field]) - computed).filtered(
926 lambda rec: rec._mapped_cache(path) & records
929 target = target.browse(env.cache[field]) - computed
932 spec.append((field, target._ids))
937 class Boolean(Field):
940 def convert_to_cache(self, value, record, validate=True):
943 def convert_to_export(self, value, env):
944 if env.context.get('export_raw_data'):
949 class Integer(Field):
951 group_operator = None # operator for aggregating values
953 _related_group_operator = property(attrgetter('group_operator'))
955 _column_group_operator = property(attrgetter('group_operator'))
957 def convert_to_cache(self, value, record, validate=True):
958 if isinstance(value, dict):
959 # special case, when an integer field is used as inverse for a one2many
960 return value.get('id', False)
961 return int(value or 0)
963 def convert_to_read(self, value, use_name_get=True):
964 # Integer values greater than 2^31-1 are not supported in pure XMLRPC,
965 # so we have to pass them as floats :-(
966 if value and value > xmlrpclib.MAXINT:
970 def _update(self, records, value):
971 # special case, when an integer field is used as inverse for a one2many
972 records._cache[self] = value.id or 0
976 """ The precision digits are given by the attribute
978 :param digits: a pair (total, decimal), or a function taking a database
979 cursor and returning a pair (total, decimal)
982 _digits = None # digits argument passed to class initializer
983 digits = None # digits as computed by setup()
984 group_operator = None # operator for aggregating values
986 def __init__(self, string=None, digits=None, **kwargs):
987 super(Float, self).__init__(string=string, _digits=digits, **kwargs)
989 def _setup_digits(self, env):
990 """ Setup the digits for `self` and its corresponding column """
991 self.digits = self._digits(env.cr) if callable(self._digits) else self._digits
993 assert isinstance(self.digits, (tuple, list)) and len(self.digits) >= 2, \
994 "Float field %s with digits %r, expecting (total, decimal)" % (self, self.digits)
996 self.column.digits_change(env.cr)
998 def _setup_regular(self, env):
999 super(Float, self)._setup_regular(env)
1000 self._setup_digits(env)
1002 _related_digits = property(attrgetter('digits'))
1003 _related_group_operator = property(attrgetter('group_operator'))
1005 _description_digits = property(attrgetter('digits'))
1007 _column_digits = property(lambda self: not callable(self._digits) and self._digits)
1008 _column_digits_compute = property(lambda self: callable(self._digits) and self._digits)
1009 _column_group_operator = property(attrgetter('group_operator'))
1011 def convert_to_cache(self, value, record, validate=True):
1012 # apply rounding here, otherwise value in cache may be wrong!
1014 return float_round(float(value or 0.0), precision_digits=self.digits[1])
1016 return float(value or 0.0)
1019 class _String(Field):
1020 """ Abstract class for string fields. """
1023 _column_translate = property(attrgetter('translate'))
1024 _related_translate = property(attrgetter('translate'))
1025 _description_translate = property(attrgetter('translate'))
1028 class Char(_String):
1029 """ Basic string field, can be length-limited, usually displayed as a
1030 single-line string in clients
1032 :param int size: the maximum size of values stored for that field
1033 :param bool translate: whether the values of this field can be translated
1038 def _setup(self, env):
1039 super(Char, self)._setup(env)
1040 assert isinstance(self.size, (NoneType, int)), \
1041 "Char field %s with non-integer size %r" % (self, self.size)
1043 _column_size = property(attrgetter('size'))
1044 _related_size = property(attrgetter('size'))
1045 _description_size = property(attrgetter('size'))
1047 def convert_to_cache(self, value, record, validate=True):
1048 if value is None or value is False:
1050 return ustr(value)[:self.size]
1052 class Text(_String):
1053 """ Text field. Very similar to :class:`~.Char` but used for longer
1054 contents and displayed as a multiline text box
1056 :param translate: whether the value of this field can be translated
1060 def convert_to_cache(self, value, record, validate=True):
1061 if value is None or value is False:
1065 class Html(_String):
1067 sanitize = True # whether value must be sanitized
1069 _column_sanitize = property(attrgetter('sanitize'))
1070 _related_sanitize = property(attrgetter('sanitize'))
1071 _description_sanitize = property(attrgetter('sanitize'))
1073 def convert_to_cache(self, value, record, validate=True):
1074 if value is None or value is False:
1076 if validate and self.sanitize:
1077 return html_sanitize(value)
1086 """ Return the current day in the format expected by the ORM.
1087 This function may be used to compute default values.
1089 return date.today().strftime(DATE_FORMAT)
1092 def context_today(record, timestamp=None):
1093 """ Return the current date as seen in the client's timezone in a format
1094 fit for date fields. This method may be used to compute default
1097 :param datetime timestamp: optional datetime value to use instead of
1098 the current date and time (must be a datetime, regular dates
1099 can't be converted between timezones.)
1102 today = timestamp or datetime.now()
1103 context_today = None
1104 tz_name = record._context.get('tz') or record.env.user.tz
1107 today_utc = pytz.timezone('UTC').localize(today, is_dst=False) # UTC = no DST
1108 context_today = today_utc.astimezone(pytz.timezone(tz_name))
1110 _logger.debug("failed to compute context/client-specific today date, using UTC value for `today`",
1112 return (context_today or today).strftime(DATE_FORMAT)
1115 def from_string(value):
1116 """ Convert an ORM `value` into a :class:`date` value. """
1117 value = value[:DATE_LENGTH]
1118 return datetime.strptime(value, DATE_FORMAT).date()
1121 def to_string(value):
1122 """ Convert a :class:`date` value into the format expected by the ORM. """
1123 return value.strftime(DATE_FORMAT)
1125 def convert_to_cache(self, value, record, validate=True):
1128 if isinstance(value, basestring):
1130 # force parsing for validation
1131 self.from_string(value)
1132 return value[:DATE_LENGTH]
1133 return self.to_string(value)
1135 def convert_to_export(self, value, env):
1136 if value and env.context.get('export_raw_data'):
1137 return self.from_string(value)
1138 return bool(value) and ustr(value)
1141 class Datetime(Field):
1146 """ Return the current day and time in the format expected by the ORM.
1147 This function may be used to compute default values.
1149 return datetime.now().strftime(DATETIME_FORMAT)
1152 def context_timestamp(record, timestamp):
1153 """Returns the given timestamp converted to the client's timezone.
1154 This method is *not* meant for use as a _defaults initializer,
1155 because datetime fields are automatically converted upon
1156 display on client side. For _defaults you :meth:`fields.datetime.now`
1157 should be used instead.
1159 :param datetime timestamp: naive datetime value (expressed in UTC)
1160 to be converted to the client timezone
1162 :return: timestamp converted to timezone-aware datetime in context
1165 assert isinstance(timestamp, datetime), 'Datetime instance expected'
1166 tz_name = record._context.get('tz') or record.env.user.tz
1169 utc = pytz.timezone('UTC')
1170 context_tz = pytz.timezone(tz_name)
1171 utc_timestamp = utc.localize(timestamp, is_dst=False) # UTC = no DST
1172 return utc_timestamp.astimezone(context_tz)
1174 _logger.debug("failed to compute context/client-specific timestamp, "
1175 "using the UTC value",
1180 def from_string(value):
1181 """ Convert an ORM `value` into a :class:`datetime` value. """
1182 value = value[:DATETIME_LENGTH]
1183 if len(value) == DATE_LENGTH:
1184 value += " 00:00:00"
1185 return datetime.strptime(value, DATETIME_FORMAT)
1188 def to_string(value):
1189 """ Convert a :class:`datetime` value into the format expected by the ORM. """
1190 return value.strftime(DATETIME_FORMAT)
1192 def convert_to_cache(self, value, record, validate=True):
1195 if isinstance(value, basestring):
1197 # force parsing for validation
1198 self.from_string(value)
1199 value = value[:DATETIME_LENGTH]
1200 if len(value) == DATE_LENGTH:
1201 value += " 00:00:00"
1203 return self.to_string(value)
1205 def convert_to_export(self, value, env):
1206 if value and env.context.get('export_raw_data'):
1207 return self.from_string(value)
1208 return bool(value) and ustr(value)
1211 class Binary(Field):
1215 class Selection(Field):
1217 :param selection: specifies the possible values for this field.
1218 It is given as either a list of pairs (`value`, `string`), or a
1219 model method, or a method name.
1220 :param selection_add: provides an extension of the selection in the case
1221 of an overridden field. It is a list of pairs (`value`, `string`).
1223 The attribute `selection` is mandatory except in the case of
1224 :ref:`related fields <field-related>` or :ref:`field extensions
1225 <field-incremental-definition>`.
1228 selection = None # [(value, string), ...], function or method name
1229 selection_add = None # [(value, string), ...]
1231 def __init__(self, selection=None, string=None, **kwargs):
1232 if callable(selection):
1233 from openerp import api
1234 selection = api.expected(api.model, selection)
1235 super(Selection, self).__init__(selection=selection, string=string, **kwargs)
1237 def _setup(self, env):
1238 super(Selection, self)._setup(env)
1239 assert self.selection is not None, "Field %s without selection" % self
1241 def _setup_related(self, env):
1242 super(Selection, self)._setup_related(env)
1243 # selection must be computed on related field
1244 field = self.related_field
1245 self.selection = lambda model: field._description_selection(model.env)
1247 def set_class_name(self, cls, name):
1248 super(Selection, self).set_class_name(cls, name)
1249 # determine selection (applying 'selection_add' extensions)
1251 for field in resolve_all_mro(cls, name, reverse=True):
1252 if isinstance(field, type(self)):
1253 # We cannot use field.selection or field.selection_add here
1254 # because those attributes are overridden by `set_class_name`.
1255 if 'selection' in field._attrs:
1256 selection = field._attrs['selection']
1257 if 'selection_add' in field._attrs:
1258 selection = selection + field._attrs['selection_add']
1261 self.selection = selection
1263 def _description_selection(self, env):
1264 """ return the selection list (pairs (value, label)); labels are
1265 translated according to context language
1267 selection = self.selection
1268 if isinstance(selection, basestring):
1269 return getattr(env[self.model_name], selection)()
1270 if callable(selection):
1271 return selection(env[self.model_name])
1273 # translate selection labels
1275 name = "%s,%s" % (self.model_name, self.name)
1276 translate = partial(
1277 env['ir.translation']._get_source, name, 'selection', env.lang)
1278 return [(value, translate(label) if label else label) for value, label in selection]
1283 def _column_selection(self):
1284 if isinstance(self.selection, basestring):
1285 method = self.selection
1286 return lambda self, *a, **kw: getattr(self, method)(*a, **kw)
1288 return self.selection
1290 def get_values(self, env):
1291 """ return a list of the possible values """
1292 selection = self.selection
1293 if isinstance(selection, basestring):
1294 selection = getattr(env[self.model_name], selection)()
1295 elif callable(selection):
1296 selection = selection(env[self.model_name])
1297 return [value for value, _ in selection]
1299 def convert_to_cache(self, value, record, validate=True):
1301 return value or False
1302 if value in self.get_values(record.env):
1306 raise ValueError("Wrong value for %s: %r" % (self, value))
1308 def convert_to_export(self, value, env):
1309 if not isinstance(self.selection, list):
1310 # FIXME: this reproduces an existing buggy behavior!
1312 for item in self._description_selection(env):
1313 if item[0] == value:
1318 class Reference(Selection):
1322 def __init__(self, selection=None, string=None, **kwargs):
1323 super(Reference, self).__init__(selection=selection, string=string, **kwargs)
1325 def _setup(self, env):
1326 super(Reference, self)._setup(env)
1327 assert isinstance(self.size, (NoneType, int)), \
1328 "Reference field %s with non-integer size %r" % (self, self.size)
1330 _related_size = property(attrgetter('size'))
1332 _column_size = property(attrgetter('size'))
1334 def convert_to_cache(self, value, record, validate=True):
1335 if isinstance(value, BaseModel):
1336 if ((not validate or value._name in self.get_values(record.env))
1337 and len(value) <= 1):
1338 return value.with_env(record.env) or False
1339 elif isinstance(value, basestring):
1340 res_model, res_id = value.split(',')
1341 return record.env[res_model].browse(int(res_id))
1344 raise ValueError("Wrong value for %s: %r" % (self, value))
1346 def convert_to_read(self, value, use_name_get=True):
1347 return "%s,%s" % (value._name, value.id) if value else False
1349 def convert_to_export(self, value, env):
1350 return bool(value) and value.name_get()[0][1]
1352 def convert_to_display_name(self, value):
1353 return ustr(value and value.display_name)
1356 class _Relational(Field):
1357 """ Abstract class for relational fields. """
1359 domain = None # domain for searching values
1360 context = None # context for searching values
1362 def _setup(self, env):
1363 super(_Relational, self)._setup(env)
1364 assert self.comodel_name in env.registry, \
1365 "Field %s with unknown comodel_name %r" % (self, self.comodel_name)
1368 def _related_domain(self):
1369 if callable(self.domain):
1370 # will be called with another model than self's
1371 return lambda recs: self.domain(recs.env[self.model_name])
1373 # maybe not correct if domain is a string...
1376 _related_context = property(attrgetter('context'))
1378 _description_relation = property(attrgetter('comodel_name'))
1379 _description_context = property(attrgetter('context'))
1381 def _description_domain(self, env):
1382 return self.domain(env[self.model_name]) if callable(self.domain) else self.domain
1384 _column_obj = property(attrgetter('comodel_name'))
1385 _column_domain = property(attrgetter('domain'))
1386 _column_context = property(attrgetter('context'))
1388 def null(self, env):
1389 return env[self.comodel_name]
1391 def modified(self, records):
1392 # Invalidate cache for self.inverse_fields, too. Note that recomputation
1393 # of fields that depend on self.inverse_fields is already covered by the
1394 # triggers (see above).
1395 spec = super(_Relational, self).modified(records)
1396 for invf in self.inverse_fields:
1397 spec.append((invf, None))
1401 class Many2one(_Relational):
1402 """ The value of such a field is a recordset of size 0 (no
1403 record) or 1 (a single record).
1405 :param comodel_name: name of the target model (string)
1407 :param domain: an optional domain to set on candidate values on the
1408 client side (domain or string)
1410 :param context: an optional context to use on the client side when
1411 handling that field (dictionary)
1413 :param ondelete: what to do when the referred record is deleted;
1414 possible values are: ``'set null'``, ``'restrict'``, ``'cascade'``
1416 :param auto_join: whether JOINs are generated upon search through that
1417 field (boolean, by default ``False``)
1419 :param delegate: set it to ``True`` to make fields of the target model
1420 accessible from the current model (corresponds to ``_inherits``)
1422 The attribute `comodel_name` is mandatory except in the case of related
1423 fields or field extensions.
1426 ondelete = 'set null' # what to do when value is deleted
1427 auto_join = False # whether joins are generated upon search
1428 delegate = False # whether self implements delegation
1430 def __init__(self, comodel_name=None, string=None, **kwargs):
1431 super(Many2one, self).__init__(comodel_name=comodel_name, string=string, **kwargs)
1433 def set_class_name(self, cls, name):
1434 super(Many2one, self).set_class_name(cls, name)
1435 # determine self.delegate
1436 if not self.delegate:
1437 self.delegate = name in cls._inherits.values()
1439 _column_ondelete = property(attrgetter('ondelete'))
1440 _column_auto_join = property(attrgetter('auto_join'))
1442 def _update(self, records, value):
1443 """ Update the cached value of `self` for `records` with `value`. """
1444 records._cache[self] = value
1446 def convert_to_cache(self, value, record, validate=True):
1447 if isinstance(value, (NoneType, int)):
1448 return record.env[self.comodel_name].browse(value)
1449 if isinstance(value, BaseModel):
1450 if value._name == self.comodel_name and len(value) <= 1:
1451 return value.with_env(record.env)
1452 raise ValueError("Wrong value for %s: %r" % (self, value))
1453 elif isinstance(value, tuple):
1454 return record.env[self.comodel_name].browse(value[0])
1455 elif isinstance(value, dict):
1456 return record.env[self.comodel_name].new(value)
1458 return record.env[self.comodel_name].browse(value)
1460 def convert_to_read(self, value, use_name_get=True):
1461 if use_name_get and value:
1462 # evaluate name_get() as superuser, because the visibility of a
1463 # many2one field value (id and name) depends on the current record's
1464 # access rights, and not the value's access rights.
1466 return value.sudo().name_get()[0]
1467 except MissingError:
1468 # Should not happen, unless the foreign key is missing.
1473 def convert_to_write(self, value, target=None, fnames=None):
1476 def convert_to_onchange(self, value):
1479 def convert_to_export(self, value, env):
1480 return bool(value) and value.name_get()[0][1]
1482 def convert_to_display_name(self, value):
1483 return ustr(value.display_name)
1486 class UnionUpdate(SpecialValue):
1487 """ Placeholder for a value update; when this value is taken from the cache,
1488 it returns ``record[field.name] | value`` and stores it in the cache.
1490 def __init__(self, field, record, value):
1491 self.args = (field, record, value)
1494 field, record, value = self.args
1495 # in order to read the current field's value, remove self from cache
1496 del record._cache[field]
1497 # read the current field's value, and update it in cache only
1498 record._cache[field] = new_value = record[field.name] | value
1502 class _RelationalMulti(_Relational):
1503 """ Abstract class for relational fields *2many. """
1505 def _update(self, records, value):
1506 """ Update the cached value of `self` for `records` with `value`. """
1507 for record in records:
1508 if self in record._cache:
1509 record._cache[self] = record[self.name] | value
1511 record._cache[self] = UnionUpdate(self, record, value)
1513 def convert_to_cache(self, value, record, validate=True):
1514 if isinstance(value, BaseModel):
1515 if value._name == self.comodel_name:
1516 return value.with_env(record.env)
1517 elif isinstance(value, list):
1518 # value is a list of record ids or commands
1520 record = record.browse() # new record has no value
1521 result = record[self.name]
1522 # modify result with the commands;
1523 # beware to not introduce duplicates in result
1524 for command in value:
1525 if isinstance(command, (tuple, list)):
1527 result += result.new(command[2])
1528 elif command[0] == 1:
1529 result.browse(command[1]).update(command[2])
1530 result += result.browse(command[1]) - result
1531 elif command[0] == 2:
1532 # note: the record will be deleted by write()
1533 result -= result.browse(command[1])
1534 elif command[0] == 3:
1535 result -= result.browse(command[1])
1536 elif command[0] == 4:
1537 result += result.browse(command[1]) - result
1538 elif command[0] == 5:
1539 result = result.browse()
1540 elif command[0] == 6:
1541 result = result.browse(command[2])
1542 elif isinstance(command, dict):
1543 result += result.new(command)
1545 result += result.browse(command) - result
1548 return self.null(record.env)
1549 raise ValueError("Wrong value for %s: %s" % (self, value))
1551 def convert_to_read(self, value, use_name_get=True):
1554 def convert_to_write(self, value, target=None, fnames=None):
1555 # remove/delete former records
1558 result = [(6, 0, set_ids)]
1559 add_existing = lambda id: set_ids.append(id)
1561 tag = 2 if self.type == 'one2many' else 3
1562 result = [(tag, record.id) for record in target[self.name] - value]
1563 add_existing = lambda id: result.append((4, id))
1566 # take all fields in cache, except the inverses of self
1567 fnames = set(value._fields) - set(MAGIC_COLUMNS)
1568 for invf in self.inverse_fields:
1569 fnames.discard(invf.name)
1571 # add new and existing records
1572 for record in value:
1573 if not record.id or record._dirty:
1574 values = dict((k, v) for k, v in record._cache.iteritems() if k in fnames)
1575 values = record._convert_to_write(values)
1577 result.append((0, 0, values))
1579 result.append((1, record.id, values))
1581 add_existing(record.id)
1585 def convert_to_export(self, value, env):
1586 return bool(value) and ','.join(name for id, name in value.name_get())
1588 def convert_to_display_name(self, value):
1589 raise NotImplementedError()
1591 def _compute_related(self, records):
1592 """ Compute the related field `self` on `records`. """
1593 for record in records:
1595 # traverse the intermediate fields, and keep at most one record
1596 for name in self.related[:-1]:
1597 value = value[name][:1]
1598 record[self.name] = value[self.related[-1]]
1601 class One2many(_RelationalMulti):
1602 """ One2many field; the value of such a field is the recordset of all the
1603 records in `comodel_name` such that the field `inverse_name` is equal to
1606 :param comodel_name: name of the target model (string)
1608 :param inverse_name: name of the inverse `Many2one` field in
1609 `comodel_name` (string)
1611 :param domain: an optional domain to set on candidate values on the
1612 client side (domain or string)
1614 :param context: an optional context to use on the client side when
1615 handling that field (dictionary)
1617 :param auto_join: whether JOINs are generated upon search through that
1618 field (boolean, by default ``False``)
1620 :param limit: optional limit to use upon read (integer)
1622 The attributes `comodel_name` and `inverse_name` are mandatory except in
1623 the case of related fields or field extensions.
1626 inverse_name = None # name of the inverse field
1627 auto_join = False # whether joins are generated upon search
1628 limit = None # optional limit to use upon read
1629 copy = False # o2m are not copied by default
1631 def __init__(self, comodel_name=None, inverse_name=None, string=None, **kwargs):
1632 super(One2many, self).__init__(
1633 comodel_name=comodel_name,
1634 inverse_name=inverse_name,
1639 def _setup_regular(self, env):
1640 super(One2many, self)._setup_regular(env)
1642 if self.inverse_name:
1643 # link self to its inverse field and vice-versa
1644 invf = env[self.comodel_name]._fields[self.inverse_name]
1645 # In some rare cases, a `One2many` field can link to `Int` field
1646 # (res_model/res_id pattern). Only inverse the field if this is
1647 # a `Many2one` field.
1648 if isinstance(invf, Many2one):
1649 self.inverse_fields.append(invf)
1650 invf.inverse_fields.append(self)
1652 _description_relation_field = property(attrgetter('inverse_name'))
1654 _column_fields_id = property(attrgetter('inverse_name'))
1655 _column_auto_join = property(attrgetter('auto_join'))
1656 _column_limit = property(attrgetter('limit'))
1659 class Many2many(_RelationalMulti):
1660 """ Many2many field; the value of such a field is the recordset.
1662 :param comodel_name: name of the target model (string)
1664 The attribute `comodel_name` is mandatory except in the case of related
1665 fields or field extensions.
1667 :param relation: optional name of the table that stores the relation in
1668 the database (string)
1670 :param column1: optional name of the column referring to "these" records
1671 in the table `relation` (string)
1673 :param column2: optional name of the column referring to "those" records
1674 in the table `relation` (string)
1676 The attributes `relation`, `column1` and `column2` are optional. If not
1677 given, names are automatically generated from model names, provided
1678 `model_name` and `comodel_name` are different!
1680 :param domain: an optional domain to set on candidate values on the
1681 client side (domain or string)
1683 :param context: an optional context to use on the client side when
1684 handling that field (dictionary)
1686 :param limit: optional limit to use upon read (integer)
1690 relation = None # name of table
1691 column1 = None # column of table referring to model
1692 column2 = None # column of table referring to comodel
1693 limit = None # optional limit to use upon read
1695 def __init__(self, comodel_name=None, relation=None, column1=None, column2=None,
1696 string=None, **kwargs):
1697 super(Many2many, self).__init__(
1698 comodel_name=comodel_name,
1706 def _setup_regular(self, env):
1707 super(Many2many, self)._setup_regular(env)
1709 if not self.relation:
1710 if isinstance(self.column, fields.many2many):
1711 self.relation, self.column1, self.column2 = \
1712 self.column._sql_names(env[self.model_name])
1715 m2m = env.registry._m2m
1716 # if inverse field has already been setup, it is present in m2m
1717 invf = m2m.get((self.relation, self.column2, self.column1))
1719 self.inverse_fields.append(invf)
1720 invf.inverse_fields.append(self)
1722 # add self in m2m, so that its inverse field can find it
1723 m2m[(self.relation, self.column1, self.column2)] = self
1725 _column_rel = property(attrgetter('relation'))
1726 _column_id1 = property(attrgetter('column1'))
1727 _column_id2 = property(attrgetter('column2'))
1728 _column_limit = property(attrgetter('limit'))
1732 """ Special case for field 'id'. """
1734 #: Can't write this!
1737 def __init__(self, string=None, **kwargs):
1738 super(Id, self).__init__(type='integer', string=string, **kwargs)
1740 def to_column(self):
1741 self.column = fields.integer('ID')
1744 def __get__(self, record, owner):
1746 return self # the field is accessed through the class owner
1749 return record.ensure_one()._ids[0]
1751 def __set__(self, record, value):
1752 raise TypeError("field 'id' cannot be assigned")
1755 # imported here to avoid dependency cycle issues
1756 from openerp import SUPERUSER_ID
1757 from .exceptions import Warning, AccessError, MissingError
1758 from .models import BaseModel, MAGIC_COLUMNS
1759 from .osv import fields