Maintainers
+Maintainers
This module is maintained by the OCA.
@@ -437,6 +439,5 @@ diff --git a/web_m2x_options_manager/README.rst b/web_m2x_options_manager/README.rst index 882470619bb3..4b0d5e7d2e15 100644 --- a/web_m2x_options_manager/README.rst +++ b/web_m2x_options_manager/README.rst @@ -1,7 +1,3 @@ -.. image:: https://odoo-community.org/readme-banner-image - :target: https://odoo-community.org/get-involved?utm_source=readme - :alt: Odoo Community Association - ======================= Web M2X Options Manager ======================= @@ -17,7 +13,7 @@ Web M2X Options Manager .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png :target: https://odoo-community.org/page/development-status :alt: Beta -.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github @@ -45,11 +41,16 @@ Usage Go to Settings > Technical > Models. -Choose the model you wish to edit, and open its form view. Go to the -"Create/Edit Options" tab, and add the fields you want to manage. +Choose the model you wish to edit, and open its form view. Go to the "Create/Edit Options" tab, +and add the fields you want to manage in 2 different sections: + +* the first list view allows you to handle fields for the selected model +* the second list view allows you to handle fields where the selected model is the comodel + +For both sections: -Button "Fill" will add every missing field to the options. -Button "Empty" will remove every option. +* button "Fill" will add every missing field to the options +* button "Empty" will remove every option Bug Tracker =========== diff --git a/web_m2x_options_manager/__init__.py b/web_m2x_options_manager/__init__.py index 2a7f1c54aafa..6d58305f5ddc 100644 --- a/web_m2x_options_manager/__init__.py +++ b/web_m2x_options_manager/__init__.py @@ -1,4 +1,2 @@ -# Copyright 2021 Camptocamp SA -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - from . import models +from .hooks import pre_init_hook diff --git a/web_m2x_options_manager/__manifest__.py b/web_m2x_options_manager/__manifest__.py index 98d3c75522fb..2415ebde740f 100644 --- a/web_m2x_options_manager/__manifest__.py +++ b/web_m2x_options_manager/__manifest__.py @@ -6,18 +6,25 @@ "summary": 'Adds an interface to manage the "Create" and' ' "Create and Edit" options for specific models and' " fields.", - "version": "14.0.1.4.0", + "version": "14.0.2.0.0", "author": "Camptocamp, Odoo Community Association (OCA)", "license": "AGPL-3", "category": "Web", "data": [ "security/ir.model.access.csv", "views/ir_model.xml", + "views/m2x_create_edit_option.xml", ], "demo": [ "demo/res_partner_demo_view.xml", ], - "depends": ["base", "web_m2x_options"], + "depends": [ + # OCA/server-tools + "base_view_inheritance_extension", + # OCA/web + "web_m2x_options", + ], "website": "https://github.com/OCA/web", "installable": True, + "pre_init_hook": "pre_init_hook", } diff --git a/web_m2x_options_manager/demo/res_partner_demo_view.xml b/web_m2x_options_manager/demo/res_partner_demo_view.xml index 8a53c630c561..1861d5379e42 100644 --- a/web_m2x_options_manager/demo/res_partner_demo_view.xml +++ b/web_m2x_options_manager/demo/res_partner_demo_view.xml @@ -9,17 +9,18 @@
diff --git a/web_m2x_options_manager/hooks.py b/web_m2x_options_manager/hooks.py new file mode 100644 index 000000000000..3e3195c8ca3b --- /dev/null +++ b/web_m2x_options_manager/hooks.py @@ -0,0 +1,11 @@ +# Copyright 2025 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from .tools import prepare_column_can_have_options, prepare_column_comodel_id + + +def pre_init_hook(cr): + # Pre-create and pre-fill these columns for perf reasons (might take a while to + # let Odoo do it via the ORM for huge DBs) + prepare_column_can_have_options(cr) + prepare_column_comodel_id(cr) diff --git a/web_m2x_options_manager/i18n/it.po b/web_m2x_options_manager/i18n/it.po index 6ad77573f954..4328039cc326 100644 --- a/web_m2x_options_manager/i18n/it.po +++ b/web_m2x_options_manager/i18n/it.po @@ -34,7 +34,7 @@ msgid "Create & Edit Option" msgstr "Opzione crea & modifica" #. module: web_m2x_options_manager -#: model:ir.model.fields,field_description:web_m2x_options_manager.field_m2x_create_edit_option__option_create_edit_wizard +#: model:ir.model.fields,field_description:web_m2x_options_manager.field_m2x_create_edit_option__option_m2o_dialog msgid "Create & Edit Wizard" msgstr "Procedura guidata crea & modifica" @@ -59,7 +59,7 @@ msgid "Created on" msgstr "Creato il" #. module: web_m2x_options_manager -#: model:ir.model.fields,help:web_m2x_options_manager.field_m2x_create_edit_option__option_create_edit_wizard +#: model:ir.model.fields,help:web_m2x_options_manager.field_m2x_create_edit_option__option_m2o_dialog msgid "" "Defines behaviour for 'Create & Edit' Wizard\n" "Set to False to prevent 'Create & Edit' Wizard to pop up" @@ -180,7 +180,7 @@ msgid "Last Updated on" msgstr "Ultimo aggiornamento il" #. module: web_m2x_options_manager -#: model:ir.model.fields,field_description:web_m2x_options_manager.field_ir_model__m2x_create_edit_option_ids +#: model:ir.model.fields,field_description:web_m2x_options_manager.field_ir_model__m2x_option_ids msgid "M2X Create Edit Option" msgstr "Crea opzione modifica M2X" diff --git a/web_m2x_options_manager/i18n/web_m2x_options_manager.pot b/web_m2x_options_manager/i18n/web_m2x_options_manager.pot index 37bc7324b0cb..ba45c8d8906e 100644 --- a/web_m2x_options_manager/i18n/web_m2x_options_manager.pot +++ b/web_m2x_options_manager/i18n/web_m2x_options_manager.pot @@ -31,7 +31,7 @@ msgid "Create & Edit Option" msgstr "" #. module: web_m2x_options_manager -#: model:ir.model.fields,field_description:web_m2x_options_manager.field_m2x_create_edit_option__option_create_edit_wizard +#: model:ir.model.fields,field_description:web_m2x_options_manager.field_m2x_create_edit_option__option_m2o_dialog msgid "Create & Edit Wizard" msgstr "" @@ -56,7 +56,7 @@ msgid "Created on" msgstr "" #. module: web_m2x_options_manager -#: model:ir.model.fields,help:web_m2x_options_manager.field_m2x_create_edit_option__option_create_edit_wizard +#: model:ir.model.fields,help:web_m2x_options_manager.field_m2x_create_edit_option__option_m2o_dialog msgid "" "Defines behaviour for 'Create & Edit' Wizard\n" "Set to False to prevent 'Create & Edit' Wizard to pop up" @@ -158,7 +158,7 @@ msgid "Last Updated on" msgstr "" #. module: web_m2x_options_manager -#: model:ir.model.fields,field_description:web_m2x_options_manager.field_ir_model__m2x_create_edit_option_ids +#: model:ir.model.fields,field_description:web_m2x_options_manager.field_ir_model__m2x_option_ids msgid "M2X Create Edit Option" msgstr "" diff --git a/web_m2x_options_manager/migrations/14.0.2.0.0/pre-mig.py b/web_m2x_options_manager/migrations/14.0.2.0.0/pre-mig.py new file mode 100644 index 000000000000..20c66af03e83 --- /dev/null +++ b/web_m2x_options_manager/migrations/14.0.2.0.0/pre-mig.py @@ -0,0 +1,41 @@ +# Copyright 2025 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.tools.sql import column_exists, create_column, drop_constraint + +# pylint: disable=odoo-addons-relative-import +from odoo.addons.web_m2x_options_manager.tools import ( + prepare_column_can_have_options, + prepare_column_comodel_id, +) + + +def migrate(cr, version): + if not version: + return + + # Migrate values from ``option_create_edit_wizard`` to ``option_m2o_dialog`` + if not column_exists(cr, "m2x_create_edit_option", "option_m2o_dialog"): + create_column(cr, "m2x_create_edit_option", "option_m2o_dialog", "varchar") + cr.execute( + """ + UPDATE m2x_create_edit_option + SET option_m2o_dialog = + CASE + WHEN not option_create_edit_wizard THEN 'set_false' + ELSE 'null' + END + """ + ) + + # Pre-create and pre-fill these columns for perf reasons (might take a while to + # let Odoo do it via the ORM for huge DBs) + prepare_column_can_have_options(cr) + prepare_column_comodel_id(cr) + + # Replaced by SQL constraint ``m2x_create_edit_option_field_uniqueness`` + drop_constraint( + cr, + tablename="m2x_create_edit_option", + constraintname="m2x_create_edit_option_model_field_uniqueness", + ) diff --git a/web_m2x_options_manager/models/__init__.py b/web_m2x_options_manager/models/__init__.py index 699352799994..5e0ca203bf12 100644 --- a/web_m2x_options_manager/models/__init__.py +++ b/web_m2x_options_manager/models/__init__.py @@ -1,6 +1,4 @@ -# Copyright 2021 Camptocamp SA -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - from . import ir_model +from . import ir_model_fields from . import ir_ui_view from . import m2x_create_edit_option diff --git a/web_m2x_options_manager/models/ir_model.py b/web_m2x_options_manager/models/ir_model.py index e514465720d3..9900c25d5418 100644 --- a/web_m2x_options_manager/models/ir_model.py +++ b/web_m2x_options_manager/models/ir_model.py @@ -1,52 +1,64 @@ # Copyright 2021 Camptocamp SA # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import api, fields, models +from odoo import fields, models class IrModel(models.Model): _inherit = "ir.model" - m2x_create_edit_option_ids = fields.One2many( + m2x_option_ids = fields.One2many( "m2x.create.edit.option", "model_id", ) + m2x_comodels_option_ids = fields.One2many( + "m2x.create.edit.option", + "comodel_id", + ) + comodel_field_ids = fields.One2many("ir.model.fields", "comodel_id") + + def button_empty_m2x_options(self): + self._empty_m2x_options(own=True) + + def button_fill_m2x_options(self): + self._fill_m2x_options(own=True) + + def button_empty_m2x_comodels_options(self): + self._empty_m2x_options(comodels=True) + + def button_fill_m2x_comodels_options(self): + self._fill_m2x_options(comodels=True) + + def _empty_m2x_options(self, own=False, comodels=False): + """Removes every option for model ``self``'s fields + + :param bool own: if True, deletes options for model's fields + :param bool comodels: if True, deletes options for fields where ``self`` is + the field's comodel + """ + to_delete = self.env["m2x.create.edit.option"] + if own: + to_delete += self.m2x_option_ids + if comodels: + to_delete += self.m2x_comodels_option_ids + if to_delete: + to_delete.unlink() + + def _fill_m2x_options(self, own=False, comodels=False): + """Adds every missing field option for model ``self`` (with default values) - def button_empty(self): - for ir_model in self: - ir_model._empty_m2x_create_edit_option() - - def button_fill(self): - for ir_model in self: - ir_model._fill_m2x_create_edit_option() - - def _empty_m2x_create_edit_option(self): - """Removes every option for model ``self``""" - self.ensure_one() - self.m2x_create_edit_option_ids.unlink() - - def _fill_m2x_create_edit_option(self): - """Adds every missing field option for model ``self``""" - self.ensure_one() - existing = self.m2x_create_edit_option_ids.mapped("field_id") - valid = self.field_id.filtered(lambda f: f.ttype in ("many2many", "many2one")) - vals = [(0, 0, {"field_id": f.id}) for f in valid - existing] - self.write({"m2x_create_edit_option_ids": vals}) - - -class IrModelFields(models.Model): - _inherit = "ir.model.fields" - - @api.model - def name_search(self, name="", args=None, operator="ilike", limit=100): - res = super().name_search(name, args, operator, limit) - if not (name and self.env.context.get("search_by_technical_name")): - return res - domain = list(args or []) + [("name", operator, name)] - new_fids = self.search(domain, limit=limit).ids - for fid in [x[0] for x in res]: - if fid not in new_fids: - new_fids.append(fid) - if limit and limit > 0: - new_fids = new_fids[:limit] - return self.browse(new_fids).sudo().name_get() + :param bool own: if True, creates options for model's fields + :param bool comodels: if True, creates options for fields where ``self`` is + the field's comodel + """ + todo = set() + if own: + exist = self.m2x_option_ids.field_id + valid = self.field_id.filtered("can_have_options") + todo.update((valid - exist).ids) + if comodels: + exist = self.m2x_comodels_option_ids.field_id + valid = self.comodel_field_ids.filtered("can_have_options") + todo.update((valid - exist).ids) + if todo: + self.env["m2x.create.edit.option"].create([{"field_id": i} for i in todo]) diff --git a/web_m2x_options_manager/models/ir_model_fields.py b/web_m2x_options_manager/models/ir_model_fields.py new file mode 100644 index 000000000000..efff18e74c68 --- /dev/null +++ b/web_m2x_options_manager/models/ir_model_fields.py @@ -0,0 +1,53 @@ +# Copyright 2025 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models +from odoo.osv.expression import AND + + +class IrModelFields(models.Model): + _inherit = "ir.model.fields" + + can_have_options = fields.Boolean(compute="_compute_can_have_options", store=True) + comodel_id = fields.Many2one( + "ir.model", compute="_compute_comodel_id", store=True, index=True + ) + + @api.depends("ttype") + def _compute_can_have_options(self): + for field in self: + field.can_have_options = field.ttype in ("many2many", "many2one") + + @api.depends("relation") + def _compute_comodel_id(self): + empty = self.env["ir.model"] + getter = self.env["ir.model"]._get + for field in self: + if field.relation: + field.comodel_id = getter(field.relation) + else: + field.comodel_id = empty + + @api.model + def name_search(self, name="", args=None, operator="ilike", limit=100): + # OVERRIDE: allow searching by field tech name if the correct context key is + # used; in this case, fields fetched by tech name are prepended to other fields + result = super().name_search(name, args, operator, limit) + if not (name and self.env.context.get("search_by_technical_name")): + return result + domain = AND([args or [], [("name", operator, name)]]) + new_fields = self.search_read(domain, fields=["display_name"], limit=limit) + new_result = {f["id"]: f["display_name"] for f in new_fields} + while result and not (limit and 0 < limit <= len(new_result)): + field_id, field_display_name = result.pop(0) + if field_id not in new_result: + new_result[field_id] = field_display_name + return list(new_result.items()) + + @api.model + def _search(self, args, **kwargs): + # OVERRIDE: allow defining filtering custom domain on model/comodel when + # searching fields for O2M list views on ``m2x.create.edit.option`` + if self.env.context.get("o2m_list_view_m2x_domain"): + args = AND([list(args or []), self.env.context["o2m_list_view_m2x_domain"]]) + return super()._search(args, **kwargs) diff --git a/web_m2x_options_manager/models/ir_ui_view.py b/web_m2x_options_manager/models/ir_ui_view.py index 172ef66964cf..3c0cc97d3485 100644 --- a/web_m2x_options_manager/models/ir_ui_view.py +++ b/web_m2x_options_manager/models/ir_ui_view.py @@ -7,14 +7,15 @@ class IrUiView(models.Model): _inherit = "ir.ui.view" - def postprocess(self, node, current_node_path, editable, name_manager): - res = super().postprocess(node, current_node_path, editable, name_manager) - if node.tag == "field": - mname = name_manager.Model._name - fname = node.attrib["name"] - field = self.env[mname]._fields.get(fname) - if field and field.type in ("many2many", "many2one"): - rec = self.env["m2x.create.edit.option"].get(mname, field.name) - if rec: - rec._apply_options(node) + def _postprocess_tag_field(self, node, name_manager, node_info): + # OVERRIDE: check ``m2x.create.edit.option`` config when processing a ``field`` + # node in views + res = super()._postprocess_tag_field(node, name_manager, node_info) + m2x_option = self.env["m2x.create.edit.option"].get( + name_manager.Model._name, + # ``name`` is required in ``Allows managing the “Create…” and “Create and Edit…” options for Many2one and Many2many fields directly from the ir.model form view.
Table of contents
@@ -391,15 +386,22 @@Go to Settings > Technical > Models.
-Choose the model you wish to edit, and open its form view. Go to the -“Create/Edit Options” tab, and add the fields you want to manage.
-Button “Fill” will add every missing field to the options. -Button “Empty” will remove every option.
+Choose the model you wish to edit, and open its form view. Go to the “Create/Edit Options” tab, +and add the fields you want to manage in 2 different sections:
+For both sections:
+Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed @@ -407,15 +409,15 @@
Do not contact contributors directly about support or help with technical issues.