You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
222 lines
8.8 KiB
222 lines
8.8 KiB
# -*- coding: utf-8 -*-
|
|
# © 2016-2017 Akretion (http://www.akretion.com)
|
|
# © 2016-2017 Camptocamp (http://www.camptocamp.com/)
|
|
# © 2019 ACSONE SA/NV
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
|
|
|
from odoo import api, models, fields
|
|
|
|
|
|
class Base(models.AbstractModel):
|
|
_inherit = "base"
|
|
|
|
@api.model
|
|
def _compute_onchange_dirty(
|
|
self, original_record, modified_record, fieldname_onchange=None
|
|
):
|
|
"""
|
|
Return the list of dirty fields. (designed to be called by
|
|
play_onchanges)
|
|
The list of dirty fields is computed from the list marked as dirty
|
|
on the record. Form this list, we remove the fields for which the value
|
|
into the original record is the same as the one into the current record
|
|
:param original_record:
|
|
:return: changed values
|
|
"""
|
|
dirties = []
|
|
if fieldname_onchange:
|
|
for field_name, field in modified_record._fields.items():
|
|
# special case. We consider that a related field is modified
|
|
# if a modified field is in the first position of the path
|
|
# to traverse to get the value.
|
|
if field.related and field.related[0].startswith(
|
|
fieldname_onchange
|
|
):
|
|
dirties.append(field_name)
|
|
for field_name in modified_record._get_dirty():
|
|
original_value = original_record[field_name]
|
|
new_value = modified_record[field_name]
|
|
if original_value == new_value:
|
|
continue
|
|
dirties.append(field_name)
|
|
for field_name, field in modified_record._fields.items():
|
|
new_value = modified_record[field_name]
|
|
if field.type not in ("one2many", "many2many"):
|
|
continue
|
|
# if the field is a one2many or many2many, check that any
|
|
# item is a new Id
|
|
if models.NewId in [type(i.id) for i in new_value]:
|
|
dirties.append(field_name)
|
|
continue
|
|
# if the field is a one2many or many2many, check if any item
|
|
# is dirty
|
|
for r in new_value:
|
|
if r._get_dirty():
|
|
ori = [
|
|
r1
|
|
for r1 in original_record[field_name]
|
|
if r1.id == r.id
|
|
][0]
|
|
# if fieldname_onchange is None avoid recurssion...
|
|
if fieldname_onchange and self._compute_onchange_dirty(
|
|
ori, r
|
|
):
|
|
dirties.append(field_name)
|
|
break
|
|
return dirties
|
|
|
|
def _convert_to_onchange(self, record, field, value):
|
|
if field.type == "many2one":
|
|
# for many2one, we keep the id and don't call the
|
|
# convert_on_change to avoid the call to name_get by the
|
|
# convert_to_onchange
|
|
if value.id:
|
|
return value.id
|
|
return False
|
|
elif field.type in ("one2many", "many2many"):
|
|
result = [(5,)]
|
|
for record in value:
|
|
vals = {}
|
|
for name in record._cache:
|
|
if name in models.LOG_ACCESS_COLUMNS:
|
|
continue
|
|
v = record[name]
|
|
f = record._fields[name]
|
|
if f.type == "many2one" and isinstance(v.id, models.NewId):
|
|
continue
|
|
vals[name] = self._convert_to_onchange(record, f, v)
|
|
if not record.id:
|
|
result.append((0, 0, vals))
|
|
elif vals:
|
|
result.append((1, record.id, vals))
|
|
else:
|
|
result.append((4, record.id))
|
|
return result
|
|
else:
|
|
return field.convert_to_onchange(value, record, [field.name])
|
|
|
|
def play_onchanges(self, values, onchange_fields=None):
|
|
"""
|
|
Play the onchange methods defined on the current record and return the
|
|
changed values.
|
|
The record is not modified by the onchange.
|
|
|
|
The intend of this method is to provide a way to get on the server side
|
|
the values returned by the onchange methods when called by the UI.
|
|
This method is useful in B2B contexts where records are created or
|
|
modified from server side.
|
|
|
|
The returned values are those changed by the execution of onchange
|
|
methods registered for the onchange_fields according to the provided
|
|
values. As consequence, the result will not contain the modifications
|
|
that could occurs by the execution of compute methods registered for
|
|
the same onchange_fields.
|
|
|
|
It's on purpose that we avoid to trigger the compute methods for the
|
|
onchange_fields. These compute methods will be triggered when calling
|
|
the create or write method. In this way we avoid to compute useless
|
|
information.
|
|
|
|
|
|
:param values: dict of input value that
|
|
:param onchange_fields: fields for which onchange methods will be
|
|
played. If not provided, the list of field is based on the values keys.
|
|
Order in onchange_fields is very important as onchanges methods will
|
|
be played in that order.
|
|
:return: changed values
|
|
|
|
This method reimplement the onchange method to be able to work on the
|
|
current recordset if provided.
|
|
"""
|
|
env = self.env
|
|
if self:
|
|
self.ensure_one()
|
|
|
|
if not onchange_fields:
|
|
onchange_fields = values.keys()
|
|
|
|
elif not isinstance(onchange_fields, list):
|
|
onchange_fields = [onchange_fields]
|
|
|
|
if not onchange_fields:
|
|
onchange_fields = values.keys()
|
|
|
|
# filter out keys in field_onchange that do not refer to actual fields
|
|
names = [n for n in onchange_fields if n in self._fields]
|
|
|
|
# create a new record with values, and attach ``self`` to it
|
|
with env.do_in_onchange():
|
|
# keep a copy of the original record.
|
|
# attach ``self`` with a different context (for cache consistency)
|
|
origin = self.with_context(__onchange=True)
|
|
origin_dirty = set(self._get_dirty())
|
|
fields.copy_cache(self, origin.env)
|
|
if self:
|
|
record = self
|
|
record.update(values)
|
|
else:
|
|
# initialize with default values, they may be used in onchange
|
|
new_values = self.default_get(self._fields.keys())
|
|
new_values.update(values)
|
|
record = self.new(new_values)
|
|
values = {name: record[name] for name in record._cache}
|
|
record._origin = origin
|
|
|
|
# determine which field(s) should be triggered an onchange
|
|
todo = list(names) or list(values)
|
|
done = set()
|
|
|
|
# dummy assignment: trigger invalidations on the record
|
|
with env.do_in_onchange():
|
|
for name in todo:
|
|
if name == "id":
|
|
continue
|
|
value = record[name]
|
|
field = self._fields[name]
|
|
if field.type == "many2one" and field.delegate and not value:
|
|
# do not nullify all fields of parent record for new
|
|
# records
|
|
continue
|
|
record[name] = value
|
|
|
|
dirty = set()
|
|
|
|
# process names in order (or the keys of values if no name given)
|
|
while todo:
|
|
name = todo.pop(0)
|
|
if name in done:
|
|
continue
|
|
done.add(name)
|
|
|
|
with env.do_in_onchange():
|
|
# apply field-specific onchange methods
|
|
record._onchange_eval(name, "1", {})
|
|
|
|
# determine which fields have been modified
|
|
dirties = self._compute_onchange_dirty(origin, record, name)
|
|
dirty |= set(dirties)
|
|
todo.extend(dirties)
|
|
|
|
# prepare the result to return a dictionary with the new values for
|
|
# the dirty fields
|
|
result = {}
|
|
for name in dirty:
|
|
field = self._fields[name]
|
|
value = record[name]
|
|
if field.type == "many2one" and isinstance(value.id, models.NewId):
|
|
continue
|
|
result[name] = self._convert_to_onchange(record, field, value)
|
|
|
|
# reset dirty values into the current record
|
|
if self:
|
|
to_reset = dirty | set(values.keys())
|
|
with env.do_in_onchange():
|
|
for name in to_reset:
|
|
original = origin[name]
|
|
new = self[name]
|
|
if original == new:
|
|
continue
|
|
self[name] = origin[name]
|
|
env.dirty[record].discard(name)
|
|
env.dirty[record].update(origin_dirty)
|
|
return result
|