1# -*- coding: utf-8 -*-
2# Part of Odoo. See LICENSE file for full copyright and licensing details.
3
4""" High-level objects for fields. """
5
6from collections import defaultdict
7from datetime import date, datetime, time
8from operator import attrgetter
9from xmlrpc.client import MAXINT
10import itertools
11import logging
12import base64
13import binascii
14import pytz
15import psycopg2
16
17from .tools import (
18    float_repr, float_round, float_compare, float_is_zero, html_sanitize, human_size,
19    pg_varchar, ustr, OrderedSet, pycompat, sql, date_utils, unique, IterableGenerator,
20    image_process, merge_sequences,
21)
22from .tools import DEFAULT_SERVER_DATE_FORMAT as DATE_FORMAT
23from .tools import DEFAULT_SERVER_DATETIME_FORMAT as DATETIME_FORMAT
24from .tools.translate import html_translate, _
25from .tools.mimetypes import guess_mimetype
26
27from odoo.exceptions import CacheMiss
28
29DATE_LENGTH = len(date.today().strftime(DATE_FORMAT))
30DATETIME_LENGTH = len(datetime.now().strftime(DATETIME_FORMAT))
31
32# hacky-ish way to prevent access to a field through the ORM (except for sudo mode)
33NO_ACCESS='.'
34
35IR_MODELS = (
36    'ir.model', 'ir.model.data', 'ir.model.fields', 'ir.model.fields.selection',
37    'ir.model.relation', 'ir.model.constraint', 'ir.module.module',
38)
39
40_logger = logging.getLogger(__name__)
41_schema = logging.getLogger(__name__[:-7] + '.schema')
42
43Default = object()                      # default value for __init__() methods
44
45
46def first(records):
47    """ Return the first record in ``records``, with the same prefetching. """
48    return next(iter(records)) if len(records) > 1 else records
49
50
51def resolve_mro(model, name, predicate):
52    """ Return the list of successively overridden values of attribute ``name``
53        in mro order on ``model`` that satisfy ``predicate``.  Model classes
54        (the ones that appear in the registry) are ignored.
55    """
56    result = []
57    for cls in model._model_classes:
58        value = cls.__dict__.get(name, Default)
59        if value is Default:
60            continue
61        if not predicate(value):
62            break
63        result.append(value)
64    return result
65
66
67class MetaField(type):
68    """ Metaclass for field classes. """
69    by_type = {}
70
71    def __init__(cls, name, bases, attrs):
72        super(MetaField, cls).__init__(name, bases, attrs)
73        if not hasattr(cls, 'type'):
74            return
75
76        if cls.type and cls.type not in MetaField.by_type:
77            MetaField.by_type[cls.type] = cls
78
79        # compute class attributes to avoid calling dir() on fields
80        cls.related_attrs = []
81        cls.description_attrs = []
82        for attr in dir(cls):
83            if attr.startswith('_related_'):
84                cls.related_attrs.append((attr[9:], attr))
85            elif attr.startswith('_description_'):
86                cls.description_attrs.append((attr[13:], attr))
87
88
89_global_seq = iter(itertools.count())
90
91
92class Field(MetaField('DummyField', (object,), {})):
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:
96
97    :param str string: the label of the field seen by users; if not
98        set, the ORM takes the field name in the class (capitalized).
99
100    :param str help: the tooltip of the field seen by users
101
102    :param invisible: whether the field is invisible (boolean, by default ``False``)
103
104    :param bool readonly: whether the field is readonly (default: ``False``)
105
106        This only has an impact on the UI. Any field assignation in code will work
107        (if the field is a stored field or an inversable one).
108
109    :param bool required: whether the value of the field is required (default: ``False``)
110
111    :param bool index: whether the field is indexed in database. Note: no effect
112        on non-stored and virtual fields. (default: ``False``)
113
114    :param default: the default value for the field; this is either a static
115        value, or a function taking a recordset and returning a value; use
116        ``default=None`` to discard default values for the field
117    :type default: value or callable
118
119    :param dict states: a dictionary mapping state values to lists of UI attribute-value
120        pairs; possible attributes are: ``readonly``, ``required``, ``invisible``.
121
122        .. warning:: Any state-based condition requires the ``state`` field value to be
123            available on the client-side UI. This is typically done by including it in
124            the relevant views, possibly made invisible if not relevant for the
125            end-user.
126
127    :param str groups: comma-separated list of group xml ids (string); this
128        restricts the field access to the users of the given groups only
129
130    :param bool company_dependent: whether the field value is dependent of the current company;
131
132        The value isn't stored on the model table.  It is registered as `ir.property`.
133        When the value of the company_dependent field is needed, an `ir.property`
134        is searched, linked to the current company (and current record if one property
135        exists).
136
137        If the value is changed on the record, it either modifies the existing property
138        for the current record (if one exists), or creates a new one for the current company
139        and res_id.
140
141        If the value is changed on the company side, it will impact all records on which
142        the value hasn't been changed.
143
144    :param bool copy: whether the field value should be copied when the record
145        is duplicated (default: ``True`` for normal fields, ``False`` for
146        ``one2many`` and computed fields, including property fields and
147        related fields)
148
149    :param bool store: whether the field is stored in database
150        (default:``True``, ``False`` for computed fields)
151
152    :param str group_operator: aggregate function used by :meth:`~odoo.models.Model.read_group`
153        when grouping on this field.
154
155        Supported aggregate functions are:
156
157        * ``array_agg`` : values, including nulls, concatenated into an array
158        * ``count`` : number of rows
159        * ``count_distinct`` : number of distinct rows
160        * ``bool_and`` : true if all values are true, otherwise false
161        * ``bool_or`` : true if at least one value is true, otherwise false
162        * ``max`` : maximum value of all values
163        * ``min`` : minimum value of all values
164        * ``avg`` : the average (arithmetic mean) of all values
165        * ``sum`` : sum of all values
166
167    :param str group_expand: function used to expand read_group results when grouping on
168        the current field.
169
170        .. code-block:: python
171
172            @api.model
173            def _read_group_selection_field(self, values, domain, order):
174                return ['choice1', 'choice2', ...] # available selection choices.
175
176            @api.model
177            def _read_group_many2one_field(self, records, domain, order):
178                return records + self.search([custom_domain])
179
180    .. rubric:: Computed Fields
181
182    :param str compute: name of a method that computes the field
183
184        .. seealso:: :ref:`Advanced Fields/Compute fields <reference/fields/compute>`
185
186    :param bool compute_sudo: whether the field should be recomputed as superuser
187        to bypass access rights (by default ``True`` for stored fields, ``False``
188        for non stored fields)
189
190    :param str inverse: name of a method that inverses the field (optional)
191
192    :param str search: name of a method that implement search on the field (optional)
193
194    :param str related: sequence of field names
195
196        .. seealso:: :ref:`Advanced fields/Related fields <reference/fields/related>`
197    """
198
199    type = None                         # type of the field (string)
200    relational = False                  # whether the field is a relational one
201    translate = False                   # whether the field is translated
202
203    column_type = None                  # database column type (ident, spec)
204    column_format = '%s'                # placeholder for value in queries
205    column_cast_from = ()               # column types that may be cast to this
206
207    args = None                         # the parameters given to __init__()
208    _module = None                      # the field's module name
209    _modules = None                     # modules that define this field
210    _setup_done = None                  # the field's setup state: None, 'base' or 'full'
211    _sequence = None                    # absolute ordering of the field
212
213    automatic = False                   # whether the field is automatically created ("magic" field)
214    inherited = False                   # whether the field is inherited (_inherits)
215    inherited_field = None              # the corresponding inherited field
216
217    name = None                         # name of the field
218    model_name = None                   # name of the model of this field
219    comodel_name = None                 # name of the model of values (if relational)
220
221    store = True                        # whether the field is stored in database
222    index = False                       # whether the field is indexed in database
223    manual = False                      # whether the field is a custom field
224    copy = True                         # whether the field is copied over by BaseModel.copy()
225    _depends = None                     # collection of field dependencies
226    _depends_context = None             # collection of context key dependencies
227    recursive = False                   # whether self depends on itself
228    compute = None                      # compute(recs) computes field on recs
229    compute_sudo = False                # whether field should be recomputed as superuser
230    inverse = None                      # inverse(recs) inverses field on recs
231    search = None                       # search(recs, operator, value) searches on self
232    related = None                      # sequence of field names, for related fields
233    company_dependent = False           # whether ``self`` is company-dependent (property field)
234    default = None                      # default(recs) returns the default value
235
236    string = None                       # field label
237    help = None                         # field tooltip
238    invisible = False             # whether the field is invisible
239    readonly = False                    # whether the field is readonly
240    required = False                    # whether the field is required
241    states = None                       # set readonly and required depending on state
242    groups = None                       # csv list of group xml ids
243    change_default = False              # whether the field may trigger a "user-onchange"
244    deprecated = None                   # whether the field is deprecated
245
246    related_field = None                # corresponding related field
247    group_operator = None               # operator for aggregating values
248    group_expand = None                 # name of method to expand groups in read_group()
249    prefetch = True                     # whether the field is prefetched
250
251    def __init__(self, string=Default, **kwargs):
252        kwargs['string'] = string
253        self._sequence = kwargs['_sequence'] = next(_global_seq)
254        self.args = {key: val for key, val in kwargs.items() if val is not Default}
255
256    def new(self, **kwargs):
257        """ Return a field of the same type as ``self``, with its own parameters. """
258        return type(self)(**kwargs)
259
260    def __str__(self):
261        return "%s.%s" % (self.model_name, self.name)
262
263    def __repr__(self):
264        return "%s.%s" % (self.model_name, self.name)
265
266    ############################################################################
267    #
268    # Base field setup: things that do not depend on other models/fields
269    #
270
271    def setup_base(self, model, name):
272        """ Base setup: things that do not depend on other models/fields. """
273        if self._setup_done and not self.related:
274            # optimization for regular fields: keep the base setup
275            self._setup_done = 'base'
276        else:
277            # do the base setup from scratch
278            self._setup_attrs(model, name)
279            if not self.related:
280                self._setup_regular_base(model)
281            self._setup_done = 'base'
282
283    #
284    # Setup field parameter attributes
285    #
286
287    def _can_setup_from(self, field):
288        """ Return whether ``self`` can retrieve parameters from ``field``. """
289        return isinstance(field, type(self))
290
291    def _get_attrs(self, model, name):
292        """ Return the field parameter attributes as a dictionary. """
293        # determine all inherited field attributes
294        modules = set()
295        attrs = {}
296        if self.args.get('automatic') and resolve_mro(model, name, self._can_setup_from):
297            # prevent an automatic field from overriding a real field
298            self.args.clear()
299        if not (self.args.get('automatic') or self.args.get('manual')):
300            # magic and custom fields do not inherit from parent classes
301            for field in reversed(resolve_mro(model, name, self._can_setup_from)):
302                attrs.update(field.args)
303                if '_module' in field.args:
304                    modules.add(field.args['_module'])
305        attrs.update(self.args)         # necessary in case self is not in class
306
307        attrs['args'] = self.args
308        attrs['model_name'] = model._name
309        attrs['name'] = name
310        attrs['_modules'] = modules
311
312        # initialize ``self`` with ``attrs``
313        if name == 'state':
314            # by default, `state` fields should be reset on copy
315            attrs['copy'] = attrs.get('copy', False)
316        if attrs.get('compute'):
317            # by default, computed fields are not stored, computed in superuser
318            # mode if stored, not copied (unless stored and explicitly not
319            # readonly), and readonly (unless inversible)
320            attrs['store'] = store = attrs.get('store', False)
321            attrs['compute_sudo'] = attrs.get('compute_sudo', store)
322            if not (attrs['store'] and not attrs.get('readonly', True)):
323                attrs['copy'] = attrs.get('copy', False)
324            attrs['readonly'] = attrs.get('readonly', not attrs.get('inverse'))
325        if attrs.get('related'):
326            # by default, related fields are not stored, computed in superuser
327            # mode, not copied and readonly
328            attrs['store'] = store = attrs.get('store', False)
329            attrs['compute_sudo'] = attrs.get('compute_sudo', attrs.get('related_sudo', True))
330            attrs['copy'] = attrs.get('copy', False)
331            attrs['readonly'] = attrs.get('readonly', True)
332        if attrs.get('company_dependent'):
333            # by default, company-dependent fields are not stored, not computed
334            # in superuser mode and not copied
335            attrs['store'] = False
336            attrs['compute_sudo'] = attrs.get('compute_sudo', False)
337            attrs['copy'] = attrs.get('copy', False)
338            attrs['default'] = attrs.get('default', self._default_company_dependent)
339            attrs['compute'] = self._compute_company_dependent
340            if not attrs.get('readonly'):
341                attrs['inverse'] = self._inverse_company_dependent
342            attrs['search'] = self._search_company_dependent
343            attrs['depends_context'] = attrs.get('depends_context', ()) + ('company',)
344        if attrs.get('translate'):
345            # by default, translatable fields are context-dependent
346            attrs['depends_context'] = attrs.get('depends_context', ()) + ('lang',)
347
348        # parameters 'depends' and 'depends_context' are stored in attributes
349        # '_depends' and '_depends_context', respectively
350        if 'depends' in attrs:
351            attrs['_depends'] = tuple(attrs.pop('depends'))
352        if 'depends_context' in attrs:
353            attrs['_depends_context'] = tuple(attrs.pop('depends_context'))
354
355        return attrs
356
357    def _setup_attrs(self, model, name):
358        """ Initialize the field parameter attributes. """
359        attrs = self._get_attrs(model, name)
360        # validate arguments
361        for key in attrs:
362            # TODO: improve filter as there are attributes on the class which
363            #       are not valid on the field, probably
364            if not (hasattr(self, key) or model._valid_field_parameter(self, key)):
365                _logger.warning(
366                    "Field %s.%s: unknown parameter %r, if this is an actual"
367                    " parameter you may want to override the method"
368                    " _valid_field_parameter on the relevant model in order to"
369                    " allow it",
370                    model._name, name, key
371                )
372        self.__dict__.update(attrs)
373
374        # prefetch only stored, column, non-manual and non-deprecated fields
375        if not (self.store and self.column_type) or self.manual or self.deprecated:
376            self.prefetch = False
377
378        if not self.string and not self.related:
379            # related fields get their string from their parent field
380            self.string = (
381                name[:-4] if name.endswith('_ids') else
382                name[:-3] if name.endswith('_id') else name
383            ).replace('_', ' ').title()
384
385        # self.default must be a callable
386        if self.default is not None:
387            value = self.default
388            self.default = value if callable(value) else lambda model: value
389
390    ############################################################################
391    #
392    # Full field setup: everything else, except recomputation triggers
393    #
394
395    def setup_full(self, model):
396        """ Full setup: everything else, except recomputation triggers. """
397        if self._setup_done != 'full':
398            if not self.related:
399                self._setup_regular_full(model)
400            else:
401                self._setup_related_full(model)
402            self._setup_done = 'full'
403
404    #
405    # Setup of non-related fields
406    #
407
408    def _setup_regular_base(self, model):
409        """ Setup the attributes of a non-related field. """
410        pass
411
412    def _setup_regular_full(self, model):
413        """ Determine the dependencies and inverse field(s) of ``self``. """
414        if self._depends is not None:
415            # the parameter 'depends' has priority over 'depends' on compute
416            self.depends = self._depends
417            self.depends_context = self._depends_context or ()
418            return
419
420        # determine the functions implementing self.compute
421        if isinstance(self.compute, str):
422            funcs = resolve_mro(model, self.compute, callable)
423        elif self.compute:
424            funcs = [self.compute]
425        else:
426            funcs = []
427
428        # collect depends and depends_context
429        depends = []
430        depends_context = list(self._depends_context or ())
431        for func in funcs:
432            deps = getattr(func, '_depends', ())
433            depends.extend(deps(model) if callable(deps) else deps)
434            depends_context.extend(getattr(func, '_depends_context', ()))
435
436        self.depends = tuple(depends)
437        self.depends_context = tuple(depends_context)
438
439        # display_name may depend on context['lang'] (`test_lp1071710`)
440        if self.automatic and self.name == 'display_name' and model._rec_name:
441            if model._fields[model._rec_name].base_field.translate:
442                if 'lang' not in self.depends_context:
443                    self.depends_context += ('lang',)
444
445    #
446    # Setup of related fields
447    #
448
449    def _setup_related_full(self, model):
450        """ Setup the attributes of a related field. """
451        # fix the type of self.related if necessary
452        if isinstance(self.related, str):
453            self.related = tuple(self.related.split('.'))
454
455        # determine the chain of fields, and make sure they are all set up
456        model_name = self.model_name
457        for name in self.related:
458            field = model.pool[model_name]._fields[name]
459            if field._setup_done != 'full':
460                field.setup_full(model.env[model_name])
461            model_name = field.comodel_name
462
463        self.related_field = field
464
465        # check type consistency
466        if self.type != field.type:
467            raise TypeError("Type of related field %s is inconsistent with %s" % (self, field))
468
469        # determine dependencies, compute, inverse, and search
470        if self._depends is not None:
471            self.depends = self._depends
472        else:
473            self.depends = ('.'.join(self.related),)
474        self.compute = self._compute_related
475        if self.inherited or not (self.readonly or field.readonly):
476            self.inverse = self._inverse_related
477        if field._description_searchable:
478            # allow searching on self only if the related field is searchable
479            self.search = self._search_related
480
481        # copy attributes from field to self (string, help, etc.)
482        for attr, prop in self.related_attrs:
483            if not getattr(self, attr):
484                setattr(self, attr, getattr(field, prop))
485
486        for attr, value in field.__dict__.items():
487            if not hasattr(self, attr) and model._valid_field_parameter(self, attr):
488                setattr(self, attr, value)
489
490        # special cases of inherited fields
491        if self.inherited:
492            if not self.states:
493                self.states = field.states
494            if field.required:
495                self.required = True
496            self._modules.update(field._modules)
497
498        if self._depends_context is not None:
499            self.depends_context = self._depends_context
500        else:
501            self.depends_context = field.depends_context
502
503    def traverse_related(self, record):
504        """ Traverse the fields of the related field `self` except for the last
505        one, and return it as a pair `(last_record, last_field)`. """
506        for name in self.related[:-1]:
507            record = first(record[name])
508        return record, self.related_field
509
510    def _compute_related(self, records):
511        """ Compute the related field ``self`` on ``records``. """
512        #
513        # Traverse fields one by one for all records, in order to take advantage
514        # of prefetching for each field access. In order to clarify the impact
515        # of the algorithm, consider traversing 'foo.bar' for records a1 and a2,
516        # where 'foo' is already present in cache for a1, a2. Initially, both a1
517        # and a2 are marked for prefetching. As the commented code below shows,
518        # traversing all fields one record at a time will fetch 'bar' one record
519        # at a time.
520        #
521        #       b1 = a1.foo         # mark b1 for prefetching
522        #       v1 = b1.bar         # fetch/compute bar for b1
523        #       b2 = a2.foo         # mark b2 for prefetching
524        #       v2 = b2.bar         # fetch/compute bar for b2
525        #
526        # On the other hand, traversing all records one field at a time ensures
527        # maximal prefetching for each field access.
528        #
529        #       b1 = a1.foo         # mark b1 for prefetching
530        #       b2 = a2.foo         # mark b2 for prefetching
531        #       v1 = b1.bar         # fetch/compute bar for b1, b2
532        #       v2 = b2.bar         # value already in cache
533        #
534        # This difference has a major impact on performance, in particular in
535        # the case where 'bar' is a computed field that takes advantage of batch
536        # computation.
537        #
538        values = list(records)
539        for name in self.related[:-1]:
540            try:
541                values = [first(value[name]) for value in values]
542            except AccessError as e:
543                description = records.env['ir.model']._get(records._name).name
544                raise AccessError(
545                    _("%(previous_message)s\n\nImplicitly accessed through '%(document_kind)s' (%(document_model)s).") % {
546                        'previous_message': e.args[0],
547                        'document_kind': description,
548                        'document_model': records._name,
549                    }
550                )
551        # assign final values to records
552        for record, value in zip(records, values):
553            record[self.name] = self._process_related(value[self.related_field.name])
554
555    def _process_related(self, value):
556        """No transformation by default, but allows override."""
557        return value
558
559    def _inverse_related(self, records):
560        """ Inverse the related field ``self`` on ``records``. """
561        # store record values, otherwise they may be lost by cache invalidation!
562        record_value = {record: record[self.name] for record in records}
563        for record in records:
564            target, field = self.traverse_related(record)
565            # update 'target' only if 'record' and 'target' are both real or
566            # both new (see `test_base_objects.py`, `test_basic`)
567            if target and bool(target.id) == bool(record.id):
568                target[field.name] = record_value[record]
569
570    def _search_related(self, records, operator, value):
571        """ Determine the domain to search on field ``self``. """
572        return [('.'.join(self.related), operator, value)]
573
574    # properties used by _setup_related_full() to copy values from related field
575    _related_comodel_name = property(attrgetter('comodel_name'))
576    _related_string = property(attrgetter('string'))
577    _related_help = property(attrgetter('help'))
578    _related_groups = property(attrgetter('groups'))
579    _related_group_operator = property(attrgetter('group_operator'))
580
581    @property
582    def base_field(self):
583        """ Return the base field of an inherited field, or ``self``. """
584        return self.inherited_field.base_field if self.inherited_field else self
585
586    #
587    # Company-dependent fields
588    #
589
590    def _default_company_dependent(self, model):
591        return model.env['ir.property']._get(self.name, self.model_name)
592
593    def _compute_company_dependent(self, records):
594        # read property as superuser, as the current user may not have access
595        Property = records.env['ir.property'].sudo()
596        values = Property._get_multi(self.name, self.model_name, records.ids)
597        for record in records:
598            record[self.name] = values.get(record.id)
599
600    def _inverse_company_dependent(self, records):
601        # update property as superuser, as the current user may not have access
602        Property = records.env['ir.property'].sudo()
603        values = {
604            record.id: self.convert_to_write(record[self.name], record)
605            for record in records
606        }
607        Property._set_multi(self.name, self.model_name, values)
608
609    def _search_company_dependent(self, records, operator, value):
610        Property = records.env['ir.property'].sudo()
611        return Property.search_multi(self.name, self.model_name, operator, value)
612
613    #
614    # Setup of field triggers
615    #
616
617    def resolve_depends(self, registry):
618        """ Return the dependencies of `self` as a collection of field tuples. """
619        Model0 = registry[self.model_name]
620
621        for dotnames in self.depends:
622            field_seq = []
623            model_name = self.model_name
624
625            for index, fname in enumerate(dotnames.split('.')):
626                Model = registry[model_name]
627                if Model0._transient and not Model._transient:
628                    # modifying fields on regular models should not trigger
629                    # recomputations of fields on transient models
630                    break
631
632                try:
633                    field = Model._fields[fname]
634                except KeyError:
635                    msg = "Field %s cannot find dependency %r on model %r."
636                    raise ValueError(msg % (self, fname, model_name))
637                if field is self and index:
638                    self.recursive = True
639
640                field_seq.append(field)
641
642                # do not make self trigger itself: for instance, a one2many
643                # field line_ids with domain [('foo', ...)] will have
644                # 'line_ids.foo' as a dependency
645                if not (field is self and not index):
646                    yield tuple(field_seq)
647
648                if field.type in ('one2many', 'many2many'):
649                    for inv_field in Model._field_inverses[field]:
650                        yield tuple(field_seq) + (inv_field,)
651
652                model_name = field.comodel_name
653
654    ############################################################################
655    #
656    # Field description
657    #
658
659    def get_description(self, env):
660        """ Return a dictionary that describes the field ``self``. """
661        desc = {'type': self.type}
662        for attr, prop in self.description_attrs:
663            value = getattr(self, prop)
664            if callable(value):
665                value = value(env)
666            if value is not None:
667                desc[attr] = value
668
669        return desc
670
671    # properties used by get_description()
672    _description_store = property(attrgetter('store'))
673    _description_manual = property(attrgetter('manual'))
674    _description_depends = property(attrgetter('depends'))
675    _description_related = property(attrgetter('related'))
676    _description_company_dependent = property(attrgetter('company_dependent'))
677    _description_readonly = property(attrgetter('readonly'))
678    _description_required = property(attrgetter('required'))
679    _description_states = property(attrgetter('states'))
680    _description_groups = property(attrgetter('groups'))
681    _description_change_default = property(attrgetter('change_default'))
682    _description_deprecated = property(attrgetter('deprecated'))
683    _description_group_operator = property(attrgetter('group_operator'))
684
685    @property
686    def _description_searchable(self):
687        return bool(self.store or self.search)
688
689    @property
690    def _description_sortable(self):
691        return (self.column_type and self.store) or (self.inherited and self.related_field._description_sortable)
692
693    def _description_string(self, env):
694        if self.string and env.lang:
695            model_name = self.base_field.model_name
696            field_string = env['ir.translation'].get_field_string(model_name)
697            return field_string.get(self.name) or self.string
698        return self.string
699
700    def _description_help(self, env):
701        if self.help and env.lang:
702            model_name = self.base_field.model_name
703            field_help = env['ir.translation'].get_field_help(model_name)
704            return field_help.get(self.name) or self.help
705        return self.help
706
707    def is_editable(self):
708        """ Return whether the field can be editable in a view. """
709        return not self.readonly or self.states and any(
710            'readonly' in item for items in self.states.values() for item in items
711        )
712
713    ############################################################################
714    #
715    # Conversion of values
716    #
717
718    def null(self, record):
719        """ Return the null value for this field in the record format. """
720        return False
721
722    def convert_to_column(self, value, record, values=None, validate=True):
723        """ Convert ``value`` from the ``write`` format to the SQL format. """
724        if value is None or value is False:
725            return None
726        return pycompat.to_text(value)
727
728    def convert_to_cache(self, value, record, validate=True):
729        """ Convert ``value`` to the cache format; ``value`` may come from an
730        assignment, or have the format of methods :meth:`BaseModel.read` or
731        :meth:`BaseModel.write`. If the value represents a recordset, it should
732        be added for prefetching on ``record``.
733
734        :param bool validate: when True, field-specific validation of ``value``
735            will be performed
736        """
737        return value
738
739    def convert_to_record(self, value, record):
740        """ Convert ``value`` from the cache format to the record format.
741        If the value represents a recordset, it should share the prefetching of
742        ``record``.
743        """
744        return False if value is None else value
745
746    def convert_to_record_multi(self, values, records):
747        """ Convert a list of values from the cache format to the record format.
748        Some field classes may override this method to add optimizations for
749        batch processing.
750        """
751        # spare the method lookup overhead
752        convert = self.convert_to_record
753        return [convert(value, records) for value in values]
754
755    def convert_to_read(self, value, record, use_name_get=True):
756        """ Convert ``value`` from the record format to the format returned by
757        method :meth:`BaseModel.read`.
758
759        :param bool use_name_get: when True, the value's display name will be
760            computed using :meth:`BaseModel.name_get`, if relevant for the field
761        """
762        return False if value is None else value
763
764    def convert_to_write(self, value, record):
765        """ Convert ``value`` from any format to the format of method
766        :meth:`BaseModel.write`.
767        """
768        cache_value = self.convert_to_cache(value, record, validate=False)
769        record_value = self.convert_to_record(cache_value, record)
770        return self.convert_to_read(record_value, record)
771
772    def convert_to_onchange(self, value, record, names):
773        """ Convert ``value`` from the record format to the format returned by
774        method :meth:`BaseModel.onchange`.
775
776        :param names: a tree of field names (for relational fields only)
777        """
778        return self.convert_to_read(value, record)
779
780    def convert_to_export(self, value, record):
781        """ Convert ``value`` from the record format to the export format. """
782        if not value:
783            return ''
784        return value
785
786    def convert_to_display_name(self, value, record):
787        """ Convert ``value`` from the record format to a suitable display name. """
788        return ustr(value)
789
790    ############################################################################
791    #
792    # Update database schema
793    #
794
795    def update_db(self, model, columns):
796        """ Update the database schema to implement this field.
797
798            :param model: an instance of the field's model
799            :param columns: a dict mapping column names to their configuration in database
800            :return: ``True`` if the field must be recomputed on existing rows
801        """
802        if not self.column_type:
803            return
804
805        column = columns.get(self.name)
806
807        # create/update the column, not null constraint; the index will be
808        # managed by registry.check_indexes()
809        self.update_db_column(model, column)
810        self.update_db_notnull(model, column)
811
812        # optimization for computing simple related fields like 'foo_id.bar'
813        if (
814            not column
815            and len(self.related or ()) == 2
816            and self.related_field.store and not self.related_field.compute
817            and not (self.related_field.type == 'binary' and self.related_field.attachment)
818            and self.related_field.type not in ('one2many', 'many2many')
819        ):
820            join_field = model._fields[self.related[0]]
821            if (
822                join_field.type == 'many2one'
823                and join_field.store and not join_field.compute
824            ):
825                model.pool.post_init(self.update_db_related, model)
826                # discard the "classical" computation
827                return False
828
829        return not column
830
831    def update_db_column(self, model, column):
832        """ Create/update the column corresponding to ``self``.
833
834            :param model: an instance of the field's model
835            :param column: the column's configuration (dict) if it exists, or ``None``
836        """
837        if not column:
838            # the column does not exist, create it
839            sql.create_column(model._cr, model._table, self.name, self.column_type[1], self.string)
840            return
841        if column['udt_name'] == self.column_type[0]:
842            return
843        if column['udt_name'] in self.column_cast_from:
844            sql.convert_column(model._cr, model._table, self.name, self.column_type[1])
845        else:
846            newname = (self.name + '_moved{}').format
847            i = 0
848            while sql.column_exists(model._cr, model._table, newname(i)):
849                i += 1
850            if column['is_nullable'] == 'NO':
851                sql.drop_not_null(model._cr, model._table, self.name)
852            sql.rename_column(model._cr, model._table, self.name, newname(i))
853            sql.create_column(model._cr, model._table, self.name, self.column_type[1], self.string)
854
855    def update_db_notnull(self, model, column):
856        """ Add or remove the NOT NULL constraint on ``self``.
857
858            :param model: an instance of the field's model
859            :param column: the column's configuration (dict) if it exists, or ``None``
860        """
861        has_notnull = column and column['is_nullable'] == 'NO'
862
863        if not column or (self.required and not has_notnull):
864            # the column is new or it becomes required; initialize its values
865            if model._table_has_rows():
866                model._init_column(self.name)
867
868        if self.required and not has_notnull:
869            # _init_column may delay computations in post-init phase
870            @model.pool.post_init
871            def add_not_null():
872                # flush values before adding NOT NULL constraint
873                model.flush([self.name])
874                model.pool.post_constraint(apply_required, model, self.name)
875
876        elif not self.required and has_notnull:
877            sql.drop_not_null(model._cr, model._table, self.name)
878
879    def update_db_related(self, model):
880        """ Compute a stored related field directly in SQL. """
881        comodel = model.env[self.related_field.model_name]
882        model.env.cr.execute("""
883            UPDATE "{model_table}" AS x
884            SET "{model_field}" = y."{comodel_field}"
885            FROM "{comodel_table}" AS y
886            WHERE x."{join_field}" = y.id
887        """.format(
888            model_table=model._table,
889            model_field=self.name,
890            comodel_table=comodel._table,
891            comodel_field=self.related[1],
892            join_field=self.related[0],
893        ))
894
895    ############################################################################
896    #
897    # Alternatively stored fields: if fields don't have a `column_type` (not
898    # stored as regular db columns) they go through a read/create/write
899    # protocol instead
900    #
901
902    def read(self, records):
903        """ Read the value of ``self`` on ``records``, and store it in cache. """
904        raise NotImplementedError("Method read() undefined on %s" % self)
905
906    def create(self, record_values):
907        """ Write the value of ``self`` on the given records, which have just
908        been created.
909
910        :param record_values: a list of pairs ``(record, value)``, where
911            ``value`` is in the format of method :meth:`BaseModel.write`
912        """
913        for record, value in record_values:
914            self.write(record, value)
915
916    def write(self, records, value):
917        """ Write the value of ``self`` on ``records``. This method must update
918        the cache and prepare database updates.
919
920        :param value: a value in any format
921        :return: the subset of `records` that have been modified
922        """
923        # discard recomputation of self on records
924        records.env.remove_to_compute(self, records)
925
926        # update the cache, and discard the records that are not modified
927        cache = records.env.cache
928        cache_value = self.convert_to_cache(value, records)
929        records = cache.get_records_different_from(records, self, cache_value)
930        if not records:
931            return records
932        cache.update(records, self, [cache_value] * len(records))
933
934        # update towrite
935        if self.store:
936            towrite = records.env.all.towrite[self.model_name]
937            record = records[:1]
938            write_value = self.convert_to_write(cache_value, record)
939            column_value = self.convert_to_column(write_value, record)
940            for record in records.filtered('id'):
941                towrite[record.id][self.name] = column_value
942
943        return records
944
945    ############################################################################
946    #
947    # Descriptor methods
948    #
949
950    def __get__(self, record, owner):
951        """ return the value of field ``self`` on ``record`` """
952        if record is None:
953            return self         # the field is accessed through the owner class
954
955        if not record._ids:
956            # null record -> return the null value for this field
957            value = self.convert_to_cache(False, record, validate=False)
958            return self.convert_to_record(value, record)
959
960        env = record.env
961
962        # only a single record may be accessed
963        record.ensure_one()
964
965        if self.compute and self.store:
966            # process pending computations
967            self.recompute(record)
968
969        try:
970            value = env.cache.get(record, self)
971
972        except KeyError:
973            # behavior in case of cache miss:
974            #
975            #   on a real record:
976            #       stored -> fetch from database (computation done above)
977            #       not stored and computed -> compute
978            #       not stored and not computed -> default
979            #
980            #   on a new record w/ origin:
981            #       stored and not (computed and readonly) -> fetch from origin
982            #       stored and computed and readonly -> compute
983            #       not stored and computed -> compute
984            #       not stored and not computed -> default
985            #
986            #   on a new record w/o origin:
987            #       stored and computed -> compute
988            #       stored and not computed -> new delegate or default
989            #       not stored and computed -> compute
990            #       not stored and not computed -> default
991            #
992            if self.store and record.id:
993                # real record: fetch from database
994                recs = record._in_cache_without(self)
995                try:
996                    recs._fetch_field(self)
997                except AccessError:
998                    record._fetch_field(self)
999                if not env.cache.contains(record, self) and not record.exists():
1000                    raise MissingError("\n".join([
1001                        _("Record does not exist or has been deleted."),
1002                        _("(Record: %s, User: %s)") % (record, env.uid),
1003                    ]))
1004                value = env.cache.get(record, self)
1005
1006            elif self.store and record._origin and not (self.compute and self.readonly):
1007                # new record with origin: fetch from origin
1008                value = self.convert_to_cache(record._origin[self.name], record)
1009                env.cache.set(record, self, value)
1010
1011            elif self.compute:
1012                # non-stored field or new record without origin: compute
1013                if env.is_protected(self, record):
1014                    value = self.convert_to_cache(False, record, validate=False)
1015                    env.cache.set(record, self, value)
1016                else:
1017                    recs = record if self.recursive else record._in_cache_without(self)
1018                    try:
1019                        self.compute_value(recs)
1020                    except (AccessError, MissingError):
1021                        self.compute_value(record)
1022                    try:
1023                        value = env.cache.get(record, self)
1024                    except CacheMiss:
1025                        if self.readonly and not self.store:
1026                            raise ValueError("Compute method failed to assign %s.%s" % (record, self.name))
1027                        # fallback to null value if compute gives nothing
1028                        value = self.convert_to_cache(False, record, validate=False)
1029                        env.cache.set(record, self, value)
1030
1031            elif self.type == 'many2one' and self.delegate and not record.id:
1032                # parent record of a new record: new record, with the same
1033                # values as record for the corresponding inherited fields
1034                def is_inherited_field(name):
1035                    field = record._fields[name]
1036                    return field.inherited and field.related[0] == self.name
1037
1038                parent = record.env[self.comodel_name].new({
1039                    name: value
1040                    for name, value in record._cache.items()
1041                    if is_inherited_field(name)
1042                })
1043                value = self.convert_to_cache(parent, record)
1044                env.cache.set(record, self, value)
1045
1046            else:
1047                # non-stored field or stored field on new record: default value
1048                value = self.convert_to_cache(False, record, validate=False)
1049                env.cache.set(record, self, value)
1050                defaults = record.default_get([self.name])
1051                if self.name in defaults:
1052                    # The null value above is necessary to convert x2many field
1053                    # values. For instance, converting [(4, id)] accesses the
1054                    # field's current value, then adds the given id. Without an
1055                    # initial value, the conversion ends up here to determine
1056                    # the field's value, and generates an infinite recursion.
1057                    value = self.convert_to_cache(defaults[self.name], record)
1058                    env.cache.set(record, self, value)
1059
1060        return self.convert_to_record(value, record)
1061
1062    def mapped(self, records):
1063        """ Return the values of ``self`` for ``records``, either as a list
1064        (scalar fields), or as a recordset (relational fields).
1065
1066        This method is meant to be used internally and has very little benefit
1067        over a simple call to `~odoo.models.BaseModel.mapped()` on a recordset.
1068        """
1069        if self.name == 'id':
1070            # not stored in cache
1071            return list(records._ids)
1072
1073        if self.compute and self.store:
1074            # process pending computations
1075            self.recompute(records)
1076
1077        # retrieve values in cache, and fetch missing ones
1078        vals = records.env.cache.get_until_miss(records, self)
1079        while len(vals) < len(records):
1080            # It is important to construct a 'remaining' recordset with the
1081            # _prefetch_ids of the original recordset, in order to prefetch as
1082            # many records as possible. If not done this way, scenarios such as
1083            # [rec.line_ids.mapped('name') for rec in recs] would generate one
1084            # query per record in `recs`!
1085            remaining = records._browse(records.env, records[len(vals):]._ids, records._prefetch_ids)
1086            self.__get__(first(remaining), type(remaining))
1087            vals += records.env.cache.get_until_miss(remaining, self)
1088
1089        return self.convert_to_record_multi(vals, records)
1090
1091    def __set__(self, records, value):
1092        """ set the value of field ``self`` on ``records`` """
1093        protected_ids = []
1094        new_ids = []
1095        other_ids = []
1096        for record_id in records._ids:
1097            if record_id in records.env._protected.get(self, ()):
1098                protected_ids.append(record_id)
1099            elif not record_id:
1100                new_ids.append(record_id)
1101            else:
1102                other_ids.append(record_id)
1103
1104        if protected_ids:
1105            # records being computed: no business logic, no recomputation
1106            protected_records = records.browse(protected_ids)
1107            self.write(protected_records, value)
1108
1109        if new_ids:
1110            # new records: no business logic
1111            new_records = records.browse(new_ids)
1112            with records.env.protecting(records.pool.field_computed.get(self, [self]), records):
1113                if self.relational:
1114                    new_records.modified([self.name], before=True)
1115                self.write(new_records, value)
1116                new_records.modified([self.name])
1117
1118            if self.inherited:
1119                # special case: also assign parent records if they are new
1120                parents = records[self.related[0]]
1121                parents.filtered(lambda r: not r.id)[self.name] = value
1122
1123        if other_ids:
1124            # base case: full business logic
1125            records = records.browse(other_ids)
1126            write_value = self.convert_to_write(value, records)
1127            records.write({self.name: write_value})
1128
1129    ############################################################################
1130    #
1131    # Computation of field values
1132    #
1133
1134    def recompute(self, records):
1135        """ Process the pending computations of ``self`` on ``records``. This
1136        should be called only if ``self`` is computed and stored.
1137        """
1138        to_compute_ids = records.env.all.tocompute.get(self)
1139        if not to_compute_ids:
1140            return
1141
1142        if self.recursive:
1143            for record in records:
1144                if record.id in to_compute_ids:
1145                    self.compute_value(record)
1146            return
1147
1148        for record in records:
1149            if record.id in to_compute_ids:
1150                ids = expand_ids(record.id, to_compute_ids)
1151                recs = record.browse(itertools.islice(ids, PREFETCH_MAX))
1152                try:
1153                    self.compute_value(recs)
1154                except (AccessError, MissingError):
1155                    self.compute_value(record)
1156
1157    def compute_value(self, records):
1158        """ Invoke the compute method on ``records``; the results are in cache. """
1159        env = records.env
1160        if self.compute_sudo:
1161            records = records.sudo()
1162        fields = records.pool.field_computed[self]
1163
1164        # Just in case the compute method does not assign a value, we already
1165        # mark the computation as done. This is also necessary if the compute
1166        # method accesses the old value of the field: the field will be fetched
1167        # with _read(), which will flush() it. If the field is still to compute,
1168        # the latter flush() will recursively compute this field!
1169        for field in fields:
1170            if field.store:
1171                env.remove_to_compute(field, records)
1172
1173        try:
1174            with records.env.protecting(fields, records):
1175                records._compute_field_value(self)
1176        except Exception:
1177            for field in fields:
1178                if field.store:
1179                    env.add_to_compute(field, records)
1180            raise
1181
1182    def determine_inverse(self, records):
1183        """ Given the value of ``self`` on ``records``, inverse the computation. """
1184        if isinstance(self.inverse, str):
1185            getattr(records, self.inverse)()
1186        else:
1187            self.inverse(records)
1188
1189    def determine_domain(self, records, operator, value):
1190        """ Return a domain representing a condition on ``self``. """
1191        if isinstance(self.search, str):
1192            return getattr(records, self.search)(operator, value)
1193        else:
1194            return self.search(records, operator, value)
1195
1196    ############################################################################
1197    #
1198    # Notification when fields are modified
1199    #
1200
1201
1202class Boolean(Field):
1203    """ Encapsulates a :class:`bool`. """
1204    type = 'boolean'
1205    column_type = ('bool', 'bool')
1206
1207    def convert_to_column(self, value, record, values=None, validate=True):
1208        return bool(value)
1209
1210    def convert_to_cache(self, value, record, validate=True):
1211        return bool(value)
1212
1213    def convert_to_export(self, value, record):
1214        return value
1215
1216
1217class Integer(Field):
1218    """ Encapsulates an :class:`int`. """
1219    type = 'integer'
1220    column_type = ('int4', 'int4')
1221
1222    group_operator = 'sum'
1223
1224    def convert_to_column(self, value, record, values=None, validate=True):
1225        return int(value or 0)
1226
1227    def convert_to_cache(self, value, record, validate=True):
1228        if isinstance(value, dict):
1229            # special case, when an integer field is used as inverse for a one2many
1230            return value.get('id', None)
1231        return int(value or 0)
1232
1233    def convert_to_record(self, value, record):
1234        return value or 0
1235
1236    def convert_to_read(self, value, record, use_name_get=True):
1237        # Integer values greater than 2^31-1 are not supported in pure XMLRPC,
1238        # so we have to pass them as floats :-(
1239        if value and value > MAXINT:
1240            return float(value)
1241        return value
1242
1243    def _update(self, records, value):
1244        # special case, when an integer field is used as inverse for a one2many
1245        cache = records.env.cache
1246        for record in records:
1247            cache.set(record, self, value.id or 0)
1248
1249    def convert_to_export(self, value, record):
1250        if value or value == 0:
1251            return value
1252        return ''
1253
1254
1255class Float(Field):
1256    """ Encapsulates a :class:`float`.
1257
1258    The precision digits are given by the (optional) ``digits`` attribute.
1259
1260    :param digits: a pair (total, decimal) or a string referencing a
1261        :class:`~odoo.addons.base.models.decimal_precision.DecimalPrecision` record name.
1262    :type digits: tuple(int,int) or str
1263
1264    When a float is a quantity associated with an unit of measure, it is important
1265    to use the right tool to compare or round values with the correct precision.
1266
1267    The Float class provides some static methods for this purpose:
1268
1269    :func:`~odoo.fields.Float.round()` to round a float with the given precision.
1270    :func:`~odoo.fields.Float.is_zero()` to check if a float equals zero at the given precision.
1271    :func:`~odoo.fields.Float.compare()` to compare two floats at the given precision.
1272
1273    .. admonition:: Example
1274
1275        To round a quantity with the precision of the unit of mesure::
1276
1277            fields.Float.round(self.product_uom_qty, precision_rounding=self.product_uom_id.rounding)
1278
1279        To check if the quantity is zero with the precision of the unit of mesure::
1280
1281            fields.Float.is_zero(self.product_uom_qty, precision_rounding=self.product_uom_id.rounding)
1282
1283        To compare two quantities::
1284
1285            field.Float.compare(self.product_uom_qty, self.qty_done, precision_rounding=self.product_uom_id.rounding)
1286
1287        The compare helper uses the __cmp__ semantics for historic purposes, therefore
1288        the proper, idiomatic way to use this helper is like so:
1289
1290            if result == 0, the first and second floats are equal
1291            if result < 0, the first float is lower than the second
1292            if result > 0, the first float is greater than the second
1293    """
1294
1295    type = 'float'
1296    column_cast_from = ('int4', 'numeric', 'float8')
1297
1298    _digits = None                      # digits argument passed to class initializer
1299    group_operator = 'sum'
1300
1301    def __init__(self, string=Default, digits=Default, **kwargs):
1302        super(Float, self).__init__(string=string, _digits=digits, **kwargs)
1303
1304    @property
1305    def column_type(self):
1306        # Explicit support for "falsy" digits (0, False) to indicate a NUMERIC
1307        # field with no fixed precision. The values are saved in the database
1308        # with all significant digits.
1309        # FLOAT8 type is still the default when there is no precision because it
1310        # is faster for most operations (sums, etc.)
1311        return ('numeric', 'numeric') if self._digits is not None else \
1312               ('float8', 'double precision')
1313
1314    def get_digits(self, env):
1315        if isinstance(self._digits, str):
1316            precision = env['decimal.precision'].precision_get(self._digits)
1317            return 16, precision
1318        else:
1319            return self._digits
1320
1321    _related__digits = property(attrgetter('_digits'))
1322
1323    def _description_digits(self, env):
1324        return self.get_digits(env)
1325
1326    def convert_to_column(self, value, record, values=None, validate=True):
1327        result = float(value or 0.0)
1328        digits = self.get_digits(record.env)
1329        if digits:
1330            precision, scale = digits
1331            result = float_repr(float_round(result, precision_digits=scale), precision_digits=scale)
1332        return result
1333
1334    def convert_to_cache(self, value, record, validate=True):
1335        # apply rounding here, otherwise value in cache may be wrong!
1336        value = float(value or 0.0)
1337        if not validate:
1338            return value
1339        digits = self.get_digits(record.env)
1340        return float_round(value, precision_digits=digits[1]) if digits else value
1341
1342    def convert_to_record(self, value, record):
1343        return value or 0.0
1344
1345    def convert_to_export(self, value, record):
1346        if value or value == 0.0:
1347            return value
1348        return ''
1349
1350    round = staticmethod(float_round)
1351    is_zero = staticmethod(float_is_zero)
1352    compare = staticmethod(float_compare)
1353
1354
1355class Monetary(Field):
1356    """ Encapsulates a :class:`float` expressed in a given
1357    :class:`res_currency<odoo.addons.base.models.res_currency.Currency>`.
1358
1359    The decimal precision and currency symbol are taken from the ``currency_field`` attribute.
1360
1361    :param str currency_field: name of the :class:`Many2one` field
1362        holding the :class:`res_currency <odoo.addons.base.models.res_currency.Currency>`
1363        this monetary field is expressed in (default: `\'currency_id\'`)
1364    """
1365    type = 'monetary'
1366    column_type = ('numeric', 'numeric')
1367    column_cast_from = ('float8',)
1368
1369    currency_field = None
1370    group_operator = 'sum'
1371
1372    def __init__(self, string=Default, currency_field=Default, **kwargs):
1373        super(Monetary, self).__init__(string=string, currency_field=currency_field, **kwargs)
1374
1375    _description_currency_field = property(attrgetter('currency_field'))
1376
1377    def _setup_currency_field(self, model):
1378        if not self.currency_field:
1379            # pick a default, trying in order: 'currency_id', 'x_currency_id'
1380            if 'currency_id' in model._fields:
1381                self.currency_field = 'currency_id'
1382            elif 'x_currency_id' in model._fields:
1383                self.currency_field = 'x_currency_id'
1384        assert self.currency_field in model._fields, \
1385            "Field %s with unknown currency_field %r" % (self, self.currency_field)
1386
1387    def _setup_regular_full(self, model):
1388        super(Monetary, self)._setup_regular_full(model)
1389        self._setup_currency_field(model)
1390
1391    def _setup_related_full(self, model):
1392        super(Monetary, self)._setup_related_full(model)
1393        if self.inherited:
1394            self.currency_field = self.related_field.currency_field
1395        self._setup_currency_field(model)
1396
1397    def convert_to_column(self, value, record, values=None, validate=True):
1398        # retrieve currency from values or record
1399        if values and self.currency_field in values:
1400            field = record._fields[self.currency_field]
1401            currency = field.convert_to_cache(values[self.currency_field], record, validate)
1402            currency = field.convert_to_record(currency, record)
1403        else:
1404            # Note: this is wrong if 'record' is several records with different
1405            # currencies, which is functional nonsense and should not happen
1406            # BEWARE: do not prefetch other fields, because 'value' may be in
1407            # cache, and would be overridden by the value read from database!
1408            currency = record[:1].with_context(prefetch_fields=False)[self.currency_field]
1409
1410        value = float(value or 0.0)
1411        if currency:
1412            return float_repr(currency.round(value), currency.decimal_places)
1413        return value
1414
1415    def convert_to_cache(self, value, record, validate=True):
1416        # cache format: float
1417        value = float(value or 0.0)
1418        if value and validate:
1419            # FIXME @rco-odoo: currency may not be already initialized if it is
1420            # a function or related field!
1421            # BEWARE: do not prefetch other fields, because 'value' may be in
1422            # cache, and would be overridden by the value read from database!
1423            currency = record.sudo().with_context(prefetch_fields=False)[self.currency_field]
1424            if len(currency) > 1:
1425                raise ValueError("Got multiple currencies while assigning values of monetary field %s" % str(self))
1426            elif currency:
1427                value = currency.round(value)
1428        return value
1429
1430    def convert_to_record(self, value, record):
1431        return value or 0.0
1432
1433    def convert_to_read(self, value, record, use_name_get=True):
1434        return value
1435
1436    def convert_to_write(self, value, record):
1437        return value
1438
1439
1440class _String(Field):
1441    """ Abstract class for string fields. """
1442    translate = False                   # whether the field is translated
1443    prefetch = None
1444
1445    def __init__(self, string=Default, **kwargs):
1446        # translate is either True, False, or a callable
1447        if 'translate' in kwargs and not callable(kwargs['translate']):
1448            kwargs['translate'] = bool(kwargs['translate'])
1449        super(_String, self).__init__(string=string, **kwargs)
1450
1451    def _setup_attrs(self, model, name):
1452        super()._setup_attrs(model, name)
1453        if self.prefetch is None:
1454            # do not prefetch complex translated fields by default
1455            self.prefetch = not callable(self.translate)
1456
1457    _related_translate = property(attrgetter('translate'))
1458
1459    def _description_translate(self, env):
1460        return bool(self.translate)
1461
1462    def get_trans_terms(self, value):
1463        """ Return the sequence of terms to translate found in `value`. """
1464        if not callable(self.translate):
1465            return [value] if value else []
1466        terms = []
1467        self.translate(terms.append, value)
1468        return terms
1469
1470    def get_trans_func(self, records):
1471        """ Return a translation function `translate` for `self` on the given
1472        records; the function call `translate(record_id, value)` translates the
1473        field value to the language given by the environment of `records`.
1474        """
1475        if callable(self.translate):
1476            rec_src_trans = records.env['ir.translation']._get_terms_translations(self, records)
1477
1478            def translate(record_id, value):
1479                src_trans = rec_src_trans[record_id]
1480                return self.translate(src_trans.get, value)
1481
1482        else:
1483            rec_trans = records.env['ir.translation']._get_ids(
1484                '%s,%s' % (self.model_name, self.name), 'model', records.env.lang, records.ids)
1485
1486            def translate(record_id, value):
1487                return rec_trans.get(record_id) or value
1488
1489        return translate
1490
1491    def check_trans_value(self, value):
1492        """ Check and possibly sanitize the translated term `value`. """
1493        if callable(self.translate):
1494            # do a "no-translation" to sanitize the value
1495            callback = lambda term: None
1496            return self.translate(callback, value)
1497        else:
1498            return value
1499
1500    def write(self, records, value):
1501        # discard recomputation of self on records
1502        records.env.remove_to_compute(self, records)
1503
1504        # update the cache, and discard the records that are not modified
1505        cache = records.env.cache
1506        cache_value = self.convert_to_cache(value, records)
1507        records = cache.get_records_different_from(records, self, cache_value)
1508        if not records:
1509            return records
1510        cache.update(records, self, [cache_value] * len(records))
1511
1512        if not self.store:
1513            return records
1514
1515        real_recs = records.filtered('id')
1516        if not real_recs._ids:
1517            return records
1518
1519        update_column = True
1520        update_trans = False
1521        single_lang = len(records.env['res.lang'].get_installed()) <= 1
1522        if self.translate:
1523            lang = records.env.lang or None  # used in _update_translations below
1524            if single_lang:
1525                # a single language is installed
1526                update_trans = True
1527            elif callable(self.translate) or lang == 'en_US':
1528                # update the source and synchronize translations
1529                update_column = True
1530                update_trans = True
1531            elif lang != 'en_US' and lang is not None:
1532                # update the translations only except if emptying
1533                update_column = not cache_value
1534                update_trans = True
1535            # else: lang = None
1536
1537        # update towrite if modifying the source
1538        if update_column:
1539            towrite = records.env.all.towrite[self.model_name]
1540            for rid in real_recs._ids:
1541                # cache_value is already in database format
1542                towrite[rid][self.name] = cache_value
1543            if self.translate is True and cache_value:
1544                tname = "%s,%s" % (records._name, self.name)
1545                records.env['ir.translation']._set_source(tname, real_recs._ids, value)
1546            if self.translate:
1547                # invalidate the field in the other languages
1548                cache.invalidate([(self, records.ids)])
1549                cache.update(records, self, [cache_value] * len(records))
1550
1551        if update_trans:
1552            if callable(self.translate):
1553                # the source value of self has been updated, synchronize
1554                # translated terms when possible
1555                records.env['ir.translation']._sync_terms_translations(self, real_recs)
1556
1557            else:
1558                # update translations
1559                value = self.convert_to_column(value, records)
1560                source_recs = real_recs.with_context(lang=None)
1561                source_value = first(source_recs)[self.name]
1562                if not source_value:
1563                    source_recs[self.name] = value
1564                    source_value = value
1565                tname = "%s,%s" % (self.model_name, self.name)
1566                if not value:
1567                    records.env['ir.translation'].search([
1568                        ('name', '=', tname),
1569                        ('type', '=', 'model'),
1570                        ('res_id', 'in', real_recs._ids)
1571                    ]).unlink()
1572                elif single_lang:
1573                    records.env['ir.translation']._update_translations([dict(
1574                        src=source_value,
1575                        value=value,
1576                        name=tname,
1577                        lang=lang,
1578                        type='model',
1579                        state='translated',
1580                        res_id=res_id) for res_id in real_recs._ids])
1581                else:
1582                    records.env['ir.translation']._set_ids(
1583                        tname, 'model', lang, real_recs._ids, value, source_value,
1584                    )
1585
1586        return records
1587
1588
1589class Char(_String):
1590    """ Basic string field, can be length-limited, usually displayed as a
1591    single-line string in clients.
1592
1593    :param int size: the maximum size of values stored for that field
1594
1595    :param bool trim: states whether the value is trimmed or not (by default,
1596        ``True``). Note that the trim operation is applied only by the web client.
1597
1598    :param translate: enable the translation of the field's values; use
1599        ``translate=True`` to translate field values as a whole; ``translate``
1600        may also be a callable such that ``translate(callback, value)``
1601        translates ``value`` by using ``callback(term)`` to retrieve the
1602        translation of terms.
1603    :type translate: bool or callable
1604    """
1605    type = 'char'
1606    column_cast_from = ('text',)
1607
1608    size = None                         # maximum size of values (deprecated)
1609    trim = True                         # whether value is trimmed (only by web client)
1610
1611    @property
1612    def column_type(self):
1613        return ('varchar', pg_varchar(self.size))
1614
1615    def update_db_column(self, model, column):
1616        if (
1617            column and column['udt_name'] == 'varchar' and column['character_maximum_length'] and
1618            (self.size is None or column['character_maximum_length'] < self.size)
1619        ):
1620            # the column's varchar size does not match self.size; convert it
1621            sql.convert_column(model._cr, model._table, self.name, self.column_type[1])
1622        super(Char, self).update_db_column(model, column)
1623
1624    _related_size = property(attrgetter('size'))
1625    _related_trim = property(attrgetter('trim'))
1626    _description_size = property(attrgetter('size'))
1627    _description_trim = property(attrgetter('trim'))
1628
1629    def _setup_regular_base(self, model):
1630        super(Char, self)._setup_regular_base(model)
1631        assert self.size is None or isinstance(self.size, int), \
1632            "Char field %s with non-integer size %r" % (self, self.size)
1633
1634    def convert_to_column(self, value, record, values=None, validate=True):
1635        if value is None or value is False:
1636            return None
1637        # we need to convert the string to a unicode object to be able
1638        # to evaluate its length (and possibly truncate it) reliably
1639        return pycompat.to_text(value)[:self.size]
1640
1641    def convert_to_cache(self, value, record, validate=True):
1642        if value is None or value is False:
1643            return None
1644        return pycompat.to_text(value)[:self.size]
1645
1646
1647class Text(_String):
1648    """ Very similar to :class:`Char` but used for longer contents, does not
1649    have a size and usually displayed as a multiline text box.
1650
1651    :param translate: enable the translation of the field's values; use
1652        ``translate=True`` to translate field values as a whole; ``translate``
1653        may also be a callable such that ``translate(callback, value)``
1654        translates ``value`` by using ``callback(term)`` to retrieve the
1655        translation of terms.
1656    :type translate: bool or callable
1657    """
1658    type = 'text'
1659    column_type = ('text', 'text')
1660    column_cast_from = ('varchar',)
1661
1662    def convert_to_cache(self, value, record, validate=True):
1663        if value is None or value is False:
1664            return None
1665        return ustr(value)
1666
1667
1668class Html(_String):
1669    """ Encapsulates an html code content.
1670
1671    :param bool sanitize: whether value must be sanitized (default: ``True``)
1672    :param bool sanitize_tags: whether to sanitize tags
1673        (only a white list of attributes is accepted, default: ``True``)
1674    :param bool sanitize_attributes: whether to sanitize attributes
1675        (only a white list of attributes is accepted, default: ``True``)
1676    :param bool sanitize_style: whether to sanitize style attributes (default: ``False``)
1677    :param bool strip_style: whether to strip style attributes
1678        (removed and therefore not sanitized, default: ``False``)
1679    :param bool strip_classes: whether to strip classes attributes (default: ``False``)
1680    """
1681    type = 'html'
1682    column_type = ('text', 'text')
1683
1684    sanitize = True                     # whether value must be sanitized
1685    sanitize_tags = True                # whether to sanitize tags (only a white list of attributes is accepted)
1686    sanitize_attributes = True          # whether to sanitize attributes (only a white list of attributes is accepted)
1687    sanitize_style = False              # whether to sanitize style attributes
1688    sanitize_form = True                # whether to sanitize forms
1689    strip_style = False                 # whether to strip style attributes (removed and therefore not sanitized)
1690    strip_classes = False               # whether to strip classes attributes
1691
1692    def _get_attrs(self, model, name):
1693        # called by _setup_attrs(), working together with _String._setup_attrs()
1694        attrs = super()._get_attrs(model, name)
1695        # Translated sanitized html fields must use html_translate or a callable.
1696        if attrs.get('translate') is True and attrs.get('sanitize', True):
1697            attrs['translate'] = html_translate
1698        return attrs
1699
1700    _related_sanitize = property(attrgetter('sanitize'))
1701    _related_sanitize_tags = property(attrgetter('sanitize_tags'))
1702    _related_sanitize_attributes = property(attrgetter('sanitize_attributes'))
1703    _related_sanitize_style = property(attrgetter('sanitize_style'))
1704    _related_strip_style = property(attrgetter('strip_style'))
1705    _related_strip_classes = property(attrgetter('strip_classes'))
1706
1707    _description_sanitize = property(attrgetter('sanitize'))
1708    _description_sanitize_tags = property(attrgetter('sanitize_tags'))
1709    _description_sanitize_attributes = property(attrgetter('sanitize_attributes'))
1710    _description_sanitize_style = property(attrgetter('sanitize_style'))
1711    _description_strip_style = property(attrgetter('strip_style'))
1712    _description_strip_classes = property(attrgetter('strip_classes'))
1713
1714    def convert_to_column(self, value, record, values=None, validate=True):
1715        if value is None or value is False:
1716            return None
1717        if self.sanitize:
1718            return html_sanitize(
1719                value, silent=True,
1720                sanitize_tags=self.sanitize_tags,
1721                sanitize_attributes=self.sanitize_attributes,
1722                sanitize_style=self.sanitize_style,
1723                sanitize_form=self.sanitize_form,
1724                strip_style=self.strip_style,
1725                strip_classes=self.strip_classes)
1726        return value
1727
1728    def convert_to_cache(self, value, record, validate=True):
1729        if value is None or value is False:
1730            return None
1731        if validate and self.sanitize:
1732            return html_sanitize(
1733                value, silent=True,
1734                sanitize_tags=self.sanitize_tags,
1735                sanitize_attributes=self.sanitize_attributes,
1736                sanitize_style=self.sanitize_style,
1737                sanitize_form=self.sanitize_form,
1738                strip_style=self.strip_style,
1739                strip_classes=self.strip_classes)
1740        return value
1741
1742
1743class Date(Field):
1744    """ Encapsulates a python :class:`date <datetime.date>` object. """
1745    type = 'date'
1746    column_type = ('date', 'date')
1747    column_cast_from = ('timestamp',)
1748
1749    start_of = staticmethod(date_utils.start_of)
1750    end_of = staticmethod(date_utils.end_of)
1751    add = staticmethod(date_utils.add)
1752    subtract = staticmethod(date_utils.subtract)
1753
1754    @staticmethod
1755    def today(*args):
1756        """Return the current day in the format expected by the ORM.
1757
1758        .. note:: This function may be used to compute default values.
1759        """
1760        return date.today()
1761
1762    @staticmethod
1763    def context_today(record, timestamp=None):
1764        """Return the current date as seen in the client's timezone in a format
1765        fit for date fields.
1766
1767        .. note:: This method may be used to compute default values.
1768
1769        :param record: recordset from which the timezone will be obtained.
1770        :param datetime timestamp: optional datetime value to use instead of
1771            the current date and time (must be a datetime, regular dates
1772            can't be converted between timezones).
1773        :rtype: date
1774        """
1775        today = timestamp or datetime.now()
1776        context_today = None
1777        tz_name = record._context.get('tz') or record.env.user.tz
1778        if tz_name:
1779            try:
1780                today_utc = pytz.timezone('UTC').localize(today, is_dst=False)  # UTC = no DST
1781                context_today = today_utc.astimezone(pytz.timezone(tz_name))
1782            except Exception:
1783                _logger.debug("failed to compute context/client-specific today date, using UTC value for `today`",
1784                              exc_info=True)
1785        return (context_today or today).date()
1786
1787    @staticmethod
1788    def to_date(value):
1789        """Attempt to convert ``value`` to a :class:`date` object.
1790
1791        .. warning::
1792
1793            If a datetime object is given as value,
1794            it will be converted to a date object and all
1795            datetime-specific information will be lost (HMS, TZ, ...).
1796
1797        :param value: value to convert.
1798        :type value: str or date or datetime
1799        :return: an object representing ``value``.
1800        :rtype: date or None
1801        """
1802        if not value:
1803            return None
1804        if isinstance(value, date):
1805            if isinstance(value, datetime):
1806                return value.date()
1807            return value
1808        value = value[:DATE_LENGTH]
1809        return datetime.strptime(value, DATE_FORMAT).date()
1810
1811    # kept for backwards compatibility, but consider `from_string` as deprecated, will probably
1812    # be removed after V12
1813    from_string = to_date
1814
1815    @staticmethod
1816    def to_string(value):
1817        """
1818        Convert a :class:`date` or :class:`datetime` object to a string.
1819
1820        :param value: value to convert.
1821        :return: a string representing ``value`` in the server's date format, if ``value`` is of
1822            type :class:`datetime`, the hours, minute, seconds, tzinfo will be truncated.
1823        :rtype: str
1824        """
1825        return value.strftime(DATE_FORMAT) if value else False
1826
1827    def convert_to_cache(self, value, record, validate=True):
1828        if not value:
1829            return None
1830        if isinstance(value, datetime):
1831            # TODO: better fix data files (crm demo data)
1832            value = value.date()
1833            # raise TypeError("%s (field %s) must be string or date, not datetime." % (value, self))
1834        return self.to_date(value)
1835
1836    def convert_to_export(self, value, record):
1837        if not value:
1838            return ''
1839        return self.from_string(value)
1840
1841
1842class Datetime(Field):
1843    """ Encapsulates a python :class:`datetime <datetime.datetime>` object. """
1844    type = 'datetime'
1845    column_type = ('timestamp', 'timestamp')
1846    column_cast_from = ('date',)
1847
1848    start_of = staticmethod(date_utils.start_of)
1849    end_of = staticmethod(date_utils.end_of)
1850    add = staticmethod(date_utils.add)
1851    subtract = staticmethod(date_utils.subtract)
1852
1853    @staticmethod
1854    def now(*args):
1855        """Return the current day and time in the format expected by the ORM.
1856
1857        .. note:: This function may be used to compute default values.
1858        """
1859        # microseconds must be annihilated as they don't comply with the server datetime format
1860        return datetime.now().replace(microsecond=0)
1861
1862    @staticmethod
1863    def today(*args):
1864        """Return the current day, at midnight (00:00:00)."""
1865        return Datetime.now().replace(hour=0, minute=0, second=0)
1866
1867    @staticmethod
1868    def context_timestamp(record, timestamp):
1869        """Return the given timestamp converted to the client's timezone.
1870
1871        .. note:: This method is *not* meant for use as a default initializer,
1872            because datetime fields are automatically converted upon
1873            display on client side. For default values, :meth:`now`
1874            should be used instead.
1875
1876        :param record: recordset from which the timezone will be obtained.
1877        :param datetime timestamp: naive datetime value (expressed in UTC)
1878            to be converted to the client timezone.
1879        :return: timestamp converted to timezone-aware datetime in context timezone.
1880        :rtype: datetime
1881        """
1882        assert isinstance(timestamp, datetime), 'Datetime instance expected'
1883        tz_name = record._context.get('tz') or record.env.user.tz
1884        utc_timestamp = pytz.utc.localize(timestamp, is_dst=False)  # UTC = no DST
1885        if tz_name:
1886            try:
1887                context_tz = pytz.timezone(tz_name)
1888                return utc_timestamp.astimezone(context_tz)
1889            except Exception:
1890                _logger.debug("failed to compute context/client-specific timestamp, "
1891                              "using the UTC value",
1892                              exc_info=True)
1893        return utc_timestamp
1894
1895    @staticmethod
1896    def to_datetime(value):
1897        """Convert an ORM ``value`` into a :class:`datetime` value.
1898
1899        :param value: value to convert.
1900        :type value: str or date or datetime
1901        :return: an object representing ``value``.
1902        :rtype: datetime or None
1903        """
1904        if not value:
1905            return None
1906        if isinstance(value, date):
1907            if isinstance(value, datetime):
1908                if value.tzinfo:
1909                    raise ValueError("Datetime field expects a naive datetime: %s" % value)
1910                return value
1911            return datetime.combine(value, time.min)
1912
1913        # TODO: fix data files
1914        return datetime.strptime(value, DATETIME_FORMAT[:len(value)-2])
1915
1916    # kept for backwards compatibility, but consider `from_string` as deprecated, will probably
1917    # be removed after V12
1918    from_string = to_datetime
1919
1920    @staticmethod
1921    def to_string(value):
1922        """Convert a :class:`datetime` or :class:`date` object to a string.
1923
1924        :param value: value to convert.
1925        :type value: datetime or date
1926        :return: a string representing ``value`` in the server's datetime format,
1927            if ``value`` is of type :class:`date`,
1928            the time portion will be midnight (00:00:00).
1929        :rtype: str
1930        """
1931        return value.strftime(DATETIME_FORMAT) if value else False
1932
1933    def convert_to_cache(self, value, record, validate=True):
1934        return self.to_datetime(value)
1935
1936    def convert_to_export(self, value, record):
1937        if not value:
1938            return ''
1939        value = self.convert_to_display_name(value, record)
1940        return self.from_string(value)
1941
1942    def convert_to_display_name(self, value, record):
1943        assert record, 'Record expected'
1944        return Datetime.to_string(Datetime.context_timestamp(record, Datetime.from_string(value)))
1945
1946# http://initd.org/psycopg/docs/usage.html#binary-adaptation
1947# Received data is returned as buffer (in Python 2) or memoryview (in Python 3).
1948_BINARY = memoryview
1949
1950
1951class Binary(Field):
1952    """Encapsulates a binary content (e.g. a file).
1953
1954    :param bool attachment: whether the field should be stored as `ir_attachment`
1955        or in a column of the model's table (default: ``True``).
1956    """
1957    type = 'binary'
1958
1959    prefetch = False                    # not prefetched by default
1960    _depends_context = ('bin_size',)    # depends on context (content or size)
1961    attachment = True                   # whether value is stored in attachment
1962
1963    @property
1964    def column_type(self):
1965        return None if self.attachment else ('bytea', 'bytea')
1966
1967    def _get_attrs(self, model, name):
1968        attrs = super(Binary, self)._get_attrs(model, name)
1969        if not attrs.get('store', True):
1970            attrs['attachment'] = False
1971        return attrs
1972
1973    _description_attachment = property(attrgetter('attachment'))
1974
1975    def convert_to_column(self, value, record, values=None, validate=True):
1976        # Binary values may be byte strings (python 2.6 byte array), but
1977        # the legacy OpenERP convention is to transfer and store binaries
1978        # as base64-encoded strings. The base64 string may be provided as a
1979        # unicode in some circumstances, hence the str() cast here.
1980        # This str() coercion will only work for pure ASCII unicode strings,
1981        # on purpose - non base64 data must be passed as a 8bit byte strings.
1982        if not value:
1983            return None
1984        # Detect if the binary content is an SVG for restricting its upload
1985        # only to system users.
1986        magic_bytes = {
1987            b'P',  # first 6 bits of '<' (0x3C) b64 encoded
1988            b'<',  # plaintext XML tag opening
1989        }
1990        if isinstance(value, str):
1991            value = value.encode()
1992        if value[:1] in magic_bytes:
1993            try:
1994                decoded_value = base64.b64decode(value.translate(None, delete=b'\r\n'), validate=True)
1995            except binascii.Error:
1996                decoded_value = value
1997            # Full mimetype detection
1998            if (guess_mimetype(decoded_value).startswith('image/svg') and
1999                    not record.env.is_system()):
2000                raise UserError(_("Only admins can upload SVG files."))
2001        if isinstance(value, bytes):
2002            return psycopg2.Binary(value)
2003        try:
2004            return psycopg2.Binary(str(value).encode('ascii'))
2005        except UnicodeEncodeError:
2006            raise UserError(_("ASCII characters are required for %s in %s") % (value, self.name))
2007
2008    def convert_to_cache(self, value, record, validate=True):
2009        if isinstance(value, _BINARY):
2010            return bytes(value)
2011        if isinstance(value, str):
2012            # the cache must contain bytes or memoryview, but sometimes a string
2013            # is given when assigning a binary field (test `TestFileSeparator`)
2014            return value.encode()
2015        if isinstance(value, int) and \
2016                (record._context.get('bin_size') or
2017                 record._context.get('bin_size_' + self.name)):
2018            # If the client requests only the size of the field, we return that
2019            # instead of the content. Presumably a separate request will be done
2020            # to read the actual content, if necessary.
2021            value = human_size(value)
2022            # human_size can return False (-> None) or a string (-> encoded)
2023            return value.encode() if value else None
2024        return None if value is False else value
2025
2026    def convert_to_record(self, value, record):
2027        if isinstance(value, _BINARY):
2028            return bytes(value)
2029        return False if value is None else value
2030
2031    def compute_value(self, records):
2032        bin_size_name = 'bin_size_' + self.name
2033        if records.env.context.get('bin_size') or records.env.context.get(bin_size_name):
2034            # always compute without bin_size
2035            records_no_bin_size = records.with_context(**{'bin_size': False, bin_size_name: False})
2036            super().compute_value(records_no_bin_size)
2037            # manually update the bin_size cache
2038            cache = records.env.cache
2039            for record_no_bin_size, record in zip(records_no_bin_size, records):
2040                try:
2041                    value = cache.get(record_no_bin_size, self)
2042                    try:
2043                        value = base64.b64decode(value)
2044                    except (TypeError, binascii.Error):
2045                        pass
2046                    try:
2047                        if isinstance(value, (bytes, _BINARY)):
2048                            value = human_size(len(value))
2049                    except (TypeError):
2050                        pass
2051                    cache_value = self.convert_to_cache(value, record)
2052                    cache.set(record, self, cache_value)
2053                except CacheMiss:
2054                    pass
2055        else:
2056            super().compute_value(records)
2057
2058    def read(self, records):
2059        # values are stored in attachments, retrieve them
2060        assert self.attachment
2061        domain = [
2062            ('res_model', '=', records._name),
2063            ('res_field', '=', self.name),
2064            ('res_id', 'in', records.ids),
2065        ]
2066        # Note: the 'bin_size' flag is handled by the field 'datas' itself
2067        data = {
2068            att.res_id: att.datas
2069            for att in records.env['ir.attachment'].sudo().search(domain)
2070        }
2071        cache = records.env.cache
2072        for record in records:
2073            cache.set(record, self, data.get(record.id, False))
2074
2075    def create(self, record_values):
2076        assert self.attachment
2077        if not record_values:
2078            return
2079        # create the attachments that store the values
2080        env = record_values[0][0].env
2081        with env.norecompute():
2082            env['ir.attachment'].sudo().with_context(
2083                binary_field_real_user=env.user,
2084            ).create([{
2085                    'name': self.name,
2086                    'res_model': self.model_name,
2087                    'res_field': self.name,
2088                    'res_id': record.id,
2089                    'type': 'binary',
2090                    'datas': value,
2091                }
2092                for record, value in record_values
2093                if value
2094            ])
2095
2096    def write(self, records, value):
2097        if not self.attachment:
2098            return super().write(records, value)
2099
2100        # discard recomputation of self on records
2101        records.env.remove_to_compute(self, records)
2102
2103        # update the cache, and discard the records that are not modified
2104        cache = records.env.cache
2105        cache_value = self.convert_to_cache(value, records)
2106        records = cache.get_records_different_from(records, self, cache_value)
2107        if not records:
2108            return records
2109        if self.store:
2110            # determine records that are known to be not null
2111            not_null = cache.get_records_different_from(records, self, None)
2112
2113        cache.update(records, self, [cache_value] * len(records))
2114
2115        # retrieve the attachments that store the values, and adapt them
2116        if self.store and any(records._ids):
2117            real_records = records.filtered('id')
2118            atts = records.env['ir.attachment'].sudo()
2119            if not_null:
2120                atts = atts.search([
2121                    ('res_model', '=', self.model_name),
2122                    ('res_field', '=', self.name),
2123                    ('res_id', 'in', real_records.ids),
2124                ])
2125            if value:
2126                # update the existing attachments
2127                atts.write({'datas': value})
2128                atts_records = records.browse(atts.mapped('res_id'))
2129                # create the missing attachments
2130                missing = (real_records - atts_records)
2131                if missing:
2132                    atts.create([{
2133                            'name': self.name,
2134                            'res_model': record._name,
2135                            'res_field': self.name,
2136                            'res_id': record.id,
2137                            'type': 'binary',
2138                            'datas': value,
2139                        }
2140                        for record in missing
2141                    ])
2142            else:
2143                atts.unlink()
2144
2145        return records
2146
2147
2148class Image(Binary):
2149    """Encapsulates an image, extending :class:`Binary`.
2150
2151    If image size is greater than the ``max_width``/``max_height`` limit of pixels, the image will be
2152    resized to the limit by keeping aspect ratio.
2153
2154    :param int max_width: the maximum width of the image (default: ``0``, no limit)
2155    :param int max_height: the maximum height of the image (default: ``0``, no limit)
2156    :param bool verify_resolution: whether the image resolution should be verified
2157        to ensure it doesn't go over the maximum image resolution (default: ``True``).
2158        See :class:`odoo.tools.image.ImageProcess` for maximum image resolution (default: ``45e6``).
2159
2160    .. note::
2161
2162        If no ``max_width``/``max_height`` is specified (or is set to 0) and ``verify_resolution`` is False,
2163        the field content won't be verified at all and a :class:`Binary` field should be used.
2164    """
2165    max_width = 0
2166    max_height = 0
2167    verify_resolution = True
2168
2169    def create(self, record_values):
2170        new_record_values = []
2171        for record, value in record_values:
2172            # strange behavior when setting related image field, when `self`
2173            # does not resize the same way as its related field
2174            new_value = self._image_process(value)
2175            new_record_values.append((record, new_value))
2176            cache_value = self.convert_to_cache(value if self.related else new_value, record)
2177            record.env.cache.update(record, self, [cache_value] * len(record))
2178        super(Image, self).create(new_record_values)
2179
2180    def write(self, records, value):
2181        try:
2182            new_value = self._image_process(value)
2183        except UserError:
2184            if not any(records._ids):
2185                # Some crap is assigned to a new record. This can happen in an
2186                # onchange, where the client sends the "bin size" value of the
2187                # field instead of its full value (this saves bandwidth). In
2188                # this case, we simply don't assign the field: its value will be
2189                # taken from the records' origin.
2190                return
2191            raise
2192
2193        super(Image, self).write(records, new_value)
2194        cache_value = self.convert_to_cache(value if self.related else new_value, records)
2195        records.env.cache.update(records, self, [cache_value] * len(records))
2196
2197    def _image_process(self, value):
2198        return image_process(value,
2199            size=(self.max_width, self.max_height),
2200            verify_resolution=self.verify_resolution,
2201        )
2202
2203    def _process_related(self, value):
2204        """Override to resize the related value before saving it on self."""
2205        try:
2206            return self._image_process(super()._process_related(value))
2207        except UserError:
2208            # Avoid the following `write` to fail if the related image was saved
2209            # invalid, which can happen for pre-existing databases.
2210            return False
2211
2212
2213class Selection(Field):
2214    """ Encapsulates an exclusive choice between different values.
2215
2216    :param selection: specifies the possible values for this field.
2217        It is given as either a list of pairs ``(value, label)``, or a model
2218        method, or a method name.
2219    :type selection: list(tuple(str,str)) or callable or str
2220
2221    :param selection_add: provides an extension of the selection in the case
2222        of an overridden field. It is a list of pairs ``(value, label)`` or
2223        singletons ``(value,)``, where singleton values must appear in the
2224        overridden selection. The new values are inserted in an order that is
2225        consistent with the overridden selection and this list::
2226
2227            selection = [('a', 'A'), ('b', 'B')]
2228            selection_add = [('c', 'C'), ('b',)]
2229            > result = [('a', 'A'), ('c', 'C'), ('b', 'B')]
2230    :type selection_add: list(tuple(str,str))
2231
2232    :param ondelete: provides a fallback mechanism for any overridden
2233        field with a selection_add. It is a dict that maps every option
2234        from the selection_add to a fallback action.
2235
2236        This fallback action will be applied to all records whose
2237        selection_add option maps to it.
2238
2239        The actions can be any of the following:
2240            - 'set null' -- the default, all records with this option
2241              will have their selection value set to False.
2242            - 'cascade' -- all records with this option will be
2243              deleted along with the option itself.
2244            - 'set default' -- all records with this option will be
2245              set to the default of the field definition
2246            - <callable> -- a callable whose first and only argument will be
2247              the set of records containing the specified Selection option,
2248              for custom processing
2249
2250    The attribute ``selection`` is mandatory except in the case of
2251    ``related`` or extended fields.
2252    """
2253    type = 'selection'
2254    column_type = ('varchar', pg_varchar())
2255
2256    selection = None            # [(value, string), ...], function or method name
2257    validate = True             # whether validating upon write
2258    ondelete = None             # {value: policy} (what to do when value is deleted)
2259
2260    def __init__(self, selection=Default, string=Default, **kwargs):
2261        super(Selection, self).__init__(selection=selection, string=string, **kwargs)
2262
2263    def _setup_regular_base(self, model):
2264        super(Selection, self)._setup_regular_base(model)
2265        assert self.selection is not None, "Field %s without selection" % self
2266        if isinstance(self.selection, list):
2267            assert all(isinstance(v, str) for v, _ in self.selection), \
2268                "Field %s with non-str value in selection" % self
2269
2270    def _setup_related_full(self, model):
2271        super(Selection, self)._setup_related_full(model)
2272        # selection must be computed on related field
2273        field = self.related_field
2274        self.selection = lambda model: field._description_selection(model.env)
2275
2276    def _get_attrs(self, model, name):
2277        attrs = super(Selection, self)._get_attrs(model, name)
2278        # arguments 'selection' and 'selection_add' are processed below
2279        attrs.pop('selection_add', None)
2280        return attrs
2281
2282    def _setup_attrs(self, model, name):
2283        super(Selection, self)._setup_attrs(model, name)
2284
2285        # determine selection (applying 'selection_add' extensions)
2286        values = None
2287        labels = {}
2288
2289        for field in reversed(resolve_mro(model, name, self._can_setup_from)):
2290            # We cannot use field.selection or field.selection_add here
2291            # because those attributes are overridden by ``_setup_attrs``.
2292            if 'selection' in field.args:
2293                if self.related:
2294                    _logger.warning("%s: selection attribute will be ignored as the field is related", self)
2295                selection = field.args['selection']
2296                if isinstance(selection, list):
2297                    if values is not None and values != [kv[0] for kv in selection]:
2298                        _logger.warning("%s: selection=%r overrides existing selection; use selection_add instead", self, selection)
2299                    values = [kv[0] for kv in selection]
2300                    labels = dict(selection)
2301                    self.ondelete = {}
2302                else:
2303                    values = None
2304                    labels = {}
2305                    self.selection = selection
2306                    self.ondelete = None
2307
2308            if 'selection_add' in field.args:
2309                if self.related:
2310                    _logger.warning("%s: selection_add attribute will be ignored as the field is related", self)
2311                selection_add = field.args['selection_add']
2312                assert isinstance(selection_add, list), \
2313                    "%s: selection_add=%r must be a list" % (self, selection_add)
2314                assert values is not None, \
2315                    "%s: selection_add=%r on non-list selection %r" % (self, selection_add, self.selection)
2316
2317                ondelete = field.args.get('ondelete') or {}
2318                new_values = [kv[0] for kv in selection_add if kv[0] not in values]
2319                for key in new_values:
2320                    ondelete.setdefault(key, 'set null')
2321                if self.required and new_values and 'set null' in ondelete.values():
2322                    raise ValueError(
2323                        "%r: required selection fields must define an ondelete policy that "
2324                        "implements the proper cleanup of the corresponding records upon "
2325                        "module uninstallation. Please use one or more of the following "
2326                        "policies: 'set default' (if the field has a default defined), 'cascade', "
2327                        "or a single-argument callable where the argument is the recordset "
2328                        "containing the specified option." % self
2329                    )
2330
2331                # check ondelete values
2332                for key, val in ondelete.items():
2333                    if callable(val) or val in ('set null', 'cascade'):
2334                        continue
2335                    if val == 'set default':
2336                        assert self.default is not None, (
2337                            "%r: ondelete policy of type 'set default' is invalid for this field "
2338                            "as it does not define a default! Either define one in the base "
2339                            "field, or change the chosen ondelete policy" % self
2340                        )
2341                        continue
2342                    raise ValueError(
2343                        "%r: ondelete policy %r for selection value %r is not a valid ondelete "
2344                        "policy, please choose one of 'set null', 'set default', 'cascade' or "
2345                        "a callable" % (self, val, key)
2346                    )
2347
2348                values = merge_sequences(values, [kv[0] for kv in selection_add])
2349                labels.update(kv for kv in selection_add if len(kv) == 2)
2350                self.ondelete.update(ondelete)
2351
2352        if values is not None:
2353            self.selection = [(value, labels[value]) for value in values]
2354
2355    def _selection_modules(self, model):
2356        """ Return a mapping from selection values to modules defining each value. """
2357        if not isinstance(self.selection, list):
2358            return {}
2359        value_modules = defaultdict(set)
2360        for field in reversed(resolve_mro(model, self.name, self._can_setup_from)):
2361            module = field.args.get('_module')
2362            if not module:
2363                continue
2364            if 'selection' in field.args:
2365                value_modules.clear()
2366                if isinstance(field.args['selection'], list):
2367                    for value, label in field.args['selection']:
2368                        value_modules[value].add(module)
2369            if 'selection_add' in field.args:
2370                for value_label in field.args['selection_add']:
2371                    if len(value_label) > 1:
2372                        value_modules[value_label[0]].add(module)
2373        return value_modules
2374
2375    def _description_selection(self, env):
2376        """ return the selection list (pairs (value, label)); labels are
2377            translated according to context language
2378        """
2379        selection = self.selection
2380        if isinstance(selection, str):
2381            return getattr(env[self.model_name], selection)()
2382        if callable(selection):
2383            return selection(env[self.model_name])
2384
2385        # translate selection labels
2386        if env.lang:
2387            return env['ir.translation'].get_field_selection(self.model_name, self.name)
2388        else:
2389            return selection
2390
2391    def get_values(self, env):
2392        """Return a list of the possible values."""
2393        selection = self.selection
2394        if isinstance(selection, str):
2395            selection = getattr(env[self.model_name], selection)()
2396        elif callable(selection):
2397            selection = selection(env[self.model_name])
2398        return [value for value, _ in selection]
2399
2400    def convert_to_column(self, value, record, values=None, validate=True):
2401        if validate and self.validate:
2402            value = self.convert_to_cache(value, record)
2403        return super(Selection, self).convert_to_column(value, record, values, validate)
2404
2405    def convert_to_cache(self, value, record, validate=True):
2406        if not validate:
2407            return value or None
2408        if value and self.column_type[0] == 'int4':
2409            value = int(value)
2410        if value in self.get_values(record.env):
2411            return value
2412        elif not value:
2413            return None
2414        raise ValueError("Wrong value for %s: %r" % (self, value))
2415
2416    def convert_to_export(self, value, record):
2417        if not isinstance(self.selection, list):
2418            # FIXME: this reproduces an existing buggy behavior!
2419            return value if value else ''
2420        for item in self._description_selection(record.env):
2421            if item[0] == value:
2422                return item[1]
2423        return ''
2424
2425
2426class Reference(Selection):
2427    """ Pseudo-relational field (no FK in database).
2428
2429    The field value is stored as a :class:`string <str>` following the pattern
2430    ``"res_model.res_id"`` in database.
2431    """
2432    type = 'reference'
2433
2434    @property
2435    def column_type(self):
2436        return ('varchar', pg_varchar())
2437
2438    def convert_to_column(self, value, record, values=None, validate=True):
2439        return Field.convert_to_column(self, value, record, values, validate)
2440
2441    def convert_to_cache(self, value, record, validate=True):
2442        # cache format: str ("model,id") or None
2443        if isinstance(value, BaseModel):
2444            if not validate or (value._name in self.get_values(record.env) and len(value) <= 1):
2445                return "%s,%s" % (value._name, value.id) if value else None
2446        elif isinstance(value, str):
2447            res_model, res_id = value.split(',')
2448            if not validate or res_model in self.get_values(record.env):
2449                if record.env[res_model].browse(int(res_id)).exists():
2450                    return value
2451                else:
2452                    return None
2453        elif not value:
2454            return None
2455        raise ValueError("Wrong value for %s: %r" % (self, value))
2456
2457    def convert_to_record(self, value, record):
2458        if value:
2459            res_model, res_id = value.split(',')
2460            return record.env[res_model].browse(int(res_id))
2461        return None
2462
2463    def convert_to_read(self, value, record, use_name_get=True):
2464        return "%s,%s" % (value._name, value.id) if value else False
2465
2466    def convert_to_export(self, value, record):
2467        return value.display_name if value else ''
2468
2469    def convert_to_display_name(self, value, record):
2470        return ustr(value and value.display_name)
2471
2472
2473class _Relational(Field):
2474    """ Abstract class for relational fields. """
2475    relational = True
2476    domain = []                         # domain for searching values
2477    context = {}                        # context for searching values
2478    check_company = False
2479
2480    def __get__(self, records, owner):
2481        # base case: do the regular access
2482        if records is None or len(records._ids) <= 1:
2483            return super().__get__(records, owner)
2484        # multirecord case: use mapped
2485        return self.mapped(records)
2486
2487    def _setup_regular_base(self, model):
2488        super(_Relational, self)._setup_regular_base(model)
2489        if self.comodel_name not in model.pool:
2490            _logger.warning("Field %s with unknown comodel_name %r", self, self.comodel_name)
2491            self.comodel_name = '_unknown'
2492
2493    def get_domain_list(self, model):
2494        """ Return a list domain from the domain parameter. """
2495        domain = self.domain
2496        if callable(domain):
2497            domain = domain(model)
2498        return domain if isinstance(domain, list) else []
2499
2500    @property
2501    def _related_domain(self):
2502        if callable(self.domain):
2503            # will be called with another model than self's
2504            return lambda recs: self.domain(recs.env[self.model_name])
2505        else:
2506            # maybe not correct if domain is a string...
2507            return self.domain
2508
2509    _related_context = property(attrgetter('context'))
2510
2511    _description_relation = property(attrgetter('comodel_name'))
2512    _description_context = property(attrgetter('context'))
2513
2514    def _description_domain(self, env):
2515        if self.check_company and not self.domain:
2516            if self.company_dependent:
2517                if self.comodel_name == "res.users":
2518                    # user needs access to current company (self.env.company)
2519                    return "[('company_ids', 'in', allowed_company_ids[0])]"
2520                else:
2521                    return "[('company_id', 'in', [allowed_company_ids[0], False])]"
2522            else:
2523                # when using check_company=True on a field on 'res.company', the
2524                # company_id comes from the id of the current record
2525                cid = "id" if self.model_name == "res.company" else "company_id"
2526                if self.comodel_name == "res.users":
2527                    # User allowed company ids = user.company_ids
2528                    return f"['|', (not {cid}, '=', True), ('company_ids', 'in', [{cid}])]"
2529                else:
2530                    return f"[('company_id', 'in', [{cid}, False])]"
2531        return self.domain(env[self.model_name]) if callable(self.domain) else self.domain
2532
2533    def null(self, record):
2534        return record.env[self.comodel_name]
2535
2536
2537class Many2one(_Relational):
2538    """ The value of such a field is a recordset of size 0 (no
2539    record) or 1 (a single record).
2540
2541    :param str comodel_name: name of the target model
2542        ``Mandatory`` except for related or extended fields.
2543
2544    :param domain: an optional domain to set on candidate values on the
2545        client side (domain or string)
2546
2547    :param dict context: an optional context to use on the client side when
2548        handling that field
2549
2550    :param str ondelete: what to do when the referred record is deleted;
2551        possible values are: ``'set null'``, ``'restrict'``, ``'cascade'``
2552
2553    :param bool auto_join: whether JOINs are generated upon search through that
2554        field (default: ``False``)
2555
2556    :param bool delegate: set it to ``True`` to make fields of the target model
2557        accessible from the current model (corresponds to ``_inherits``)
2558
2559    :param bool check_company: Mark the field to be verified in
2560        :meth:`~odoo.models.Model._check_company`. Add a default company
2561        domain depending on the field attributes.
2562    """
2563    type = 'many2one'
2564    column_type = ('int4', 'int4')
2565
2566    ondelete = None                     # what to do when value is deleted
2567    auto_join = False                   # whether joins are generated upon search
2568    delegate = False                    # whether self implements delegation
2569
2570    def __init__(self, comodel_name=Default, string=Default, **kwargs):
2571        super(Many2one, self).__init__(comodel_name=comodel_name, string=string, **kwargs)
2572
2573    def _setup_attrs(self, model, name):
2574        super(Many2one, self)._setup_attrs(model, name)
2575        # determine self.delegate
2576        if not self.delegate:
2577            self.delegate = name in model._inherits.values()
2578
2579    def _setup_regular_base(self, model):
2580        super()._setup_regular_base(model)
2581        # 3 cases:
2582        # 1) The ondelete attribute is not defined, we assign it a sensible default
2583        # 2) The ondelete attribute is defined and its definition makes sense
2584        # 3) The ondelete attribute is explicitly defined as 'set null' for a required m2o,
2585        #    this is considered a programming error.
2586        if not self.ondelete:
2587            comodel = model.env[self.comodel_name]
2588            if model.is_transient() and not comodel.is_transient():
2589                # Many2one relations from TransientModel Model are annoying because
2590                # they can block deletion due to foreign keys. So unless stated
2591                # otherwise, we default them to ondelete='cascade'.
2592                self.ondelete = 'cascade' if self.required else 'set null'
2593            else:
2594                self.ondelete = 'restrict' if self.required else 'set null'
2595        if self.ondelete == 'set null' and self.required:
2596            raise ValueError(
2597                "The m2o field %s of model %s is required but declares its ondelete policy "
2598                "as being 'set null'. Only 'restrict' and 'cascade' make sense."
2599                % (self.name, model._name)
2600            )
2601        if self.ondelete == 'restrict' and self.comodel_name in IR_MODELS:
2602            raise ValueError(
2603                f"Field {self.name} of model {model._name} is defined as ondelete='restrict' "
2604                f"while having {self.comodel_name} as comodel, the 'restrict' mode is not "
2605                f"supported for this type of field as comodel."
2606            )
2607
2608    def update_db(self, model, columns):
2609        comodel = model.env[self.comodel_name]
2610        if not model.is_transient() and comodel.is_transient():
2611            raise ValueError('Many2one %s from Model to TransientModel is forbidden' % self)
2612        return super(Many2one, self).update_db(model, columns)
2613
2614    def update_db_column(self, model, column):
2615        super(Many2one, self).update_db_column(model, column)
2616        model.pool.post_init(self.update_db_foreign_key, model, column)
2617
2618    def update_db_foreign_key(self, model, column):
2619        comodel = model.env[self.comodel_name]
2620        # foreign keys do not work on views, and users can define custom models on sql views.
2621        if not model._is_an_ordinary_table() or not comodel._is_an_ordinary_table():
2622            return
2623        # ir_actions is inherited, so foreign key doesn't work on it
2624        if not comodel._auto or comodel._table == 'ir_actions':
2625            return
2626        # create/update the foreign key, and reflect it in 'ir.model.constraint'
2627        model.pool.add_foreign_key(
2628            model._table, self.name, comodel._table, 'id', self.ondelete or 'set null',
2629            model, self._module
2630        )
2631
2632    def _update(self, records, value):
2633        """ Update the cached value of ``self`` for ``records`` with ``value``. """
2634        cache = records.env.cache
2635        for record in records:
2636            cache.set(record, self, self.convert_to_cache(value, record, validate=False))
2637
2638    def convert_to_column(self, value, record, values=None, validate=True):
2639        return value or None
2640
2641    def convert_to_cache(self, value, record, validate=True):
2642        # cache format: id or None
2643        if type(value) in IdType:
2644            id_ = value
2645        elif isinstance(value, BaseModel):
2646            if validate and (value._name != self.comodel_name or len(value) > 1):
2647                raise ValueError("Wrong value for %s: %r" % (self, value))
2648            id_ = value._ids[0] if value._ids else None
2649        elif isinstance(value, tuple):
2650            # value is either a pair (id, name), or a tuple of ids
2651            id_ = value[0] if value else None
2652        elif isinstance(value, dict):
2653            # return a new record (with the given field 'id' as origin)
2654            comodel = record.env[self.comodel_name]
2655            origin = comodel.browse(value.get('id'))
2656            id_ = comodel.new(value, origin=origin).id
2657        else:
2658            id_ = None
2659
2660        if self.delegate and record and not any(record._ids):
2661            # if all records are new, then so is the parent
2662            id_ = id_ and NewId(id_)
2663
2664        return id_
2665
2666    def convert_to_record(self, value, record):
2667        # use registry to avoid creating a recordset for the model
2668        ids = () if value is None else (value,)
2669        prefetch_ids = IterableGenerator(prefetch_many2one_ids, record, self)
2670        return record.pool[self.comodel_name]._browse(record.env, ids, prefetch_ids)
2671
2672    def convert_to_record_multi(self, values, records):
2673        # return the ids as a recordset without duplicates
2674        prefetch_ids = IterableGenerator(prefetch_many2one_ids, records, self)
2675        ids = tuple(unique(id_ for id_ in values if id_ is not None))
2676        return records.pool[self.comodel_name]._browse(records.env, ids, prefetch_ids)
2677
2678    def convert_to_read(self, value, record, use_name_get=True):
2679        if use_name_get and value:
2680            # evaluate name_get() as superuser, because the visibility of a
2681            # many2one field value (id and name) depends on the current record's
2682            # access rights, and not the value's access rights.
2683            try:
2684                # performance: value.sudo() prefetches the same records as value
2685                return (value.id, value.sudo().display_name)
2686            except MissingError:
2687                # Should not happen, unless the foreign key is missing.
2688                return False
2689        else:
2690            return value.id
2691
2692    def convert_to_write(self, value, record):
2693        if type(value) in IdType:
2694            return value
2695        if not value:
2696            return False
2697        if isinstance(value, BaseModel) and value._name == self.comodel_name:
2698            return value.id
2699        if isinstance(value, tuple):
2700            # value is either a pair (id, name), or a tuple of ids
2701            return value[0] if value else False
2702        if isinstance(value, dict):
2703            return record.env[self.comodel_name].new(value).id
2704        raise ValueError("Wrong value for %s: %r" % (self, value))
2705
2706    def convert_to_export(self, value, record):
2707        return value.display_name if value else ''
2708
2709    def convert_to_display_name(self, value, record):
2710        return ustr(value.display_name)
2711
2712    def convert_to_onchange(self, value, record, names):
2713        if not value.id:
2714            return False
2715        return super(Many2one, self).convert_to_onchange(value, record, names)
2716
2717    def write(self, records, value):
2718        # discard recomputation of self on records
2719        records.env.remove_to_compute(self, records)
2720
2721        # discard the records that are not modified
2722        cache = records.env.cache
2723        cache_value = self.convert_to_cache(value, records)
2724        records = cache.get_records_different_from(records, self, cache_value)
2725        if not records:
2726            return records
2727
2728        # remove records from the cache of one2many fields of old corecords
2729        self._remove_inverses(records, cache_value)
2730
2731        # update the cache of self
2732        cache.update(records, self, [cache_value] * len(records))
2733
2734        # update towrite
2735        if self.store:
2736            towrite = records.env.all.towrite[self.model_name]
2737            for record in records.filtered('id'):
2738                # cache_value is already in database format
2739                towrite[record.id][self.name] = cache_value
2740
2741        # update the cache of one2many fields of new corecord
2742        self._update_inverses(records, cache_value)
2743
2744        return records
2745
2746    def _remove_inverses(self, records, value):
2747        """ Remove `records` from the cached values of the inverse fields of `self`. """
2748        cache = records.env.cache
2749        record_ids = set(records._ids)
2750
2751        # align(id) returns a NewId if records are new, a real id otherwise
2752        align = (lambda id_: id_) if all(record_ids) else (lambda id_: id_ and NewId(id_))
2753
2754        for invf in records._field_inverses[self]:
2755            corecords = records.env[self.comodel_name].browse(
2756                align(id_) for id_ in cache.get_values(records, self)
2757            )
2758            for corecord in corecords:
2759                ids0 = cache.get(corecord, invf, None)
2760                if ids0 is not None:
2761                    ids1 = tuple(id_ for id_ in ids0 if id_ not in record_ids)
2762                    cache.set(corecord, invf, ids1)
2763
2764    def _update_inverses(self, records, value):
2765        """ Add `records` to the cached values of the inverse fields of `self`. """
2766        if value is None:
2767            return
2768        cache = records.env.cache
2769        corecord = self.convert_to_record(value, records)
2770        for invf in records._field_inverses[self]:
2771            valid_records = records.filtered_domain(invf.get_domain_list(corecord))
2772            if not valid_records:
2773                continue
2774            ids0 = cache.get(corecord, invf, None)
2775            # if the value for the corecord is not in cache, but this is a new
2776            # record, assign it anyway, as you won't be able to fetch it from
2777            # database (see `test_sale_order`)
2778            if ids0 is not None or not corecord.id:
2779                ids1 = tuple(unique((ids0 or ()) + valid_records._ids))
2780                cache.set(corecord, invf, ids1)
2781
2782
2783class Many2oneReference(Integer):
2784    """ Pseudo-relational field (no FK in database).
2785
2786    The field value is stored as an :class:`integer <int>` id in database.
2787
2788    Contrary to :class:`Reference` fields, the model has to be specified
2789    in a :class:`Char` field, whose name has to be specified in the
2790    `model_field` attribute for the current :class:`Many2oneReference` field.
2791
2792    :param str model_field: name of the :class:`Char` where the model name is stored.
2793    """
2794    type = 'many2one_reference'
2795
2796    model_field = None
2797
2798    _related_model_field = property(attrgetter('model_field'))
2799
2800    def convert_to_cache(self, value, record, validate=True):
2801        # cache format: id or None
2802        if isinstance(value, BaseModel):
2803            value = value._ids[0] if value._ids else None
2804        return super().convert_to_cache(value, record, validate)
2805
2806    def _remove_inverses(self, records, value):
2807        # TODO: unused
2808        # remove records from the cache of one2many fields of old corecords
2809        cache = records.env.cache
2810        record_ids = set(records._ids)
2811        model_ids = self._record_ids_per_res_model(records)
2812
2813        for invf in records._field_inverses[self]:
2814            records = records.browse(model_ids[invf.model_name])
2815            if not records:
2816                continue
2817            corecords = records.env[invf.model_name].browse(
2818                id_ for id_ in cache.get_values(records, self)
2819            )
2820            for corecord in corecords:
2821                ids0 = cache.get(corecord, invf, None)
2822                if ids0 is not None:
2823                    ids1 = tuple(id_ for id_ in ids0 if id_ not in record_ids)
2824                    cache.set(corecord, invf, ids1)
2825
2826    def _update_inverses(self, records, value):
2827        """ Add `records` to the cached values of the inverse fields of `self`. """
2828        if not value:
2829            return
2830        cache = records.env.cache
2831        model_ids = self._record_ids_per_res_model(records)
2832
2833        for invf in records._field_inverses[self]:
2834            records = records.browse(model_ids[invf.model_name])
2835            if not records:
2836                continue
2837            corecord = records.env[invf.model_name].browse(value)
2838            records = records.filtered_domain(invf.get_domain_list(corecord))
2839            if not records:
2840                continue
2841            ids0 = cache.get(corecord, invf, None)
2842            # if the value for the corecord is not in cache, but this is a new
2843            # record, assign it anyway, as you won't be able to fetch it from
2844            # database (see `test_sale_order`)
2845            if ids0 is not None or not corecord.id:
2846                ids1 = tuple(unique((ids0 or ()) + records._ids))
2847                cache.set(corecord, invf, ids1)
2848
2849    def _record_ids_per_res_model(self, records):
2850        model_ids = defaultdict(set)
2851        for record in records:
2852            model = record[self.model_field]
2853            if not model and record._fields[self.model_field].compute:
2854                # fallback when the model field is computed :-/
2855                record._fields[self.model_field].compute_value(record)
2856                model = record[self.model_field]
2857                if not model:
2858                    continue
2859            model_ids[model].add(record.id)
2860        return model_ids
2861
2862
2863class _RelationalMulti(_Relational):
2864    """ Abstract class for relational fields *2many. """
2865
2866    # Important: the cache contains the ids of all the records in the relation,
2867    # including inactive records.  Inactive records are filtered out by
2868    # convert_to_record(), depending on the context.
2869
2870    def _update(self, records, value):
2871        """ Update the cached value of ``self`` for ``records`` with ``value``,
2872            and return whether everything is in cache.
2873        """
2874        if not isinstance(records, BaseModel):
2875            # the inverse of self is a non-relational field; `value` is a
2876            # corecord that refers to `records` by an integer field
2877            model = value.env[self.model_name]
2878            domain = self.domain(model) if callable(self.domain) else self.domain
2879            if not value.filtered_domain(domain):
2880                return
2881            records = model.browse(records)
2882
2883        result = True
2884
2885        if value:
2886            cache = records.env.cache
2887            for record in records:
2888                if cache.contains(record, self):
2889                    val = self.convert_to_cache(record[self.name] | value, record, validate=False)
2890                    cache.set(record, self, val)
2891                else:
2892                    result = False
2893            records.modified([self.name])
2894
2895        return result
2896
2897    def convert_to_cache(self, value, record, validate=True):
2898        # cache format: tuple(ids)
2899        if isinstance(value, BaseModel):
2900            if validate and value._name != self.comodel_name:
2901                raise ValueError("Wrong value for %s: %s" % (self, value))
2902            ids = value._ids
2903            if record and not record.id:
2904                # x2many field value of new record is new records
2905                ids = tuple(it and NewId(it) for it in ids)
2906            return ids
2907
2908        elif isinstance(value, (list, tuple)):
2909            # value is a list/tuple of commands, dicts or record ids
2910            comodel = record.env[self.comodel_name]
2911            # if record is new, the field's value is new records
2912            if record and not record.id:
2913                browse = lambda it: comodel.browse([it and NewId(it)])
2914            else:
2915                browse = comodel.browse
2916            # determine the value ids
2917            ids = OrderedSet(record[self.name]._ids if validate else ())
2918            # modify ids with the commands
2919            for command in value:
2920                if isinstance(command, (tuple, list)):
2921                    if command[0] == 0:
2922                        ids.add(comodel.new(command[2], ref=command[1]).id)
2923                    elif command[0] == 1:
2924                        line = browse(command[1])
2925                        if validate:
2926                            line.update(command[2])
2927                        else:
2928                            line._update_cache(command[2], validate=False)
2929                        ids.add(line.id)
2930                    elif command[0] in (2, 3):
2931                        ids.discard(browse(command[1]).id)
2932                    elif command[0] == 4:
2933                        ids.add(browse(command[1]).id)
2934                    elif command[0] == 5:
2935                        ids.clear()
2936                    elif command[0] == 6:
2937                        ids = OrderedSet(browse(it).id for it in command[2])
2938                elif isinstance(command, dict):
2939                    ids.add(comodel.new(command).id)
2940                else:
2941                    ids.add(browse(command).id)
2942            # return result as a tuple
2943            return tuple(ids)
2944
2945        elif not value:
2946            return ()
2947
2948        raise ValueError("Wrong value for %s: %s" % (self, value))
2949
2950    def convert_to_record(self, value, record):
2951        # use registry to avoid creating a recordset for the model
2952        prefetch_ids = IterableGenerator(prefetch_x2many_ids, record, self)
2953        Comodel = record.pool[self.comodel_name]
2954        corecords = Comodel._browse(record.env, value, prefetch_ids)
2955        if (
2956            Comodel._active_name
2957            and self.context.get('active_test', record.env.context.get('active_test', True))
2958        ):
2959            corecords = corecords.filtered(Comodel._active_name).with_prefetch(prefetch_ids)
2960        return corecords
2961
2962    def convert_to_record_multi(self, values, records):
2963        # return the list of ids as a recordset without duplicates
2964        prefetch_ids = IterableGenerator(prefetch_x2many_ids, records, self)
2965        Comodel = records.pool[self.comodel_name]
2966        ids = tuple(unique(id_ for ids in values for id_ in ids))
2967        corecords = Comodel._browse(records.env, ids, prefetch_ids)
2968        if (
2969            Comodel._active_name
2970            and self.context.get('active_test', records.env.context.get('active_test', True))
2971        ):
2972            corecords = corecords.filtered(Comodel._active_name).with_prefetch(prefetch_ids)
2973        return corecords
2974
2975    def convert_to_read(self, value, record, use_name_get=True):
2976        return value.ids
2977
2978    def convert_to_write(self, value, record):
2979        if isinstance(value, tuple):
2980            # a tuple of ids, this is the cache format
2981            value = record.env[self.comodel_name].browse(value)
2982
2983        if isinstance(value, BaseModel) and value._name == self.comodel_name:
2984            def get_origin(val):
2985                return val._origin if isinstance(val, BaseModel) else val
2986
2987            # make result with new and existing records
2988            inv_names = {field.name for field in record._field_inverses[self]}
2989            result = [(6, 0, [])]
2990            for record in value:
2991                origin = record._origin
2992                if not origin:
2993                    values = record._convert_to_write({
2994                        name: record[name]
2995                        for name in record._cache
2996                        if name not in inv_names
2997                    })
2998                    result.append((0, 0, values))
2999                else:
3000                    result[0][2].append(origin.id)
3001                    if record != origin:
3002                        values = record._convert_to_write({
3003                            name: record[name]
3004                            for name in record._cache
3005                            if name not in inv_names and get_origin(record[name]) != origin[name]
3006                        })
3007                        if values:
3008                            result.append((1, origin.id, values))
3009            return result
3010
3011        if value is False or value is None:
3012            return [(5,)]
3013
3014        if isinstance(value, list):
3015            return value
3016
3017        raise ValueError("Wrong value for %s: %s" % (self, value))
3018
3019    def convert_to_export(self, value, record):
3020        return ','.join(name for id, name in value.name_get()) if value else ''
3021
3022    def convert_to_display_name(self, value, record):
3023        raise NotImplementedError()
3024
3025    def _setup_regular_full(self, model):
3026        super(_RelationalMulti, self)._setup_regular_full(model)
3027        if not self.compute and isinstance(self.domain, list):
3028            self.depends = tuple(unique(itertools.chain(self.depends, (
3029                self.name + '.' + arg[0]
3030                for arg in self.domain
3031                if isinstance(arg, (tuple, list)) and isinstance(arg[0], str)
3032            ))))
3033
3034    def create(self, record_values):
3035        """ Write the value of ``self`` on the given records, which have just
3036        been created.
3037
3038        :param record_values: a list of pairs ``(record, value)``, where
3039            ``value`` is in the format of method :meth:`BaseModel.write`
3040        """
3041        self.write_batch(record_values, True)
3042
3043    def write(self, records, value):
3044        # discard recomputation of self on records
3045        records.env.remove_to_compute(self, records)
3046        return self.write_batch([(records, value)])
3047
3048    def write_batch(self, records_commands_list, create=False):
3049        if not records_commands_list:
3050            return False
3051
3052        for idx, (recs, value) in enumerate(records_commands_list):
3053            if isinstance(value, tuple):
3054                value = [(6, 0, value)]
3055            elif isinstance(value, BaseModel) and value._name == self.comodel_name:
3056                value = [(6, 0, value._ids)]
3057            elif value is False or value is None:
3058                value = [(5,)]
3059            elif isinstance(value, list) and value and not isinstance(value[0], (tuple, list)):
3060                value = [(6, 0, tuple(value))]
3061            if not isinstance(value, list):
3062                raise ValueError("Wrong value for %s: %s" % (self, value))
3063            records_commands_list[idx] = (recs, value)
3064
3065        record_ids = {rid for recs, cs in records_commands_list for rid in recs._ids}
3066        if all(record_ids):
3067            return self.write_real(records_commands_list, create)
3068        else:
3069            assert not any(record_ids)
3070            return self.write_new(records_commands_list)
3071
3072
3073class One2many(_RelationalMulti):
3074    """One2many field; the value of such a field is the recordset of all the
3075    records in ``comodel_name`` such that the field ``inverse_name`` is equal to
3076    the current record.
3077
3078    :param str comodel_name: name of the target model
3079
3080    :param str inverse_name: name of the inverse ``Many2one`` field in
3081        ``comodel_name``
3082
3083    :param domain: an optional domain to set on candidate values on the
3084        client side (domain or string)
3085
3086    :param dict context: an optional context to use on the client side when
3087        handling that field
3088
3089    :param bool auto_join: whether JOINs are generated upon search through that
3090        field (default: ``False``)
3091
3092    :param int limit: optional limit to use upon read
3093
3094    The attributes ``comodel_name`` and ``inverse_name`` are mandatory except in
3095    the case of related fields or field extensions.
3096    """
3097    type = 'one2many'
3098
3099    inverse_name = None                 # name of the inverse field
3100    auto_join = False                   # whether joins are generated upon search
3101    limit = None                        # optional limit to use upon read
3102    copy = False                        # o2m are not copied by default
3103
3104    def __init__(self, comodel_name=Default, inverse_name=Default, string=Default, **kwargs):
3105        super(One2many, self).__init__(
3106            comodel_name=comodel_name,
3107            inverse_name=inverse_name,
3108            string=string,
3109            **kwargs
3110        )
3111
3112    def _setup_regular_full(self, model):
3113        super(One2many, self)._setup_regular_full(model)
3114        if self.inverse_name:
3115            # link self to its inverse field and vice-versa
3116            comodel = model.env[self.comodel_name]
3117            invf = comodel._fields[self.inverse_name]
3118            if isinstance(invf, (Many2one, Many2oneReference)):
3119                # setting one2many fields only invalidates many2one inverses;
3120                # integer inverses (res_model/res_id pairs) are not supported
3121                model._field_inverses.add(self, invf)
3122            comodel._field_inverses.add(invf, self)
3123
3124    _description_relation_field = property(attrgetter('inverse_name'))
3125
3126    def update_db(self, model, columns):
3127        if self.comodel_name in model.env:
3128            comodel = model.env[self.comodel_name]
3129            if self.inverse_name not in comodel._fields:
3130                raise UserError(_("No inverse field %r found for %r") % (self.inverse_name, self.comodel_name))
3131
3132    def get_domain_list(self, records):
3133        comodel = records.env.registry[self.comodel_name]
3134        inverse_field = comodel._fields[self.inverse_name]
3135        domain = super(One2many, self).get_domain_list(records)
3136        if inverse_field.type == 'many2one_reference':
3137            domain = domain + [(inverse_field.model_field, '=', records._name)]
3138        return domain
3139
3140    def __get__(self, records, owner):
3141        if records is not None and self.inverse_name is not None:
3142            # force the computation of the inverse field to ensure that the
3143            # cache value of self is consistent
3144            inverse_field = records.pool[self.comodel_name]._fields[self.inverse_name]
3145            if inverse_field.compute:
3146                records.env[self.comodel_name].recompute([self.inverse_name])
3147        return super().__get__(records, owner)
3148
3149    def read(self, records):
3150        # retrieve the lines in the comodel
3151        context = {'active_test': False}
3152        context.update(self.context)
3153        comodel = records.env[self.comodel_name].with_context(**context)
3154        inverse = self.inverse_name
3155        inverse_field = comodel._fields[inverse]
3156        get_id = (lambda rec: rec.id) if inverse_field.type == 'many2one' else int
3157        domain = self.get_domain_list(records) + [(inverse, 'in', records.ids)]
3158        lines = comodel.search(domain, limit=self.limit)
3159
3160        # group lines by inverse field (without prefetching other fields)
3161        group = defaultdict(list)
3162        for line in lines.with_context(prefetch_fields=False):
3163            # line[inverse] may be a record or an integer
3164            group[get_id(line[inverse])].append(line.id)
3165
3166        # store result in cache
3167        cache = records.env.cache
3168        for record in records:
3169            cache.set(record, self, tuple(group[record.id]))
3170
3171    def write_real(self, records_commands_list, create=False):
3172        """ Update real records. """
3173        # records_commands_list = [(records, commands), ...]
3174        if not records_commands_list:
3175            return
3176
3177        model = records_commands_list[0][0].browse()
3178        comodel = model.env[self.comodel_name].with_context(**self.context)
3179
3180        ids = {rid for recs, cs in records_commands_list for rid in recs.ids}
3181        records = records_commands_list[0][0].browse(ids)
3182
3183        if self.store:
3184            inverse = self.inverse_name
3185            to_create = []                  # line vals to create
3186            to_delete = []                  # line ids to delete
3187            to_inverse = {}
3188            allow_full_delete = not create
3189
3190            def unlink(lines):
3191                if getattr(comodel._fields[inverse], 'ondelete', False) == 'cascade':
3192                    to_delete.extend(lines._ids)
3193                else:
3194                    lines[inverse] = False
3195
3196            def flush():
3197                if to_delete:
3198                    # unlink() will remove the lines from the cache
3199                    comodel.browse(to_delete).unlink()
3200                    to_delete.clear()
3201                if to_create:
3202                    # create() will add the new lines to the cache of records
3203                    comodel.create(to_create)
3204                    to_create.clear()
3205                if to_inverse:
3206                    for record, inverse_ids in to_inverse.items():
3207                        lines = comodel.browse(inverse_ids)
3208                        lines = lines.filtered(lambda line: int(line[inverse]) != record.id)
3209                        lines[inverse] = record
3210
3211            for recs, commands in records_commands_list:
3212                for command in (commands or ()):
3213                    if command[0] == 0:
3214                        for record in recs:
3215                            to_create.append(dict(command[2], **{inverse: record.id}))
3216                        allow_full_delete = False
3217                    elif command[0] == 1:
3218                        comodel.browse(command[1]).write(command[2])
3219                    elif command[0] == 2:
3220                        to_delete.append(command[1])
3221                    elif command[0] == 3:
3222                        unlink(comodel.browse(command[1]))
3223                    elif command[0] == 4:
3224                        to_inverse.setdefault(recs[-1], set()).add(command[1])
3225                        allow_full_delete = False
3226                    elif command[0] in (5, 6) :
3227                        # do not try to delete anything in creation mode if nothing has been created before
3228                        line_ids = command[2] if command[0] == 6 else []
3229                        if not allow_full_delete and not line_ids:
3230                            continue
3231                        flush()
3232                        # assign the given lines to the last record only
3233                        lines = comodel.browse(line_ids)
3234                        domain = self.get_domain_list(model) + \
3235                            [(inverse, 'in', recs.ids), ('id', 'not in', lines.ids)]
3236                        unlink(comodel.search(domain))
3237                        lines[inverse] = recs[-1]
3238
3239            flush()
3240
3241        else:
3242            cache = records.env.cache
3243
3244            def link(record, lines):
3245                ids = record[self.name]._ids
3246                cache.set(record, self, tuple(unique(ids + lines._ids)))
3247
3248            def unlink(lines):
3249                for record in records:
3250                    cache.set(record, self, (record[self.name] - lines)._ids)
3251
3252            for recs, commands in records_commands_list:
3253                for command in (commands or ()):
3254                    if command[0] == 0:
3255                        for record in recs:
3256                            link(record, comodel.new(command[2], ref=command[1]))
3257                    elif command[0] == 1:
3258                        comodel.browse(command[1]).write(command[2])
3259                    elif command[0] == 2:
3260                        unlink(comodel.browse(command[1]))
3261                    elif command[0] == 3:
3262                        unlink(comodel.browse(command[1]))
3263                    elif command[0] == 4:
3264                        link(recs[-1], comodel.browse(command[1]))
3265                    elif command[0] in (5, 6):
3266                        # assign the given lines to the last record only
3267                        cache.update(recs, self, [()] * len(recs))
3268                        lines = comodel.browse(command[2] if command[0] == 6 else [])
3269                        cache.set(recs[-1], self, lines._ids)
3270
3271        return records
3272
3273    def write_new(self, records_commands_list):
3274        if not records_commands_list:
3275            return
3276
3277        model = records_commands_list[0][0].browse()
3278        cache = model.env.cache
3279        comodel = model.env[self.comodel_name].with_context(**self.context)
3280
3281        ids = {record.id for records, _ in records_commands_list for record in records}
3282        records = model.browse(ids)
3283
3284        def browse(ids):
3285            return comodel.browse([id_ and NewId(id_) for id_ in ids])
3286
3287        # make sure self is in cache
3288        records[self.name]
3289
3290        if self.store:
3291            inverse = self.inverse_name
3292
3293            # make sure self's inverse is in cache
3294            inverse_field = comodel._fields[inverse]
3295            for record in records:
3296                cache.update(record[self.name], inverse_field, itertools.repeat(record.id))
3297
3298            for recs, commands in records_commands_list:
3299                for command in commands:
3300                    if command[0] == 0:
3301                        for record in recs:
3302                            line = comodel.new(command[2], ref=command[1])
3303                            line[inverse] = record
3304                    elif command[0] == 1:
3305                        browse([command[1]]).update(command[2])
3306                    elif command[0] == 2:
3307                        browse([command[1]])[inverse] = False
3308                    elif command[0] == 3:
3309                        browse([command[1]])[inverse] = False
3310                    elif command[0] == 4:
3311                        browse([command[1]])[inverse] = recs[-1]
3312                    elif command[0] == 5:
3313                        cache.update(recs, self, itertools.repeat(()))
3314                    elif command[0] == 6:
3315                        # assign the given lines to the last record only
3316                        cache.update(recs, self, itertools.repeat(()))
3317                        last, lines = recs[-1], browse(command[2])
3318                        cache.set(last, self, lines._ids)
3319                        cache.update(lines, inverse_field, itertools.repeat(last.id))
3320
3321        else:
3322            def link(record, lines):
3323                ids = record[self.name]._ids
3324                cache.set(record, self, tuple(unique(ids + lines._ids)))
3325
3326            def unlink(lines):
3327                for record in records:
3328                    cache.set(record, self, (record[self.name] - lines)._ids)
3329
3330            for recs, commands in records_commands_list:
3331                for command in commands:
3332                    if command[0] == 0:
3333                        for record in recs:
3334                            link(record, comodel.new(command[2], ref=command[1]))
3335                    elif command[0] == 1:
3336                        browse([command[1]]).update(command[2])
3337                    elif command[0] == 2:
3338                        unlink(browse([command[1]]))
3339                    elif command[0] == 3:
3340                        unlink(browse([command[1]]))
3341                    elif command[0] == 4:
3342                        link(recs[-1], browse([command[1]]))
3343                    elif command[0] in (5, 6):
3344                        # assign the given lines to the last record only
3345                        cache.update(recs, self, [()] * len(recs))
3346                        lines = comodel.browse(command[2] if command[0] == 6 else [])
3347                        cache.set(recs[-1], self, lines._ids)
3348
3349        return records
3350
3351
3352class Many2many(_RelationalMulti):
3353    """ Many2many field; the value of such a field is the recordset.
3354
3355    :param comodel_name: name of the target model (string)
3356        mandatory except in the case of related or extended fields
3357
3358    :param str relation: optional name of the table that stores the relation in
3359        the database
3360
3361    :param str column1: optional name of the column referring to "these" records
3362        in the table ``relation``
3363
3364    :param str column2: optional name of the column referring to "those" records
3365        in the table ``relation``
3366
3367    The attributes ``relation``, ``column1`` and ``column2`` are optional.
3368    If not given, names are automatically generated from model names,
3369    provided ``model_name`` and ``comodel_name`` are different!
3370
3371    Note that having several fields with implicit relation parameters on a
3372    given model with the same comodel is not accepted by the ORM, since
3373    those field would use the same table. The ORM prevents two many2many
3374    fields to use the same relation parameters, except if
3375
3376    - both fields use the same model, comodel, and relation parameters are
3377      explicit; or
3378
3379    - at least one field belongs to a model with ``_auto = False``.
3380
3381    :param domain: an optional domain to set on candidate values on the
3382        client side (domain or string)
3383
3384    :param dict context: an optional context to use on the client side when
3385        handling that field
3386
3387    :param bool check_company: Mark the field to be verified in
3388        :meth:`~odoo.models.Model._check_company`. Add a default company
3389        domain depending on the field attributes.
3390
3391    :param int limit: optional limit to use upon read
3392    """
3393    type = 'many2many'
3394
3395    _explicit = True                    # whether schema is explicitly given
3396    relation = None                     # name of table
3397    column1 = None                      # column of table referring to model
3398    column2 = None                      # column of table referring to comodel
3399    auto_join = False                   # whether joins are generated upon search
3400    limit = None                        # optional limit to use upon read
3401    ondelete = None                     # optional ondelete for the column2 fkey
3402
3403    def __init__(self, comodel_name=Default, relation=Default, column1=Default,
3404                 column2=Default, string=Default, **kwargs):
3405        super(Many2many, self).__init__(
3406            comodel_name=comodel_name,
3407            relation=relation,
3408            column1=column1,
3409            column2=column2,
3410            string=string,
3411            **kwargs
3412        )
3413
3414    def _setup_regular_base(self, model):
3415        super(Many2many, self)._setup_regular_base(model)
3416        # 3 cases:
3417        # 1) The ondelete attribute is not defined, we assign it a sensible default
3418        # 2) The ondelete attribute is defined and its definition makes sense
3419        # 3) The ondelete attribute is explicitly defined as 'set null' for a m2m,
3420        #    this is considered a programming error.
3421        self.ondelete = self.ondelete or 'cascade'
3422        if self.ondelete == 'set null':
3423            raise ValueError(
3424                "The m2m field %s of model %s declares its ondelete policy "
3425                "as being 'set null'. Only 'restrict' and 'cascade' make sense."
3426                % (self.name, model._name)
3427            )
3428        if self.store:
3429            if not (self.relation and self.column1 and self.column2):
3430                self._explicit = False
3431                # table name is based on the stable alphabetical order of tables
3432                comodel = model.env[self.comodel_name]
3433                if not self.relation:
3434                    tables = sorted([model._table, comodel._table])
3435                    assert tables[0] != tables[1], \
3436                        "%s: Implicit/canonical naming of many2many relationship " \
3437                        "table is not possible when source and destination models " \
3438                        "are the same" % self
3439                    self.relation = '%s_%s_rel' % tuple(tables)
3440                if not self.column1:
3441                    self.column1 = '%s_id' % model._table
3442                if not self.column2:
3443                    self.column2 = '%s_id' % comodel._table
3444            # check validity of table name
3445            check_pg_name(self.relation)
3446        else:
3447            self.relation = self.column1 = self.column2 = None
3448
3449    def _setup_regular_full(self, model):
3450        super(Many2many, self)._setup_regular_full(model)
3451        if self.relation:
3452            m2m = model.pool._m2m
3453
3454            # check whether other fields use the same schema
3455            fields = m2m[(self.relation, self.column1, self.column2)]
3456            for field in fields:
3457                if (    # same model: relation parameters must be explicit
3458                    self.model_name == field.model_name and
3459                    self.comodel_name == field.comodel_name and
3460                    self._explicit and field._explicit
3461                ) or (  # different models: one model must be _auto=False
3462                    self.model_name != field.model_name and
3463                    not (model._auto and model.env[field.model_name]._auto)
3464                ):
3465                    continue
3466                msg = "Many2many fields %s and %s use the same table and columns"
3467                raise TypeError(msg % (self, field))
3468            fields.append(self)
3469
3470            # retrieve inverse fields, and link them in _field_inverses
3471            for field in m2m[(self.relation, self.column2, self.column1)]:
3472                model._field_inverses.add(self, field)
3473                model.env[field.model_name]._field_inverses.add(field, self)
3474
3475    def update_db(self, model, columns):
3476        cr = model._cr
3477        # Do not reflect relations for custom fields, as they do not belong to a
3478        # module. They are automatically removed when dropping the corresponding
3479        # 'ir.model.field'.
3480        if not self.manual:
3481            model.pool.post_init(model.env['ir.model.relation']._reflect_relation,
3482                                 model, self.relation, self._module)
3483        comodel = model.env[self.comodel_name]
3484        if not sql.table_exists(cr, self.relation):
3485            query = """
3486                CREATE TABLE "{rel}" ("{id1}" INTEGER NOT NULL,
3487                                      "{id2}" INTEGER NOT NULL,
3488                                      PRIMARY KEY("{id1}","{id2}"));
3489                COMMENT ON TABLE "{rel}" IS %s;
3490                CREATE INDEX ON "{rel}" ("{id2}","{id1}");
3491            """.format(rel=self.relation, id1=self.column1, id2=self.column2)
3492            cr.execute(query, ['RELATION BETWEEN %s AND %s' % (model._table, comodel._table)])
3493            _schema.debug("Create table %r: m2m relation between %r and %r", self.relation, model._table, comodel._table)
3494
3495        model.pool.post_init(self.update_db_foreign_keys, model)
3496
3497    def update_db_foreign_keys(self, model):
3498        """ Add the foreign keys corresponding to the field's relation table. """
3499        comodel = model.env[self.comodel_name]
3500        if model._is_an_ordinary_table():
3501            model.pool.add_foreign_key(
3502                self.relation, self.column1, model._table, 'id', 'cascade',
3503                model, self._module, force=False,
3504            )
3505        if comodel._is_an_ordinary_table():
3506            model.pool.add_foreign_key(
3507                self.relation, self.column2, comodel._table, 'id', self.ondelete,
3508                model, self._module,
3509            )
3510
3511    def read(self, records):
3512        context = {'active_test': False}
3513        context.update(self.context)
3514        comodel = records.env[self.comodel_name].with_context(**context)
3515        domain = self.get_domain_list(records)
3516        comodel._flush_search(domain)
3517        wquery = comodel._where_calc(domain)
3518        comodel._apply_ir_rules(wquery, 'read')
3519        order_by = comodel._generate_order_by(None, wquery)
3520        from_c, where_c, where_params = wquery.get_sql()
3521        query = """ SELECT {rel}.{id1}, {rel}.{id2} FROM {rel}, {from_c}
3522                    WHERE {where_c} AND {rel}.{id1} IN %s AND {rel}.{id2} = {tbl}.id
3523                    {order_by} {limit} OFFSET {offset}
3524                """.format(rel=self.relation, id1=self.column1, id2=self.column2,
3525                           tbl=comodel._table, from_c=from_c, where_c=where_c or '1=1',
3526                           limit=(' LIMIT %d' % self.limit) if self.limit else '',
3527                           offset=0, order_by=order_by)
3528        where_params.append(tuple(records.ids))
3529
3530        # retrieve lines and group them by record
3531        group = defaultdict(list)
3532        records._cr.execute(query, where_params)
3533        for row in records._cr.fetchall():
3534            group[row[0]].append(row[1])
3535
3536        # store result in cache
3537        cache = records.env.cache
3538        for record in records:
3539            cache.set(record, self, tuple(group[record.id]))
3540
3541    def write_real(self, records_commands_list, create=False):
3542        # records_commands_list = [(records, commands), ...]
3543        if not records_commands_list:
3544            return
3545
3546        comodel = records_commands_list[0][0].env[self.comodel_name].with_context(**self.context)
3547        cr = records_commands_list[0][0].env.cr
3548
3549        # determine old and new relation {x: ys}
3550        set = OrderedSet
3551        ids = {rid for recs, cs in records_commands_list for rid in recs.ids}
3552        records = records_commands_list[0][0].browse(ids)
3553
3554        if self.store:
3555            # Using `record[self.name]` generates 2 SQL queries when the value
3556            # is not in cache: one that actually checks access rules for
3557            # records, and the other one fetching the actual data. We use
3558            # `self.read` instead to shortcut the first query.
3559            missing_ids = list(records.env.cache.get_missing_ids(records, self))
3560            if missing_ids:
3561                self.read(records.browse(missing_ids))
3562
3563        old_relation = {record.id: set(record[self.name]._ids) for record in records}
3564        new_relation = {x: set(ys) for x, ys in old_relation.items()}
3565
3566        # determine new relation {x: ys}
3567        new_relation = defaultdict(set)
3568        for x, ys in old_relation.items():
3569            new_relation[x] = set(ys)
3570
3571        # operations on new relation
3572        def relation_add(xs, y):
3573            for x in xs:
3574                new_relation[x].add(y)
3575
3576        def relation_remove(xs, y):
3577            for x in xs:
3578                new_relation[x].discard(y)
3579
3580        def relation_set(xs, ys):
3581            for x in xs:
3582                new_relation[x] = set(ys)
3583
3584        def relation_delete(ys):
3585            # the pairs (x, y) have been cascade-deleted from relation
3586            for ys1 in old_relation.values():
3587                ys1 -= ys
3588            for ys1 in new_relation.values():
3589                ys1 -= ys
3590
3591        for recs, commands in records_commands_list:
3592            to_create = []  # line vals to create
3593            to_delete = []  # line ids to delete
3594            for command in (commands or ()):
3595                if not isinstance(command, (list, tuple)) or not command:
3596                    continue
3597                if command[0] == 0:
3598                    to_create.append((recs._ids, command[2]))
3599                elif command[0] == 1:
3600                    comodel.browse(command[1]).write(command[2])
3601                elif command[0] == 2:
3602                    to_delete.append(command[1])
3603                elif command[0] == 3:
3604                    relation_remove(recs._ids, command[1])
3605                elif command[0] == 4:
3606                    relation_add(recs._ids, command[1])
3607                elif command[0] in (5, 6):
3608                    # new lines must no longer be linked to records
3609                    to_create = [(set(ids) - set(recs._ids), vals) for (ids, vals) in to_create]
3610                    relation_set(recs._ids, command[2] if command[0] == 6 else ())
3611
3612            if to_create:
3613                # create lines in batch, and link them
3614                lines = comodel.create([vals for ids, vals in to_create])
3615                for line, (ids, vals) in zip(lines, to_create):
3616                    relation_add(ids, line.id)
3617
3618            if to_delete:
3619                # delete lines in batch
3620                comodel.browse(to_delete).unlink()
3621                relation_delete(to_delete)
3622
3623        # update the cache of self
3624        cache = records.env.cache
3625        for record in records:
3626            cache.set(record, self, tuple(new_relation[record.id]))
3627
3628        # process pairs to add (beware of duplicates)
3629        pairs = [(x, y) for x, ys in new_relation.items() for y in ys - old_relation[x]]
3630        if pairs:
3631            if self.store:
3632                query = "INSERT INTO {} ({}, {}) VALUES {} ON CONFLICT DO NOTHING".format(
3633                    self.relation, self.column1, self.column2, ", ".join(["%s"] * len(pairs)),
3634                )
3635                cr.execute(query, pairs)
3636
3637            # update the cache of inverse fields
3638            y_to_xs = defaultdict(set)
3639            for x, y in pairs:
3640                y_to_xs[y].add(x)
3641            for invf in records._field_inverses[self]:
3642                domain = invf.get_domain_list(comodel)
3643                valid_ids = set(records.filtered_domain(domain)._ids)
3644                if not valid_ids:
3645                    continue
3646                for y, xs in y_to_xs.items():
3647                    corecord = comodel.browse(y)
3648                    try:
3649                        ids0 = cache.get(corecord, invf)
3650                        ids1 = tuple(set(ids0) | (xs & valid_ids))
3651                        cache.set(corecord, invf, ids1)
3652                    except KeyError:
3653                        pass
3654
3655        # process pairs to remove
3656        pairs = [(x, y) for x, ys in old_relation.items() for y in ys - new_relation[x]]
3657        if pairs:
3658            y_to_xs = defaultdict(set)
3659            for x, y in pairs:
3660                y_to_xs[y].add(x)
3661
3662            if self.store:
3663                # express pairs as the union of cartesian products:
3664                #    pairs = [(1, 11), (1, 12), (1, 13), (2, 11), (2, 12), (2, 14)]
3665                # -> y_to_xs = {11: {1, 2}, 12: {1, 2}, 13: {1}, 14: {2}}
3666                # -> xs_to_ys = {{1, 2}: {11, 12}, {2}: {14}, {1}: {13}}
3667                xs_to_ys = defaultdict(set)
3668                for y, xs in y_to_xs.items():
3669                    xs_to_ys[frozenset(xs)].add(y)
3670                # delete the rows where (id1 IN xs AND id2 IN ys) OR ...
3671                COND = "{} IN %s AND {} IN %s".format(self.column1, self.column2)
3672                query = "DELETE FROM {} WHERE {}".format(
3673                    self.relation, " OR ".join([COND] * len(xs_to_ys)),
3674                )
3675                params = [arg for xs, ys in xs_to_ys.items() for arg in [tuple(xs), tuple(ys)]]
3676                cr.execute(query, params)
3677
3678            # update the cache of inverse fields
3679            for invf in records._field_inverses[self]:
3680                for y, xs in y_to_xs.items():
3681                    corecord = comodel.browse(y)
3682                    try:
3683                        ids0 = cache.get(corecord, invf)
3684                        ids1 = tuple(id_ for id_ in ids0 if id_ not in xs)
3685                        cache.set(corecord, invf, ids1)
3686                    except KeyError:
3687                        pass
3688
3689        return records.filtered(
3690            lambda record: new_relation[record.id] != old_relation[record.id]
3691        )
3692
3693    def write_new(self, records_commands_list):
3694        """ Update self on new records. """
3695        if not records_commands_list:
3696            return
3697
3698        model = records_commands_list[0][0].browse()
3699        comodel = model.env[self.comodel_name].with_context(**self.context)
3700        new = lambda id_: id_ and NewId(id_)
3701
3702        # determine old and new relation {x: ys}
3703        set = OrderedSet
3704        old_relation = {record.id: set(record[self.name]._ids) for records, _ in records_commands_list for record in records}
3705        new_relation = {x: set(ys) for x, ys in old_relation.items()}
3706        ids = set(old_relation.keys())
3707
3708        records = model.browse(ids)
3709
3710        for recs, commands in records_commands_list:
3711            for command in commands:
3712                if not isinstance(command, (list, tuple)) or not command:
3713                    continue
3714                if command[0] == 0:
3715                    line_id = comodel.new(command[2], ref=command[1]).id
3716                    for line_ids in new_relation.values():
3717                        line_ids.add(line_id)
3718                elif command[0] == 1:
3719                    line_id = new(command[1])
3720                    comodel.browse([line_id]).update(command[2])
3721                elif command[0] == 2:
3722                    line_id = new(command[1])
3723                    for line_ids in new_relation.values():
3724                        line_ids.discard(line_id)
3725                elif command[0] == 3:
3726                    line_id = new(command[1])
3727                    for line_ids in new_relation.values():
3728                        line_ids.discard(line_id)
3729                elif command[0] == 4:
3730                    line_id = new(command[1])
3731                    for line_ids in new_relation.values():
3732                        line_ids.add(line_id)
3733                elif command[0] in (5, 6):
3734                    # new lines must no longer be linked to records
3735                    line_ids = command[2] if command[0] == 6 else ()
3736                    line_ids = set(new(line_id) for line_id in line_ids)
3737                    for id_ in recs._ids:
3738                        new_relation[id_] = set(line_ids)
3739
3740        if new_relation == old_relation:
3741            return records.browse()
3742
3743        # update the cache of self
3744        cache = records.env.cache
3745        for record in records:
3746            cache.set(record, self, tuple(new_relation[record.id]))
3747
3748        # process pairs to add (beware of duplicates)
3749        pairs = [(x, y) for x, ys in new_relation.items() for y in ys - old_relation[x]]
3750        if pairs:
3751            # update the cache of inverse fields
3752            y_to_xs = defaultdict(set)
3753            for x, y in pairs:
3754                y_to_xs[y].add(x)
3755            for invf in records._field_inverses[self]:
3756                domain = invf.get_domain_list(comodel)
3757                valid_ids = set(records.filtered_domain(domain)._ids)
3758                if not valid_ids:
3759                    continue
3760                for y, xs in y_to_xs.items():
3761                    corecord = comodel.browse([y])
3762                    try:
3763                        ids0 = cache.get(corecord, invf)
3764                        ids1 = tuple(set(ids0) | (xs & valid_ids))
3765                        cache.set(corecord, invf, ids1)
3766                    except KeyError:
3767                        pass
3768
3769        # process pairs to remove
3770        pairs = [(x, y) for x, ys in old_relation.items() for y in ys - new_relation[x]]
3771        if pairs:
3772            # update the cache of inverse fields
3773            y_to_xs = defaultdict(set)
3774            for x, y in pairs:
3775                y_to_xs[y].add(x)
3776            for invf in records._field_inverses[self]:
3777                for y, xs in y_to_xs.items():
3778                    corecord = comodel.browse([y])
3779                    try:
3780                        ids0 = cache.get(corecord, invf)
3781                        ids1 = tuple(id_ for id_ in ids0 if id_ not in xs)
3782                        cache.set(corecord, invf, ids1)
3783                    except KeyError:
3784                        pass
3785
3786        return records.filtered(
3787            lambda record: new_relation[record.id] != old_relation[record.id]
3788        )
3789
3790
3791class Id(Field):
3792    """ Special case for field 'id'. """
3793    type = 'integer'
3794    column_type = ('int4', 'int4')
3795
3796    string = 'ID'
3797    store = True
3798    readonly = True
3799    prefetch = False
3800
3801    def update_db(self, model, columns):
3802        pass                            # this column is created with the table
3803
3804    def __get__(self, record, owner):
3805        if record is None:
3806            return self         # the field is accessed through the class owner
3807
3808        # the code below is written to make record.id as quick as possible
3809        ids = record._ids
3810        size = len(ids)
3811        if size == 0:
3812            return False
3813        elif size == 1:
3814            return ids[0]
3815        raise ValueError("Expected singleton: %s" % record)
3816
3817    def __set__(self, record, value):
3818        raise TypeError("field 'id' cannot be assigned")
3819
3820
3821def prefetch_many2one_ids(record, field):
3822    """ Return an iterator over the ids of the cached values of a many2one
3823        field for the prefetch set of a record.
3824    """
3825    records = record.browse(record._prefetch_ids)
3826    ids = record.env.cache.get_values(records, field)
3827    return unique(id_ for id_ in ids if id_ is not None)
3828
3829
3830def prefetch_x2many_ids(record, field):
3831    """ Return an iterator over the ids of the cached values of an x2many
3832        field for the prefetch set of a record.
3833    """
3834    records = record.browse(record._prefetch_ids)
3835    ids_list = record.env.cache.get_values(records, field)
3836    return unique(id_ for ids in ids_list for id_ in ids)
3837
3838
3839def apply_required(model, field_name):
3840    """ Set a NOT NULL constraint on the given field, if necessary. """
3841    # At the time this function is called, the model's _fields may have been reset, although
3842    # the model's class is still the same. Retrieve the field to see whether the NOT NULL
3843    # constraint still applies
3844    field = model._fields[field_name]
3845    if field.store and field.required:
3846        sql.set_not_null(model.env.cr, model._table, field_name)
3847
3848
3849# imported here to avoid dependency cycle issues
3850from .exceptions import AccessError, MissingError, UserError
3851from .models import check_pg_name, BaseModel, NewId, IdType, expand_ids, PREFETCH_MAX
3852