diff --git a/setup/web_widget_many2one_simple/odoo/addons/web_widget_many2one_simple b/setup/web_widget_many2one_simple/odoo/addons/web_widget_many2one_simple new file mode 120000 index 000000000..841068953 --- /dev/null +++ b/setup/web_widget_many2one_simple/odoo/addons/web_widget_many2one_simple @@ -0,0 +1 @@ +../../../../web_widget_many2one_simple \ No newline at end of file diff --git a/setup/web_widget_many2one_simple/setup.py b/setup/web_widget_many2one_simple/setup.py new file mode 100644 index 000000000..28c57bb64 --- /dev/null +++ b/setup/web_widget_many2one_simple/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/web_widget_many2one_simple/README.rst b/web_widget_many2one_simple/README.rst new file mode 100644 index 000000000..f777cfb08 --- /dev/null +++ b/web_widget_many2one_simple/README.rst @@ -0,0 +1,124 @@ +====================== +Simple many2one widget +====================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! 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_many2one_simple + :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_many2one_simple + :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 adds a new widget to many2one fields that allows to link records +without any extra features (no autocomplete and no partial search). + +* Preview + + .. image:: https://raw.githubusercontent.com/OCA/web/13.0/web_widget_many2one_simple/static/img/preview.gif + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +Attributes: +~~~~~~~~~~~ + +* regex [String]: Defines the regex to test the search criteria +* can_create [Boolean]: Allow create new records +* can_write [Boolean]: Allow edit the linked record + +Options: +~~~~~~~~ + +* search [Dictionary] + * field: The field to use in the search process [Default is 'id'] + * oper: The operator (like, ilike, etc...) [Default is '='] + +* no_create: Invalidates the 'can_create' flag +* no_write: Invalidates the 'can_write' flag +* no_open: Disables open the linked record + +Usage +===== + +You need to declare the usage of the new widget as follows: +```` + +Example: +~~~~~~~~ + +.. code:: xml + + `_. +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 +~~~~~~~ + +* Tecnativa + +Contributors +~~~~~~~~~~~~ + +* `Tecnativa `_: + + * Alexandre D. Díaz + +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. + +.. |maintainer-Tardo| image:: https://github.com/Tardo.png?size=40px + :target: https://github.com/Tardo + :alt: Tardo + +Current `maintainer `__: + +|maintainer-Tardo| + +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_many2one_simple/__init__.py b/web_widget_many2one_simple/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/web_widget_many2one_simple/__manifest__.py b/web_widget_many2one_simple/__manifest__.py new file mode 100644 index 000000000..120fb868c --- /dev/null +++ b/web_widget_many2one_simple/__manifest__.py @@ -0,0 +1,15 @@ +# Copyright 2020 Tecnativa - Alexandre D. Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Simple many2one widget", + "version": "13.0.1.0.0", + "license": "AGPL-3", + "author": "Tecnativa, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/web", + "depends": ["web"], + "data": ["views/assets.xml"], + "qweb": ["static/src/xml/many2one_simple.xml"], + "installable": True, + "maintainers": ["Tardo"], +} diff --git a/web_widget_many2one_simple/readme/CONFIGURE.rst b/web_widget_many2one_simple/readme/CONFIGURE.rst new file mode 100644 index 000000000..5e589ff36 --- /dev/null +++ b/web_widget_many2one_simple/readme/CONFIGURE.rst @@ -0,0 +1,17 @@ +Attributes: +~~~~~~~~~~~ + +* regex [String]: Defines the regex to test the search criteria +* can_create [Boolean]: Allow create new records +* can_write [Boolean]: Allow edit the linked record + +Options: +~~~~~~~~ + +* search [Dictionary] + * field: The field to use in the search process [Default is 'id'] + * oper: The operator (like, ilike, etc...) [Default is '='] + +* no_create: Invalidates the 'can_create' flag +* no_write: Invalidates the 'can_write' flag +* no_open: Disables open the linked record diff --git a/web_widget_many2one_simple/readme/CONTRIBUTORS.rst b/web_widget_many2one_simple/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..4417daf09 --- /dev/null +++ b/web_widget_many2one_simple/readme/CONTRIBUTORS.rst @@ -0,0 +1,3 @@ +* `Tecnativa `_: + + * Alexandre D. Díaz diff --git a/web_widget_many2one_simple/readme/DESCRIPTION.rst b/web_widget_many2one_simple/readme/DESCRIPTION.rst new file mode 100644 index 000000000..c64017749 --- /dev/null +++ b/web_widget_many2one_simple/readme/DESCRIPTION.rst @@ -0,0 +1,6 @@ +This module adds a new widget to many2one fields that allows to link records +without any extra features (no autocomplete and no partial search). + +* Preview + + .. image:: ../static/img/preview.gif diff --git a/web_widget_many2one_simple/readme/USAGE.rst b/web_widget_many2one_simple/readme/USAGE.rst new file mode 100644 index 000000000..49cc6acfb --- /dev/null +++ b/web_widget_many2one_simple/readme/USAGE.rst @@ -0,0 +1,11 @@ +You need to declare the usage of the new widget as follows: +```` + +Example: +~~~~~~~~ + +.. code:: xml + + + + + + + +Simple many2one widget + + + +
+

Simple many2one widget

+ + +

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

+

This module adds a new widget to many2one fields that allows to link records +without any extra features (no autocomplete and no partial search).

+
    +
  • Preview

    +https://raw.githubusercontent.com/OCA/web/13.0/web_widget_many2one_simple/static/img/preview.gif +
  • +
+

Table of contents

+ +
+

Configuration

+
+

Attributes:

+
    +
  • regex [String]: Defines the regex to test the search criteria
  • +
  • can_create [Boolean]: Allow create new records
  • +
  • can_write [Boolean]: Allow edit the linked record
  • +
+
+
+

Options:

+
    +
  • search [Dictionary] +* field: The field to use in the search process [Default is ‘id’] +* oper: The operator (like, ilike, etc…) [Default is ‘=’]
  • +
  • no_create: Invalidates the ‘can_create’ flag
  • +
  • no_write: Invalidates the ‘can_write’ flag
  • +
  • no_open: Disables open the linked record
  • +
+
+
+
+

Usage

+

You need to declare the usage of the new widget as follows: +<field name="xxx" widget="many2one_simple" options="...">

+
+

Example:

+
+<field name="partner_id" widget="many2one_simple" regex="^1\d*" options="{&quot;search&quot;: { &quot;field&quot;: &quot;name&quot;, &quot;oper&quot;: &quot;ilike&quot; }}"/
+
+

** Only allows search by id’s that starts with ‘1’ …

+
+
+
+

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

+
    +
  • Tecnativa
  • +
+
+
+

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.

+

Current maintainer:

+

Tardo

+

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_many2one_simple/static/img/preview.gif b/web_widget_many2one_simple/static/img/preview.gif new file mode 100644 index 000000000..2198bf7a9 Binary files /dev/null and b/web_widget_many2one_simple/static/img/preview.gif differ diff --git a/web_widget_many2one_simple/static/src/css/many2one_simple.scss b/web_widget_many2one_simple/static/src/css/many2one_simple.scss new file mode 100644 index 000000000..5745f95c5 --- /dev/null +++ b/web_widget_many2one_simple/static/src/css/many2one_simple.scss @@ -0,0 +1,21 @@ +.o_field_widget { + // Flex fields (inline) + &.o_field_many2one_simple { + display: inline-flex; + > span, + > button { + flex: 0 0 auto; + } + + .o_external_button { + padding: 0; + margin-left: 2px; + font-size: 19px; + color: #7c7bad; + border: none; + &:hover { + background-color: transparent; + } + } + } +} diff --git a/web_widget_many2one_simple/static/src/js/many2one_simple_field.js b/web_widget_many2one_simple/static/src/js/many2one_simple_field.js new file mode 100644 index 000000000..ce796e48a --- /dev/null +++ b/web_widget_many2one_simple/static/src/js/many2one_simple_field.js @@ -0,0 +1,340 @@ +odoo.define("web_widget_many2one_simple.FieldMany2OneSimple", function(require) { + "use strict"; + + const core = require("web.core"); + const Dialog = require("web.Dialog"); + const dialogs = require("web.view_dialogs"); + const AbstractField = require("web.AbstractField"); + const field_registry = require("web.field_registry"); + + const _t = core._t; + const _lt = core._lt; + + const FieldMany2OneSimple = AbstractField.extend({ + description: _lt("Many2one Simple"), + supportedFieldTypes: ["many2one"], + template: "web_widget_many2one_simple.FieldMany2OneSimple", + custom_events: _.extend({}, AbstractField.prototype.custom_events, { + quick_create: "_onQuickCreate", + field_changed: "_onFieldChanged", + }), + events: _.extend({}, AbstractField.prototype.events, { + "focusout input": "_onInputFocusout", + "keyup input": "_onInputKeyup", + "click .o_external_button": "_onExternalButtonClick", + click: "_onClick", + }), + + /** + * @override + */ + init: function(parent, name, record, options) { + this._super.apply(this, arguments); + + this.can_create = + ("can_create" in this.attrs + ? JSON.parse(this.attrs.can_create) + : true) && !this.nodeOptions.no_create; + this.can_write = + "can_write" in this.attrs ? JSON.parse(this.attrs.can_write) : true; + this.regex = + !this.nodeOptions.no_regex && "regex" in this.attrs + ? new RegExp(this.attrs.regex) + : undefined; + + this.nodeOptions = _.defaults(this.nodeOptions, { + search: { + field: "id", + oper: "=", + }, + }); + this.noOpen = + "noOpen" in (options || {}) ? options.noOpen : this.nodeOptions.no_open; + this.search = + "search" in (options || {}) ? options.search : this.nodeOptions.search; + }, + + /** + * @override + */ + start: function() { + this.$input = this.$("input"); + this.$external_button = this.$(".o_external_button"); + return this._super.apply(this, arguments); + }, + + /** + * @param {Object} value (format: {id: ..., display_name: ...}) + * @returns {Promise} + */ + reinitialize: function(value) { + this.isDirty = false; + return this._setValue(value).then(() => { + const formatted_value = this._formatValue(this.value); + this.$input.val(formatted_value); + }); + }, + + /** + * @returns {Promise} + */ + _search: function() { + if (!this.isDirty) { + return Promise.resolve(); + } + const search_value = this.$input.val(); + if (search_value === "") { + this.reinitialize(false); + return Promise.resolve(false); + } + + if (this.regex && !this.regex.test(search_value)) { + this._showErrorMessage(_t("The given search criteria is not valid.")); + this.reinitialize(false); + return Promise.resolve(false); + } + + const context = this.record.getContext( + _.extend({}, this.recordParams, { + additionalContext: this.attrs.context || {}, + }) + ); + context.many2one_simple = true; + const domain = this.record.getDomain(this.recordParams); + + return this._rpc({ + model: this.field.relation, + method: "search_read", + fields: ["display_name"], + domain: _.union(domain, this._getDomain()), + limit: 1, + kwargs: {context: context}, + }).then(results => { + if (_.isEmpty(results)) { + if (this.can_create) { + const create_context = _.extend({}, this.attrs.context); + if (this.search.field !== "id") { + create_context[ + "default_" + this.search.field + ] = this.$input.val(); + } + this._createPopup("form", create_context); + } else { + this._showErrorMessage( + _t("Can't found any record with the given criteria.") + ); + } + this.reinitialize(false); + } else { + this.reinitialize(results[0]); + } + }); + }, + + /** + * @private + */ + _renderEdit: function() { + const value = this._formatValue(this.value); + this.$input.val(value); + this._updateExternalButton(); + }, + + /** + * @private + */ + _renderReadonly: function() { + const value = this._formatValue(this.value); + const escapedValue = _.escape((value || "").trim()); + const mapvalue = escapedValue + .split("\n") + .map(function(line) { + return "" + line + ""; + }) + .join("
"); + this.$el.html(mapvalue); + if (!this.noOpen && 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"); + } + }, + + /** + * Show a message to the user + * + * @private + * @param {String} error + */ + _showErrorMessage: function(error) { + Dialog.alert(this, error, { + title: _t("Many2One Simple"), + }); + }, + + /** + * Prepares and returns options for SelectCreateDialog + * + * @private + * @param {String} view + * @param {Object} context + * @returns {Object} + */ + _getCreatePopupOptions: function(view, context) { + return { + res_model: this.field.relation, + domain: this.record.getDomain({fieldName: this.name}), + context: _.extend( + {}, + this.record.getContext(this.recordParams), + context || {} + ), + dynamicFilters: [], + title: _t("Create: ") + this.string, + initial_view: view, + disable_multiple_selection: true, + no_create: !this.can_create, + kanban_view_ref: this.attrs.kanban_view_ref, + on_selected: records => this.reinitialize(records[0]), + on_closed: () => this.activate(), + }; + }, + + /** + * Create popup handling + * + * @private + * @param {any} view + * @param {any} context + * @returns {DialogClass} + */ + _createPopup: function(view, context) { + const options = this._getCreatePopupOptions(view, context); + return new dialogs.SelectCreateDialog( + this, + _.extend({}, this.nodeOptions, options) + ).open(); + }, + + /** + * Construct the domain used to found the related record + * + * @private + * @returns {Array} + */ + _getDomain: function() { + return [[this.search.field, this.search.oper, this.$input.val()]]; + }, + + /** + * @private + * @param {OdooEvent} event 'field_changed' event + */ + _onFieldChanged: function(event) { + this.$input.val(event.data.changes[this.name].display_name); + }, + + /** + * @private + */ + _onExternalButtonClick: function() { + if (!this.value) { + this.activate(); + return; + } + const context = this.record.getContext( + _.extend({}, this.recordParams, { + additionalContext: this.attrs.context || {}, + }) + ); + this._rpc({ + model: this.field.relation, + method: "get_formview_id", + args: [[this.value.res_id]], + context: context, + }).then(view_id => { + new dialogs.FormViewDialog(this, { + res_model: this.field.relation, + res_id: this.value.res_id, + context: context, + title: _t("Open: ") + this.string, + view_id: view_id, + readonly: !this.can_write, + on_saved: (record, changed) => { + if (changed) { + const _setValue = this._setValue.bind( + this, + this.value.data, + { + forceChange: true, + } + ); + this.trigger_up("reload", { + db_id: this.value.id, + onSuccess: _setValue, + onFailure: _setValue, + }); + } + }, + }).open(); + }); + }, + + /** + * @private + * @param {MouseEvent} event + */ + _onClick: function(event) { + if (this.mode === "readonly" && !this.noOpen) { + event.preventDefault(); + event.stopPropagation(); + this._rpc({ + model: this.field.relation, + method: "get_formview_action", + args: [[this.value.res_id]], + context: this.record.getContext(this.recordParams), + }).then(action => { + this.trigger_up("do_action", {action: action}); + }); + } + }, + + /** + * @private + */ + _onInputFocusout: function() { + this._search(); + }, + + /** + * @private + * + * @param {OdooEvent} ev + */ + _onInputKeyup: function(ev) { + if (ev.which === $.ui.keyCode.ENTER || ev.which === $.ui.keyCode.TAB) { + this._search(); + } else { + this.isDirty = true; + } + }, + + /** + * @private + */ + _updateExternalButton: function() { + const has_external_button = !this.noOpen && !this.floating && this.isSet(); + this.$external_button.toggle(has_external_button); + }, + }); + + field_registry.add("many2one_simple", FieldMany2OneSimple); + + return FieldMany2OneSimple; +}); diff --git a/web_widget_many2one_simple/static/src/xml/many2one_simple.xml b/web_widget_many2one_simple/static/src/xml/many2one_simple.xml new file mode 100644 index 000000000..aef7e0a4e --- /dev/null +++ b/web_widget_many2one_simple/static/src/xml/many2one_simple.xml @@ -0,0 +1,38 @@ + + + + + + + +
+ +
+
+
diff --git a/web_widget_many2one_simple/views/assets.xml b/web_widget_many2one_simple/views/assets.xml new file mode 100644 index 000000000..e0d1243d0 --- /dev/null +++ b/web_widget_many2one_simple/views/assets.xml @@ -0,0 +1,19 @@ + + +