Skip to content

Commit

Permalink
[ADD] server_action_mass_edit: add support for o2m fields
Browse files Browse the repository at this point in the history
  • Loading branch information
hbrunn committed Aug 23, 2023
1 parent fc3c4e7 commit 0ac9fdb
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 5 deletions.
4 changes: 4 additions & 0 deletions server_action_mass_edit/demo/mass_editing.xml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
<field name="field_id" ref="base.field_res_users__image_1920" />
<field name="widget_option">image</field>
</record>
<record id="mass_editing_user_line_11" model="ir.actions.server.mass.edit.line">
<field name="server_action_id" ref="mass_editing_user" />
<field name="field_id" ref="base.field_res_users__bank_ids" />
</record>

<!-- Mass Edit Partner Title -->
<record id="mass_editing_partner_title" model="ir.actions.server">
Expand Down
49 changes: 49 additions & 0 deletions server_action_mass_edit/tests/test_mass_editing.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,25 @@ def test_wiz_fields_view_get(self):
"Fields view get must return architecture with fields" "created dynamicaly",
)

# test the code path where we extract an embedded tree for o2m fields
self.env["ir.ui.view"].search(
[
("model", "in", ("res.partner.bank", "res.partner", "res.users")),
("id", "!=", self.env.ref("base.res_partner_view_form_private").id),
]
).unlink()
self.env.ref("base.res_partner_view_form_private").model = "res.users"
result = self.MassEditingWizard.with_context(
server_action_id=self.mass_editing_user.id,
active_ids=[],
).get_view()
arch = result.get("arch", "")
self.assertIn(
"<tree editable=",
arch,
"Fields view get must return architecture with embedded tree",
)

def test_wzd_clean_check_company_field_domain(self):
"""
Test company field domain replacement
Expand Down Expand Up @@ -262,6 +281,36 @@ def test_mass_edit_email(self):
self._create_wizard_and_apply_values(self.mass_editing_user, self.user, vals)
self.assertNotEqual(self.user.email, False, "User's Email should be set.")

def test_mass_edit_o2m_banks(self):
"""Test Case for MASS EDITING which will remove and add
Partner's bank o2m."""
# Set another bank (must replace existing one)
bank_vals = {"acc_number": "account number"}
self.user.write(
{
"bank_ids": [(6, 0, []), (0, 0, bank_vals)],
}
)
vals = {
"selection__bank_ids": "set_o2m",
"bank_ids": [(0, 0, dict(bank_vals, acc_number="new number"))],
}
self._create_wizard_and_apply_values(self.mass_editing_user, self.user, vals)
self.assertEqual(self.user.bank_ids.acc_number, "new number")
# Add bank (must keep existing one)
vals = {
"selection__bank_ids": "add_o2m",
"bank_ids": [(0, 0, dict(bank_vals, acc_number="new number2"))],
}
self._create_wizard_and_apply_values(self.mass_editing_user, self.user, vals)
self.assertEqual(
self.user.bank_ids.mapped("acc_number"), ["new number", "new number2"]
)
# Set empty list (must remove all banks)
vals = {"selection__bank_ids": "set_o2m"}
self._create_wizard_and_apply_values(self.mass_editing_user, self.user, vals)
self.assertFalse(self.user.bank_ids)

def test_mass_edit_m2m_categ(self):
"""Test Case for MASS EDITING which will remove and add
Partner's category m2m."""
Expand Down
60 changes: 55 additions & 5 deletions server_action_mass_edit/wizard/mass_editing_wizard.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,17 @@
# Copyright (C) 2020 Iván Todorovich (https://twitter.com/ivantodorovich)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

import json

from lxml import etree

from odoo import _, api, fields, models

from odoo.addons.base.models.ir_ui_view import (
transfer_modifiers_to_node,
transfer_node_to_modifiers,
)


class MassEditingWizard(models.TransientModel):
_name = "mass.editing.wizard"
Expand Down Expand Up @@ -99,6 +106,12 @@ def _prepare_fields(self, line, field, field_info):
("remove_m2m", _("Remove")),
("add", _("Add")),
]
if field.ttype == "one2many":
selection = [
("ignore", _("Don't touch")),
("set_o2m", _("Set")),
("add_o2m", _("Add")),
]
else:
selection = [
("ignore", _("Don't touch")),
Expand Down Expand Up @@ -142,7 +155,32 @@ def _insert_field_in_arch(self, line, field, main_xml_group):
field_vals = self._get_field_options(field)
if line.widget_option:
field_vals["widget"] = line.widget_option
etree.SubElement(div, "field", field_vals)
field_element = etree.SubElement(div, "field", field_vals)
if field.ttype == "one2many":
comodel = self.env[field.relation]
dummy, form_view = comodel._get_view(view_type="form")
dummy, tree_view = comodel._get_view(view_type="tree")
field_context = {}
if form_view:
field_context["form_view_ref"] = form_view.xml_id
if tree_view:
field_context["tree_view_ref"] = tree_view.xml_id
if field_context:
field_element.attrib["context"] = json.dumps(field_context)
else:
model_arch, dummy = self.env[field.model]._get_view(view_type="form")
embedded_tree = None
for node in model_arch.xpath(
"//field[@name='%s'][./tree]" % field.name
):
embedded_tree = node.xpath("./tree")[0]
break
if embedded_tree is not None:
for node in embedded_tree.xpath("./*"):
modifiers = {}
transfer_node_to_modifiers(node, modifiers)
transfer_modifiers_to_node(modifiers, node)
field_element.insert(0, embedded_tree)

def _get_field_options(self, field):
return {
Expand All @@ -163,6 +201,11 @@ def get_view(self, view_id=None, view_type="form", **options):
main_xml_group = arch.find('.//group[@name="group_field_list"]')
for line in server_action.mapped("mass_edit_line_ids"):
self._insert_field_in_arch(line, line.field_id, main_xml_group)
if line.field_id.ttype == "one2many":
comodel = self.env[line.field_id.relation]
result["models"] = dict(
result["models"], **{comodel._name: tuple(comodel.fields_get())}
)
result["arch"] = etree.tostring(arch, encoding="unicode")
return result

Expand All @@ -179,6 +222,7 @@ def fields_get(self, allfields=None, attributes=None):
field_info = self._clean_check_company_field_domain(
self.env[server_action.model_id.model], field, fields_info[field.name]
)
field_info["relation_field"] = False
if not line.apply_domain and "domain" in field_info:
field_info["domain"] = "[]"
res.update(self._prepare_fields(line, field, field_info))
Expand Down Expand Up @@ -209,9 +253,14 @@ def create(self, vals_list):
for key, val in vals.items():
if key.startswith("selection_"):
split_key = key.split("__", 1)[1]
if val == "set":
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, [])}
)

elif val == "remove":
values.update({split_key: False})

Expand All @@ -230,10 +279,11 @@ def create(self, vals_list):
for m2m_id in vals.get(split_key, False)[0][2]:
m2m_list.append((4, m2m_id))
values.update({split_key: m2m_list})

if values:
self.env[server_action.model_id.model].browse(active_ids).write(
values
)
self.env[server_action.model_id.model].browse(
active_ids
).with_context(mass_edit=True,).write(values)
return super().create([{}])

def _prepare_create_values(self, vals_list):
Expand Down

0 comments on commit 0ac9fdb

Please sign in to comment.