OCA-git-bot
5 years ago
32 changed files with 1072 additions and 57 deletions
-
6.travis.yml
-
1onchange_helper/README.rst
-
7onchange_helper/__manifest__.py
-
249onchange_helper/models/models.py
-
64onchange_helper/tests/test_onchange.py
-
1setup/test_onchange_helper/odoo/__init__.py
-
1setup/test_onchange_helper/odoo/addons/__init__.py
-
1setup/test_onchange_helper/odoo/addons/test_onchange_helper
-
6setup/test_onchange_helper/setup.py
-
9test_onchange_helper/README.rst
-
1test_onchange_helper/__init__.py
-
26test_onchange_helper/__manifest__.py
-
12test_onchange_helper/demo/test_onchange_helper_discussion.xml
-
23test_onchange_helper/demo/test_onchange_helper_message.xml
-
6test_onchange_helper/models/__init__.py
-
44test_onchange_helper/models/test_onchange_helper_category.py
-
72test_onchange_helper/models/test_onchange_helper_discussion.py
-
20test_onchange_helper/models/test_onchange_helper_emailmessage.py
-
57test_onchange_helper/models/test_onchange_helper_message.py
-
25test_onchange_helper/models/test_onchange_helper_multi.py
-
15test_onchange_helper/models/test_onchange_helper_multi_line.py
-
1test_onchange_helper/readme/CONTRIBUTORS.rst
-
1test_onchange_helper/readme/DESCRIPTION.rst
-
17test_onchange_helper/security/test_onchange_helper_category.xml
-
17test_onchange_helper/security/test_onchange_helper_discussion.xml
-
17test_onchange_helper/security/test_onchange_helper_emailmessage.xml
-
17test_onchange_helper/security/test_onchange_helper_message.xml
-
17test_onchange_helper/security/test_onchange_helper_multi.xml
-
17test_onchange_helper/security/test_onchange_helper_multi_line.xml
-
BINtest_onchange_helper/static/description/icon.png
-
1test_onchange_helper/tests/__init__.py
-
378test_onchange_helper/tests/test_onchange_helper.py
@ -1,51 +1,222 @@ |
|||||
# -*- coding: utf-8 -*- |
# -*- coding: utf-8 -*- |
||||
# © 2016-2017 Akretion (http://www.akretion.com) |
# © 2016-2017 Akretion (http://www.akretion.com) |
||||
# © 2016-2017 Camptocamp (http://www.camptocamp.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). |
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
||||
|
|
||||
from odoo import api, models |
|
||||
|
from odoo import api, models, fields |
||||
|
|
||||
|
|
||||
class Base(models.AbstractModel): |
class Base(models.AbstractModel): |
||||
_inherit = 'base' |
|
||||
|
_inherit = "base" |
||||
|
|
||||
@api.model |
@api.model |
||||
def _get_new_values(self, record, on_change_result): |
|
||||
vals = on_change_result.get('value', {}) |
|
||||
new_values = {} |
|
||||
for fieldname, value in vals.iteritems(): |
|
||||
if fieldname not in record: |
|
||||
column = self._fields[fieldname] |
|
||||
if value and column.type == 'many2one': |
|
||||
value = value[0] # many2one are tuple (id, name) |
|
||||
new_values[fieldname] = value |
|
||||
return new_values |
|
||||
|
|
||||
def play_onchanges(self, values, onchange_fields): |
|
||||
onchange_specs = self._onchange_spec() |
|
||||
# we need all fields in the dict even the empty ones |
|
||||
# otherwise 'onchange()' will not apply changes to them |
|
||||
all_values = values.copy() |
|
||||
|
|
||||
# If self is a record (play onchange on existing record) |
|
||||
# we take the value of the field |
|
||||
# If self is an empty record we will have an empty value |
|
||||
|
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: |
if self: |
||||
self.ensure_one() |
self.ensure_one() |
||||
record_values = self._convert_to_write(self.read()[0]) |
|
||||
else: |
|
||||
record_values = {} |
|
||||
for field in self._fields: |
|
||||
if field not in all_values: |
|
||||
all_values[field] = record_values.get(field, False) |
|
||||
|
|
||||
new_values = {} |
|
||||
for field in onchange_fields: |
|
||||
onchange_values = self.onchange(all_values, field, onchange_specs) |
|
||||
new_values.update(self._get_new_values(values, onchange_values)) |
|
||||
all_values.update(new_values) |
|
||||
|
|
||||
return { |
|
||||
f: v for f, v in all_values.iteritems() |
|
||||
if not (self._fields[f].compute and not self._fields[f].inverse) |
|
||||
and (f in values or f in new_values)} |
|
||||
|
|
||||
|
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 |
@ -1,26 +1,64 @@ |
|||||
# -*- coding: utf-8 -*- |
# -*- coding: utf-8 -*- |
||||
# Copyright 2018 Akretion (http://www.akretion.com). |
# Copyright 2018 Akretion (http://www.akretion.com). |
||||
|
# Copyright 2019 ACSONE SA/NV |
||||
# @author Sébastien BEAU <sebastien.beau@akretion.com> |
# @author Sébastien BEAU <sebastien.beau@akretion.com> |
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
||||
|
|
||||
import openerp.tests.common as common |
|
||||
|
import mock |
||||
|
import odoo.tests.common as common |
||||
|
|
||||
|
|
||||
class TestOnchange(common.TransactionCase): |
class TestOnchange(common.TransactionCase): |
||||
|
|
||||
def test_playing_onchange_on_model(self): |
def test_playing_onchange_on_model(self): |
||||
result = self.env['res.partner'].play_onchanges({ |
|
||||
'company_type': 'company', |
|
||||
}, ['company_type']) |
|
||||
self.assertEqual(result['is_company'], True) |
|
||||
|
res_partner = self.env["res.partner"] |
||||
|
with mock.patch.object( |
||||
|
res_partner.__class__, "write" |
||||
|
) as patched_write: |
||||
|
result = self.env["res.partner"].play_onchanges( |
||||
|
{"company_type": "company"}, ["company_type"] |
||||
|
) |
||||
|
patched_write.assert_not_called() |
||||
|
self.assertEqual(result["is_company"], True) |
||||
|
|
||||
def test_playing_onchange_on_record(self): |
def test_playing_onchange_on_record(self): |
||||
company = self.env.ref('base.main_company') |
|
||||
result = company.play_onchanges({ |
|
||||
'email': 'contact@akretion.com'}, |
|
||||
['email']) |
|
||||
|
company = self.env.ref("base.main_company") |
||||
|
with mock.patch.object(company.__class__, "write") as patched_write: |
||||
|
result = company.play_onchanges( |
||||
|
{"email": "contact@akretion.com"}, ["email"] |
||||
|
) |
||||
|
patched_write.assert_not_called() |
||||
|
modified_fields = set(result.keys()) |
||||
|
self.assertSetEqual( |
||||
|
modified_fields, {"rml_footer", "rml_footer_readonly"} |
||||
|
) |
||||
self.assertEqual( |
self.assertEqual( |
||||
result['rml_footer'], |
|
||||
u'Phone: +1 555 123 8069 | Email: contact@akretion.com | ' |
|
||||
u'Website: http://www.example.com') |
|
||||
self.assertEqual(company.email, u'info@yourcompany.example.com') |
|
||||
|
result["rml_footer"], |
||||
|
u"Phone: +1 555 123 8069 | Email: contact@akretion.com | " |
||||
|
u"Website: http://www.example.com", |
||||
|
) |
||||
|
self.assertEqual(result["rml_footer_readonly"], result["rml_footer"]) |
||||
|
|
||||
|
# check that the original record is not modified |
||||
|
self.assertFalse(company._get_dirty()) |
||||
|
self.assertEqual(company.email, u"info@yourcompany.example.com") |
||||
|
|
||||
|
def test_onchange_record_with_dirty_field(self): |
||||
|
company = self.env.ref("base.main_company") |
||||
|
company._set_dirty("name") |
||||
|
self.assertListEqual(company._get_dirty(), ["name"]) |
||||
|
company.play_onchanges({"email": "contact@akretion.com"}, ["email"]) |
||||
|
self.assertListEqual(company._get_dirty(), ["name"]) |
||||
|
|
||||
|
def test_onchange_wrong_key(self): |
||||
|
res_partner = self.env["res.partner"] |
||||
|
with mock.patch.object( |
||||
|
res_partner.__class__, "write" |
||||
|
) as patched_write: |
||||
|
# we specify a wrong field name... This field should be |
||||
|
# ignored |
||||
|
result = self.env["res.partner"].play_onchanges( |
||||
|
{"company_type": "company"}, ["company_type", "wrong_key"] |
||||
|
) |
||||
|
patched_write.assert_not_called() |
||||
|
self.assertEqual(result["is_company"], True) |
@ -0,0 +1 @@ |
|||||
|
__import__('pkg_resources').declare_namespace(__name__) |
@ -0,0 +1 @@ |
|||||
|
__import__('pkg_resources').declare_namespace(__name__) |
@ -0,0 +1 @@ |
|||||
|
../../../../test_onchange_helper |
@ -0,0 +1,6 @@ |
|||||
|
import setuptools |
||||
|
|
||||
|
setuptools.setup( |
||||
|
setup_requires=['setuptools-odoo'], |
||||
|
odoo_addon=True, |
||||
|
) |
@ -0,0 +1,9 @@ |
|||||
|
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg |
||||
|
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html |
||||
|
:alt: License: AGPL-3 |
||||
|
|
||||
|
==================== |
||||
|
Onchange Helper TEST |
||||
|
==================== |
||||
|
|
||||
|
*This module is only intended to test the onchange_helper addon.* |
@ -0,0 +1 @@ |
|||||
|
from . import models |
@ -0,0 +1,26 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2019 ACSONE SA/NV |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
||||
|
|
||||
|
{ |
||||
|
"name": "Onchange Helper TEST", |
||||
|
"summary": """ |
||||
|
Test addon for the onchange_helper addon""", |
||||
|
"version": "10.0.1.0.0", |
||||
|
"license": "AGPL-3", |
||||
|
"author": "ACSONE SA/NV,Odoo Community Association (OCA)", |
||||
|
"website": "https://acsone.eu/", |
||||
|
"depends": ["onchange_helper"], |
||||
|
"data": [ |
||||
|
'security/test_onchange_helper_multi_line.xml', |
||||
|
'security/test_onchange_helper_multi.xml', |
||||
|
"security/test_onchange_helper_emailmessage.xml", |
||||
|
"security/test_onchange_helper_message.xml", |
||||
|
"security/test_onchange_helper_discussion.xml", |
||||
|
"security/test_onchange_helper_category.xml", |
||||
|
], |
||||
|
"demo": [ |
||||
|
"demo/test_onchange_helper_discussion.xml", |
||||
|
"demo/test_onchange_helper_message.xml", |
||||
|
] |
||||
|
} |
@ -0,0 +1,12 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<!-- Copyright 2019 ACSONE SA/NV |
||||
|
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). --> |
||||
|
|
||||
|
<odoo noupdate="1"> |
||||
|
|
||||
|
<record id="discussion_demo_0" model="test_onchange_helper.discussion"> |
||||
|
<field name="name">Stuff</field> |
||||
|
<field name="participants" eval="[(4, ref('base.user_root')), (4, ref('base.user_demo'))]"/> |
||||
|
</record> |
||||
|
|
||||
|
</odoo> |
@ -0,0 +1,23 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<!-- Copyright 2019 ACSONE SA/NV |
||||
|
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). --> |
||||
|
|
||||
|
<odoo noupdate="1"> |
||||
|
|
||||
|
<record id="message_demo_0_0" model="test_onchange_helper.message"> |
||||
|
<field name="discussion" ref="discussion_demo_0"/> |
||||
|
<field name="body">Hey dude!</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="message_demo_0_1" model="test_onchange_helper.message"> |
||||
|
<field name="discussion" ref="discussion_demo_0"/> |
||||
|
<field name="author" ref="base.user_demo"/> |
||||
|
<field name="body">What's up?</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="message_demo_0_2" model="test_onchange_helper.message"> |
||||
|
<field name="discussion" ref="discussion_demo_0"/> |
||||
|
<field name="body">This is a much longer message</field> |
||||
|
</record> |
||||
|
|
||||
|
</odoo> |
@ -0,0 +1,6 @@ |
|||||
|
from . import test_onchange_helper_category |
||||
|
from . import test_onchange_helper_discussion |
||||
|
from . import test_onchange_helper_message |
||||
|
from . import test_onchange_helper_emailmessage |
||||
|
from . import test_onchange_helper_multi |
||||
|
from . import test_onchange_helper_multi_line |
@ -0,0 +1,44 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2019 ACSONE SA/NV |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
||||
|
|
||||
|
from odoo import api, fields, models |
||||
|
|
||||
|
|
||||
|
class TestOnchangeHelperCategory(models.Model): |
||||
|
|
||||
|
_name = "test_onchange_helper.category" |
||||
|
_description = "Test Onchange Helper Category" |
||||
|
|
||||
|
name = fields.Char(required=True) |
||||
|
parent = fields.Many2one(_name) |
||||
|
root_categ = fields.Many2one(_name) |
||||
|
display_name = fields.Char() |
||||
|
computed_display_name = fields.Char( |
||||
|
compute="_compute_computed_display_name" |
||||
|
) |
||||
|
|
||||
|
@api.onchange("name", "parent") |
||||
|
def _onchange_name_or_parent(self): |
||||
|
if self.parent: |
||||
|
self.display_name = self.parent.display_name + " / " + self.name |
||||
|
else: |
||||
|
self.display_name = self.name |
||||
|
|
||||
|
@api.onchange("parent") |
||||
|
def _onchange_parent(self): |
||||
|
current = self |
||||
|
while current.parent: |
||||
|
current = current.parent |
||||
|
if current == self: |
||||
|
self.root_categ = False |
||||
|
else: |
||||
|
self.root_categ = current |
||||
|
|
||||
|
@api.depends("name", "parent.display_name") |
||||
|
def _compute_computed_display_name(self): |
||||
|
for cat in self: |
||||
|
if cat.parent: |
||||
|
self.display_name = cat.parent.display_name + " / " + cat.name |
||||
|
else: |
||||
|
cat.display_name = cat.name |
@ -0,0 +1,72 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2019 ACSONE SA/NV |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
||||
|
|
||||
|
from odoo import api, fields, models |
||||
|
|
||||
|
|
||||
|
class TestOnchangeHelperDiscussion(models.Model): |
||||
|
|
||||
|
_name = "test_onchange_helper.discussion" |
||||
|
_description = "Test Onchange Helper Discussion" |
||||
|
|
||||
|
name = fields.Char( |
||||
|
string="Title", |
||||
|
required=True, |
||||
|
help="General description of what this discussion is about.", |
||||
|
) |
||||
|
moderator = fields.Many2one("res.users") |
||||
|
categories = fields.Many2many( |
||||
|
"test_onchange_helper.category", |
||||
|
"test_onchange_helper_discussion_category", |
||||
|
"discussion", |
||||
|
"category", |
||||
|
) |
||||
|
participants = fields.Many2many("res.users") |
||||
|
messages = fields.One2many("test_onchange_helper.message", "discussion") |
||||
|
message_concat = fields.Text(string="Message concatenate") |
||||
|
important_messages = fields.One2many( |
||||
|
"test_onchange_helper.message", |
||||
|
"discussion", |
||||
|
domain=[("important", "=", True)], |
||||
|
) |
||||
|
very_important_messages = fields.One2many( |
||||
|
"test_onchange_helper.message", |
||||
|
"discussion", |
||||
|
domain=lambda self: self._domain_very_important(), |
||||
|
) |
||||
|
emails = fields.One2many("test_onchange_helper.emailmessage", "discussion") |
||||
|
important_emails = fields.One2many( |
||||
|
"test_onchange_helper.emailmessage", |
||||
|
"discussion", |
||||
|
domain=[("important", "=", True)], |
||||
|
) |
||||
|
|
||||
|
def _domain_very_important(self): |
||||
|
"""Ensure computed O2M domains work as expected.""" |
||||
|
return [("important", "=", True)] |
||||
|
|
||||
|
@api.onchange("name") |
||||
|
def _onchange_name(self): |
||||
|
# test onchange modifying one2many field values |
||||
|
# update body of existings messages and emails |
||||
|
for message in self.messages: |
||||
|
message.body = "not last dummy message" |
||||
|
for message in self.important_messages: |
||||
|
message.body = "not last dummy message" |
||||
|
# add new dummy message |
||||
|
message_vals = self.messages._add_missing_default_values( |
||||
|
{"body": "dummy message", "important": True} |
||||
|
) |
||||
|
self.messages |= self.messages.new(message_vals) |
||||
|
self.important_messages |= self.messages.new(message_vals) |
||||
|
|
||||
|
@api.onchange("moderator") |
||||
|
def _onchange_moderator(self): |
||||
|
self.participants |= self.moderator |
||||
|
|
||||
|
@api.onchange("messages") |
||||
|
def _onchange_messages(self): |
||||
|
self.message_concat = "\n".join( |
||||
|
["%s:%s" % (m.name, m.body) for m in self.messages] |
||||
|
) |
@ -0,0 +1,20 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2019 ACSONE SA/NV |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
||||
|
|
||||
|
from odoo import fields, models |
||||
|
|
||||
|
|
||||
|
class TestOnchangeHelperEmailmessage(models.Model): |
||||
|
|
||||
|
_name = "test_onchange_helper.emailmessage" |
||||
|
_description = "Test Onchange Helper Emailmessage" |
||||
|
_inherits = {"test_onchange_helper.message": "message"} |
||||
|
|
||||
|
message = fields.Many2one( |
||||
|
"test_onchange_helper.message", |
||||
|
"Message", |
||||
|
required=True, |
||||
|
ondelete="cascade", |
||||
|
) |
||||
|
email_to = fields.Char("To") |
@ -0,0 +1,57 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2019 ACSONE SA/NV |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
||||
|
|
||||
|
from odoo import api, fields, models |
||||
|
|
||||
|
|
||||
|
class TestOnchangeHelperMessage(models.Model): |
||||
|
|
||||
|
_name = "test_onchange_helper.message" |
||||
|
_description = "Test Onchange Helper Message" |
||||
|
|
||||
|
discussion = fields.Many2one( |
||||
|
"test_onchange_helper.discussion", ondelete="cascade" |
||||
|
) |
||||
|
body = fields.Text() |
||||
|
author = fields.Many2one("res.users", default=lambda self: self.env.user) |
||||
|
name = fields.Char(string="Title", compute="_compute_name", store=True) |
||||
|
display_name = fields.Char( |
||||
|
string="Abstract", compute="_compute_display_name" |
||||
|
) |
||||
|
discussion_name = fields.Char( |
||||
|
related="discussion.name", string="Discussion Name" |
||||
|
) |
||||
|
author_partner = fields.Many2one( |
||||
|
"res.partner", |
||||
|
compute="_compute_author_partner", |
||||
|
search="_search_author_partner", |
||||
|
) |
||||
|
important = fields.Boolean() |
||||
|
|
||||
|
@api.one |
||||
|
@api.depends("author.name", "discussion.name") |
||||
|
def _compute_name(self): |
||||
|
self.name = "[%s] %s" % ( |
||||
|
self.discussion.name or "", |
||||
|
self.author.name or "", |
||||
|
) |
||||
|
|
||||
|
@api.one |
||||
|
@api.depends("author.name", "discussion.name", "body") |
||||
|
def _compute_display_name(self): |
||||
|
stuff = "[%s] %s: %s" % ( |
||||
|
self.author.name, |
||||
|
self.discussion.name or "", |
||||
|
self.body or "", |
||||
|
) |
||||
|
self.display_name = stuff[:80] |
||||
|
|
||||
|
@api.one |
||||
|
@api.depends("author", "author.partner_id") |
||||
|
def _compute_author_partner(self): |
||||
|
self.author_partner = self.author.partner_id |
||||
|
|
||||
|
@api.model |
||||
|
def _search_author_partner(self, operator, value): |
||||
|
return [("author.partner_id", operator, value)] |
@ -0,0 +1,25 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2019 ACSONE SA/NV |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
||||
|
|
||||
|
from odoo import api, fields, models |
||||
|
|
||||
|
|
||||
|
class TestOnchangeHelperMulti(models.Model): |
||||
|
|
||||
|
_name = "test_onchange_helper.multi" |
||||
|
_description = "Test Onchange Helper Multi" |
||||
|
|
||||
|
name = fields.Char(related="partner.name", readonly=True) |
||||
|
partner = fields.Many2one("res.partner") |
||||
|
lines = fields.One2many("test_onchange_helper.multi.line", "multi") |
||||
|
|
||||
|
@api.onchange("name") |
||||
|
def _onchange_name(self): |
||||
|
for line in self.lines: |
||||
|
line.name = self.name |
||||
|
|
||||
|
@api.onchange("partner") |
||||
|
def _onchange_partner(self): |
||||
|
for line in self.lines: |
||||
|
line.partner = self.partner |
@ -0,0 +1,15 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2019 ACSONE SA/NV |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
||||
|
|
||||
|
from odoo import fields, models |
||||
|
|
||||
|
|
||||
|
class TestOnchangeHelperMultiLine(models.Model): |
||||
|
|
||||
|
_name = "test_onchange_helper.multi.line" |
||||
|
_description = "Test Onchange Helper Multi Line" |
||||
|
|
||||
|
multi = fields.Many2one("test_onchange_helper.multi", ondelete="cascade") |
||||
|
name = fields.Char() |
||||
|
partner = fields.Many2one("res.partner") |
@ -0,0 +1 @@ |
|||||
|
* Laurent Mignon <laurent.mignon@acsone.eu> (https://acsone.eu) |
@ -0,0 +1 @@ |
|||||
|
*This module is only intended to test the onchange_helper addon.* |
@ -0,0 +1,17 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<!-- Copyright 2019 ACSONE SA/NV |
||||
|
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). --> |
||||
|
|
||||
|
<odoo> |
||||
|
|
||||
|
<record model="ir.model.access" id="test_onchange_helper_category_access_name"> |
||||
|
<field name="name">test_onchange_helper.category access name</field> |
||||
|
<field name="model_id" ref="model_test_onchange_helper_category"/> |
||||
|
<field name="group_id" ref="base.group_user"/> |
||||
|
<field name="perm_read" eval="1"/> |
||||
|
<field name="perm_create" eval="1"/> |
||||
|
<field name="perm_write" eval="1"/> |
||||
|
<field name="perm_unlink" eval="1"/> |
||||
|
</record> |
||||
|
|
||||
|
</odoo> |
@ -0,0 +1,17 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<!-- Copyright 2019 ACSONE SA/NV |
||||
|
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). --> |
||||
|
|
||||
|
<odoo> |
||||
|
|
||||
|
<record model="ir.model.access" id="test_onchange_helper_discussion_access_name"> |
||||
|
<field name="name">test_onchange_helperdiscussion access name</field> |
||||
|
<field name="model_id" ref="model_test_onchange_helper_discussion"/> |
||||
|
<field name="group_id" ref="base.group_user"/> |
||||
|
<field name="perm_read" eval="1"/> |
||||
|
<field name="perm_create" eval="1"/> |
||||
|
<field name="perm_write" eval="1"/> |
||||
|
<field name="perm_unlink" eval="1"/> |
||||
|
</record> |
||||
|
|
||||
|
</odoo> |
@ -0,0 +1,17 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<!-- Copyright 2019 ACSONE SA/NV |
||||
|
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). --> |
||||
|
|
||||
|
<odoo> |
||||
|
|
||||
|
<record model="ir.model.access" id="test_onchange_helper_emailmessage_access_name"> |
||||
|
<field name="name">test_onchange_helperemailmessage access name</field> |
||||
|
<field name="model_id" ref="model_test_onchange_helper_emailmessage"/> |
||||
|
<field name="group_id" ref="base.group_user"/> |
||||
|
<field name="perm_read" eval="1"/> |
||||
|
<field name="perm_create" eval="1"/> |
||||
|
<field name="perm_write" eval="1"/> |
||||
|
<field name="perm_unlink" eval="1"/> |
||||
|
</record> |
||||
|
|
||||
|
</odoo> |
@ -0,0 +1,17 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<!-- Copyright 2019 ACSONE SA/NV |
||||
|
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). --> |
||||
|
|
||||
|
<odoo> |
||||
|
|
||||
|
<record model="ir.model.access" id="test_onchange_helper_message_access_name"> |
||||
|
<field name="name">test_onchange_helper.message access name</field> |
||||
|
<field name="model_id" ref="model_test_onchange_helper_message"/> |
||||
|
<field name="group_id" ref="base.group_user"/> |
||||
|
<field name="perm_read" eval="1"/> |
||||
|
<field name="perm_create" eval="0"/> |
||||
|
<field name="perm_write" eval="0"/> |
||||
|
<field name="perm_unlink" eval="0"/> |
||||
|
</record> |
||||
|
|
||||
|
</odoo> |
@ -0,0 +1,17 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<!-- Copyright 2019 ACSONE SA/NV |
||||
|
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). --> |
||||
|
|
||||
|
<odoo> |
||||
|
|
||||
|
<record model="ir.model.access" id="test_onchange_helper_multi_access_name"> |
||||
|
<field name="name">test.onchange.helper.multi access name</field> |
||||
|
<field name="model_id" ref="model_test_onchange_helper_multi"/> |
||||
|
<field name="group_id" ref="base.group_user"/> |
||||
|
<field name="perm_read" eval="1"/> |
||||
|
<field name="perm_create" eval="0"/> |
||||
|
<field name="perm_write" eval="0"/> |
||||
|
<field name="perm_unlink" eval="0"/> |
||||
|
</record> |
||||
|
|
||||
|
</odoo> |
@ -0,0 +1,17 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<!-- Copyright 2019 ACSONE SA/NV |
||||
|
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). --> |
||||
|
|
||||
|
<odoo> |
||||
|
|
||||
|
<record model="ir.model.access" id="test_onchange_helper_multi_line_access_name"> |
||||
|
<field name="name">test.onchange.helper.multi.line access name</field> |
||||
|
<field name="model_id" ref="model_test_onchange_helper_multi_line"/> |
||||
|
<field name="group_id" ref="base.group_user"/> |
||||
|
<field name="perm_read" eval="1"/> |
||||
|
<field name="perm_create" eval="0"/> |
||||
|
<field name="perm_write" eval="0"/> |
||||
|
<field name="perm_unlink" eval="0"/> |
||||
|
</record> |
||||
|
|
||||
|
</odoo> |
After Width: 128 | Height: 128 | Size: 9.2 KiB |
@ -0,0 +1 @@ |
|||||
|
from . import test_onchange_helper |
@ -0,0 +1,378 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2019 ACSONE SA/NV |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
||||
|
|
||||
|
import mock |
||||
|
from contextlib import contextmanager |
||||
|
import odoo.tests.common as common |
||||
|
|
||||
|
|
||||
|
class TestOnchangeHelper(common.TransactionCase): |
||||
|
def setUp(self): |
||||
|
super(TestOnchangeHelper, self).setUp() |
||||
|
self.Category = self.env["test_onchange_helper.category"] |
||||
|
self.Message = self.env["test_onchange_helper.message"] |
||||
|
self.Discussion = self.env["test_onchange_helper.discussion"] |
||||
|
|
||||
|
@contextmanager |
||||
|
def assertNoOrmWrite(self, model): |
||||
|
with mock.patch.object( |
||||
|
model.__class__, "create" |
||||
|
) as mocked_create, mock.patch.object( |
||||
|
model.__class__, "write" |
||||
|
) as mocked_write: |
||||
|
yield |
||||
|
mocked_create.assert_not_called() |
||||
|
mocked_write.assert_not_called() |
||||
|
|
||||
|
def test_play_onhanges_no_recompute(self): |
||||
|
# play_onchanges must not trigger recomputes except if an onchange |
||||
|
# method access a computed field. |
||||
|
# changing 'discussion' should recompute 'name' |
||||
|
values = {"name": "Cat Name"} |
||||
|
self.env.invalidate_all() |
||||
|
with self.assertNoOrmWrite(self.Category): |
||||
|
result = self.Category.play_onchanges(values, ["name"]) |
||||
|
self.assertNotIn("computed_display_name", result) |
||||
|
|
||||
|
def test_play_onchanges_many2one_new_record(self): |
||||
|
root = self.Category.create({"name": "root"}) |
||||
|
|
||||
|
values = {"name": "test", "parent": root.id, "root_categ": False} |
||||
|
|
||||
|
self.env.invalidate_all() |
||||
|
with self.assertNoOrmWrite(self.Category): |
||||
|
result = self.Category.play_onchanges(values, "parent") |
||||
|
self.assertIn("root_categ", result) |
||||
|
self.assertEqual(result["root_categ"], root.id) |
||||
|
|
||||
|
values.update(result) |
||||
|
values["parent"] = False |
||||
|
|
||||
|
self.env.invalidate_all() |
||||
|
with self.assertNoOrmWrite(self.Category): |
||||
|
result = self.Category.play_onchanges(values, "parent") |
||||
|
# since the root_categ is already False into values the field is not |
||||
|
# changed by the onchange |
||||
|
self.assertNotIn("root_categ", result) |
||||
|
|
||||
|
def test_play_onchanges_many2one_existing_record(self): |
||||
|
root = self.Category.create({"name": "root"}) |
||||
|
|
||||
|
values = {"name": "test", "parent": root.id, "root_categ": False} |
||||
|
|
||||
|
self.env.invalidate_all() |
||||
|
with self.assertNoOrmWrite(self.Category): |
||||
|
result = self.Category.play_onchanges(values, "parent") |
||||
|
self.assertIn("root_categ", result) |
||||
|
self.assertEqual(result["root_categ"], root.id) |
||||
|
|
||||
|
# create child catefory |
||||
|
values.update(result) |
||||
|
child = self.Category.create(values) |
||||
|
self.assertEqual(root.id, child.root_categ.id) |
||||
|
|
||||
|
# since the parent is set to False and the root_categ |
||||
|
values = {"parent": False} |
||||
|
self.env.invalidate_all() |
||||
|
with self.assertNoOrmWrite(child): |
||||
|
result = child.play_onchanges(values, "parent") |
||||
|
|
||||
|
self.assertIn("root_categ", result) |
||||
|
self.assertEqual(result["root_categ"], False) |
||||
|
|
||||
|
def test_play_onchange_one2many_new_record(self): |
||||
|
""" test the effect of play_onchanges() on one2many fields on new |
||||
|
record""" |
||||
|
BODY = "What a beautiful day!" |
||||
|
USER = self.env.user |
||||
|
|
||||
|
# create an independent message |
||||
|
message = self.Message.create({"body": BODY}) |
||||
|
|
||||
|
# modify discussion name |
||||
|
values = { |
||||
|
"name": "Foo", |
||||
|
"categories": [], |
||||
|
"moderator": False, |
||||
|
"participants": [], |
||||
|
"messages": [ |
||||
|
(4, message.id), |
||||
|
( |
||||
|
0, |
||||
|
0, |
||||
|
{ |
||||
|
"name": "[%s] %s" % ("", USER.name), |
||||
|
"body": BODY, |
||||
|
"author": USER.id, |
||||
|
"important": False, |
||||
|
}, |
||||
|
), |
||||
|
], |
||||
|
} |
||||
|
self.env.invalidate_all() |
||||
|
with self.assertNoOrmWrite(self.Discussion): |
||||
|
result = self.Discussion.play_onchanges(values, "name") |
||||
|
self.assertIn("messages", result) |
||||
|
self.assertItemsEqual( |
||||
|
result["messages"], |
||||
|
[ |
||||
|
(5,), |
||||
|
( |
||||
|
1, |
||||
|
message.id, |
||||
|
{ |
||||
|
"name": "[%s] %s" % ("Foo", USER.name), |
||||
|
"body": "not last dummy message", |
||||
|
"author": message.author.id, |
||||
|
"important": message.important, |
||||
|
}, |
||||
|
), |
||||
|
( |
||||
|
0, |
||||
|
0, |
||||
|
{ |
||||
|
"name": "[%s] %s" % ("Foo", USER.name), |
||||
|
"body": "not last dummy message", |
||||
|
"author": USER.id, |
||||
|
"important": False, |
||||
|
}, |
||||
|
), |
||||
|
( |
||||
|
0, |
||||
|
0, |
||||
|
{ |
||||
|
"name": "[%s] %s" % ("Foo", USER.name), |
||||
|
"body": "dummy message", |
||||
|
"author": USER.id, |
||||
|
"important": True, |
||||
|
}, |
||||
|
), |
||||
|
], |
||||
|
) |
||||
|
|
||||
|
self.assertIn("important_messages", result) |
||||
|
self.assertItemsEqual( |
||||
|
result["important_messages"], |
||||
|
[ |
||||
|
(5,), |
||||
|
( |
||||
|
0, |
||||
|
0, |
||||
|
{ |
||||
|
"author": USER.id, |
||||
|
"body": "dummy message", |
||||
|
"important": True, |
||||
|
}, |
||||
|
), |
||||
|
], |
||||
|
) |
||||
|
|
||||
|
def test_play_onchange_one2many_existing_record(self): |
||||
|
""" test the effect of play_onchanges() on one2many fields on existing |
||||
|
record""" |
||||
|
BODY = "What a beautiful day!" |
||||
|
USER = self.env.user |
||||
|
|
||||
|
# create an independent message |
||||
|
message = self.Message.create({"body": BODY}) |
||||
|
|
||||
|
# modify discussion name |
||||
|
values = { |
||||
|
"name": "Foo", |
||||
|
"categories": [], |
||||
|
"moderator": False, |
||||
|
"participants": [], |
||||
|
"messages": [ |
||||
|
(4, message.id), |
||||
|
( |
||||
|
0, |
||||
|
0, |
||||
|
{ |
||||
|
"name": "[%s] %s" % ("", USER.name), |
||||
|
"body": BODY, |
||||
|
"author": USER.id, |
||||
|
"important": False, |
||||
|
}, |
||||
|
), |
||||
|
], |
||||
|
} |
||||
|
discussion = self.Discussion.create(values) |
||||
|
|
||||
|
values = {"name": "New foo"} |
||||
|
with self.assertNoOrmWrite(discussion): |
||||
|
result = discussion.play_onchanges(values, "name") |
||||
|
self.assertIn("messages", result) |
||||
|
self.assertItemsEqual( |
||||
|
result["messages"], |
||||
|
[ |
||||
|
(5,), |
||||
|
( |
||||
|
1, |
||||
|
discussion.messages[0].id, |
||||
|
{ |
||||
|
"name": "[%s] %s" % ("New foo", USER.name), |
||||
|
"body": "not last dummy message", |
||||
|
"author": message.author.id, |
||||
|
"important": message.important, |
||||
|
"discussion": discussion.id, |
||||
|
}, |
||||
|
), |
||||
|
( |
||||
|
1, |
||||
|
discussion.messages[1].id, |
||||
|
{ |
||||
|
"name": "[%s] %s" % ("New foo", USER.name), |
||||
|
"body": "not last dummy message", |
||||
|
"author": USER.id, |
||||
|
"important": False, |
||||
|
"discussion": discussion.id, |
||||
|
}, |
||||
|
), |
||||
|
( |
||||
|
0, |
||||
|
0, |
||||
|
{ |
||||
|
"name": "[%s] %s" % ("New foo", USER.name), |
||||
|
"body": "dummy message", |
||||
|
"author": USER.id, |
||||
|
"important": True, |
||||
|
"discussion": discussion.id, |
||||
|
}, |
||||
|
), |
||||
|
], |
||||
|
) |
||||
|
|
||||
|
self.assertIn("important_messages", result) |
||||
|
self.assertItemsEqual( |
||||
|
result["important_messages"], |
||||
|
[ |
||||
|
(5,), |
||||
|
( |
||||
|
0, |
||||
|
0, |
||||
|
{ |
||||
|
"author": USER.id, |
||||
|
"body": "dummy message", |
||||
|
"important": True, |
||||
|
"discussion": discussion.id, |
||||
|
}, |
||||
|
), |
||||
|
], |
||||
|
) |
||||
|
|
||||
|
def test_onchange_specific(self): |
||||
|
"""test that only the id is added if a new item is added to an |
||||
|
existing relation""" |
||||
|
discussion = self.env.ref("test_onchange_helper.discussion_demo_0") |
||||
|
demo = self.env.ref("base.user_demo") |
||||
|
|
||||
|
# first remove demo user from participants |
||||
|
discussion.participants -= demo |
||||
|
self.assertNotIn(demo, discussion.participants) |
||||
|
|
||||
|
# check that demo_user is added to participants when set as moderator |
||||
|
values = { |
||||
|
"name": discussion.name, |
||||
|
"moderator": demo.id, |
||||
|
"categories": [(4, cat.id) for cat in discussion.categories], |
||||
|
"messages": [(4, msg.id) for msg in discussion.messages], |
||||
|
"participants": [(4, usr.id) for usr in discussion.participants], |
||||
|
} |
||||
|
self.env.invalidate_all() |
||||
|
with self.assertNoOrmWrite(discussion): |
||||
|
result = discussion.play_onchanges(values, "moderator") |
||||
|
|
||||
|
self.assertIn("participants", result) |
||||
|
self.assertItemsEqual( |
||||
|
result["participants"], |
||||
|
[(5,)] + [(4, user.id) for user in discussion.participants + demo], |
||||
|
) |
||||
|
|
||||
|
def test_onchange_one2many_value(self): |
||||
|
""" test that the values provided for a one2many field inside are used |
||||
|
by the play_onchanges """ |
||||
|
discussion = self.env.ref("test_onchange_helper.discussion_demo_0") |
||||
|
demo = self.env.ref("base.user_demo") |
||||
|
|
||||
|
self.assertEqual(len(discussion.messages), 3) |
||||
|
messages = [(4, msg.id) for msg in discussion.messages] |
||||
|
messages[0] = (1, messages[0][1], {"body": "test onchange"}) |
||||
|
values = { |
||||
|
"name": discussion.name, |
||||
|
"moderator": demo.id, |
||||
|
"categories": [(4, cat.id) for cat in discussion.categories], |
||||
|
"messages": messages, |
||||
|
"participants": [(4, usr.id) for usr in discussion.participants], |
||||
|
"message_concat": False, |
||||
|
} |
||||
|
with self.assertNoOrmWrite(discussion): |
||||
|
result = discussion.play_onchanges(values, "messages") |
||||
|
self.assertIn("message_concat", result) |
||||
|
self.assertEqual( |
||||
|
result["message_concat"], |
||||
|
"\n".join( |
||||
|
["%s:%s" % (m.name, m.body) for m in discussion.messages] |
||||
|
), |
||||
|
) |
||||
|
|
||||
|
def test_onchange_one2many_line(self): |
||||
|
""" test that changes on a field used as first position into the |
||||
|
related path of a related field will trigger the onchange also on the |
||||
|
related field """ |
||||
|
partner = self.env.ref("base.res_partner_1") |
||||
|
multi = self.env["test_onchange_helper.multi"].create( |
||||
|
{"partner": partner.id} |
||||
|
) |
||||
|
line = multi.lines.create({"multi": multi.id}) |
||||
|
|
||||
|
values = multi._convert_to_write( |
||||
|
{key: multi[key] for key in ("partner", "lines")} |
||||
|
) |
||||
|
self.assertEqual( |
||||
|
values, {"partner": partner.id, "lines": [(6, 0, [line.id])]} |
||||
|
) |
||||
|
|
||||
|
# modify 'partner' |
||||
|
# -> set 'partner' on all lines |
||||
|
# -> recompute 'name' (related on partner) |
||||
|
# -> set 'name' on all lines |
||||
|
partner = self.env.ref("base.res_partner_2") |
||||
|
values = { |
||||
|
"partner": partner.id, |
||||
|
"lines": [ |
||||
|
(6, 0, [line.id]), |
||||
|
(0, 0, {"name": False, "partner": False}), |
||||
|
], |
||||
|
} |
||||
|
|
||||
|
self.env.invalidate_all() |
||||
|
with self.assertNoOrmWrite(multi): |
||||
|
result = multi.play_onchanges(values, "partner") |
||||
|
self.assertEqual( |
||||
|
result, |
||||
|
{ |
||||
|
"name": partner.name, |
||||
|
"lines": [ |
||||
|
(5,), |
||||
|
( |
||||
|
1, |
||||
|
line.id, |
||||
|
{ |
||||
|
"name": partner.name, |
||||
|
"partner": partner.id, |
||||
|
"multi": multi.id, |
||||
|
}, |
||||
|
), |
||||
|
( |
||||
|
0, |
||||
|
0, |
||||
|
{ |
||||
|
"name": partner.name, |
||||
|
"partner": partner.id, |
||||
|
"multi": multi.id, |
||||
|
}, |
||||
|
), |
||||
|
], |
||||
|
}, |
||||
|
) |
Write
Preview
Loading…
Cancel
Save
Reference in new issue