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 if name in klass.__dict__:
351 field = klass.__dict__[name]
352 if not isinstance(field, type(self)):
353 # klass contains another value overridden by self
356 if 'default' in field._attrs:
357 # take the default in field, and adapt it for cls._defaults
358 value = field._attrs['default']
360 from openerp import api
362 cls._defaults[name] = api.model(
363 lambda recs: self.convert_to_write(value(recs))
366 self.default = lambda recs: value
367 cls._defaults[name] = value
370 defaults = klass.__dict__.get('_defaults') or {}
372 # take the value from _defaults, and adapt it for self.default
373 value = defaults[name]
375 func = lambda recs: value(recs._model, recs._cr, recs._uid, recs._context)
377 func = lambda recs: value
378 self.default = lambda recs: self.convert_to_cache(
379 func(recs), recs, validate=False,
381 cls._defaults[name] = value
385 return "%s.%s" % (self.model_name, self.name)
388 return "%s.%s" % (self.model_name, self.name)
390 ############################################################################
396 """ Prepare `self` for a new setup. """
397 self.setup_done = False
398 # self._triggers is a set of pairs (field, path) that represents the
399 # computed fields that depend on `self`. When `self` is modified, it
400 # invalidates the cache of each `field`, and registers the records to
401 # recompute based on `path`. See method `modified` below for details.
402 self._triggers = set()
403 self.inverse_fields = []
405 def setup(self, env):
406 """ Complete the setup of `self` (dependencies, recomputation triggers,
407 and other properties). This method is idempotent: it has no effect
408 if `self` has already been set up.
410 if not self.setup_done:
412 self.setup_done = True
414 def _setup(self, env):
415 """ Do the actual setup of `self`. """
417 self._setup_related(env)
419 self._setup_regular(env)
421 # put invalidation/recomputation triggers on field dependencies
422 model = env[self.model_name]
423 for path in self.depends:
424 self._setup_dependency([], model, path.split('.'))
426 # put invalidation triggers on model dependencies
427 for dep_model_name, field_names in model._depends.iteritems():
428 dep_model = env[dep_model_name]
429 dep_model._setup_fields()
430 for field_name in field_names:
431 field = dep_model._fields[field_name]
432 field._triggers.add((self, None))
435 # Setup of related fields
438 def _setup_related(self, env):
439 """ Setup the attributes of a related field. """
440 # fix the type of self.related if necessary
441 if isinstance(self.related, basestring):
442 self.related = tuple(self.related.split('.'))
444 # determine the chain of fields, and make sure they are all set up
445 recs = env[self.model_name]
447 for name in self.related:
449 field = recs._fields[name]
453 self.related_field = field
455 # check type consistency
456 if self.type != field.type:
457 raise Warning("Type of related field %s is inconsistent with %s" % (self, field))
459 # determine dependencies, compute, inverse, and search
460 self.depends = ('.'.join(self.related),)
461 self.compute = self._compute_related
462 self.inverse = self._inverse_related
463 if field._description_searchable:
464 # allow searching on self only if the related field is searchable
465 self.search = self._search_related
467 # copy attributes from field to self (string, help, etc.)
468 for attr, prop in self.related_attrs:
469 if not getattr(self, attr):
470 setattr(self, attr, getattr(field, prop))
472 for attr in field._free_attrs:
473 if attr not in self._free_attrs:
474 self._free_attrs.append(attr)
475 setattr(self, attr, getattr(field, attr))
477 # special case for required: check if all fields are required
478 if not self.store and not self.required:
479 self.required = all(field.required for field in fields)
481 def _compute_related(self, records):
482 """ Compute the related field `self` on `records`. """
483 # when related_sudo, bypass access rights checks when reading values
484 others = records.sudo() if self.related_sudo else records
485 for record, other in zip(records, others):
487 # draft record, do not switch to another environment
489 # traverse the intermediate fields; follow the first record at each step
490 for name in self.related[:-1]:
491 other = other[name][:1]
492 record[self.name] = other[self.related[-1]]
494 def _inverse_related(self, records):
495 """ Inverse the related field `self` on `records`. """
496 for record in records:
498 # traverse the intermediate fields, and keep at most one record
499 for name in self.related[:-1]:
500 other = other[name][:1]
502 other[self.related[-1]] = record[self.name]
504 def _search_related(self, records, operator, value):
505 """ Determine the domain to search on field `self`. """
506 return [('.'.join(self.related), operator, value)]
508 # properties used by _setup_related() to copy values from related field
509 _related_comodel_name = property(attrgetter('comodel_name'))
510 _related_string = property(attrgetter('string'))
511 _related_help = property(attrgetter('help'))
512 _related_readonly = property(attrgetter('readonly'))
513 _related_groups = property(attrgetter('groups'))
516 def base_field(self):
517 """ Return the base field of an inherited field, or `self`. """
518 return self.related_field if self.inherited else self
521 # Setup of non-related fields
524 def _setup_regular(self, env):
525 """ Setup the attributes of a non-related field. """
526 recs = env[self.model_name]
528 def make_depends(deps):
529 return tuple(deps(recs) if callable(deps) else deps)
531 # convert compute into a callable and determine depends
532 if isinstance(self.compute, basestring):
533 # if the compute method has been overridden, concatenate all their _depends
535 for method in resolve_all_mro(type(recs), self.compute, reverse=True):
536 self.depends += make_depends(getattr(method, '_depends', ()))
537 self.compute = getattr(type(recs), self.compute)
539 self.depends = make_depends(getattr(self.compute, '_depends', ()))
541 # convert inverse and search into callables
542 if isinstance(self.inverse, basestring):
543 self.inverse = getattr(type(recs), self.inverse)
544 if isinstance(self.search, basestring):
545 self.search = getattr(type(recs), self.search)
547 def _setup_dependency(self, path0, model, path1):
548 """ Make `self` depend on `model`; `path0 + path1` is a dependency of
549 `self`, and `path0` is the sequence of field names from `self.model`
553 head, tail = path1[0], path1[1:]
555 model._setup_fields()
557 # special case: add triggers on all fields of model (except self)
558 fields = set(model._fields.itervalues()) - set([self])
560 fields = [model._fields[head]]
564 _logger.debug("Field %s is recursively defined", self)
565 self.recursive = True
568 #_logger.debug("Add trigger on %s to recompute %s", field, self)
569 field._triggers.add((self, '.'.join(path0 or ['id'])))
571 # add trigger on inverse fields, too
572 for invf in field.inverse_fields:
573 #_logger.debug("Add trigger on %s to recompute %s", invf, self)
574 invf._triggers.add((self, '.'.join(path0 + [head])))
576 # recursively traverse the dependency
578 comodel = env[field.comodel_name]
579 self._setup_dependency(path0 + [head], comodel, tail)
582 def dependents(self):
583 """ Return the computed fields that depend on `self`. """
584 return (field for field, path in self._triggers)
586 ############################################################################
591 def get_description(self, env):
592 """ Return a dictionary that describes the field `self`. """
593 desc = {'type': self.type}
594 for attr, prop in self.description_attrs:
595 value = getattr(self, prop)
598 if value is not None:
603 # properties used by get_description()
604 _description_store = property(attrgetter('store'))
605 _description_manual = property(attrgetter('manual'))
606 _description_depends = property(attrgetter('depends'))
607 _description_related = property(attrgetter('related'))
608 _description_company_dependent = property(attrgetter('company_dependent'))
609 _description_readonly = property(attrgetter('readonly'))
610 _description_required = property(attrgetter('required'))
611 _description_states = property(attrgetter('states'))
612 _description_groups = property(attrgetter('groups'))
613 _description_change_default = property(attrgetter('change_default'))
614 _description_deprecated = property(attrgetter('deprecated'))
617 def _description_searchable(self):
618 return bool(self.store or self.search or (self.column and self.column._fnct_search))
621 def _description_sortable(self):
622 return self.store or (self.inherited and self.related_field._description_sortable)
624 def _description_string(self, env):
625 if self.string and env.lang:
626 field = self.base_field
627 name = "%s,%s" % (field.model_name, field.name)
628 trans = env['ir.translation']._get_source(name, 'field', env.lang)
629 return trans or self.string
632 def _description_help(self, env):
633 if self.help and env.lang:
634 name = "%s,%s" % (self.model_name, self.name)
635 trans = env['ir.translation']._get_source(name, 'help', env.lang)
636 return trans or self.help
639 ############################################################################
641 # Conversion to column instance
645 """ return a low-level field object corresponding to `self` """
646 assert self.store or self.column
648 # determine column parameters
649 _logger.debug("Create fields._column for Field %s", self)
651 for attr, prop in self.column_attrs:
652 args[attr] = getattr(self, prop)
653 for attr in self._free_attrs:
654 args[attr] = getattr(self, attr)
656 if self.company_dependent:
657 # company-dependent fields are mapped to former property fields
658 args['type'] = self.type
659 args['relation'] = self.comodel_name
660 self.column = fields.property(**args)
662 # let the column provide a valid column for the given parameters
663 self.column = self.column.new(**args)
665 # create a fresh new column of the right type
666 self.column = getattr(fields, self.type)(**args)
670 # properties used by to_column() to create a column instance
671 _column_copy = property(attrgetter('copy'))
672 _column_select = property(attrgetter('index'))
673 _column_manual = property(attrgetter('manual'))
674 _column_string = property(attrgetter('string'))
675 _column_help = property(attrgetter('help'))
676 _column_readonly = property(attrgetter('readonly'))
677 _column_required = property(attrgetter('required'))
678 _column_states = property(attrgetter('states'))
679 _column_groups = property(attrgetter('groups'))
680 _column_change_default = property(attrgetter('change_default'))
681 _column_deprecated = property(attrgetter('deprecated'))
683 ############################################################################
685 # Conversion of values
689 """ return the null value for this field in the given environment """
692 def convert_to_cache(self, value, record, validate=True):
693 """ convert `value` to the cache level in `env`; `value` may come from
694 an assignment, or have the format of methods :meth:`BaseModel.read`
695 or :meth:`BaseModel.write`
697 :param record: the target record for the assignment, or an empty recordset
699 :param bool validate: when True, field-specific validation of
700 `value` will be performed
704 def convert_to_read(self, value, use_name_get=True):
705 """ convert `value` from the cache to a value as returned by method
706 :meth:`BaseModel.read`
708 :param bool use_name_get: when True, value's diplay name will
709 be computed using :meth:`BaseModel.name_get`, if relevant
712 return False if value is None else value
714 def convert_to_write(self, value, target=None, fnames=None):
715 """ convert `value` from the cache to a valid value for method
716 :meth:`BaseModel.write`.
718 :param target: optional, the record to be modified with this value
719 :param fnames: for relational fields only, an optional collection of
720 field names to convert
722 return self.convert_to_read(value)
724 def convert_to_onchange(self, value):
725 """ convert `value` from the cache to a valid value for an onchange
728 return self.convert_to_write(value)
730 def convert_to_export(self, value, env):
731 """ convert `value` from the cache to a valid value for export. The
732 parameter `env` is given for managing translations.
734 if env.context.get('export_raw_data'):
736 return bool(value) and ustr(value)
738 def convert_to_display_name(self, value):
739 """ convert `value` from the cache to a suitable display name. """
742 ############################################################################
747 def __get__(self, record, owner):
748 """ return the value of field `self` on `record` """
750 return self # the field is accessed through the owner class
753 # null record -> return the null value for this field
754 return self.null(record.env)
756 # only a single record may be accessed
760 return record._cache[self]
764 # cache miss, retrieve value
766 # normal record -> read or compute value for this field
767 self.determine_value(record)
769 # draft record -> compute the value or let it be null
770 self.determine_draft_value(record)
772 # the result should be in cache now
773 return record._cache[self]
775 def __set__(self, record, value):
776 """ set the value of field `self` on `record` """
779 # only a single record may be updated
782 # adapt value to the cache level
783 value = self.convert_to_cache(value, record)
785 if env.in_draft or not record.id:
786 # determine dependent fields
787 spec = self.modified_draft(record)
789 # set value in cache, inverse field, and mark record as dirty
790 record._cache[self] = value
792 for invf in self.inverse_fields:
793 invf._update(value, record)
796 # determine more dependent fields, and invalidate them
798 spec += self.modified_draft(record)
802 # simply write to the database, and update cache
803 record.write({self.name: self.convert_to_write(value)})
804 record._cache[self] = value
806 ############################################################################
808 # Computation of field values
811 def _compute_value(self, records):
812 """ Invoke the compute method on `records`. """
813 # initialize the fields to their corresponding null value in cache
814 for field in self.computed_fields:
815 records._cache[field] = field.null(records.env)
816 records.env.computed[field].update(records._ids)
817 self.compute(records)
818 for field in self.computed_fields:
819 records.env.computed[field].difference_update(records._ids)
821 def compute_value(self, records):
822 """ Invoke the compute method on `records`; the results are in cache. """
823 with records.env.do_in_draft():
825 self._compute_value(records)
826 except (AccessError, MissingError):
827 # some record is forbidden or missing, retry record by record
828 for record in records:
830 self._compute_value(record)
831 except Exception as exc:
832 record._cache[self.name] = FailedValue(exc)
834 def determine_value(self, record):
835 """ Determine the value of `self` for `record`. """
838 if self.column and not (self.depends and env.in_draft):
839 # this is a stored field or an old-style function field
841 # this is a stored computed field, check for recomputation
842 recs = record._recompute_check(self)
844 # recompute the value (only in cache)
845 self.compute_value(recs)
846 # HACK: if result is in the wrong cache, copy values
848 for source, target in zip(recs, recs.with_env(env)):
850 values = target._convert_to_cache({
851 f.name: source[f.name] for f in self.computed_fields
853 except MissingError as e:
854 values = FailedValue(e)
855 target._cache.update(values)
856 # the result is saved to database by BaseModel.recompute()
859 # read the field from database
860 record._prefetch_field(self)
863 # this is either a non-stored computed field, or a stored computed
864 # field in draft mode
866 self.compute_value(record)
868 recs = record._in_cache_without(self)
869 self.compute_value(recs)
872 # this is a non-stored non-computed field
873 record._cache[self] = self.null(env)
875 def determine_draft_value(self, record):
876 """ Determine the value of `self` for the given draft `record`. """
878 self._compute_value(record)
880 record._cache[self] = SpecialValue(self.null(record.env))
882 def determine_inverse(self, records):
883 """ Given the value of `self` on `records`, inverse the computation. """
885 self.inverse(records)
887 def determine_domain(self, records, operator, value):
888 """ Return a domain representing a condition on `self`. """
890 return self.search(records, operator, value)
892 return [(self.name, operator, value)]
894 ############################################################################
896 # Notification when fields are modified
899 def modified(self, records):
900 """ Notify that field `self` has been modified on `records`: prepare the
901 fields/records to recompute, and return a spec indicating what to
904 # invalidate the fields that depend on self, and prepare recomputation
905 spec = [(self, records._ids)]
906 for field, path in self._triggers:
907 if path and field.store:
908 # don't move this line to function top, see log
909 env = records.env(user=SUPERUSER_ID, context={'active_test': False})
910 target = env[field.model_name].search([(path, 'in', records.ids)])
912 spec.append((field, target._ids))
913 target.with_env(records.env)._recompute_todo(field)
915 spec.append((field, None))
919 def modified_draft(self, records):
920 """ Same as :meth:`modified`, but in draft mode. """
923 # invalidate the fields on the records in cache that depend on
924 # `records`, except fields currently being computed
926 for field, path in self._triggers:
927 target = env[field.model_name]
928 computed = target.browse(env.computed[field])
930 target = records - computed
932 target = (target.browse(env.cache[field]) - computed).filtered(
933 lambda rec: rec._mapped_cache(path) & records
936 target = target.browse(env.cache[field]) - computed
939 spec.append((field, target._ids))
944 class Boolean(Field):
947 def convert_to_cache(self, value, record, validate=True):
950 def convert_to_export(self, value, env):
951 if env.context.get('export_raw_data'):
956 class Integer(Field):
958 group_operator = None # operator for aggregating values
960 _related_group_operator = property(attrgetter('group_operator'))
962 _column_group_operator = property(attrgetter('group_operator'))
964 def convert_to_cache(self, value, record, validate=True):
965 if isinstance(value, dict):
966 # special case, when an integer field is used as inverse for a one2many
967 return value.get('id', False)
968 return int(value or 0)
970 def convert_to_read(self, value, use_name_get=True):
971 # Integer values greater than 2^31-1 are not supported in pure XMLRPC,
972 # so we have to pass them as floats :-(
973 if value and value > xmlrpclib.MAXINT:
977 def _update(self, records, value):
978 # special case, when an integer field is used as inverse for a one2many
979 records._cache[self] = value.id or 0
983 """ The precision digits are given by the attribute
985 :param digits: a pair (total, decimal), or a function taking a database
986 cursor and returning a pair (total, decimal)
989 _digits = None # digits argument passed to class initializer
990 digits = None # digits as computed by setup()
991 group_operator = None # operator for aggregating values
993 def __init__(self, string=None, digits=None, **kwargs):
994 super(Float, self).__init__(string=string, _digits=digits, **kwargs)
996 def _setup_digits(self, env):
997 """ Setup the digits for `self` and its corresponding column """
998 self.digits = self._digits(env.cr) if callable(self._digits) else self._digits
1000 assert isinstance(self.digits, (tuple, list)) and len(self.digits) >= 2, \
1001 "Float field %s with digits %r, expecting (total, decimal)" % (self, self.digits)
1003 self.column.digits_change(env.cr)
1005 def _setup_regular(self, env):
1006 super(Float, self)._setup_regular(env)
1007 self._setup_digits(env)
1009 _related_digits = property(attrgetter('digits'))
1010 _related_group_operator = property(attrgetter('group_operator'))
1012 _description_digits = property(attrgetter('digits'))
1014 _column_digits = property(lambda self: not callable(self._digits) and self._digits)
1015 _column_digits_compute = property(lambda self: callable(self._digits) and self._digits)
1016 _column_group_operator = property(attrgetter('group_operator'))
1018 def convert_to_cache(self, value, record, validate=True):
1019 # apply rounding here, otherwise value in cache may be wrong!
1021 return float_round(float(value or 0.0), precision_digits=self.digits[1])
1023 return float(value or 0.0)
1026 class _String(Field):
1027 """ Abstract class for string fields. """
1030 _column_translate = property(attrgetter('translate'))
1031 _related_translate = property(attrgetter('translate'))
1032 _description_translate = property(attrgetter('translate'))
1035 class Char(_String):
1036 """ Basic string field, can be length-limited, usually displayed as a
1037 single-line string in clients
1039 :param int size: the maximum size of values stored for that field
1040 :param bool translate: whether the values of this field can be translated
1045 def _setup(self, env):
1046 super(Char, self)._setup(env)
1047 assert isinstance(self.size, (NoneType, int)), \
1048 "Char field %s with non-integer size %r" % (self, self.size)
1050 _column_size = property(attrgetter('size'))
1051 _related_size = property(attrgetter('size'))
1052 _description_size = property(attrgetter('size'))
1054 def convert_to_cache(self, value, record, validate=True):
1055 if value is None or value is False:
1057 return ustr(value)[:self.size]
1059 class Text(_String):
1060 """ Text field. Very similar to :class:`~.Char` but used for longer
1061 contents and displayed as a multiline text box
1063 :param translate: whether the value of this field can be translated
1067 def convert_to_cache(self, value, record, validate=True):
1068 if value is None or value is False:
1072 class Html(_String):
1074 sanitize = True # whether value must be sanitized
1076 _column_sanitize = property(attrgetter('sanitize'))
1077 _related_sanitize = property(attrgetter('sanitize'))
1078 _description_sanitize = property(attrgetter('sanitize'))
1080 def convert_to_cache(self, value, record, validate=True):
1081 if value is None or value is False:
1083 if validate and self.sanitize:
1084 return html_sanitize(value)
1093 """ Return the current day in the format expected by the ORM.
1094 This function may be used to compute default values.
1096 return date.today().strftime(DATE_FORMAT)
1099 def context_today(record, timestamp=None):
1100 """ Return the current date as seen in the client's timezone in a format
1101 fit for date fields. This method may be used to compute default
1104 :param datetime timestamp: optional datetime value to use instead of
1105 the current date and time (must be a datetime, regular dates
1106 can't be converted between timezones.)
1109 today = timestamp or datetime.now()
1110 context_today = None
1111 tz_name = record._context.get('tz') or record.env.user.tz
1114 today_utc = pytz.timezone('UTC').localize(today, is_dst=False) # UTC = no DST
1115 context_today = today_utc.astimezone(pytz.timezone(tz_name))
1117 _logger.debug("failed to compute context/client-specific today date, using UTC value for `today`",
1119 return (context_today or today).strftime(DATE_FORMAT)
1122 def from_string(value):
1123 """ Convert an ORM `value` into a :class:`date` value. """
1124 value = value[:DATE_LENGTH]
1125 return datetime.strptime(value, DATE_FORMAT).date()
1128 def to_string(value):
1129 """ Convert a :class:`date` value into the format expected by the ORM. """
1130 return value.strftime(DATE_FORMAT)
1132 def convert_to_cache(self, value, record, validate=True):
1135 if isinstance(value, basestring):
1137 # force parsing for validation
1138 self.from_string(value)
1139 return value[:DATE_LENGTH]
1140 return self.to_string(value)
1142 def convert_to_export(self, value, env):
1143 if value and env.context.get('export_raw_data'):
1144 return self.from_string(value)
1145 return bool(value) and ustr(value)
1148 class Datetime(Field):
1153 """ Return the current day and time in the format expected by the ORM.
1154 This function may be used to compute default values.
1156 return datetime.now().strftime(DATETIME_FORMAT)
1159 def context_timestamp(record, timestamp):
1160 """Returns the given timestamp converted to the client's timezone.
1161 This method is *not* meant for use as a _defaults initializer,
1162 because datetime fields are automatically converted upon
1163 display on client side. For _defaults you :meth:`fields.datetime.now`
1164 should be used instead.
1166 :param datetime timestamp: naive datetime value (expressed in UTC)
1167 to be converted to the client timezone
1169 :return: timestamp converted to timezone-aware datetime in context
1172 assert isinstance(timestamp, datetime), 'Datetime instance expected'
1173 tz_name = record._context.get('tz') or record.env.user.tz
1176 utc = pytz.timezone('UTC')
1177 context_tz = pytz.timezone(tz_name)
1178 utc_timestamp = utc.localize(timestamp, is_dst=False) # UTC = no DST
1179 return utc_timestamp.astimezone(context_tz)
1181 _logger.debug("failed to compute context/client-specific timestamp, "
1182 "using the UTC value",
1187 def from_string(value):
1188 """ Convert an ORM `value` into a :class:`datetime` value. """
1189 value = value[:DATETIME_LENGTH]
1190 if len(value) == DATE_LENGTH:
1191 value += " 00:00:00"
1192 return datetime.strptime(value, DATETIME_FORMAT)
1195 def to_string(value):
1196 """ Convert a :class:`datetime` value into the format expected by the ORM. """
1197 return value.strftime(DATETIME_FORMAT)
1199 def convert_to_cache(self, value, record, validate=True):
1202 if isinstance(value, basestring):
1204 # force parsing for validation
1205 self.from_string(value)
1206 value = value[:DATETIME_LENGTH]
1207 if len(value) == DATE_LENGTH:
1208 value += " 00:00:00"
1210 return self.to_string(value)
1212 def convert_to_export(self, value, env):
1213 if value and env.context.get('export_raw_data'):
1214 return self.from_string(value)
1215 return bool(value) and ustr(value)
1218 class Binary(Field):
1222 class Selection(Field):
1224 :param selection: specifies the possible values for this field.
1225 It is given as either a list of pairs (`value`, `string`), or a
1226 model method, or a method name.
1227 :param selection_add: provides an extension of the selection in the case
1228 of an overridden field. It is a list of pairs (`value`, `string`).
1230 The attribute `selection` is mandatory except in the case of
1231 :ref:`related fields <field-related>` or :ref:`field extensions
1232 <field-incremental-definition>`.
1235 selection = None # [(value, string), ...], function or method name
1236 selection_add = None # [(value, string), ...]
1238 def __init__(self, selection=None, string=None, **kwargs):
1239 if callable(selection):
1240 from openerp import api
1241 selection = api.expected(api.model, selection)
1242 super(Selection, self).__init__(selection=selection, string=string, **kwargs)
1244 def _setup(self, env):
1245 super(Selection, self)._setup(env)
1246 assert self.selection is not None, "Field %s without selection" % self
1248 def _setup_related(self, env):
1249 super(Selection, self)._setup_related(env)
1250 # selection must be computed on related field
1251 field = self.related_field
1252 self.selection = lambda model: field._description_selection(model.env)
1254 def set_class_name(self, cls, name):
1255 super(Selection, self).set_class_name(cls, name)
1256 # determine selection (applying 'selection_add' extensions)
1258 for field in resolve_all_mro(cls, name, reverse=True):
1259 if isinstance(field, type(self)):
1260 # We cannot use field.selection or field.selection_add here
1261 # because those attributes are overridden by `set_class_name`.
1262 if 'selection' in field._attrs:
1263 selection = field._attrs['selection']
1264 if 'selection_add' in field._attrs:
1265 selection = selection + field._attrs['selection_add']
1268 self.selection = selection
1270 def _description_selection(self, env):
1271 """ return the selection list (pairs (value, label)); labels are
1272 translated according to context language
1274 selection = self.selection
1275 if isinstance(selection, basestring):
1276 return getattr(env[self.model_name], selection)()
1277 if callable(selection):
1278 return selection(env[self.model_name])
1280 # translate selection labels
1282 name = "%s,%s" % (self.model_name, self.name)
1283 translate = partial(
1284 env['ir.translation']._get_source, name, 'selection', env.lang)
1285 return [(value, translate(label) if label else label) for value, label in selection]
1290 def _column_selection(self):
1291 if isinstance(self.selection, basestring):
1292 method = self.selection
1293 return lambda self, *a, **kw: getattr(self, method)(*a, **kw)
1295 return self.selection
1297 def get_values(self, env):
1298 """ return a list of the possible values """
1299 selection = self.selection
1300 if isinstance(selection, basestring):
1301 selection = getattr(env[self.model_name], selection)()
1302 elif callable(selection):
1303 selection = selection(env[self.model_name])
1304 return [value for value, _ in selection]
1306 def convert_to_cache(self, value, record, validate=True):
1308 return value or False
1309 if value in self.get_values(record.env):
1313 raise ValueError("Wrong value for %s: %r" % (self, value))
1315 def convert_to_export(self, value, env):
1316 if not isinstance(self.selection, list):
1317 # FIXME: this reproduces an existing buggy behavior!
1319 for item in self._description_selection(env):
1320 if item[0] == value:
1325 class Reference(Selection):
1329 def __init__(self, selection=None, string=None, **kwargs):
1330 super(Reference, self).__init__(selection=selection, string=string, **kwargs)
1332 def _setup(self, env):
1333 super(Reference, self)._setup(env)
1334 assert isinstance(self.size, (NoneType, int)), \
1335 "Reference field %s with non-integer size %r" % (self, self.size)
1337 _related_size = property(attrgetter('size'))
1339 _column_size = property(attrgetter('size'))
1341 def convert_to_cache(self, value, record, validate=True):
1342 if isinstance(value, BaseModel):
1343 if ((not validate or value._name in self.get_values(record.env))
1344 and len(value) <= 1):
1345 return value.with_env(record.env) or False
1346 elif isinstance(value, basestring):
1347 res_model, res_id = value.split(',')
1348 return record.env[res_model].browse(int(res_id))
1351 raise ValueError("Wrong value for %s: %r" % (self, value))
1353 def convert_to_read(self, value, use_name_get=True):
1354 return "%s,%s" % (value._name, value.id) if value else False
1356 def convert_to_export(self, value, env):
1357 return bool(value) and value.name_get()[0][1]
1359 def convert_to_display_name(self, value):
1360 return ustr(value and value.display_name)
1363 class _Relational(Field):
1364 """ Abstract class for relational fields. """
1366 domain = None # domain for searching values
1367 context = None # context for searching values
1369 def _setup(self, env):
1370 super(_Relational, self)._setup(env)
1371 assert self.comodel_name in env.registry, \
1372 "Field %s with unknown comodel_name %r" % (self, self.comodel_name)
1375 def _related_domain(self):
1376 if callable(self.domain):
1377 # will be called with another model than self's
1378 return lambda recs: self.domain(recs.env[self.model_name])
1380 # maybe not correct if domain is a string...
1383 _related_context = property(attrgetter('context'))
1385 _description_relation = property(attrgetter('comodel_name'))
1386 _description_context = property(attrgetter('context'))
1388 def _description_domain(self, env):
1389 return self.domain(env[self.model_name]) if callable(self.domain) else self.domain
1391 _column_obj = property(attrgetter('comodel_name'))
1392 _column_domain = property(attrgetter('domain'))
1393 _column_context = property(attrgetter('context'))
1395 def null(self, env):
1396 return env[self.comodel_name]
1398 def modified(self, records):
1399 # Invalidate cache for self.inverse_fields, too. Note that recomputation
1400 # of fields that depend on self.inverse_fields is already covered by the
1401 # triggers (see above).
1402 spec = super(_Relational, self).modified(records)
1403 for invf in self.inverse_fields:
1404 spec.append((invf, None))
1408 class Many2one(_Relational):
1409 """ The value of such a field is a recordset of size 0 (no
1410 record) or 1 (a single record).
1412 :param comodel_name: name of the target model (string)
1414 :param domain: an optional domain to set on candidate values on the
1415 client side (domain or string)
1417 :param context: an optional context to use on the client side when
1418 handling that field (dictionary)
1420 :param ondelete: what to do when the referred record is deleted;
1421 possible values are: ``'set null'``, ``'restrict'``, ``'cascade'``
1423 :param auto_join: whether JOINs are generated upon search through that
1424 field (boolean, by default ``False``)
1426 :param delegate: set it to ``True`` to make fields of the target model
1427 accessible from the current model (corresponds to ``_inherits``)
1429 The attribute `comodel_name` is mandatory except in the case of related
1430 fields or field extensions.
1433 ondelete = 'set null' # what to do when value is deleted
1434 auto_join = False # whether joins are generated upon search
1435 delegate = False # whether self implements delegation
1437 def __init__(self, comodel_name=None, string=None, **kwargs):
1438 super(Many2one, self).__init__(comodel_name=comodel_name, string=string, **kwargs)
1440 def set_class_name(self, cls, name):
1441 super(Many2one, self).set_class_name(cls, name)
1442 # determine self.delegate
1443 if not self.delegate:
1444 self.delegate = name in cls._inherits.values()
1446 _column_ondelete = property(attrgetter('ondelete'))
1447 _column_auto_join = property(attrgetter('auto_join'))
1449 def _update(self, records, value):
1450 """ Update the cached value of `self` for `records` with `value`. """
1451 records._cache[self] = value
1453 def convert_to_cache(self, value, record, validate=True):
1454 if isinstance(value, (NoneType, int)):
1455 return record.env[self.comodel_name].browse(value)
1456 if isinstance(value, BaseModel):
1457 if value._name == self.comodel_name and len(value) <= 1:
1458 return value.with_env(record.env)
1459 raise ValueError("Wrong value for %s: %r" % (self, value))
1460 elif isinstance(value, tuple):
1461 return record.env[self.comodel_name].browse(value[0])
1462 elif isinstance(value, dict):
1463 return record.env[self.comodel_name].new(value)
1465 return record.env[self.comodel_name].browse(value)
1467 def convert_to_read(self, value, use_name_get=True):
1468 if use_name_get and value:
1469 # evaluate name_get() as superuser, because the visibility of a
1470 # many2one field value (id and name) depends on the current record's
1471 # access rights, and not the value's access rights.
1473 return value.sudo().name_get()[0]
1474 except MissingError:
1475 # Should not happen, unless the foreign key is missing.
1480 def convert_to_write(self, value, target=None, fnames=None):
1483 def convert_to_onchange(self, value):
1486 def convert_to_export(self, value, env):
1487 return bool(value) and value.name_get()[0][1]
1489 def convert_to_display_name(self, value):
1490 return ustr(value.display_name)
1493 class UnionUpdate(SpecialValue):
1494 """ Placeholder for a value update; when this value is taken from the cache,
1495 it returns ``record[field.name] | value`` and stores it in the cache.
1497 def __init__(self, field, record, value):
1498 self.args = (field, record, value)
1501 field, record, value = self.args
1502 # in order to read the current field's value, remove self from cache
1503 del record._cache[field]
1504 # read the current field's value, and update it in cache only
1505 record._cache[field] = new_value = record[field.name] | value
1509 class _RelationalMulti(_Relational):
1510 """ Abstract class for relational fields *2many. """
1512 def _update(self, records, value):
1513 """ Update the cached value of `self` for `records` with `value`. """
1514 for record in records:
1515 if self in record._cache:
1516 record._cache[self] = record[self.name] | value
1518 record._cache[self] = UnionUpdate(self, record, value)
1520 def convert_to_cache(self, value, record, validate=True):
1521 if isinstance(value, BaseModel):
1522 if value._name == self.comodel_name:
1523 return value.with_env(record.env)
1524 elif isinstance(value, list):
1525 # value is a list of record ids or commands
1527 record = record.browse() # new record has no value
1528 result = record[self.name]
1529 # modify result with the commands;
1530 # beware to not introduce duplicates in result
1531 for command in value:
1532 if isinstance(command, (tuple, list)):
1534 result += result.new(command[2])
1535 elif command[0] == 1:
1536 result.browse(command[1]).update(command[2])
1537 result += result.browse(command[1]) - result
1538 elif command[0] == 2:
1539 # note: the record will be deleted by write()
1540 result -= result.browse(command[1])
1541 elif command[0] == 3:
1542 result -= result.browse(command[1])
1543 elif command[0] == 4:
1544 result += result.browse(command[1]) - result
1545 elif command[0] == 5:
1546 result = result.browse()
1547 elif command[0] == 6:
1548 result = result.browse(command[2])
1549 elif isinstance(command, dict):
1550 result += result.new(command)
1552 result += result.browse(command) - result
1555 return self.null(record.env)
1556 raise ValueError("Wrong value for %s: %s" % (self, value))
1558 def convert_to_read(self, value, use_name_get=True):
1561 def convert_to_write(self, value, target=None, fnames=None):
1562 # remove/delete former records
1565 result = [(6, 0, set_ids)]
1566 add_existing = lambda id: set_ids.append(id)
1568 tag = 2 if self.type == 'one2many' else 3
1569 result = [(tag, record.id) for record in target[self.name] - value]
1570 add_existing = lambda id: result.append((4, id))
1573 # take all fields in cache, except the inverses of self
1574 fnames = set(value._fields) - set(MAGIC_COLUMNS)
1575 for invf in self.inverse_fields:
1576 fnames.discard(invf.name)
1578 # add new and existing records
1579 for record in value:
1580 if not record.id or record._dirty:
1581 values = dict((k, v) for k, v in record._cache.iteritems() if k in fnames)
1582 values = record._convert_to_write(values)
1584 result.append((0, 0, values))
1586 result.append((1, record.id, values))
1588 add_existing(record.id)
1592 def convert_to_export(self, value, env):
1593 return bool(value) and ','.join(name for id, name in value.name_get())
1595 def convert_to_display_name(self, value):
1596 raise NotImplementedError()
1598 def _compute_related(self, records):
1599 """ Compute the related field `self` on `records`. """
1600 for record in records:
1602 # traverse the intermediate fields, and keep at most one record
1603 for name in self.related[:-1]:
1604 value = value[name][:1]
1605 record[self.name] = value[self.related[-1]]
1608 class One2many(_RelationalMulti):
1609 """ One2many field; the value of such a field is the recordset of all the
1610 records in `comodel_name` such that the field `inverse_name` is equal to
1613 :param comodel_name: name of the target model (string)
1615 :param inverse_name: name of the inverse `Many2one` field in
1616 `comodel_name` (string)
1618 :param domain: an optional domain to set on candidate values on the
1619 client side (domain or string)
1621 :param context: an optional context to use on the client side when
1622 handling that field (dictionary)
1624 :param auto_join: whether JOINs are generated upon search through that
1625 field (boolean, by default ``False``)
1627 :param limit: optional limit to use upon read (integer)
1629 The attributes `comodel_name` and `inverse_name` are mandatory except in
1630 the case of related fields or field extensions.
1633 inverse_name = None # name of the inverse field
1634 auto_join = False # whether joins are generated upon search
1635 limit = None # optional limit to use upon read
1636 copy = False # o2m are not copied by default
1638 def __init__(self, comodel_name=None, inverse_name=None, string=None, **kwargs):
1639 super(One2many, self).__init__(
1640 comodel_name=comodel_name,
1641 inverse_name=inverse_name,
1646 def _setup_regular(self, env):
1647 super(One2many, self)._setup_regular(env)
1649 if self.inverse_name:
1650 # link self to its inverse field and vice-versa
1651 comodel = env[self.comodel_name]
1652 comodel._setup_fields()
1653 invf = comodel._fields[self.inverse_name]
1654 # In some rare cases, a `One2many` field can link to `Int` field
1655 # (res_model/res_id pattern). Only inverse the field if this is
1656 # a `Many2one` field.
1657 if isinstance(invf, Many2one):
1658 self.inverse_fields.append(invf)
1659 invf.inverse_fields.append(self)
1661 _description_relation_field = property(attrgetter('inverse_name'))
1663 _column_fields_id = property(attrgetter('inverse_name'))
1664 _column_auto_join = property(attrgetter('auto_join'))
1665 _column_limit = property(attrgetter('limit'))
1668 class Many2many(_RelationalMulti):
1669 """ Many2many field; the value of such a field is the recordset.
1671 :param comodel_name: name of the target model (string)
1673 The attribute `comodel_name` is mandatory except in the case of related
1674 fields or field extensions.
1676 :param relation: optional name of the table that stores the relation in
1677 the database (string)
1679 :param column1: optional name of the column referring to "these" records
1680 in the table `relation` (string)
1682 :param column2: optional name of the column referring to "those" records
1683 in the table `relation` (string)
1685 The attributes `relation`, `column1` and `column2` are optional. If not
1686 given, names are automatically generated from model names, provided
1687 `model_name` and `comodel_name` are different!
1689 :param domain: an optional domain to set on candidate values on the
1690 client side (domain or string)
1692 :param context: an optional context to use on the client side when
1693 handling that field (dictionary)
1695 :param limit: optional limit to use upon read (integer)
1699 relation = None # name of table
1700 column1 = None # column of table referring to model
1701 column2 = None # column of table referring to comodel
1702 limit = None # optional limit to use upon read
1704 def __init__(self, comodel_name=None, relation=None, column1=None, column2=None,
1705 string=None, **kwargs):
1706 super(Many2many, self).__init__(
1707 comodel_name=comodel_name,
1715 def _setup_regular(self, env):
1716 super(Many2many, self)._setup_regular(env)
1718 if not self.relation:
1719 if isinstance(self.column, fields.many2many):
1720 self.relation, self.column1, self.column2 = \
1721 self.column._sql_names(env[self.model_name])
1724 m2m = env.registry._m2m
1725 # if inverse field has already been setup, it is present in m2m
1726 invf = m2m.get((self.relation, self.column2, self.column1))
1728 self.inverse_fields.append(invf)
1729 invf.inverse_fields.append(self)
1731 # add self in m2m, so that its inverse field can find it
1732 m2m[(self.relation, self.column1, self.column2)] = self
1734 _column_rel = property(attrgetter('relation'))
1735 _column_id1 = property(attrgetter('column1'))
1736 _column_id2 = property(attrgetter('column2'))
1737 _column_limit = property(attrgetter('limit'))
1741 """ Special case for field 'id'. """
1743 #: Can't write this!
1746 def __init__(self, string=None, **kwargs):
1747 super(Id, self).__init__(type='integer', string=string, **kwargs)
1749 def to_column(self):
1750 self.column = fields.integer('ID')
1753 def __get__(self, record, owner):
1755 return self # the field is accessed through the class owner
1758 return record.ensure_one()._ids[0]
1760 def __set__(self, record, value):
1761 raise TypeError("field 'id' cannot be assigned")
1764 # imported here to avoid dependency cycle issues
1765 from openerp import SUPERUSER_ID
1766 from .exceptions import Warning, AccessError, MissingError
1767 from .models import BaseModel, MAGIC_COLUMNS
1768 from .osv import fields