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.
 
 
 

219 lines
8.7 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 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