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 states: copy it only for inherited fields
478 if not self.states and self.inherited:
479 self.states = field.states
481 # special case for required: check if all fields are required
482 if not self.store and not self.required:
483 self.required = all(field.required for field in fields)
485 def _compute_related(self, records):
486 """ Compute the related field `self` on `records`. """
487 # when related_sudo, bypass access rights checks when reading values
488 others = records.sudo() if self.related_sudo else records
489 for record, other in zip(records, others):
491 # draft record, do not switch to another environment
493 # traverse the intermediate fields; follow the first record at each step
494 for name in self.related[:-1]:
495 other = other[name][:1]
496 record[self.name] = other[self.related[-1]]
498 def _inverse_related(self, records):
499 """ Inverse the related field `self` on `records`. """
500 for record in records:
502 # traverse the intermediate fields, and keep at most one record
503 for name in self.related[:-1]:
504 other = other[name][:1]
506 other[self.related[-1]] = record[self.name]
508 def _search_related(self, records, operator, value):
509 """ Determine the domain to search on field `self`. """
510 return [('.'.join(self.related), operator, value)]
512 # properties used by _setup_related() to copy values from related field
513 _related_comodel_name = property(attrgetter('comodel_name'))
514 _related_string = property(attrgetter('string'))
515 _related_help = property(attrgetter('help'))
516 _related_readonly = property(attrgetter('readonly'))
517 _related_groups = property(attrgetter('groups'))
520 def base_field(self):
521 """ Return the base field of an inherited field, or `self`. """
522 return self.related_field if self.inherited else self
525 # Setup of non-related fields
528 def _setup_regular(self, env):
529 """ Setup the attributes of a non-related field. """
530 recs = env[self.model_name]
532 def make_depends(deps):
533 return tuple(deps(recs) if callable(deps) else deps)
535 # convert compute into a callable and determine depends
536 if isinstance(self.compute, basestring):
537 # if the compute method has been overridden, concatenate all their _depends
539 for method in resolve_all_mro(type(recs), self.compute, reverse=True):
540 self.depends += make_depends(getattr(method, '_depends', ()))
541 self.compute = getattr(type(recs), self.compute)
543 self.depends = make_depends(getattr(self.compute, '_depends', ()))
545 # convert inverse and search into callables
546 if isinstance(self.inverse, basestring):
547 self.inverse = getattr(type(recs), self.inverse)
548 if isinstance(self.search, basestring):
549 self.search = getattr(type(recs), self.search)
551 def _setup_dependency(self, path0, model, path1):
552 """ Make `self` depend on `model`; `path0 + path1` is a dependency of
553 `self`, and `path0` is the sequence of field names from `self.model`
557 head, tail = path1[0], path1[1:]
559 model._setup_fields()
561 # special case: add triggers on all fields of model (except self)
562 fields = set(model._fields.itervalues()) - set([self])
564 fields = [model._fields[head]]
568 _logger.debug("Field %s is recursively defined", self)
569 self.recursive = True
572 #_logger.debug("Add trigger on %s to recompute %s", field, self)
573 field._triggers.add((self, '.'.join(path0 or ['id'])))
575 # add trigger on inverse fields, too
576 for invf in field.inverse_fields:
577 #_logger.debug("Add trigger on %s to recompute %s", invf, self)
578 invf._triggers.add((self, '.'.join(path0 + [head])))
580 # recursively traverse the dependency
582 comodel = env[field.comodel_name]
583 self._setup_dependency(path0 + [head], comodel, tail)
586 def dependents(self):
587 """ Return the computed fields that depend on `self`. """
588 return (field for field, path in self._triggers)
590 ############################################################################
595 def get_description(self, env):
596 """ Return a dictionary that describes the field `self`. """
597 desc = {'type': self.type}
598 for attr, prop in self.description_attrs:
599 value = getattr(self, prop)
602 if value is not None:
607 # properties used by get_description()
608 _description_store = property(attrgetter('store'))
609 _description_manual = property(attrgetter('manual'))
610 _description_depends = property(attrgetter('depends'))
611 _description_related = property(attrgetter('related'))
612 _description_company_dependent = property(attrgetter('company_dependent'))
613 _description_readonly = property(attrgetter('readonly'))
614 _description_required = property(attrgetter('required'))
615 _description_states = property(attrgetter('states'))
616 _description_groups = property(attrgetter('groups'))
617 _description_change_default = property(attrgetter('change_default'))
618 _description_deprecated = property(attrgetter('deprecated'))
621 def _description_searchable(self):
622 return bool(self.store or self.search or (self.column and self.column._fnct_search))
625 def _description_sortable(self):
626 return self.store or (self.inherited and self.related_field._description_sortable)
628 def _description_string(self, env):
629 if self.string and env.lang:
630 field = self.base_field
631 name = "%s,%s" % (field.model_name, field.name)
632 trans = env['ir.translation']._get_source(name, 'field', env.lang)
633 return trans or self.string
636 def _description_help(self, env):
637 if self.help and env.lang:
638 name = "%s,%s" % (self.model_name, self.name)
639 trans = env['ir.translation']._get_source(name, 'help', env.lang)
640 return trans or self.help
643 ############################################################################
645 # Conversion to column instance
649 """ return a low-level field object corresponding to `self` """
650 assert self.store or self.column
652 # determine column parameters
653 _logger.debug("Create fields._column for Field %s", self)
655 for attr, prop in self.column_attrs:
656 args[attr] = getattr(self, prop)
657 for attr in self._free_attrs:
658 args[attr] = getattr(self, attr)
660 if self.company_dependent:
661 # company-dependent fields are mapped to former property fields
662 args['type'] = self.type
663 args['relation'] = self.comodel_name
664 self.column = fields.property(**args)
666 # let the column provide a valid column for the given parameters
667 self.column = self.column.new(**args)
669 # create a fresh new column of the right type
670 self.column = getattr(fields, self.type)(**args)
674 # properties used by to_column() to create a column instance
675 _column_copy = property(attrgetter('copy'))
676 _column_select = property(attrgetter('index'))
677 _column_manual = property(attrgetter('manual'))
678 _column_string = property(attrgetter('string'))
679 _column_help = property(attrgetter('help'))
680 _column_readonly = property(attrgetter('readonly'))
681 _column_required = property(attrgetter('required'))
682 _column_states = property(attrgetter('states'))
683 _column_groups = property(attrgetter('groups'))
684 _column_change_default = property(attrgetter('change_default'))
685 _column_deprecated = property(attrgetter('deprecated'))
687 ############################################################################
689 # Conversion of values
693 """ return the null value for this field in the given environment """
696 def convert_to_cache(self, value, record, validate=True):
697 """ convert `value` to the cache level in `env`; `value` may come from
698 an assignment, or have the format of methods :meth:`BaseModel.read`
699 or :meth:`BaseModel.write`
701 :param record: the target record for the assignment, or an empty recordset
703 :param bool validate: when True, field-specific validation of
704 `value` will be performed
708 def convert_to_read(self, value, use_name_get=True):
709 """ convert `value` from the cache to a value as returned by method
710 :meth:`BaseModel.read`
712 :param bool use_name_get: when True, value's diplay name will
713 be computed using :meth:`BaseModel.name_get`, if relevant
716 return False if value is None else value
718 def convert_to_write(self, value, target=None, fnames=None):
719 """ convert `value` from the cache to a valid value for method
720 :meth:`BaseModel.write`.
722 :param target: optional, the record to be modified with this value
723 :param fnames: for relational fields only, an optional collection of
724 field names to convert
726 return self.convert_to_read(value)
728 def convert_to_onchange(self, value):
729 """ convert `value` from the cache to a valid value for an onchange
732 return self.convert_to_write(value)
734 def convert_to_export(self, value, env):
735 """ convert `value` from the cache to a valid value for export. The
736 parameter `env` is given for managing translations.
738 if env.context.get('export_raw_data'):
740 return bool(value) and ustr(value)
742 def convert_to_display_name(self, value):
743 """ convert `value` from the cache to a suitable display name. """
746 ############################################################################
751 def __get__(self, record, owner):
752 """ return the value of field `self` on `record` """
754 return self # the field is accessed through the owner class
757 # null record -> return the null value for this field
758 return self.null(record.env)
760 # only a single record may be accessed
764 return record._cache[self]
768 # cache miss, retrieve value
770 # normal record -> read or compute value for this field
771 self.determine_value(record)
773 # draft record -> compute the value or let it be null
774 self.determine_draft_value(record)
776 # the result should be in cache now
777 return record._cache[self]
779 def __set__(self, record, value):
780 """ set the value of field `self` on `record` """
783 # only a single record may be updated
786 # adapt value to the cache level
787 value = self.convert_to_cache(value, record)
789 if env.in_draft or not record.id:
790 # determine dependent fields
791 spec = self.modified_draft(record)
793 # set value in cache, inverse field, and mark record as dirty
794 record._cache[self] = value
796 for invf in self.inverse_fields:
797 invf._update(value, record)
798 record._set_dirty(self.name)
800 # determine more dependent fields, and invalidate them
802 spec += self.modified_draft(record)
806 # simply write to the database, and update cache
807 record.write({self.name: self.convert_to_write(value)})
808 record._cache[self] = value
810 ############################################################################
812 # Computation of field values
815 def _compute_value(self, records):
816 """ Invoke the compute method on `records`. """
817 # initialize the fields to their corresponding null value in cache
818 for field in self.computed_fields:
819 records._cache[field] = field.null(records.env)
820 records.env.computed[field].update(records._ids)
821 self.compute(records)
822 for field in self.computed_fields:
823 records.env.computed[field].difference_update(records._ids)
825 def compute_value(self, records):
826 """ Invoke the compute method on `records`; the results are in cache. """
827 with records.env.do_in_draft():
829 self._compute_value(records)
830 except (AccessError, MissingError):
831 # some record is forbidden or missing, retry record by record
832 for record in records:
834 self._compute_value(record)
835 except Exception as exc:
836 record._cache[self.name] = FailedValue(exc)
838 def determine_value(self, record):
839 """ Determine the value of `self` for `record`. """
842 if self.column and not (self.depends and env.in_draft):
843 # this is a stored field or an old-style function field
845 # this is a stored computed field, check for recomputation
846 recs = record._recompute_check(self)
848 # recompute the value (only in cache)
849 self.compute_value(recs)
850 # HACK: if result is in the wrong cache, copy values
852 for source, target in zip(recs, recs.with_env(env)):
854 values = target._convert_to_cache({
855 f.name: source[f.name] for f in self.computed_fields
857 except MissingError as e:
858 values = FailedValue(e)
859 target._cache.update(values)
860 # the result is saved to database by BaseModel.recompute()
863 # read the field from database
864 record._prefetch_field(self)
867 # this is either a non-stored computed field, or a stored computed
868 # field in draft mode
870 self.compute_value(record)
872 recs = record._in_cache_without(self)
873 self.compute_value(recs)
876 # this is a non-stored non-computed field
877 record._cache[self] = self.null(env)
879 def determine_draft_value(self, record):
880 """ Determine the value of `self` for the given draft `record`. """
882 self._compute_value(record)
884 record._cache[self] = SpecialValue(self.null(record.env))
886 def determine_inverse(self, records):
887 """ Given the value of `self` on `records`, inverse the computation. """
889 self.inverse(records)
891 def determine_domain(self, records, operator, value):
892 """ Return a domain representing a condition on `self`. """
894 return self.search(records, operator, value)
896 return [(self.name, operator, value)]
898 ############################################################################
900 # Notification when fields are modified
903 def modified(self, records):
904 """ Notify that field `self` has been modified on `records`: prepare the
905 fields/records to recompute, and return a spec indicating what to
908 # invalidate the fields that depend on self, and prepare recomputation
909 spec = [(self, records._ids)]
910 for field, path in self._triggers:
911 if path and field.store:
912 # don't move this line to function top, see log
913 env = records.env(user=SUPERUSER_ID, context={'active_test': False})
914 target = env[field.model_name].search([(path, 'in', records.ids)])
916 spec.append((field, target._ids))
917 target.with_env(records.env)._recompute_todo(field)
919 spec.append((field, None))
923 def modified_draft(self, records):
924 """ Same as :meth:`modified`, but in draft mode. """
927 # invalidate the fields on the records in cache that depend on
928 # `records`, except fields currently being computed
930 for field, path in self._triggers:
931 target = env[field.model_name]
932 computed = target.browse(env.computed[field])
934 target = records - computed
936 target = (target.browse(env.cache[field]) - computed).filtered(
937 lambda rec: rec._mapped_cache(path) & records
940 target = target.browse(env.cache[field]) - computed
943 spec.append((field, target._ids))
948 class Boolean(Field):
951 def convert_to_cache(self, value, record, validate=True):
954 def convert_to_export(self, value, env):
955 if env.context.get('export_raw_data'):
960 class Integer(Field):
962 group_operator = None # operator for aggregating values
964 _related_group_operator = property(attrgetter('group_operator'))
966 _column_group_operator = property(attrgetter('group_operator'))
968 def convert_to_cache(self, value, record, validate=True):
969 if isinstance(value, dict):
970 # special case, when an integer field is used as inverse for a one2many
971 return value.get('id', False)
972 return int(value or 0)
974 def convert_to_read(self, value, use_name_get=True):
975 # Integer values greater than 2^31-1 are not supported in pure XMLRPC,
976 # so we have to pass them as floats :-(
977 if value and value > xmlrpclib.MAXINT:
981 def _update(self, records, value):
982 # special case, when an integer field is used as inverse for a one2many
983 records._cache[self] = value.id or 0
987 """ The precision digits are given by the attribute
989 :param digits: a pair (total, decimal), or a function taking a database
990 cursor and returning a pair (total, decimal)
993 _digits = None # digits argument passed to class initializer
994 digits = None # digits as computed by setup()
995 group_operator = None # operator for aggregating values
997 def __init__(self, string=None, digits=None, **kwargs):
998 super(Float, self).__init__(string=string, _digits=digits, **kwargs)
1000 def _setup_digits(self, env):
1001 """ Setup the digits for `self` and its corresponding column """
1002 self.digits = self._digits(env.cr) if callable(self._digits) else self._digits
1004 assert isinstance(self.digits, (tuple, list)) and len(self.digits) >= 2, \
1005 "Float field %s with digits %r, expecting (total, decimal)" % (self, self.digits)
1007 self.column.digits_change(env.cr)
1009 def _setup_regular(self, env):
1010 super(Float, self)._setup_regular(env)
1011 self._setup_digits(env)
1013 _related_digits = property(attrgetter('digits'))
1014 _related_group_operator = property(attrgetter('group_operator'))
1016 _description_digits = property(attrgetter('digits'))
1018 _column_digits = property(lambda self: not callable(self._digits) and self._digits)
1019 _column_digits_compute = property(lambda self: callable(self._digits) and self._digits)
1020 _column_group_operator = property(attrgetter('group_operator'))
1022 def convert_to_cache(self, value, record, validate=True):
1023 # apply rounding here, otherwise value in cache may be wrong!
1025 return float_round(float(value or 0.0), precision_digits=self.digits[1])
1027 return float(value or 0.0)
1030 class _String(Field):
1031 """ Abstract class for string fields. """
1034 _column_translate = property(attrgetter('translate'))
1035 _related_translate = property(attrgetter('translate'))
1036 _description_translate = property(attrgetter('translate'))
1039 class Char(_String):
1040 """ Basic string field, can be length-limited, usually displayed as a
1041 single-line string in clients
1043 :param int size: the maximum size of values stored for that field
1044 :param bool translate: whether the values of this field can be translated
1049 def _setup(self, env):
1050 super(Char, self)._setup(env)
1051 assert isinstance(self.size, (NoneType, int)), \
1052 "Char field %s with non-integer size %r" % (self, self.size)
1054 _column_size = property(attrgetter('size'))
1055 _related_size = property(attrgetter('size'))
1056 _description_size = property(attrgetter('size'))
1058 def convert_to_cache(self, value, record, validate=True):
1059 if value is None or value is False:
1061 return ustr(value)[:self.size]
1063 class Text(_String):
1064 """ Very similar to :class:`~.Char` but used for longer contents, does not
1065 have a size and usually displayed as a multiline text box.
1067 :param translate: whether the value of this field can be translated
1071 def convert_to_cache(self, value, record, validate=True):
1072 if value is None or value is False:
1076 class Html(_String):
1078 sanitize = True # whether value must be sanitized
1080 _column_sanitize = property(attrgetter('sanitize'))
1081 _related_sanitize = property(attrgetter('sanitize'))
1082 _description_sanitize = property(attrgetter('sanitize'))
1084 def convert_to_cache(self, value, record, validate=True):
1085 if value is None or value is False:
1087 if validate and self.sanitize:
1088 return html_sanitize(value)
1097 """ Return the current day in the format expected by the ORM.
1098 This function may be used to compute default values.
1100 return date.today().strftime(DATE_FORMAT)
1103 def context_today(record, timestamp=None):
1104 """ Return the current date as seen in the client's timezone in a format
1105 fit for date fields. This method may be used to compute default
1108 :param datetime timestamp: optional datetime value to use instead of
1109 the current date and time (must be a datetime, regular dates
1110 can't be converted between timezones.)
1113 today = timestamp or datetime.now()
1114 context_today = None
1115 tz_name = record._context.get('tz') or record.env.user.tz
1118 today_utc = pytz.timezone('UTC').localize(today, is_dst=False) # UTC = no DST
1119 context_today = today_utc.astimezone(pytz.timezone(tz_name))
1121 _logger.debug("failed to compute context/client-specific today date, using UTC value for `today`",
1123 return (context_today or today).strftime(DATE_FORMAT)
1126 def from_string(value):
1127 """ Convert an ORM `value` into a :class:`date` value. """
1128 value = value[:DATE_LENGTH]
1129 return datetime.strptime(value, DATE_FORMAT).date()
1132 def to_string(value):
1133 """ Convert a :class:`date` value into the format expected by the ORM. """
1134 return value.strftime(DATE_FORMAT)
1136 def convert_to_cache(self, value, record, validate=True):
1139 if isinstance(value, basestring):
1141 # force parsing for validation
1142 self.from_string(value)
1143 return value[:DATE_LENGTH]
1144 return self.to_string(value)
1146 def convert_to_export(self, value, env):
1147 if value and env.context.get('export_raw_data'):
1148 return self.from_string(value)
1149 return bool(value) and ustr(value)
1152 class Datetime(Field):
1157 """ Return the current day and time in the format expected by the ORM.
1158 This function may be used to compute default values.
1160 return datetime.now().strftime(DATETIME_FORMAT)
1163 def context_timestamp(record, timestamp):
1164 """Returns the given timestamp converted to the client's timezone.
1165 This method is *not* meant for use as a _defaults initializer,
1166 because datetime fields are automatically converted upon
1167 display on client side. For _defaults you :meth:`fields.datetime.now`
1168 should be used instead.
1170 :param datetime timestamp: naive datetime value (expressed in UTC)
1171 to be converted to the client timezone
1173 :return: timestamp converted to timezone-aware datetime in context
1176 assert isinstance(timestamp, datetime), 'Datetime instance expected'
1177 tz_name = record._context.get('tz') or record.env.user.tz
1180 utc = pytz.timezone('UTC')
1181 context_tz = pytz.timezone(tz_name)
1182 utc_timestamp = utc.localize(timestamp, is_dst=False) # UTC = no DST
1183 return utc_timestamp.astimezone(context_tz)
1185 _logger.debug("failed to compute context/client-specific timestamp, "
1186 "using the UTC value",
1191 def from_string(value):
1192 """ Convert an ORM `value` into a :class:`datetime` value. """
1193 value = value[:DATETIME_LENGTH]
1194 if len(value) == DATE_LENGTH:
1195 value += " 00:00:00"
1196 return datetime.strptime(value, DATETIME_FORMAT)
1199 def to_string(value):
1200 """ Convert a :class:`datetime` value into the format expected by the ORM. """
1201 return value.strftime(DATETIME_FORMAT)
1203 def convert_to_cache(self, value, record, validate=True):
1206 if isinstance(value, basestring):
1208 # force parsing for validation
1209 self.from_string(value)
1210 value = value[:DATETIME_LENGTH]
1211 if len(value) == DATE_LENGTH:
1212 value += " 00:00:00"
1214 return self.to_string(value)
1216 def convert_to_export(self, value, env):
1217 if value and env.context.get('export_raw_data'):
1218 return self.from_string(value)
1219 return bool(value) and ustr(value)
1222 class Binary(Field):
1226 class Selection(Field):
1228 :param selection: specifies the possible values for this field.
1229 It is given as either a list of pairs (`value`, `string`), or a
1230 model method, or a method name.
1231 :param selection_add: provides an extension of the selection in the case
1232 of an overridden field. It is a list of pairs (`value`, `string`).
1234 The attribute `selection` is mandatory except in the case of
1235 :ref:`related fields <field-related>` or :ref:`field extensions
1236 <field-incremental-definition>`.
1239 selection = None # [(value, string), ...], function or method name
1240 selection_add = None # [(value, string), ...]
1242 def __init__(self, selection=None, string=None, **kwargs):
1243 if callable(selection):
1244 from openerp import api
1245 selection = api.expected(api.model, selection)
1246 super(Selection, self).__init__(selection=selection, string=string, **kwargs)
1248 def _setup(self, env):
1249 super(Selection, self)._setup(env)
1250 assert self.selection is not None, "Field %s without selection" % self
1252 def _setup_related(self, env):
1253 super(Selection, self)._setup_related(env)
1254 # selection must be computed on related field
1255 field = self.related_field
1256 self.selection = lambda model: field._description_selection(model.env)
1258 def set_class_name(self, cls, name):
1259 super(Selection, self).set_class_name(cls, name)
1260 # determine selection (applying 'selection_add' extensions)
1262 for field in resolve_all_mro(cls, name, reverse=True):
1263 if isinstance(field, type(self)):
1264 # We cannot use field.selection or field.selection_add here
1265 # because those attributes are overridden by `set_class_name`.
1266 if 'selection' in field._attrs:
1267 selection = field._attrs['selection']
1268 if 'selection_add' in field._attrs:
1269 selection = selection + field._attrs['selection_add']
1272 self.selection = selection
1274 def _description_selection(self, env):
1275 """ return the selection list (pairs (value, label)); labels are
1276 translated according to context language
1278 selection = self.selection
1279 if isinstance(selection, basestring):
1280 return getattr(env[self.model_name], selection)()
1281 if callable(selection):
1282 return selection(env[self.model_name])
1284 # translate selection labels
1286 name = "%s,%s" % (self.model_name, self.name)
1287 translate = partial(
1288 env['ir.translation']._get_source, name, 'selection', env.lang)
1289 return [(value, translate(label) if label else label) for value, label in selection]
1294 def _column_selection(self):
1295 if isinstance(self.selection, basestring):
1296 method = self.selection
1297 return lambda self, *a, **kw: getattr(self, method)(*a, **kw)
1299 return self.selection
1301 def get_values(self, env):
1302 """ return a list of the possible values """
1303 selection = self.selection
1304 if isinstance(selection, basestring):
1305 selection = getattr(env[self.model_name], selection)()
1306 elif callable(selection):
1307 selection = selection(env[self.model_name])
1308 return [value for value, _ in selection]
1310 def convert_to_cache(self, value, record, validate=True):
1312 return value or False
1313 if value in self.get_values(record.env):
1317 raise ValueError("Wrong value for %s: %r" % (self, value))
1319 def convert_to_export(self, value, env):
1320 if not isinstance(self.selection, list):
1321 # FIXME: this reproduces an existing buggy behavior!
1323 for item in self._description_selection(env):
1324 if item[0] == value:
1329 class Reference(Selection):
1333 def __init__(self, selection=None, string=None, **kwargs):
1334 super(Reference, self).__init__(selection=selection, string=string, **kwargs)
1336 def _setup(self, env):
1337 super(Reference, self)._setup(env)
1338 assert isinstance(self.size, (NoneType, int)), \
1339 "Reference field %s with non-integer size %r" % (self, self.size)
1341 _related_size = property(attrgetter('size'))
1343 _column_size = property(attrgetter('size'))
1345 def convert_to_cache(self, value, record, validate=True):
1346 if isinstance(value, BaseModel):
1347 if ((not validate or value._name in self.get_values(record.env))
1348 and len(value) <= 1):
1349 return value.with_env(record.env) or False
1350 elif isinstance(value, basestring):
1351 res_model, res_id = value.split(',')
1352 return record.env[res_model].browse(int(res_id))
1355 raise ValueError("Wrong value for %s: %r" % (self, value))
1357 def convert_to_read(self, value, use_name_get=True):
1358 return "%s,%s" % (value._name, value.id) if value else False
1360 def convert_to_export(self, value, env):
1361 return bool(value) and value.name_get()[0][1]
1363 def convert_to_display_name(self, value):
1364 return ustr(value and value.display_name)
1367 class _Relational(Field):
1368 """ Abstract class for relational fields. """
1370 domain = None # domain for searching values
1371 context = None # context for searching values
1373 def _setup(self, env):
1374 super(_Relational, self)._setup(env)
1375 assert self.comodel_name in env.registry, \
1376 "Field %s with unknown comodel_name %r" % (self, self.comodel_name)
1379 def _related_domain(self):
1380 if callable(self.domain):
1381 # will be called with another model than self's
1382 return lambda recs: self.domain(recs.env[self.model_name])
1384 # maybe not correct if domain is a string...
1387 _related_context = property(attrgetter('context'))
1389 _description_relation = property(attrgetter('comodel_name'))
1390 _description_context = property(attrgetter('context'))
1392 def _description_domain(self, env):
1393 return self.domain(env[self.model_name]) if callable(self.domain) else self.domain
1395 _column_obj = property(attrgetter('comodel_name'))
1396 _column_domain = property(attrgetter('domain'))
1397 _column_context = property(attrgetter('context'))
1399 def null(self, env):
1400 return env[self.comodel_name]
1402 def modified(self, records):
1403 # Invalidate cache for self.inverse_fields, too. Note that recomputation
1404 # of fields that depend on self.inverse_fields is already covered by the
1405 # triggers (see above).
1406 spec = super(_Relational, self).modified(records)
1407 for invf in self.inverse_fields:
1408 spec.append((invf, None))
1412 class Many2one(_Relational):
1413 """ The value of such a field is a recordset of size 0 (no
1414 record) or 1 (a single record).
1416 :param comodel_name: name of the target model (string)
1418 :param domain: an optional domain to set on candidate values on the
1419 client side (domain or string)
1421 :param context: an optional context to use on the client side when
1422 handling that field (dictionary)
1424 :param ondelete: what to do when the referred record is deleted;
1425 possible values are: ``'set null'``, ``'restrict'``, ``'cascade'``
1427 :param auto_join: whether JOINs are generated upon search through that
1428 field (boolean, by default ``False``)
1430 :param delegate: set it to ``True`` to make fields of the target model
1431 accessible from the current model (corresponds to ``_inherits``)
1433 The attribute `comodel_name` is mandatory except in the case of related
1434 fields or field extensions.
1437 ondelete = 'set null' # what to do when value is deleted
1438 auto_join = False # whether joins are generated upon search
1439 delegate = False # whether self implements delegation
1441 def __init__(self, comodel_name=None, string=None, **kwargs):
1442 super(Many2one, self).__init__(comodel_name=comodel_name, string=string, **kwargs)
1444 def set_class_name(self, cls, name):
1445 super(Many2one, self).set_class_name(cls, name)
1446 # determine self.delegate
1447 if not self.delegate:
1448 self.delegate = name in cls._inherits.values()
1450 _column_ondelete = property(attrgetter('ondelete'))
1451 _column_auto_join = property(attrgetter('auto_join'))
1453 def _update(self, records, value):
1454 """ Update the cached value of `self` for `records` with `value`. """
1455 records._cache[self] = value
1457 def convert_to_cache(self, value, record, validate=True):
1458 if isinstance(value, (NoneType, int, long)):
1459 return record.env[self.comodel_name].browse(value)
1460 if isinstance(value, BaseModel):
1461 if value._name == self.comodel_name and len(value) <= 1:
1462 return value.with_env(record.env)
1463 raise ValueError("Wrong value for %s: %r" % (self, value))
1464 elif isinstance(value, tuple):
1465 return record.env[self.comodel_name].browse(value[0])
1466 elif isinstance(value, dict):
1467 return record.env[self.comodel_name].new(value)
1469 return self.null(record.env)
1471 def convert_to_read(self, value, use_name_get=True):
1472 if use_name_get and value:
1473 # evaluate name_get() as superuser, because the visibility of a
1474 # many2one field value (id and name) depends on the current record's
1475 # access rights, and not the value's access rights.
1477 return value.sudo().name_get()[0]
1478 except MissingError:
1479 # Should not happen, unless the foreign key is missing.
1484 def convert_to_write(self, value, target=None, fnames=None):
1487 def convert_to_onchange(self, value):
1490 def convert_to_export(self, value, env):
1491 return bool(value) and value.name_get()[0][1]
1493 def convert_to_display_name(self, value):
1494 return ustr(value.display_name)
1497 class UnionUpdate(SpecialValue):
1498 """ Placeholder for a value update; when this value is taken from the cache,
1499 it returns ``record[field.name] | value`` and stores it in the cache.
1501 def __init__(self, field, record, value):
1502 self.args = (field, record, value)
1505 field, record, value = self.args
1506 # in order to read the current field's value, remove self from cache
1507 del record._cache[field]
1508 # read the current field's value, and update it in cache only
1509 record._cache[field] = new_value = record[field.name] | value
1513 class _RelationalMulti(_Relational):
1514 """ Abstract class for relational fields *2many. """
1516 def _update(self, records, value):
1517 """ Update the cached value of `self` for `records` with `value`. """
1518 for record in records:
1519 if self in record._cache:
1520 record._cache[self] = record[self.name] | value
1522 record._cache[self] = UnionUpdate(self, record, value)
1524 def convert_to_cache(self, value, record, validate=True):
1525 if isinstance(value, BaseModel):
1526 if value._name == self.comodel_name:
1527 return value.with_env(record.env)
1528 elif isinstance(value, list):
1529 # value is a list of record ids or commands
1531 record = record.browse() # new record has no value
1532 result = record[self.name]
1533 # modify result with the commands;
1534 # beware to not introduce duplicates in result
1535 for command in value:
1536 if isinstance(command, (tuple, list)):
1538 result += result.new(command[2])
1539 elif command[0] == 1:
1540 result.browse(command[1]).update(command[2])
1541 result += result.browse(command[1]) - result
1542 elif command[0] == 2:
1543 # note: the record will be deleted by write()
1544 result -= result.browse(command[1])
1545 elif command[0] == 3:
1546 result -= result.browse(command[1])
1547 elif command[0] == 4:
1548 result += result.browse(command[1]) - result
1549 elif command[0] == 5:
1550 result = result.browse()
1551 elif command[0] == 6:
1552 result = result.browse(command[2])
1553 elif isinstance(command, dict):
1554 result += result.new(command)
1556 result += result.browse(command) - result
1559 return self.null(record.env)
1560 raise ValueError("Wrong value for %s: %s" % (self, value))
1562 def convert_to_read(self, value, use_name_get=True):
1565 def convert_to_write(self, value, target=None, fnames=None):
1566 # remove/delete former records
1569 result = [(6, 0, set_ids)]
1570 add_existing = lambda id: set_ids.append(id)
1572 tag = 2 if self.type == 'one2many' else 3
1573 result = [(tag, record.id) for record in target[self.name] - value]
1574 add_existing = lambda id: result.append((4, id))
1577 # take all fields in cache, except the inverses of self
1578 fnames = set(value._fields) - set(MAGIC_COLUMNS)
1579 for invf in self.inverse_fields:
1580 fnames.discard(invf.name)
1582 # add new and existing records
1583 for record in value:
1585 values = {k: v for k, v in record._cache.iteritems() if k in fnames}
1586 values = record._convert_to_write(values)
1587 result.append((0, 0, values))
1588 elif record._is_dirty():
1589 values = {k: record._cache[k] for k in record._get_dirty() if k in fnames}
1590 values = record._convert_to_write(values)
1591 result.append((1, record.id, values))
1593 add_existing(record.id)
1597 def convert_to_export(self, value, env):
1598 return bool(value) and ','.join(name for id, name in value.name_get())
1600 def convert_to_display_name(self, value):
1601 raise NotImplementedError()
1603 def _compute_related(self, records):
1604 """ Compute the related field `self` on `records`. """
1605 for record in records:
1607 # traverse the intermediate fields, and keep at most one record
1608 for name in self.related[:-1]:
1609 value = value[name][:1]
1610 record[self.name] = value[self.related[-1]]
1613 class One2many(_RelationalMulti):
1614 """ One2many field; the value of such a field is the recordset of all the
1615 records in `comodel_name` such that the field `inverse_name` is equal to
1618 :param comodel_name: name of the target model (string)
1620 :param inverse_name: name of the inverse `Many2one` field in
1621 `comodel_name` (string)
1623 :param domain: an optional domain to set on candidate values on the
1624 client side (domain or string)
1626 :param context: an optional context to use on the client side when
1627 handling that field (dictionary)
1629 :param auto_join: whether JOINs are generated upon search through that
1630 field (boolean, by default ``False``)
1632 :param limit: optional limit to use upon read (integer)
1634 The attributes `comodel_name` and `inverse_name` are mandatory except in
1635 the case of related fields or field extensions.
1638 inverse_name = None # name of the inverse field
1639 auto_join = False # whether joins are generated upon search
1640 limit = None # optional limit to use upon read
1641 copy = False # o2m are not copied by default
1643 def __init__(self, comodel_name=None, inverse_name=None, string=None, **kwargs):
1644 super(One2many, self).__init__(
1645 comodel_name=comodel_name,
1646 inverse_name=inverse_name,
1651 def _setup_regular(self, env):
1652 super(One2many, self)._setup_regular(env)
1654 if self.inverse_name:
1655 # link self to its inverse field and vice-versa
1656 comodel = env[self.comodel_name]
1657 comodel._setup_fields()
1658 invf = comodel._fields[self.inverse_name]
1659 # In some rare cases, a `One2many` field can link to `Int` field
1660 # (res_model/res_id pattern). Only inverse the field if this is
1661 # a `Many2one` field.
1662 if isinstance(invf, Many2one):
1663 self.inverse_fields.append(invf)
1664 invf.inverse_fields.append(self)
1666 _description_relation_field = property(attrgetter('inverse_name'))
1668 _column_fields_id = property(attrgetter('inverse_name'))
1669 _column_auto_join = property(attrgetter('auto_join'))
1670 _column_limit = property(attrgetter('limit'))
1673 class Many2many(_RelationalMulti):
1674 """ Many2many field; the value of such a field is the recordset.
1676 :param comodel_name: name of the target model (string)
1678 The attribute `comodel_name` is mandatory except in the case of related
1679 fields or field extensions.
1681 :param relation: optional name of the table that stores the relation in
1682 the database (string)
1684 :param column1: optional name of the column referring to "these" records
1685 in the table `relation` (string)
1687 :param column2: optional name of the column referring to "those" records
1688 in the table `relation` (string)
1690 The attributes `relation`, `column1` and `column2` are optional. If not
1691 given, names are automatically generated from model names, provided
1692 `model_name` and `comodel_name` are different!
1694 :param domain: an optional domain to set on candidate values on the
1695 client side (domain or string)
1697 :param context: an optional context to use on the client side when
1698 handling that field (dictionary)
1700 :param limit: optional limit to use upon read (integer)
1704 relation = None # name of table
1705 column1 = None # column of table referring to model
1706 column2 = None # column of table referring to comodel
1707 limit = None # optional limit to use upon read
1709 def __init__(self, comodel_name=None, relation=None, column1=None, column2=None,
1710 string=None, **kwargs):
1711 super(Many2many, self).__init__(
1712 comodel_name=comodel_name,
1720 def _setup_regular(self, env):
1721 super(Many2many, self)._setup_regular(env)
1723 if not self.relation:
1724 if isinstance(self.column, fields.many2many):
1725 self.relation, self.column1, self.column2 = \
1726 self.column._sql_names(env[self.model_name])
1729 m2m = env.registry._m2m
1730 # if inverse field has already been setup, it is present in m2m
1731 invf = m2m.get((self.relation, self.column2, self.column1))
1733 self.inverse_fields.append(invf)
1734 invf.inverse_fields.append(self)
1736 # add self in m2m, so that its inverse field can find it
1737 m2m[(self.relation, self.column1, self.column2)] = self
1739 _column_rel = property(attrgetter('relation'))
1740 _column_id1 = property(attrgetter('column1'))
1741 _column_id2 = property(attrgetter('column2'))
1742 _column_limit = property(attrgetter('limit'))
1746 """ Special case for field 'id'. """
1748 #: Can't write this!
1751 def __init__(self, string=None, **kwargs):
1752 super(Id, self).__init__(type='integer', string=string, **kwargs)
1754 def to_column(self):
1755 self.column = fields.integer('ID')
1758 def __get__(self, record, owner):
1760 return self # the field is accessed through the class owner
1763 return record.ensure_one()._ids[0]
1765 def __set__(self, record, value):
1766 raise TypeError("field 'id' cannot be assigned")
1769 # imported here to avoid dependency cycle issues
1770 from openerp import SUPERUSER_ID
1771 from .exceptions import Warning, AccessError, MissingError
1772 from .models import BaseModel, MAGIC_COLUMNS
1773 from .osv import fields