diff --git a/server_action_mass_edit/__manifest__.py b/server_action_mass_edit/__manifest__.py index 0d88db6b41..e2008c1497 100644 --- a/server_action_mass_edit/__manifest__.py +++ b/server_action_mass_edit/__manifest__.py @@ -14,6 +14,7 @@ "summary": "Mass Editing", "depends": [ "base", + "onchange_helper", ], "data": [ "security/ir.model.access.csv", diff --git a/server_action_mass_edit/models/ir_actions_server.py b/server_action_mass_edit/models/ir_actions_server.py index d577949103..f82d4191c7 100644 --- a/server_action_mass_edit/models/ir_actions_server.py +++ b/server_action_mass_edit/models/ir_actions_server.py @@ -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.", @@ -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) diff --git a/server_action_mass_edit/models/ir_actions_server_mass_edit_line.py b/server_action_mass_edit/models/ir_actions_server_mass_edit_line.py index b9a2141f29..a240cb93f8 100644 --- a/server_action_mass_edit/models/ir_actions_server_mass_edit_line.py +++ b/server_action_mass_edit/models/ir_actions_server_mass_edit_line.py @@ -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") @api.constrains("server_action_id", "field_id") def _check_field_model(self): diff --git a/server_action_mass_edit/tests/test_mass_editing.py b/server_action_mass_edit/tests/test_mass_editing.py index 4d12de1263..0271677fe1 100644 --- a/server_action_mass_edit/tests/test_mass_editing.py +++ b/server_action_mass_edit/tests/test_mass_editing.py @@ -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 @@ -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": "dummy@email.com", + } + ) + 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, "dummy@email.com") + # onchange_email is not called + patched.assert_not_called() diff --git a/server_action_mass_edit/views/ir_actions_server.xml b/server_action_mass_edit/views/ir_actions_server.xml index 869670f000..2bc814cbb6 100644 --- a/server_action_mass_edit/views/ir_actions_server.xml +++ b/server_action_mass_edit/views/ir_actions_server.xml @@ -22,6 +22,7 @@ /> + diff --git a/server_action_mass_edit/wizard/mass_editing_wizard.py b/server_action_mass_edit/wizard/mass_editing_wizard.py index bc0f688ab4..5078ab4a39 100644 --- a/server_action_mass_edit/wizard/mass_editing_wizard.py +++ b/server_action_mass_edit/wizard/mass_editing_wizard.py @@ -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): @@ -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") @@ -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