Validating some operations is a common need across different areas in a diff --git a/base_tier_validation/static/src/components/tier_review_widget/tier_review_widget.esm.js b/base_tier_validation/static/src/components/tier_review_widget/tier_review_widget.esm.js index 7514d2ad4d..b9b87135af 100644 --- a/base_tier_validation/static/src/components/tier_review_widget/tier_review_widget.esm.js +++ b/base_tier_validation/static/src/components/tier_review_widget/tier_review_widget.esm.js @@ -12,7 +12,7 @@ export class ReviewsTable extends Component { } _getReviewData() { - const records = this.env.model.root.data.review_ids.records; + const records = this.props.record.data.review_ids.records; return records.map((record) => record.data); } diff --git a/base_tier_validation/tests/common.py b/base_tier_validation/tests/common.py index 1da841584f..ccac99bffe 100644 --- a/base_tier_validation/tests/common.py +++ b/base_tier_validation/tests/common.py @@ -94,6 +94,13 @@ def setUpClass(cls): login="test3", company_ids=[Command.set([cls.main_company.id, cls.other_company.id])], ) + # Create groups + cls.test_group = cls.env["res.groups"].create( + { + "name": "TestGroup", + "users": [(4, cls.test_user_1.id), (4, cls.test_user_2.id)], + } + ) # Create tier definitions: cls.tier_def_obj = cls.env["tier.definition"] cls.tier_definition = cls.tier_def_obj.create( diff --git a/base_tier_validation/tests/test_tier_validation.py b/base_tier_validation/tests/test_tier_validation.py index 26e228a3f7..4103d54853 100644 --- a/base_tier_validation/tests/test_tier_validation.py +++ b/base_tier_validation/tests/test_tier_validation.py @@ -1202,6 +1202,57 @@ def test_31_request_validation(self): self.test_user_3_multi_company.partner_id, followers.mapped("partner_id") ) + def test_32_test_review_by_res_groups_field(self): + """Test using field-based validation with groups""" + selected_field = self.env["ir.model.fields"].search( + [("model", "=", self.test_model._name), ("name", "=", "group_id")] + ) + test_record = self.test_model.create( + {"test_field": 2.5, "group_id": self.test_group.id} + ) + + definition = self.env["tier.definition"].create( + { + "model_id": self.tester_model.id, + "review_type": "field", + "reviewer_field_id": selected_field.id, + "definition_domain": "[('test_field', '>', 1.0)]", + "approve_sequence": True, + } + ) + + reviews = test_record.request_validation() + review = reviews.filtered(lambda r: r.definition_id == definition) + self.assertTrue(review) + self.assertEqual(review.reviewer_ids, self.test_user_2 | self.test_user_1) + + def test_33_test_review_by_wrong_field_type(self): + """Test using field-based validation with groups""" + selected_field = self.env["ir.model.fields"].search( + [("model", "=", self.test_model._name), ("name", "=", "menu_id")] + ) + test_record = self.test_model.create( + { + "test_field": 2.5, + "menu_id": self.env["ir.ui.menu"].search([], limit=1).id, + } + ) + self.assertTrue(test_record.menu_id) + self.env["tier.definition"].create( + { + "model_id": self.tester_model.id, + "review_type": "field", + "reviewer_field_id": selected_field.id, + "definition_domain": "[('test_field', '>', 1.0)]", + "approve_sequence": True, + } + ) + with self.assertRaisesRegex( + ValidationError, + "Validation reviewer field should be of the appropriate type", + ): + test_record.request_validation() + @tagged("at_install") class TierTierValidationView(CommonTierValidation): diff --git a/base_tier_validation/tests/tier_validation_tester.py b/base_tier_validation/tests/tier_validation_tester.py index 7e171e753e..85cc08a8d4 100644 --- a/base_tier_validation/tests/tier_validation_tester.py +++ b/base_tier_validation/tests/tier_validation_tester.py @@ -21,6 +21,10 @@ class TierValidationTester(models.Model): test_validation_field = fields.Integer(default=0) test_field = fields.Float() user_id = fields.Many2one(string="Assigned to:", comodel_name="res.users") + group_id = fields.Many2one("res.groups", string="Assigned to (group)") + menu_id = fields.Many2one( + "ir.ui.menu", help="For testing choosing a wrong field type" + ) def action_confirm(self): self.write({"state": "confirmed"}) diff --git a/server_action_mass_edit/README.rst b/server_action_mass_edit/README.rst index 3a998a2389..2d5f61725c 100644 --- a/server_action_mass_edit/README.rst +++ b/server_action_mass_edit/README.rst @@ -11,7 +11,7 @@ Mass Editing !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:ad5b24fa705bc1129ad8931d6df7ffede0b4bfc4740e526e137dffeaee8af422 + !! source digest: sha256:c1f6aa8b7cc70dd834b9fc4bfdf6715a737a8a9ab1ea49f4867d5731ad8632f8 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png diff --git a/server_action_mass_edit/__manifest__.py b/server_action_mass_edit/__manifest__.py index f810236012..59d6f90f8c 100644 --- a/server_action_mass_edit/__manifest__.py +++ b/server_action_mass_edit/__manifest__.py @@ -2,7 +2,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). { "name": "Mass Editing", - "version": "18.0.1.1.0", + "version": "18.0.1.1.1", "author": "Serpent Consulting Services Pvt. Ltd., " "Tecnativa, " "GRAP, " diff --git a/server_action_mass_edit/static/description/index.html b/server_action_mass_edit/static/description/index.html index 0f2bfaaec0..382a2ca420 100644 --- a/server_action_mass_edit/static/description/index.html +++ b/server_action_mass_edit/static/description/index.html @@ -372,7 +372,7 @@
This module allows to edit several records at the same time in any Odoo diff --git a/server_action_mass_edit/tests/test_mass_editing.py b/server_action_mass_edit/tests/test_mass_editing.py index 77ddc70338..e152df8a75 100644 --- a/server_action_mass_edit/tests/test_mass_editing.py +++ b/server_action_mass_edit/tests/test_mass_editing.py @@ -388,6 +388,21 @@ def test_onchanges(self): mass_edit_line_form.field_id = self.env.ref("base.field_res_users__country_id") self.assertFalse(mass_edit_line_form.widget_option) + def test_onchange_call(self): + """Onchange call does not error on dynamically added fields""" + self.env["mass.editing.wizard"].with_context( + active_ids=self.env.user.ids, + active_model=self.env.user._name, + server_action_id=self.mass_editing_user.id, + ).onchange( + values={}, + field_names={}, + fields_spec={ + "selection__email": {}, + "email": {}, + }, + ) + def test_onchange_model_id(self): """Test super call of `_onchange_model_id`""" diff --git a/server_action_mass_edit/wizard/mass_editing_wizard.py b/server_action_mass_edit/wizard/mass_editing_wizard.py index d87e91501e..0f3c235260 100644 --- a/server_action_mass_edit/wizard/mass_editing_wizard.py +++ b/server_action_mass_edit/wizard/mass_editing_wizard.py @@ -69,6 +69,56 @@ def default_get(self, fields): return res + def onchange(self, values, field_names, fields_spec): + # Make sure the values passed to the super cover the dynamic fields. + # No onchanges are defined, but Odoo will call the onchange with empty + # values for all fields when opening the wizard form view. + first_call = not field_names + if first_call: + field_names = [fname for fname in values if fname != "id"] + missing_names = [fname for fname in fields_spec if fname not in values] + defaults = self.default_get(missing_names) + for field_name in missing_names: + values[field_name] = defaults.get(field_name, False) + if field_name in defaults: + field_names.append(field_name) + + server_action_id = self.env.context.get("server_action_id") + server_action = self.env["ir.actions.server"].sudo().browse(server_action_id) + if not server_action: + return super().onchange(values, field_names, fields_spec) + dynamic_fields = {} + + for line in server_action.mapped("mass_edit_line_ids"): + values["selection__" + line.field_id.name] = "ignore" + values[line.field_id.name] = False + + # Make sure there is an entry for the default value retrieved above. + dynamic_fields["selection__" + line.field_id.name] = fields.Selection( + [("ignore", _("Don't touch"))], default="ignore" + ) + dynamic_fields[line.field_id.name] = fields.Text([()], default=False) + + self._fields.update(dynamic_fields) + + res = super().onchange(values, field_names, fields_spec) + if not res["value"]: + value = {key: value for key, value in values.items() if value is not False} + res["value"] = value + + for field in dynamic_fields: + self._fields.pop(field) + + view_temp = ( + self.env["ir.ui.view"] + .sudo() + .search([("name", "=", "Temporary Mass Editing Wizard")], limit=1) + ) + if view_temp: + view_temp.unlink() + + return res + @api.model def _prepare_fields(self, line, field, field_info): result = {} diff --git a/server_action_mass_edit_onchange/README.rst b/server_action_mass_edit_onchange/README.rst index 040259c7ed..c99291990a 100644 --- a/server_action_mass_edit_onchange/README.rst +++ b/server_action_mass_edit_onchange/README.rst @@ -11,7 +11,7 @@ Server Action Mass Edit Onchange !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:4ecb83298926e7e790173247738d18205a50ef65a90e98d405627251de3de3ad + !! source digest: sha256:2f70ce6aa21a25dfd9ed1741f2fea8469e77fe5d91d0fa4ed7df629fcc0d07a4 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png diff --git a/server_action_mass_edit_onchange/__manifest__.py b/server_action_mass_edit_onchange/__manifest__.py index 87f675fe86..875856a2fe 100644 --- a/server_action_mass_edit_onchange/__manifest__.py +++ b/server_action_mass_edit_onchange/__manifest__.py @@ -4,7 +4,7 @@ { "name": "Server Action Mass Edit Onchange", "summary": """Extension of server_action_mass_edit""", - "version": "18.0.1.1.0", + "version": "18.0.1.1.1", "license": "AGPL-3", "author": "Camptocamp, Odoo Community Association (OCA)", "website": "https://github.com/OCA/server-ux", diff --git a/server_action_mass_edit_onchange/static/description/index.html b/server_action_mass_edit_onchange/static/description/index.html index 0874d6a26b..1bd6fb2545 100644 --- a/server_action_mass_edit_onchange/static/description/index.html +++ b/server_action_mass_edit_onchange/static/description/index.html @@ -372,7 +372,7 @@
This module is an extension of module Mass Editing to support playing diff --git a/server_action_mass_edit_onchange/wizard/mass_editing_wizard.py b/server_action_mass_edit_onchange/wizard/mass_editing_wizard.py index f6e5065b76..b72d4843ee 100644 --- a/server_action_mass_edit_onchange/wizard/mass_editing_wizard.py +++ b/server_action_mass_edit_onchange/wizard/mass_editing_wizard.py @@ -24,53 +24,6 @@ def default_get(self, fields): ) return res - def onchange(self, values, field_names, fields_spec): - first_call = not field_names - if first_call: - field_names = [fname for fname in values if fname != "id"] - missing_names = [fname for fname in fields_spec if fname not in values] - defaults = self.default_get(missing_names) - for field_name in missing_names: - values[field_name] = defaults.get(field_name, False) - if field_name in defaults: - field_names.append(field_name) - - server_action_id = self.env.context.get("server_action_id") - server_action = self.env["ir.actions.server"].sudo().browse(server_action_id) - if not server_action: - return super().onchange(values, field_names, fields_spec) - dynamic_fields = {} - - for line in server_action.mapped("mass_edit_line_ids"): - values["selection__" + line.field_id.name] = "ignore" - values[line.field_id.name] = False - - dynamic_fields["selection__" + line.field_id.name] = fields.Selection( - [], default="ignore" - ) - - dynamic_fields[line.field_id.name] = fields.Text([()], default=False) - - self._fields.update(dynamic_fields) - - res = super().onchange(values, field_names, fields_spec) - if not res["value"]: - value = {key: value for key, value in values.items() if value is not False} - res["value"] = value - - for field in dynamic_fields: - self._fields.pop(field) - - view_temp = ( - self.env["ir.ui.view"] - .sudo() - .search([("name", "=", "Temporary Mass Editing Wizard")], limit=1) - ) - if view_temp: - view_temp.unlink() - - return res - def _exec_write(self, server_action, vals): active_ids = self.env.context.get("active_ids", []) model = self.env[server_action.model_id.model].with_context(mass_edit=True)