Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[16.0][FIX] mass_edit: Play onchanges before writing #963

Open
wants to merge 3 commits into
base: 16.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions server_action_mass_edit/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"summary": "Mass Editing",
"depends": [
"base",
"onchange_helper",
],
"data": [
"security/ir.model.access.csv",
Expand Down
12 changes: 12 additions & 0 deletions server_action_mass_edit/models/ir_actions_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ class IrActionsServer(models.Model):
string="Apply domain in lines",
compute="_compute_mass_edit_apply_domain_in_lines",
)
mass_edit_play_onchanges = fields.Json(
string="Play onchanges from lines",
compute="_compute_mass_edit_play_onchanges",
)
mass_edit_message = fields.Text(
string="Message",
help="If set, this message will be displayed in the wizard.",
Expand Down Expand Up @@ -46,6 +50,14 @@ def _compute_mass_edit_apply_domain_in_lines(self):
record.mass_edit_line_ids.mapped("apply_domain")
)

@api.depends("mass_edit_line_ids.apply_onchanges")
def _compute_mass_edit_play_onchanges(self):
for record in self:
record.mass_edit_play_onchanges = {
line.field_id.name: line.apply_onchanges
for line in record.mass_edit_line_ids
}

def _run_action_mass_edit_multi(self, eval_context=None):
"""Show report label wizard"""
context = dict(self.env.context)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class IrActionsServerMassEditLine(models.Model):
default=False,
help="Apply default domain related to field",
)
apply_onchanges = fields.Boolean(help="Play field onchanges before writing value")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can add more information in the help text. Perfomance possible problem if it is executed on a lot of records versus inconsistency data if onchange are not executed.

Note : for new version (v18), I think we can put default="True", as it is the most expected behaviour.


@api.constrains("server_action_id", "field_id")
def _check_field_model(self):
Expand Down
66 changes: 66 additions & 0 deletions server_action_mass_edit/tests/test_mass_editing.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from ast import literal_eval
from unittest.mock import patch

from odoo.exceptions import ValidationError
from odoo.tests import Form, common, new_test_user
Expand Down Expand Up @@ -405,3 +406,68 @@ def test_onchange_model_id(self):
result,
None,
)

@patch("odoo.addons.base.models.res_partner.Partner.onchange_email")
def test_wizard_field_onchange(self, patched):
server_action_partner = self.env["ir.actions.server"].create(
{
"name": "Mass Edit Partner",
"state": "mass_edit",
"model_id": self.env.ref("base.model_res_partner").id,
}
)
self.env["ir.actions.server.mass.edit.line"].create(
[
{
"server_action_id": server_action_partner.id,
"field_id": self.env.ref("base.field_res_partner__country_id").id,
"apply_onchanges": True,
},
{
"server_action_id": server_action_partner.id,
"field_id": self.env.ref("base.field_res_partner__email").id,
"apply_onchanges": False,
},
]
)
self.assertEqual(
server_action_partner.mass_edit_play_onchanges,
{
"country_id": True,
"email": False,
},
)
us_country = self.env.ref("base.us")
mx_country = self.env.ref("base.mx")
partners = self.env["res.partner"].create(
[
{
"name": "ACME",
"country_id": us_country.id,
"state_id": self.env.ref("base.state_us_1").id,
},
{
"name": "Example.com",
"country_id": us_country.id,
"state_id": self.env.ref("base.state_us_2").id,
},
]
)
self.MassEditingWizard.with_context(
server_action_id=server_action_partner.id,
active_ids=partners.ids,
).create(
{
"selection__country_id": "set",
"selection__email": "set",
"country_id": mx_country,
"email": "[email protected]",
}
)
for partner in partners:
self.assertEqual(partner.country_id, mx_country)
# state_id is set to False by _onchange_country_id
self.assertFalse(partner.state_id)
self.assertEqual(partner.email, "[email protected]")
# onchange_email is not called
patched.assert_not_called()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very complete test. Thanks.

1 change: 1 addition & 0 deletions server_action_mass_edit/views/ir_actions_server.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
/>
<field name="widget_option" />
<field name="apply_domain" />
<field name="apply_onchanges" />
</tree>
</field>
<field name="mass_edit_apply_domain_in_lines" invisible="1" />
Expand Down
93 changes: 62 additions & 31 deletions server_action_mass_edit/wizard/mass_editing_wizard.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class MassEditingWizard(models.TransientModel):
operation_description_warning = fields.Text(readonly=True)
operation_description_danger = fields.Text(readonly=True)
message = fields.Text(readonly=True)
play_onchanges = fields.Json(readonly=True)

@api.model
def default_get(self, fields, active_ids=None):
Expand Down Expand Up @@ -67,6 +68,7 @@ def default_get(self, fields, active_ids=None):
"operation_description_warning": operation_description_warning,
"operation_description_danger": operation_description_danger,
"message": server_action.mass_edit_message,
"play_onchanges": server_action.mass_edit_play_onchanges,
}
)
server_action_id = self.env.context.get("server_action_id")
Expand Down Expand Up @@ -249,42 +251,71 @@ def create(self, vals_list):
active_ids = self.env.context.get("active_ids", [])
if server_action and active_ids:
for vals in vals_list:
values = {}
for key, val in vals.items():
if key.startswith("selection_"):
split_key = key.split("__", 1)[1]
if val == "set" or val == "add_o2m":
values.update({split_key: vals.get(split_key, False)})

elif val == "set_o2m":
values.update(
{split_key: [(6, 0, [])] + vals.get(split_key, [])}
values = self._prepare_write_values(vals)
if values:
model = self.env[server_action.model_id.model].with_context(
mass_edit=True
)
records = model.browse(active_ids)
# Check if a field in values is set to play onchanges, in which case
# each record is to be updated sequentially
onchanges_to_play = [
fname
for fname, val in server_action.mass_edit_play_onchanges.items()
if val
]
if onchanges_to_play:
onchange_values = {
k: v for k, v in values.items() if k in onchanges_to_play
}
not_onchange_values = {
k: v
for k, v in values.items()
if k not in onchanges_to_play
}
for rec in records:
rec_values = onchange_values.copy()
rec_values = rec.play_onchanges(
rec_values, list(rec_values.keys())
)
rec_values.update(not_onchange_values)
rec.write(rec_values)
else:
# If there is not any onchange to play we can write
# all the records at once
records.write(values)
return super().create([{}])

elif val == "remove":
values.update({split_key: False})
def _prepare_write_values(self, vals):
values = {}
for key, val in vals.items():
if key.startswith("selection_"):
split_key = key.split("__", 1)[1]
if val == "set" or val == "add_o2m":
values.update({split_key: vals.get(split_key, False)})

elif val == "remove_m2m":
m2m_list = []
if vals.get(split_key):
for m2m_id in vals.get(split_key)[0][2]:
m2m_list.append((3, m2m_id))
if m2m_list:
values.update({split_key: m2m_list})
else:
values.update({split_key: [(5, 0, [])]})
elif val == "set_o2m":
values.update({split_key: [(6, 0, [])] + vals.get(split_key, [])})

elif val == "add":
m2m_list = []
for m2m_id in vals.get(split_key, False)[0][2]:
m2m_list.append((4, m2m_id))
values.update({split_key: m2m_list})
elif val == "remove":
values.update({split_key: False})

if values:
self.env[server_action.model_id.model].browse(
active_ids
).with_context(mass_edit=True,).write(values)
return super().create([{}])
elif val == "remove_m2m":
m2m_list = []
if vals.get(split_key):
for m2m_id in vals.get(split_key)[0][2]:
m2m_list.append((3, m2m_id))
if m2m_list:
values.update({split_key: m2m_list})
else:
values.update({split_key: [(5, 0, [])]})

elif val == "add":
m2m_list = []
for m2m_id in vals.get(split_key, False)[0][2]:
m2m_list.append((4, m2m_id))
values.update({split_key: m2m_list})
return values

def _prepare_create_values(self, vals_list):
return vals_list
Expand Down
Loading