mirror of
https://gitlab.com/flectra-community/server-ux.git
synced 2024-11-15 02:32:06 +00:00
340 lines
13 KiB
Python
340 lines
13 KiB
Python
|
# Copyright (C) 2016 Serpent Consulting Services Pvt. Ltd. (support@serpentcs.com)
|
||
|
# 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 flectra import _, api, fields, models
|
||
|
|
||
|
|
||
|
class MassEditingWizard(models.TransientModel):
|
||
|
_name = "mass.editing.wizard"
|
||
|
_description = "Wizard for mass edition"
|
||
|
|
||
|
selected_item_qty = fields.Integer(readonly=True)
|
||
|
remaining_item_qty = fields.Integer(readonly=True)
|
||
|
operation_description_info = fields.Text(readonly=True)
|
||
|
operation_description_warning = fields.Text(readonly=True)
|
||
|
operation_description_danger = fields.Text(readonly=True)
|
||
|
message = fields.Text(readonly=True)
|
||
|
|
||
|
@api.model
|
||
|
def default_get(self, fields):
|
||
|
res = super().default_get(fields)
|
||
|
server_action_id = self.env.context.get("server_action_id")
|
||
|
server_action = self.env["ir.actions.server"].sudo().browse(server_action_id)
|
||
|
active_ids = self.env.context.get("active_ids")
|
||
|
|
||
|
if not server_action:
|
||
|
return res
|
||
|
|
||
|
original_active_ids = self.env.context.get("original_active_ids", active_ids)
|
||
|
operation_description_info = False
|
||
|
operation_description_warning = False
|
||
|
operation_description_danger = False
|
||
|
if len(active_ids) == len(original_active_ids):
|
||
|
operation_description_info = _(
|
||
|
"The treatment will be processed on the %(amount)d selected record(s)."
|
||
|
) % {
|
||
|
"amount": len(active_ids),
|
||
|
}
|
||
|
elif len(original_active_ids):
|
||
|
operation_description_warning = _(
|
||
|
"You have selected %(origin_amount)d "
|
||
|
"record(s) that can not be processed.\n"
|
||
|
"Only %(amount)d record(s) will be processed."
|
||
|
) % {
|
||
|
"origin_amount": len(original_active_ids) - len(active_ids),
|
||
|
"amount": len(active_ids),
|
||
|
}
|
||
|
else:
|
||
|
operation_description_danger = _(
|
||
|
"None of the %(amount)d record(s) you have selected can be processed."
|
||
|
) % {
|
||
|
"amount": len(active_ids),
|
||
|
}
|
||
|
# Set values
|
||
|
res.update(
|
||
|
{
|
||
|
"selected_item_qty": len(active_ids),
|
||
|
"remaining_item_qty": len(original_active_ids),
|
||
|
"operation_description_info": operation_description_info,
|
||
|
"operation_description_warning": operation_description_warning,
|
||
|
"operation_description_danger": operation_description_danger,
|
||
|
"message": server_action.mass_edit_message,
|
||
|
}
|
||
|
)
|
||
|
|
||
|
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
|
||
|
|
||
|
@api.model
|
||
|
def _prepare_fields(self, line, field, field_info):
|
||
|
result = {}
|
||
|
# Add "selection field (set / add / remove / remove_m2m)
|
||
|
if field.ttype == "many2many":
|
||
|
selection = [
|
||
|
("ignore", _("Don't touch")),
|
||
|
("set_m2m", _("Set")),
|
||
|
("remove_m2m", _("Remove")),
|
||
|
("add", _("Add")),
|
||
|
]
|
||
|
elif field.ttype == "one2many":
|
||
|
selection = [
|
||
|
("ignore", _("Don't touch")),
|
||
|
("set_o2m", _("Set")),
|
||
|
("add_o2m", _("Add")),
|
||
|
]
|
||
|
else:
|
||
|
selection = [
|
||
|
("ignore", _("Don't touch")),
|
||
|
("set", _("Set")),
|
||
|
("remove", _("Remove")),
|
||
|
]
|
||
|
result["selection__" + field.name] = {
|
||
|
"type": "selection",
|
||
|
"string": field_info["string"],
|
||
|
"selection": selection,
|
||
|
}
|
||
|
# Add field info
|
||
|
result[field.name] = field_info
|
||
|
return result
|
||
|
|
||
|
@api.model
|
||
|
def _insert_field_in_arch(self, line, field, main_xml_group):
|
||
|
etree.SubElement(
|
||
|
main_xml_group,
|
||
|
"label",
|
||
|
{
|
||
|
"for": "selection__" + field.name,
|
||
|
},
|
||
|
)
|
||
|
div = etree.SubElement(
|
||
|
main_xml_group,
|
||
|
"div",
|
||
|
{
|
||
|
"class": "d-flex",
|
||
|
},
|
||
|
)
|
||
|
etree.SubElement(
|
||
|
div,
|
||
|
"field",
|
||
|
{
|
||
|
"name": "selection__" + field.name,
|
||
|
"modifiers": '{"required": true}',
|
||
|
"class": "w-25",
|
||
|
},
|
||
|
)
|
||
|
field_vals = self._get_field_options(field)
|
||
|
if line.widget_option:
|
||
|
field_vals["widget"] = line.widget_option
|
||
|
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(f"//field[@name='{field.name}'][./tree]"):
|
||
|
embedded_tree = node.xpath("./tree")[0]
|
||
|
break
|
||
|
if embedded_tree is not None:
|
||
|
for node in embedded_tree.xpath("./*"):
|
||
|
modifiers = node.get("modifiers")
|
||
|
if modifiers:
|
||
|
node.attrib["modifiers"] = modifiers
|
||
|
field_element.insert(0, embedded_tree)
|
||
|
|
||
|
return field_element
|
||
|
|
||
|
def _get_field_options(self, field):
|
||
|
return {
|
||
|
"name": field.name,
|
||
|
"invisible": 'selection__%s in ["ignore", "remove", False]' % field.name,
|
||
|
"class": "w-75",
|
||
|
}
|
||
|
|
||
|
@api.model
|
||
|
def get_views(self, views, options=None):
|
||
|
for view, _type in views:
|
||
|
if view:
|
||
|
view = self.env["ir.ui.view"].sudo().browse(view)
|
||
|
server_action = view.mass_server_action_id
|
||
|
self = self.with_context(server_action_id=server_action.id)
|
||
|
return super().get_views(views, options)
|
||
|
|
||
|
@api.model
|
||
|
def get_view(self, view_id=None, view_type="form", **options):
|
||
|
view = self.env["ir.ui.view"].sudo().browse(view_id)
|
||
|
server_action = view.mass_server_action_id
|
||
|
self = self.with_context(server_action_id=server_action.id)
|
||
|
if not server_action:
|
||
|
return super().get_view(view_id, view_type, **options)
|
||
|
result = super().get_view(view_id, view_type, **options)
|
||
|
arch = etree.fromstring(result["arch"])
|
||
|
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
|
||
|
|
||
|
@api.model
|
||
|
def fields_get(self, allfields=None, attributes=None):
|
||
|
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().fields_get(allfields, attributes)
|
||
|
res = super().fields_get(allfields, attributes)
|
||
|
fields_info = self.env[server_action.model_id.model].fields_get()
|
||
|
for line in server_action.mapped("mass_edit_line_ids"):
|
||
|
field = line.field_id
|
||
|
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))
|
||
|
return res
|
||
|
|
||
|
@api.model
|
||
|
def _clean_check_company_field_domain(self, TargetModel, field, field_info):
|
||
|
"""
|
||
|
This method remove the field view domain added by Flectra for relational
|
||
|
fields with check_company attribute to avoid error for non exists
|
||
|
company_id or company_ids fields in wizard view.
|
||
|
See _description_domain method in _Relational Class
|
||
|
"""
|
||
|
field_class = TargetModel._fields[field.name]
|
||
|
if not field_class.relational or not field_class.check_company or field.domain:
|
||
|
return field_info
|
||
|
field_info["domain"] = "[]"
|
||
|
return field_info
|
||
|
|
||
|
@api.model_create_multi
|
||
|
def create(self, vals_list):
|
||
|
server_action_id = self.env.context.get("server_action_id")
|
||
|
server_action = self.env["ir.actions.server"].sudo().browse(server_action_id)
|
||
|
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" or val == "set_m2m":
|
||
|
values.update(
|
||
|
{split_key: [(6, 0, [])] + vals.get(split_key, [])}
|
||
|
)
|
||
|
|
||
|
elif val == "remove":
|
||
|
values.update({split_key: False})
|
||
|
|
||
|
elif val == "remove_m2m":
|
||
|
m2m_list = []
|
||
|
if vals.get(split_key):
|
||
|
for m2m_id in vals.get(split_key, False):
|
||
|
m2m_list.append((3, m2m_id[1]))
|
||
|
if m2m_list:
|
||
|
values.update({split_key: m2m_list})
|
||
|
else:
|
||
|
values.update({split_key: [(5, 0, [])]})
|
||
|
|
||
|
elif val == "add":
|
||
|
values.update({split_key: vals.get(split_key, False)})
|
||
|
|
||
|
if values:
|
||
|
for active_id in active_ids:
|
||
|
self.env[server_action.model_id.model].browse(
|
||
|
active_id
|
||
|
).with_context(
|
||
|
mass_edit=True,
|
||
|
).write(values)
|
||
|
return super().create([{}])
|
||
|
|
||
|
def _prepare_create_values(self, vals_list):
|
||
|
return vals_list
|
||
|
|
||
|
def read(self, fields=None, load="_classic_read"):
|
||
|
"""Without this call, dynamic fields build by fields_view_get()
|
||
|
generate a log warning, i.e.:
|
||
|
flectra.models:mass.editing.wizard.read() with unknown field 'myfield'
|
||
|
flectra.models:mass.editing.wizard.read()
|
||
|
with unknown field 'selection__myfield'
|
||
|
"""
|
||
|
real_fields = fields
|
||
|
if fields:
|
||
|
# We remove fields which are not in _fields
|
||
|
real_fields = [x for x in fields if x in self._fields]
|
||
|
result = super().read(real_fields, load=load)
|
||
|
# adding fields to result
|
||
|
[result[0].update({x: False}) for x in fields if x not in real_fields]
|
||
|
return result
|
||
|
|
||
|
def button_apply(self):
|
||
|
self.ensure_one()
|