diff --git a/test-requirements.txt b/test-requirements.txt index 4ad8e0ece..cdfaa6239 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1 +1,2 @@ odoo-test-helper +odoo-addon-web_m2x_options @ git+https://github.com/OCA/web.git@refs/pull/2961/head#subdirectory=web_m2x_options diff --git a/web_m2x_options_manager/README.rst b/web_m2x_options_manager/README.rst new file mode 100644 index 000000000..eba45e91e --- /dev/null +++ b/web_m2x_options_manager/README.rst @@ -0,0 +1,99 @@ +======================= +Web M2X Options Manager +======================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:e2c7c70fbcb74be8ffaed3747c322112463936bb6fbb5a48c42d659a5f8ddce7 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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/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 + :target: https://github.com/OCA/web/tree/18.0/web_m2x_options_manager + :alt: OCA/web +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/web-18-0/web-18-0-web_m2x_options_manager + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/web&target_branch=18.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +Allows managing the "Create..." and "Create and Edit..." options for +Many2one and Many2many fields directly from the ir.model form view. + +**Table of contents** + +.. contents:: + :local: + +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. + +Button "Fill" will add every missing field to the options. Button +"Empty" will remove every option. + +Bug Tracker +=========== + +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 +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Camptocamp + +Contributors +------------ + +- `Camptocamp `__: + + - Silvio Gregorini + +- Duong (Tran Quoc) +- Chau Le + +Other credits +------------- + +The migration of this module from 17.0 to 18.0 was financially supported +by Camptocamp + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/web `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/web_m2x_options_manager/__init__.py b/web_m2x_options_manager/__init__.py new file mode 100644 index 000000000..2a7f1c54a --- /dev/null +++ b/web_m2x_options_manager/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2021 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import models diff --git a/web_m2x_options_manager/__manifest__.py b/web_m2x_options_manager/__manifest__.py new file mode 100644 index 000000000..f1b5982d4 --- /dev/null +++ b/web_m2x_options_manager/__manifest__.py @@ -0,0 +1,23 @@ +# Copyright 2021 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Web M2X Options Manager", + "summary": 'Adds an interface to manage the "Create" and' + ' "Create and Edit" options for specific models and' + " fields.", + "version": "18.0.1.0.0", + "author": "Camptocamp, Odoo Community Association (OCA)", + "license": "AGPL-3", + "category": "Web", + "data": [ + "security/ir.model.access.csv", + "views/ir_model.xml", + ], + "demo": [ + "demo/res_partner_demo_view.xml", + ], + "depends": ["base", "web_m2x_options"], + "website": "https://github.com/OCA/web", + "installable": True, +} diff --git a/web_m2x_options_manager/demo/res_partner_demo_view.xml b/web_m2x_options_manager/demo/res_partner_demo_view.xml new file mode 100644 index 000000000..6e0b82bdf --- /dev/null +++ b/web_m2x_options_manager/demo/res_partner_demo_view.xml @@ -0,0 +1,27 @@ + + + + res.partner.demo.form.view + res.partner + 1000 + +
+ + + + + + + + + + + + + + + +
+
+
+
diff --git a/web_m2x_options_manager/i18n/web_m2x_options_manager.pot b/web_m2x_options_manager/i18n/web_m2x_options_manager.pot new file mode 100644 index 000000000..1014c9ca5 --- /dev/null +++ b/web_m2x_options_manager/i18n/web_m2x_options_manager.pot @@ -0,0 +1,210 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_m2x_options_manager +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: web_m2x_options_manager +#: code:addons/web_m2x_options_manager/models/m2x_create_edit_option.py:0 +#, python-format +msgid "'%s' is not a valid field for model '%s'!" +msgstr "" + +#. module: web_m2x_options_manager +#: model:ir.model.fields.selection,name:web_m2x_options_manager.selection__m2x_create_edit_option__option_create__set_true +#: model:ir.model.fields.selection,name:web_m2x_options_manager.selection__m2x_create_edit_option__option_create_edit__set_true +msgid "Add" +msgstr "" + +#. module: web_m2x_options_manager +#: model:ir.model.fields,field_description:web_m2x_options_manager.field_m2x_create_edit_option__option_create_edit +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 +msgid "Create & Edit Wizard" +msgstr "" + +#. module: web_m2x_options_manager +#: model:ir.model.fields,field_description:web_m2x_options_manager.field_m2x_create_edit_option__option_create +msgid "Create Option" +msgstr "" + +#. module: web_m2x_options_manager +#: model_terms:ir.ui.view,arch_db:web_m2x_options_manager.view_model_form_inherit +msgid "Create/Edit Options" +msgstr "" + +#. module: web_m2x_options_manager +#: model:ir.model.fields,field_description:web_m2x_options_manager.field_m2x_create_edit_option__create_uid +msgid "Created by" +msgstr "" + +#. module: web_m2x_options_manager +#: model:ir.model.fields,field_description:web_m2x_options_manager.field_m2x_create_edit_option__create_date +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 +msgid "" +"Defines behaviour for 'Create & Edit' Wizard\n" +"Set to False to prevent 'Create & Edit' Wizard to pop up" +msgstr "" + +#. module: web_m2x_options_manager +#: model:ir.model.fields,help:web_m2x_options_manager.field_m2x_create_edit_option__option_create_edit +msgid "" +"Defines behaviour for 'Create & Edit' option:\n" +"* Do nothing: nothing is done\n" +"* Add/Remove: option 'Create & Edit' is set to True/False only if not already present in view definition\n" +"* Force Add/Remove: option 'Create & Edit' is always set to True/False, overriding any pre-existing option" +msgstr "" + +#. module: web_m2x_options_manager +#: model:ir.model.fields,help:web_m2x_options_manager.field_m2x_create_edit_option__option_create +msgid "" +"Defines behaviour for 'Create' option:\n" +"* Do nothing: nothing is done\n" +"* Add/Remove: option 'Create' is set to True/False only if not already present in view definition\n" +"* Force Add/Remove: option 'Create' is always set to True/False, overriding any pre-existing option" +msgstr "" + +#. module: web_m2x_options_manager +#: model:ir.model.fields,field_description:web_m2x_options_manager.field_ir_model__display_name +#: model:ir.model.fields,field_description:web_m2x_options_manager.field_ir_model_fields__display_name +#: model:ir.model.fields,field_description:web_m2x_options_manager.field_ir_ui_view__display_name +#: model:ir.model.fields,field_description:web_m2x_options_manager.field_m2x_create_edit_option__display_name +msgid "Display Name" +msgstr "" + +#. module: web_m2x_options_manager +#: model:ir.model.fields.selection,name:web_m2x_options_manager.selection__m2x_create_edit_option__option_create__none +#: model:ir.model.fields.selection,name:web_m2x_options_manager.selection__m2x_create_edit_option__option_create_edit__none +msgid "Do nothing" +msgstr "" + +#. module: web_m2x_options_manager +#: model_terms:ir.ui.view,arch_db:web_m2x_options_manager.view_model_form_inherit +msgid "Empty" +msgstr "" + +#. module: web_m2x_options_manager +#: model:ir.model.fields,field_description:web_m2x_options_manager.field_m2x_create_edit_option__field_id +msgid "Field" +msgstr "" + +#. module: web_m2x_options_manager +#: model:ir.model.fields,field_description:web_m2x_options_manager.field_m2x_create_edit_option__field_name +msgid "Field Name" +msgstr "" + +#. module: web_m2x_options_manager +#: model:ir.model,name:web_m2x_options_manager.model_ir_model_fields +msgid "Fields" +msgstr "" + +#. module: web_m2x_options_manager +#: model_terms:ir.ui.view,arch_db:web_m2x_options_manager.view_model_form_inherit +msgid "Fields Description" +msgstr "" + +#. module: web_m2x_options_manager +#: model_terms:ir.ui.view,arch_db:web_m2x_options_manager.view_model_form_inherit +msgid "Fill" +msgstr "" + +#. module: web_m2x_options_manager +#: model:ir.model.fields.selection,name:web_m2x_options_manager.selection__m2x_create_edit_option__option_create__force_true +#: model:ir.model.fields.selection,name:web_m2x_options_manager.selection__m2x_create_edit_option__option_create_edit__force_true +msgid "Force Add" +msgstr "" + +#. module: web_m2x_options_manager +#: model:ir.model.fields.selection,name:web_m2x_options_manager.selection__m2x_create_edit_option__option_create__force_false +#: model:ir.model.fields.selection,name:web_m2x_options_manager.selection__m2x_create_edit_option__option_create_edit__force_false +msgid "Force Remove" +msgstr "" + +#. module: web_m2x_options_manager +#: model:ir.model.fields,field_description:web_m2x_options_manager.field_ir_model__id +#: model:ir.model.fields,field_description:web_m2x_options_manager.field_ir_model_fields__id +#: model:ir.model.fields,field_description:web_m2x_options_manager.field_ir_ui_view__id +#: model:ir.model.fields,field_description:web_m2x_options_manager.field_m2x_create_edit_option__id +msgid "ID" +msgstr "" + +#. module: web_m2x_options_manager +#: model:ir.model.fields,field_description:web_m2x_options_manager.field_ir_model____last_update +#: model:ir.model.fields,field_description:web_m2x_options_manager.field_ir_model_fields____last_update +#: model:ir.model.fields,field_description:web_m2x_options_manager.field_ir_ui_view____last_update +#: model:ir.model.fields,field_description:web_m2x_options_manager.field_m2x_create_edit_option____last_update +msgid "Last Modified on" +msgstr "" + +#. module: web_m2x_options_manager +#: model:ir.model.fields,field_description:web_m2x_options_manager.field_m2x_create_edit_option__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: web_m2x_options_manager +#: model:ir.model.fields,field_description:web_m2x_options_manager.field_m2x_create_edit_option__write_date +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 +msgid "M2X Create Edit Option" +msgstr "" + +#. module: web_m2x_options_manager +#: model:ir.model,name:web_m2x_options_manager.model_m2x_create_edit_option +msgid "Manage Options 'Create/Edit' For Fields" +msgstr "" + +#. module: web_m2x_options_manager +#: model:ir.model.fields,field_description:web_m2x_options_manager.field_m2x_create_edit_option__model_id +msgid "Model" +msgstr "" + +#. module: web_m2x_options_manager +#: model:ir.model.fields,field_description:web_m2x_options_manager.field_m2x_create_edit_option__model_name +msgid "Model Name" +msgstr "" + +#. module: web_m2x_options_manager +#: model:ir.model,name:web_m2x_options_manager.model_ir_model +msgid "Models" +msgstr "" + +#. module: web_m2x_options_manager +#: code:addons/web_m2x_options_manager/models/m2x_create_edit_option.py:0 +#, python-format +msgid "Only Many2many and Many2one fields can be chosen!" +msgstr "" + +#. module: web_m2x_options_manager +#: model:ir.model.constraint,message:web_m2x_options_manager.constraint_m2x_create_edit_option_model_field_uniqueness +msgid "Options must be unique for each model/field couple!" +msgstr "" + +#. module: web_m2x_options_manager +#: model:ir.model.fields.selection,name:web_m2x_options_manager.selection__m2x_create_edit_option__option_create__set_false +#: model:ir.model.fields.selection,name:web_m2x_options_manager.selection__m2x_create_edit_option__option_create_edit__set_false +msgid "Remove" +msgstr "" + +#. module: web_m2x_options_manager +#: model:ir.model,name:web_m2x_options_manager.model_ir_ui_view +msgid "View" +msgstr "" diff --git a/web_m2x_options_manager/models/__init__.py b/web_m2x_options_manager/models/__init__.py new file mode 100644 index 000000000..699352799 --- /dev/null +++ b/web_m2x_options_manager/models/__init__.py @@ -0,0 +1,6 @@ +# Copyright 2021 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import ir_model +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 new file mode 100644 index 000000000..dbf1c0277 --- /dev/null +++ b/web_m2x_options_manager/models/ir_model.py @@ -0,0 +1,39 @@ +# Copyright 2021 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class IrModel(models.Model): + _inherit = "ir.model" + + m2x_create_edit_option_ids = fields.One2many( + "m2x.create.edit.option", + "model_id", + ) + + 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" + _rec_names_search = ["name", "field_description"] diff --git a/web_m2x_options_manager/models/ir_ui_view.py b/web_m2x_options_manager/models/ir_ui_view.py new file mode 100644 index 000000000..23f26b4bf --- /dev/null +++ b/web_m2x_options_manager/models/ir_ui_view.py @@ -0,0 +1,19 @@ +# Copyright 2021 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import models + + +class IrUiView(models.Model): + _inherit = "ir.ui.view" + + def _postprocess_tag_field(self, node, name_manager, node_info): + res = super()._postprocess_tag_field(node, name_manager, node_info) + if node.tag == "field": + mname = name_manager.model._name + field = name_manager.model._fields.get(node.get("name")) + 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) + return res diff --git a/web_m2x_options_manager/models/m2x_create_edit_option.py b/web_m2x_options_manager/models/m2x_create_edit_option.py new file mode 100644 index 000000000..7adb8b7c7 --- /dev/null +++ b/web_m2x_options_manager/models/m2x_create_edit_option.py @@ -0,0 +1,184 @@ +# Copyright 2021 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models +from odoo.exceptions import ValidationError +from odoo.tools.cache import ormcache +from odoo.tools.safe_eval import safe_eval + + +class M2xCreateEditOption(models.Model): + _name = "m2x.create.edit.option" + _description = "Manage Options 'Create/Edit' For Fields" + + field_id = fields.Many2one( + "ir.model.fields", + domain=[("ttype", "in", ("many2many", "many2one"))], + ondelete="cascade", + required=True, + string="Field", + ) + + field_name = fields.Char( + related="field_id.name", + store=True, + ) + + model_id = fields.Many2one( + "ir.model", + ondelete="cascade", + required=True, + string="Model", + ) + + model_name = fields.Char( + compute="_compute_model_name", + inverse="_inverse_model_name", + store=True, + ) + + option_create = fields.Selection( + [ + ("none", "Do nothing"), + ("set_true", "Add"), + ("force_true", "Force Add"), + ("set_false", "Remove"), + ("force_false", "Force Remove"), + ], + default="set_false", + help="Defines behaviour for 'Create' option:\n" + "* Do nothing: nothing is done\n" + "* Add/Remove: option 'Create' is set to True/False only if not" + " already present in view definition\n" + "* Force Add/Remove: option 'Create' is always set to True/False," + " overriding any pre-existing option", + required=True, + string="Create Option", + ) + + option_create_edit = fields.Selection( + [ + ("none", "Do nothing"), + ("set_true", "Add"), + ("force_true", "Force Add"), + ("set_false", "Remove"), + ("force_false", "Force Remove"), + ], + default="set_false", + help="Defines behaviour for 'Create & Edit' option:\n" + "* Do nothing: nothing is done\n" + "* Add/Remove: option 'Create & Edit' is set to True/False only if not" + " already present in view definition\n" + "* Force Add/Remove: option 'Create & Edit' is always set to" + " True/False, overriding any pre-existing option", + required=True, + string="Create & Edit Option", + ) + + option_create_edit_wizard = fields.Boolean( + default=True, + help="Defines behaviour for 'Create & Edit' Wizard\n" + "Set to False to prevent 'Create & Edit' Wizard to pop up", + string="Create & Edit Wizard", + ) + + _sql_constraints = [ + ( + "model_field_uniqueness", + "unique(field_id,model_id)", + "Options must be unique for each model/field couple!", + ), + ] + + @api.model_create_multi + def create(self, vals_list): + # Clear cache to avoid misbehavior from cached :meth:`_get()` + self.env.registry.clear_all_caches() + return super().create(vals_list) + + def write(self, vals): + # Clear cache to avoid misbehavior from cached :meth:`_get()` + self.env.registry.clear_all_caches() + return super().write(vals) + + def unlink(self): + # Clear cache to avoid misbehavior from cached :meth:`_get()` + self.env.registry.clear_all_caches() + return super().unlink() + + @api.depends("model_id") + def _compute_model_name(self): + for opt in self: + opt.model_name = opt.model_id.model + + def _inverse_model_name(self): + getter = self.env["ir.model"]._get + for opt in self: + # This also works as a constrain: if ``model_name`` is not a + # valid model name, then ``model_id`` will be emptied, but it's + # a required field! + opt.model_id = getter(opt.model_name) + + @api.constrains("model_id", "field_id") + def _check_field_in_model(self): + for opt in self: + if opt.field_id.model_id != opt.model_id: + msg = self.env._( + "'%(field_name)s' is not a valid field for model '%(model_name)s'!", + field_name=opt.field_name, + model_name=opt.model_name, + ) + raise ValidationError(msg) + + @api.constrains("field_id") + def _check_field_type(self): + ttypes = ("many2many", "many2one") + if any(o.field_id.ttype not in ttypes for o in self): + msg = self.env._("Only Many2many and Many2one fields can be chosen!") + raise ValidationError(msg) + + def _apply_options(self, node): + """Applies options ``self`` to ``node``""" + self.ensure_one() + options = node.attrib.get("options") or {} + if isinstance(options, str): + options = safe_eval(options, dict(self.env.context or [])) or {} + + for k in ("create", "create_edit"): + opt = self[f"option_{k}"] + if opt == "none": + continue + mode, val = opt.split("_") + if k not in options: + options[k] = val == "true" + if mode == "force": + options[f"no_{k}"] = val == "false" + if not self.option_create_edit_wizard: + options["no_quick_create"] = True + node.set("options", str(options)) + + @api.model + def get(self, model_name, field_name): + """Returns specific record for ``field_name`` in ``model_name`` + + :param str model_name: technical model name (i.e. "sale.order") + :param str field_name: technical field name (i.e. "partner_id") + """ + return self.browse(self._get(model_name, field_name)) + + @api.model + @ormcache("model_name", "field_name") + def _get(self, model_name, field_name): + """Inner implementation of ``get``. + An ID is returned to allow caching (see :class:`ormcache`); :meth:`get` + will then convert it to a proper record. + + :param str model_name: technical model name (i.e. "sale.order") + :param str field_name: technical field name (i.e. "partner_id") + """ + dom = [ + ("model_name", "=", model_name), + ("field_name", "=", field_name), + ] + # `_check_field_model_uniqueness()` grants uniqueness if existing + return self.search(dom, limit=1).id diff --git a/web_m2x_options_manager/pyproject.toml b/web_m2x_options_manager/pyproject.toml new file mode 100644 index 000000000..4231d0ccc --- /dev/null +++ b/web_m2x_options_manager/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/web_m2x_options_manager/readme/CONTRIBUTORS.md b/web_m2x_options_manager/readme/CONTRIBUTORS.md new file mode 100644 index 000000000..ee6fdeb91 --- /dev/null +++ b/web_m2x_options_manager/readme/CONTRIBUTORS.md @@ -0,0 +1,4 @@ +- [Camptocamp](https://www.camptocamp.com): + - Silvio Gregorini +- Duong (Tran Quoc) \<\> +- Chau Le \<\> diff --git a/web_m2x_options_manager/readme/CREDITS.md b/web_m2x_options_manager/readme/CREDITS.md new file mode 100644 index 000000000..e2327d929 --- /dev/null +++ b/web_m2x_options_manager/readme/CREDITS.md @@ -0,0 +1 @@ +The migration of this module from 17.0 to 18.0 was financially supported by Camptocamp diff --git a/web_m2x_options_manager/readme/DESCRIPTION.md b/web_m2x_options_manager/readme/DESCRIPTION.md new file mode 100644 index 000000000..d3a1850ba --- /dev/null +++ b/web_m2x_options_manager/readme/DESCRIPTION.md @@ -0,0 +1,2 @@ +Allows managing the "Create..." and "Create and Edit..." options for +Many2one and Many2many fields directly from the ir.model form view. diff --git a/web_m2x_options_manager/readme/USAGE.md b/web_m2x_options_manager/readme/USAGE.md new file mode 100644 index 000000000..bd009fada --- /dev/null +++ b/web_m2x_options_manager/readme/USAGE.md @@ -0,0 +1,7 @@ +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. diff --git a/web_m2x_options_manager/security/ir.model.access.csv b/web_m2x_options_manager/security/ir.model.access.csv new file mode 100644 index 000000000..796d5b922 --- /dev/null +++ b/web_m2x_options_manager/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_m2x_create_edit_option_user,access_m2x_create_edit_option_user,model_m2x_create_edit_option,base.group_user,1,0,0,0 +access_m2x_create_edit_option_system,access_m2x_create_edit_option_system,model_m2x_create_edit_option,base.group_system,1,1,1,1 diff --git a/web_m2x_options_manager/static/description/icon.png b/web_m2x_options_manager/static/description/icon.png new file mode 100644 index 000000000..3a0328b51 Binary files /dev/null and b/web_m2x_options_manager/static/description/icon.png differ diff --git a/web_m2x_options_manager/static/description/index.html b/web_m2x_options_manager/static/description/index.html new file mode 100644 index 000000000..d8d5e1c00 --- /dev/null +++ b/web_m2x_options_manager/static/description/index.html @@ -0,0 +1,444 @@ + + + + + +Web M2X Options Manager + + + +
+

Web M2X Options Manager

+ + +

Beta License: AGPL-3 OCA/web Translate me on Weblate Try me on Runboat

+

Allows managing the “Create…” and “Create and Edit…” options for +Many2one and Many2many fields directly from the ir.model form view.

+

Table of contents

+ +
+

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.

+

Button “Fill” will add every missing field to the options. Button +“Empty” will remove every option.

+
+
+

Bug Tracker

+

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 +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Camptocamp
  • +
+
+
+

Contributors

+ +
+
+

Other credits

+

The migration of this module from 17.0 to 18.0 was financially supported +by Camptocamp

+
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/web project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/web_m2x_options_manager/tests/__init__.py b/web_m2x_options_manager/tests/__init__.py new file mode 100644 index 000000000..f803479e2 --- /dev/null +++ b/web_m2x_options_manager/tests/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2021 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import test_m2x_create_edit_option diff --git a/web_m2x_options_manager/tests/test_m2x_create_edit_option.py b/web_m2x_options_manager/tests/test_m2x_create_edit_option.py new file mode 100644 index 000000000..c65783f2b --- /dev/null +++ b/web_m2x_options_manager/tests/test_m2x_create_edit_option.py @@ -0,0 +1,120 @@ +# Copyright 2021 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from lxml import etree + +from odoo.exceptions import ValidationError +from odoo.tests.common import TransactionCase +from odoo.tools.safe_eval import safe_eval + + +class TestM2xCreateEditOption(TransactionCase): + def setUp(self): + super().setUp() + ref = self.env.ref + # View to be used + self.view = ref("web_m2x_options_manager.res_partner_demo_form_view") + # res.partner model and fields + self.res_partner_model = ref("base.model_res_partner") + self.categ_field = ref("base.field_res_partner__category_id") + self.title_field = ref("base.field_res_partner__title") + self.users_field = ref("base.field_res_partner__user_ids") + # res.users model and fields + self.res_users_model = ref("base.model_res_users") + self.company_field = ref("base.field_res_users__company_id") + # Options setup + self.title_opt = self.env["m2x.create.edit.option"].create( + { + "field_id": self.title_field.id, + "model_id": self.res_partner_model.id, + "option_create": "set_true", + "option_create_edit": "set_true", + "option_create_edit_wizard": True, + } + ) + self.categories_opt = self.env["m2x.create.edit.option"].create( + { + "field_id": self.categ_field.id, + "model_id": self.res_partner_model.id, + "option_create": "set_true", + "option_create_edit": "set_true", + "option_create_edit_wizard": True, + } + ) + self.company_opt = self.env["m2x.create.edit.option"].create( + { + "field_id": self.company_field.id, + "model_id": self.res_users_model.id, + "option_create": "force_true", + "option_create_edit": "set_true", + "option_create_edit_wizard": False, + } + ) + + def test_errors(self): + with self.assertRaises(ValidationError): + # Fails ``_check_field_in_model``: model is res.partner, field is + # res.users's company_id + self.env["m2x.create.edit.option"].create( + { + "field_id": self.company_field.id, + "model_id": self.res_partner_model.id, + "option_create": "set_true", + "option_create_edit": "set_true", + } + ) + with self.assertRaises(ValidationError): + # Fails ``_check_field_type``: users_field is a One2many + self.env["m2x.create.edit.option"].create( + { + "field_id": self.users_field.id, + "model_id": self.res_partner_model.id, + "option_create": "set_true", + "option_create_edit": "set_true", + } + ) + + def test_apply_options(self): + res = self.env["res.partner"].get_view(self.view.id) + + # Check fields on res.partner form view + form_arch = res["arch"] + form_doc = etree.XML(form_arch) + title_node = form_doc.xpath("//field[@name='title']")[0] + self.assertEqual( + safe_eval(title_node.attrib.get("options"), nocopy=True), + {"create": True, "create_edit": True}, + ) + self.assertEqual( + ( + title_node.attrib.get("can_create"), + title_node.attrib.get("can_write"), + ), + ("True", "True"), + ) + categ_node = form_doc.xpath("//field[@name='category_id']")[0] + self.assertEqual( + safe_eval(categ_node.attrib.get("options"), nocopy=True), + {"create": False, "create_edit": True}, + ) + # Check fields on res.users tree view (contained in ``user_ids`` field) + company_node = form_doc.xpath("//field[@name='company_id']")[0] + self.assertEqual( + safe_eval(company_node.attrib.get("options"), nocopy=True), + { + "create": False, + "no_create": False, + "create_edit": True, + "no_quick_create": True, + }, + ) + # Update options, check that node has been updated too + self.title_opt.option_create_edit = "force_false" + res = self.env["res.partner"].get_view(self.view.id) + form_arch = res["arch"] + form_doc = etree.XML(form_arch) + title_node = form_doc.xpath("//field[@name='title']")[0] + self.assertEqual( + safe_eval(title_node.attrib.get("options"), nocopy=True), + {"create": True, "create_edit": False, "no_create_edit": True}, + ) diff --git a/web_m2x_options_manager/views/ir_model.xml b/web_m2x_options_manager/views/ir_model.xml new file mode 100644 index 000000000..96821ae87 --- /dev/null +++ b/web_m2x_options_manager/views/ir_model.xml @@ -0,0 +1,36 @@ + + + + view.model.form.inherit + ir.model + + + + +
+
+ + + + + + + + + +
+
+
+
+