diff --git a/setup/web_widget_child_selector/odoo/addons/web_widget_child_selector b/setup/web_widget_child_selector/odoo/addons/web_widget_child_selector new file mode 120000 index 000000000..e35e692af --- /dev/null +++ b/setup/web_widget_child_selector/odoo/addons/web_widget_child_selector @@ -0,0 +1 @@ +../../../../web_widget_child_selector \ No newline at end of file diff --git a/setup/web_widget_child_selector/setup.py b/setup/web_widget_child_selector/setup.py new file mode 100644 index 000000000..28c57bb64 --- /dev/null +++ b/setup/web_widget_child_selector/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/web_widget_child_selector/README.rst b/web_widget_child_selector/README.rst new file mode 100644 index 000000000..6e3ccfca4 --- /dev/null +++ b/web_widget_child_selector/README.rst @@ -0,0 +1,89 @@ +========================= +Web Widget Child Selector +========================= + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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/13.0/web_widget_child_selector + :alt: OCA/web +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/web-13-0/web-13-0-web_widget_child_selector + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/162/13.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allows to use a new widget for many2one fields that are made hierarchically. +Now, you can navigate easily between the hierarchy. + +When we are using readonly, the behaviour is similar than Many2one, but on edition +we are able to select the parents and childs in order to navigate properly. + +.. image:: https://raw.githubusercontent.com/web_widget_child_selector/static/description/edit_mode.png + :alt: Edition Screenshot + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +Use child_selection widget on a field. +If necessary we can use child_selection_field on options in order to define +which field we will show on edition only. +For example, name instead of display_name. + +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 smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Creu Blanca + +Contributors +~~~~~~~~~~~~ + +* Enric Tobella +* Jaime Arroyo + +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_widget_child_selector/__init__.py b/web_widget_child_selector/__init__.py new file mode 100644 index 000000000..0650744f6 --- /dev/null +++ b/web_widget_child_selector/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/web_widget_child_selector/__manifest__.py b/web_widget_child_selector/__manifest__.py new file mode 100644 index 000000000..2a81bf9fb --- /dev/null +++ b/web_widget_child_selector/__manifest__.py @@ -0,0 +1,14 @@ +# Copyright 2019 Creu Blanca +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Web Widget Child Selector", + "summary": "Widget used for navigation on hierarchy fields", + "version": "14.0.1.0.0", + "license": "AGPL-3", + "author": "Creu Blanca,Odoo Community Association (OCA)", + "website": "https://github.com/OCA/web", + "depends": ["web"], + "data": ["views/assets.xml"], + "qweb": ["static/src/xml/widget_child_selector.xml"], +} diff --git a/web_widget_child_selector/i18n/pt_BR.po b/web_widget_child_selector/i18n/pt_BR.po new file mode 100644 index 000000000..dd2b57cb6 --- /dev/null +++ b/web_widget_child_selector/i18n/pt_BR.po @@ -0,0 +1,23 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_widget_child_selector +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2021-03-15 06:45+0000\n" +"Last-Translator: Marcel Savegnago \n" +"Language-Team: none\n" +"Language: pt_BR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" +"X-Generator: Weblate 4.3.2\n" + +#. module: web_widget_child_selector +#: model:ir.model,name:web_widget_child_selector.model_base +#, fuzzy +msgid "Base" +msgstr "Base" diff --git a/web_widget_child_selector/i18n/web_widget_child_selector.pot b/web_widget_child_selector/i18n/web_widget_child_selector.pot new file mode 100644 index 000000000..e930dc6e8 --- /dev/null +++ b/web_widget_child_selector/i18n/web_widget_child_selector.pot @@ -0,0 +1,19 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_widget_child_selector +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 13.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_widget_child_selector +#: model:ir.model,name:web_widget_child_selector.model_base +msgid "Base" +msgstr "" diff --git a/web_widget_child_selector/models/__init__.py b/web_widget_child_selector/models/__init__.py new file mode 100644 index 000000000..0e4444933 --- /dev/null +++ b/web_widget_child_selector/models/__init__.py @@ -0,0 +1 @@ +from . import base diff --git a/web_widget_child_selector/models/base.py b/web_widget_child_selector/models/base.py new file mode 100644 index 000000000..fa83ca824 --- /dev/null +++ b/web_widget_child_selector/models/base.py @@ -0,0 +1,32 @@ +# Copyright 2019 Creu Blanca +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import models + + +class Base(models.AbstractModel): + _inherit = "base" + + def _get_record_parents(self, field): + if not self or not hasattr(self, self._parent_name): + return [] + return getattr(self, self._parent_name)._get_record_parents(field) + [ + (self.id, str(getattr(self, field))) + ] + + def _get_record_direct_childs(self, field, domain): + if not hasattr(self, self._parent_name): + return [] + return [ + (r.id, str(getattr(r, field))) + for r in self.search([(self._parent_name, "=", self.id or False)] + domain) + ] + + def get_record_direct_childs_parents(self, options, domain=False): + if not domain: + domain = [] + field = options.get("child_selection_field", "display_name") + return { + "childs": self._get_record_direct_childs(field, domain), + "parents": self._get_record_parents(field), + } diff --git a/web_widget_child_selector/readme/CONTRIBUTORS.rst b/web_widget_child_selector/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..94e7b0a40 --- /dev/null +++ b/web_widget_child_selector/readme/CONTRIBUTORS.rst @@ -0,0 +1,2 @@ +* Enric Tobella +* Jaime Arroyo diff --git a/web_widget_child_selector/readme/DESCRIPTION.rst b/web_widget_child_selector/readme/DESCRIPTION.rst new file mode 100644 index 000000000..b4ac0a18a --- /dev/null +++ b/web_widget_child_selector/readme/DESCRIPTION.rst @@ -0,0 +1,8 @@ +This module allows to use a new widget for many2one fields that are made hierarchically. +Now, you can navigate easily between the hierarchy. + +When we are using readonly, the behaviour is similar than Many2one, but on edition +we are able to select the parents and childs in order to navigate properly. + +.. image:: /web_widget_child_selector/static/description/edit_mode.png + :alt: Edition Screenshot diff --git a/web_widget_child_selector/readme/USAGE.rst b/web_widget_child_selector/readme/USAGE.rst new file mode 100644 index 000000000..08642d4a2 --- /dev/null +++ b/web_widget_child_selector/readme/USAGE.rst @@ -0,0 +1,4 @@ +Use child_selector widget on a field. +If necessary we can use child_selection_field on options in order to define +which field we will show on edition only. +For example, name instead of display_name. diff --git a/web_widget_child_selector/static/description/edit_mode.png b/web_widget_child_selector/static/description/edit_mode.png new file mode 100644 index 000000000..4b4c0b8fd Binary files /dev/null and b/web_widget_child_selector/static/description/edit_mode.png differ diff --git a/web_widget_child_selector/static/description/icon.png b/web_widget_child_selector/static/description/icon.png new file mode 100644 index 000000000..3a0328b51 Binary files /dev/null and b/web_widget_child_selector/static/description/icon.png differ diff --git a/web_widget_child_selector/static/description/index.html b/web_widget_child_selector/static/description/index.html new file mode 100644 index 000000000..956f6b159 --- /dev/null +++ b/web_widget_child_selector/static/description/index.html @@ -0,0 +1,432 @@ + + + + + + +Web Widget Child Selector + + + +
+

Web Widget Child Selector

+ + +

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

+

This module allows to use a new widget for many2one fields that are made hierarchically. +Now, you can navigate easily between the hierarchy.

+

When we are using readonly, the behaviour is similar than Many2one, but on edition +we are able to select the parents and childs in order to navigate properly.

+Edition Screenshot +

Table of contents

+ +
+

Usage

+

Use child_selection widget on a field. +If necessary we can use child_selection_field on options in order to define +which field we will show on edition only. +For example, name instead of display_name.

+
+
+

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 smashing it by providing a detailed and welcomed +feedback.

+

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

+
+
+

Credits

+
+

Authors

+
    +
  • Creu Blanca
  • +
+
+
+

Contributors

+ +
+
+

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_widget_child_selector/static/src/js/widget_child_selector.js b/web_widget_child_selector/static/src/js/widget_child_selector.js new file mode 100644 index 000000000..d81a205fa --- /dev/null +++ b/web_widget_child_selector/static/src/js/widget_child_selector.js @@ -0,0 +1,91 @@ +odoo.define("web.web_widget_child_selector", function (require) { + "use strict"; + + var relational_fields = require("web.relational_fields"); + var field_registry = require("web.field_registry"); + var core = require("web.core"); + var qweb = core.qweb; + var FieldMany2One = relational_fields.FieldMany2One; + + var FieldChildSelector = FieldMany2One.extend({ + template: "FieldChildSelector", + events: _.extend({}, FieldMany2One.prototype.events, { + "click .o_child_selection_button": "_onChildSelectionClick", + }), + start: function () { + this.$input_dropdown = this.$(".o_input_dropdown"); + this.$input_value = this.$(".o_input_value"); + return this._super.apply(this, arguments); + }, + _renderReadonly: function () { + var value = _.escape((this.m2o_value || "").trim()) + .split("\n") + .join("
"); + this.$el.html(value); + if (!this.nodeOptions.no_open && this.value) { + this.$el.attr( + "href", + _.str.sprintf( + "#id=%s&model=%s", + this.value.res_id, + this.field.relation + ) + ); + this.$el.addClass("o_form_uri"); + } + }, + _set_childs: function () { + var self = this; + this.childs = {}; + this.parents = {}; + this.$input_dropdown.empty(); + this.$input_value.empty(); + var resources = []; + if (this.value.res_id) resources = [this.value.res_id]; + this._rpc({ + model: this.field.relation, + method: "get_record_direct_childs_parents", + args: [ + resources, + this.nodeOptions, + this.record.getDomain({fieldName: this.name}), + ], + context: this.record.getContext(this.recordParams), + }).then(function (data) { + _.each(data.parents, function (parent, key) { + self.parents[key] = parent; + }); + _.each(data.childs, function (child, key) { + self.childs[key] = child; + }); + self.$input_dropdown.append( + qweb.render("FieldChildSelectorChild", { + childs: self.childs, + }) + ); + self.$input_value.append( + qweb.render("FieldChildSelectorParent", { + parents: self.parents, + }) + ); + }); + }, + _onChildSelectionClick: function (event) { + var target = $(event.target); + var type = target.data("type"); + if (type === "clear") { + this._setValue({id: false}); + } else { + var index = target.data("index"); + var value = type === "child" ? this.childs[index] : this.parents[index]; + this._setValue({id: value[0], display_name: value[1]}); + } + }, + _renderEdit: function () { + this._set_childs(); + }, + }); + + field_registry.add("child_selector", FieldChildSelector); + return FieldChildSelector; +}); diff --git a/web_widget_child_selector/static/src/scss/widget_child_selector.scss b/web_widget_child_selector/static/src/scss/widget_child_selector.scss new file mode 100644 index 000000000..f973c606b --- /dev/null +++ b/web_widget_child_selector/static/src/scss/widget_child_selector.scss @@ -0,0 +1,14 @@ +.o_child_selection_button { + cursor: pointer; +} + +.o_child_selection_label { + cursor: pointer; +} + +.o_child_selection_clear { + opacity: 0.6; + &:hover { + opacity: 1; + } +} diff --git a/web_widget_child_selector/static/src/xml/widget_child_selector.xml b/web_widget_child_selector/static/src/xml/widget_child_selector.xml new file mode 100644 index 000000000..5b1af432d --- /dev/null +++ b/web_widget_child_selector/static/src/xml/widget_child_selector.xml @@ -0,0 +1,66 @@ + + + + + + + + + +
+ +
+
+ + +
+
+ + + +
+
+
+ + + + / +
+ + + + + + + + + diff --git a/web_widget_child_selector/tests/__init__.py b/web_widget_child_selector/tests/__init__.py new file mode 100644 index 000000000..36886fb64 --- /dev/null +++ b/web_widget_child_selector/tests/__init__.py @@ -0,0 +1 @@ +from . import test_widget_child_selector diff --git a/web_widget_child_selector/tests/test_widget_child_selector.py b/web_widget_child_selector/tests/test_widget_child_selector.py new file mode 100644 index 000000000..a9f36b65c --- /dev/null +++ b/web_widget_child_selector/tests/test_widget_child_selector.py @@ -0,0 +1,29 @@ +# Copyright 2019 Creu Blanca +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.tests.common import TransactionCase + + +class TestWidgetChildSelector(TransactionCase): + def setUp(self): + super().setUp() + ResPartner = self.env["res.partner"].with_context(tracking_disable=True) + self.partner_1 = ResPartner.create({"name": "P1"}) + self.partner_2 = ResPartner.create( + {"name": "P2", "parent_id": self.partner_1.id} + ) + self.partner_3 = ResPartner.create( + {"name": "P3", "parent_id": self.partner_2.id} + ) + # Model that doesnt have the parent/child structure + self.group = self.env["res.groups"].create({"name": "Group"}) + + def test_widget_child_selector(self): + res = self.partner_2.get_record_direct_childs_parents( + {"child_selection_field": "name"} + ) + self.assertIn((self.partner_1.id, self.partner_1.name), res["parents"]) + self.assertIn((self.partner_3.id, self.partner_3.name), res["childs"]) + res = self.group.get_record_direct_childs_parents({}) + self.assertFalse(res["parents"]) + self.assertFalse(res["childs"]) diff --git a/web_widget_child_selector/views/assets.xml b/web_widget_child_selector/views/assets.xml new file mode 100644 index 000000000..0799377fe --- /dev/null +++ b/web_widget_child_selector/views/assets.xml @@ -0,0 +1,20 @@ + + +