From 1c6d9cd008c3541bcdb7d3f56b47ee8f58fb4a7a Mon Sep 17 00:00:00 2001 From: Carlos Roca Date: Tue, 13 Aug 2024 12:33:49 +0200 Subject: [PATCH] [MIG] web_widget_dropdown_dynamic: Migration to 17.0 --- web_widget_dropdown_dynamic/README.rst | 11 ++ web_widget_dropdown_dynamic/__init__.py | 1 + web_widget_dropdown_dynamic/__manifest__.py | 11 +- .../demo/ir_filters_view.xml | 17 +++ .../demo/ir_model_fields.xml | 11 ++ .../models/__init__.py | 4 + .../models/ir_filters.py | 19 +++ .../readme/CONTRIBUTORS.md | 3 + web_widget_dropdown_dynamic/readme/USAGE.md | 6 + .../static/description/index.html | 10 ++ .../static/src/js/basic_model.esm.js | 50 -------- .../src/js/field_dynamic_dropdown.esm.js | 119 +++++++++--------- .../web_widget_dropdown_dynamic_tests.esm.js | 26 ++-- 13 files changed, 164 insertions(+), 124 deletions(-) create mode 100644 web_widget_dropdown_dynamic/demo/ir_filters_view.xml create mode 100644 web_widget_dropdown_dynamic/demo/ir_model_fields.xml create mode 100644 web_widget_dropdown_dynamic/models/__init__.py create mode 100644 web_widget_dropdown_dynamic/models/ir_filters.py delete mode 100644 web_widget_dropdown_dynamic/static/src/js/basic_model.esm.js diff --git a/web_widget_dropdown_dynamic/README.rst b/web_widget_dropdown_dynamic/README.rst index 6777712b9..06a396875 100644 --- a/web_widget_dropdown_dynamic/README.rst +++ b/web_widget_dropdown_dynamic/README.rst @@ -71,6 +71,13 @@ Usage context="{'depending_on': other_field}" /> +**DEMO** + +On User defined filters added new field to show the feature, it is +called **Dropdown Integer**. If any user selected just One option shoud +appear, but if Mitchell Admin it should be possible to select option One +and Two. + Bug Tracker =========== @@ -106,6 +113,10 @@ Contributors - Son Ho +- `Tecnativa `__: + + - Carlos Roca + Other credits ------------- diff --git a/web_widget_dropdown_dynamic/__init__.py b/web_widget_dropdown_dynamic/__init__.py index c71289ab1..c32fd62b7 100644 --- a/web_widget_dropdown_dynamic/__init__.py +++ b/web_widget_dropdown_dynamic/__init__.py @@ -1 +1,2 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from . import models diff --git a/web_widget_dropdown_dynamic/__manifest__.py b/web_widget_dropdown_dynamic/__manifest__.py index ad6fd5d23..12e1fe6a4 100644 --- a/web_widget_dropdown_dynamic/__manifest__.py +++ b/web_widget_dropdown_dynamic/__manifest__.py @@ -5,7 +5,7 @@ "name": "Dynamic Dropdown Widget", "summary": "This module adds support for dynamic dropdown widget", "category": "Web", - "version": "16.0.1.0.0", + "version": "17.0.1.0.0", "license": "AGPL-3", "author": "CorporateHub, Odoo Community Association (OCA)", "website": "https://github.com/OCA/web", @@ -13,7 +13,14 @@ "installable": True, "assets": { "web.assets_backend": [ - "web_widget_dropdown_dynamic/**/*", + "web_widget_dropdown_dynamic/static/src/js/field_dynamic_dropdown.esm.js", + ], + "web.qunit_suite_tests": [ + "web_widget_dropdown_dynamic/static/tests/web_widget_dropdown_dynamic_tests.esm.js", ], }, + "demo": [ + "demo/ir_model_fields.xml", + "demo/ir_filters_view.xml", + ], } diff --git a/web_widget_dropdown_dynamic/demo/ir_filters_view.xml b/web_widget_dropdown_dynamic/demo/ir_filters_view.xml new file mode 100644 index 000000000..adf046ab1 --- /dev/null +++ b/web_widget_dropdown_dynamic/demo/ir_filters_view.xml @@ -0,0 +1,17 @@ + + + + ir.filters + + + + + + + + diff --git a/web_widget_dropdown_dynamic/demo/ir_model_fields.xml b/web_widget_dropdown_dynamic/demo/ir_model_fields.xml new file mode 100644 index 000000000..fe5be5d12 --- /dev/null +++ b/web_widget_dropdown_dynamic/demo/ir_model_fields.xml @@ -0,0 +1,11 @@ + + + + Dropdown Integer + ir.filters + + x_dynamic_dropdown_int + manual + integer + + diff --git a/web_widget_dropdown_dynamic/models/__init__.py b/web_widget_dropdown_dynamic/models/__init__.py new file mode 100644 index 000000000..15a5fd375 --- /dev/null +++ b/web_widget_dropdown_dynamic/models/__init__.py @@ -0,0 +1,4 @@ +from odoo.tools import config + +if not config.get("without_demo"): + from . import ir_filters diff --git a/web_widget_dropdown_dynamic/models/ir_filters.py b/web_widget_dropdown_dynamic/models/ir_filters.py new file mode 100644 index 000000000..b3f9a78a8 --- /dev/null +++ b/web_widget_dropdown_dynamic/models/ir_filters.py @@ -0,0 +1,19 @@ +# Copyright 2024 Tecnativa - Carlos Roca +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import api, models + + +class IrFilters(models.Model): + _inherit = "ir.filters" + + @api.model + def dynamic_dropdown_int_method_demo(self): + values = [ + ("1", "One"), + ] + if self.env.context.get("depending_on") == self.env.ref("base.user_admin").id: + values += [ + ("2", "Two"), + ] + return values diff --git a/web_widget_dropdown_dynamic/readme/CONTRIBUTORS.md b/web_widget_dropdown_dynamic/readme/CONTRIBUTORS.md index 82d63ce62..510d7f721 100644 --- a/web_widget_dropdown_dynamic/readme/CONTRIBUTORS.md +++ b/web_widget_dropdown_dynamic/readme/CONTRIBUTORS.md @@ -11,3 +11,6 @@ - [Trobz](https://trobz.com): > - Son Ho \<\> + +- [Tecnativa](https://www.tecnativa.com): + - Carlos Roca \ No newline at end of file diff --git a/web_widget_dropdown_dynamic/readme/USAGE.md b/web_widget_dropdown_dynamic/readme/USAGE.md index 3f2522b1d..1e0af7dd6 100644 --- a/web_widget_dropdown_dynamic/readme/USAGE.md +++ b/web_widget_dropdown_dynamic/readme/USAGE.md @@ -22,3 +22,9 @@ def method_name(self): context="{'depending_on': other_field}" /> ``` + +**DEMO** + +On User defined filters added new field to show the feature, it is called +**Dropdown Integer**. If any user selected just One option shoud appear, but if +Mitchell Admin it should be possible to select option One and Two. \ No newline at end of file diff --git a/web_widget_dropdown_dynamic/static/description/index.html b/web_widget_dropdown_dynamic/static/description/index.html index 8cb1a5085..de49ad290 100644 --- a/web_widget_dropdown_dynamic/static/description/index.html +++ b/web_widget_dropdown_dynamic/static/description/index.html @@ -420,6 +420,11 @@ but to filter selection values. For fully-dynamic set of options, use context="{'depending_on': other_field}" /> +

DEMO

+

On User defined filters added new field to show the feature, it is +called Dropdown Integer. If any user selected just One option shoud +appear, but if Mitchell Admin it should be possible to select option One +and Two.

Bug Tracker

@@ -459,6 +464,11 @@ If you spotted it first, help us to smash it by providing a detailed and welcome +
  • Tecnativa:

    +
      +
    • Carlos Roca
    • +
    +
  • diff --git a/web_widget_dropdown_dynamic/static/src/js/basic_model.esm.js b/web_widget_dropdown_dynamic/static/src/js/basic_model.esm.js deleted file mode 100644 index fa16964c6..000000000 --- a/web_widget_dropdown_dynamic/static/src/js/basic_model.esm.js +++ /dev/null @@ -1,50 +0,0 @@ -/** @odoo-module **/ -import BasicModel from "web.BasicModel"; - -BasicModel.include({ - /** - * Fetches all the values associated to the given fieldName. - * - * @param {Object} record - an element from the localData - * @param {Object} fieldName - the name of the field - * @param {Object} fieldInfo - * @returns {Promise} - * The promise is resolved with the fetched special values. - * If this data is the same as the previously fetched one - * (for the given parameters), no RPC is done and the promise - * is resolved with the undefined value. - */ - _fetchDynamicDropdownValues: function (record, fieldName, fieldInfo) { - var model = fieldInfo.options.model || record.model; - var method = fieldInfo.values || fieldInfo.options.values; - if (!method) { - return Promise.resolve(); - } - - var context = record.getContext({fieldName: fieldName}); - - // Avoid rpc if not necessary - var hasChanged = this._saveSpecialDataCache(record, fieldName, { - context: context, - }); - if (!hasChanged) { - return Promise.resolve(); - } - - return this._rpc({ - model: model, - method: method, - context: context, - }).then(function (result) { - var new_result = result.map((val_updated) => { - return val_updated.map((e) => { - if (typeof e !== "string") { - return String(e); - } - return e; - }); - }); - return new_result; - }); - }, -}); diff --git a/web_widget_dropdown_dynamic/static/src/js/field_dynamic_dropdown.esm.js b/web_widget_dropdown_dynamic/static/src/js/field_dynamic_dropdown.esm.js index 2f1a44880..38752bae5 100644 --- a/web_widget_dropdown_dynamic/static/src/js/field_dynamic_dropdown.esm.js +++ b/web_widget_dropdown_dynamic/static/src/js/field_dynamic_dropdown.esm.js @@ -1,29 +1,56 @@ /** @odoo-module **/ -import core from "web.core"; +import {_lt} from "@web/core/l10n/translation"; import {registry} from "@web/core/registry"; import {standardFieldProps} from "@web/views/fields/standard_field_props"; -import {Component} from "@odoo/owl"; - -var _lt = core._lt; +import {Component, onWillStart, onWillUpdateProps} from "@odoo/owl"; export class FieldDynamicDropdown extends Component { + static template = "web.SelectionField"; + static props = { + ...standardFieldProps, + method: {type: String}, + context: {type: Object}, + }; + setup() { + this.type = this.props.record.fields[this.props.name].type; + onWillStart(async () => { + this.specialData = await this._fetchSpecialData(this.props); + }); + onWillUpdateProps(async (nextProps) => { + if (this.props.context.depending_on !== nextProps.context.depending_on) { + this.specialData = await this._fetchSpecialData(nextProps); + } + }); + } + async _fetchSpecialData(props) { + const {resModel} = props.record.model.config; + const {specialDataCaches, orm} = props.record.model; + const key = `__reference__${props.name}-${props.context.depending_on}`; + if (!specialDataCaches[key]) { + specialDataCaches[key] = await orm.call(resModel, props.method, [], { + context: props.context, + }); + } + return specialDataCaches[key]; + } get options() { - var field_type = this.props.record.fields[this.props.name].type || ""; + var field_type = this.type || ""; if (["char", "integer", "selection"].includes(field_type)) { - this._setValues(); - return this.props.record.fields[this.props.name].selection.filter( - (option) => option[0] !== false && option[1] !== "" - ); + if ( + this.props.record.data[this.props.name] && + !this.specialData + .map((val) => val[0]) + .includes(String(this.props.record.data[this.props.name])) + ) { + this.props.record.update({[this.props.name]: null}); + } + return this.specialData; } return []; } - get value() { - const rawValue = this.props.value; - this.props.setDirty(false); - return this.props.type === "many2one" && rawValue ? rawValue[0] : rawValue; + return String(this.props.record.data[this.props.name]); } - parseInteger(value) { return Number(value); } @@ -31,58 +58,32 @@ export class FieldDynamicDropdown extends Component { * @param {Event} ev */ onChange(ev) { - let lastSetValue = null; - let isInvalid = false; - var isDirty = ev.target.value !== lastSetValue; - const field = this.props.record.fields[this.props.name]; - let value = JSON.parse(ev.target.value); - if (isDirty) { - if (value && field.type === "integer") { - value = Number(value); - if (!value) { - if (this.props.record) { - this.props.record.setInvalidField(this.props.name); - } - isInvalid = true; + var isInvalid = false; + var value = JSON.parse(ev.target.value); + if (this.type === "integer") { + value = Number(value); + if (!value) { + if (this.props.record) { + this.props.record.setInvalidField(this.props.name); } - } - if (!isInvalid) { - Promise.resolve(this.props.update(value)); - lastSetValue = ev.target.value; + isInvalid = true; } } - if (this.props.setDirty) { - this.props.setDirty(isDirty); + if (!isInvalid) { + this.props.record.update({[this.props.name]: value}); } } stringify(value) { return JSON.stringify(value); } - - _setValues() { - if (this.props.record.preloadedData[this.props.name]) { - var sel_value = this.props.record.preloadedData[this.props.name]; - // Convert string element to integer if field is integer - if (this.props.record.fields[this.props.name].type === "integer") { - sel_value = sel_value.map((val_updated) => { - return val_updated.map((e) => { - if (typeof e === "string" && !isNaN(Number(e))) { - return Number(e); - } - return e; - }); - }); - } - this.props.record.fields[this.props.name].selection = sel_value; - } - } } - -FieldDynamicDropdown.description = _lt("Dynamic Dropdown"); -FieldDynamicDropdown.template = "web.SelectionField"; -FieldDynamicDropdown.legacySpecialData = "_fetchDynamicDropdownValues"; -FieldDynamicDropdown.props = { - ...standardFieldProps, +export const dynamicDropdownField = { + component: FieldDynamicDropdown, + displayName: _lt("Dynamic Dropdown"), + supportedTypes: ["char", "integer", "selection"], + extractProps: (fieldInfo, dynamicInfo) => ({ + method: fieldInfo.options?.values, + context: dynamicInfo.context, + }), }; -FieldDynamicDropdown.supportedTypes = ["char", "integer", "selection"]; -registry.category("fields").add("dynamic_dropdown", FieldDynamicDropdown); +registry.category("fields").add("dynamic_dropdown", dynamicDropdownField); diff --git a/web_widget_dropdown_dynamic/static/tests/web_widget_dropdown_dynamic_tests.esm.js b/web_widget_dropdown_dynamic/static/tests/web_widget_dropdown_dynamic_tests.esm.js index 295d6ad3e..b8b258025 100644 --- a/web_widget_dropdown_dynamic/static/tests/web_widget_dropdown_dynamic_tests.esm.js +++ b/web_widget_dropdown_dynamic/static/tests/web_widget_dropdown_dynamic_tests.esm.js @@ -30,7 +30,7 @@ QUnit.module("web_widget_dropdown_dynamic", (hooks) => { ], methods: { method_name() { - return Promise.resolve([["value a", "Value A"]]); + return [["value a", "Value A"]]; }, }, }, @@ -55,14 +55,14 @@ QUnit.module("web_widget_dropdown_dynamic", (hooks) => { assert.step(args.method); if (args.method === "method_name") { if (args.kwargs.context.depending_on === "step-1") { - return Promise.resolve([["value", "Title"]]); + return [["value", "Title"]]; } else if (args.kwargs.context.depending_on === "step-2") { - return Promise.resolve([ + return [ ["value", "Title"], ["value_2", "Title 2"], - ]); + ]; } - return Promise.resolve([]); + return []; } }, }); @@ -84,7 +84,7 @@ QUnit.module("web_widget_dropdown_dynamic", (hooks) => { assert.containsN(target, "option", 1); assert.verifySteps([ "get_views", - "read", + "web_read", "method_name", "method_name", "method_name", @@ -109,13 +109,13 @@ QUnit.module("web_widget_dropdown_dynamic", (hooks) => { assert.step(args.method); if (args.method === "method_name") { if (args.kwargs.context.depending_on) { - return Promise.resolve([["value b", "Value B"]]); + return [["value b", "Value B"]]; } } }, }); const field_target = target.querySelector("div[name='content_string']"); - assert.verifySteps(["get_views", "read", "method_name"]); + assert.verifySteps(["get_views", "web_read", "method_name"]); assert.containsN(field_target, "option", 2); assert.containsOnce( field_target, @@ -142,15 +142,15 @@ QUnit.module("web_widget_dropdown_dynamic", (hooks) => { assert.step(args.method); if (args.method === "method_name") { if (args.kwargs.context.depending_on) { - return Promise.resolve([["10", "Value B"]]); + return [["10", "Value B"]]; } } }, }); const field_target = target.querySelector("div[name='content_integer']"); - assert.verifySteps(["get_views", "read", "method_name"]); + assert.verifySteps(["get_views", "web_read", "method_name"]); assert.containsN(field_target, "option", 2); - assert.containsOnce(field_target, 'option[value="10"]'); + assert.containsOnce(field_target, 'option[value="\\"10\\""]'); }); QUnit.test("values are fetched w/o context (selection)", async (assert) => { @@ -169,13 +169,13 @@ QUnit.module("web_widget_dropdown_dynamic", (hooks) => { assert.step(args.method); if (args.method === "method_name") { if (args.kwargs.context.depending_on) { - return Promise.resolve([["choice b", "Choice B"]]); + return [["choice b", "Choice B"]]; } } }, }); const field_target = target.querySelector("div[name='content_selection']"); - assert.verifySteps(["get_views", "read", "method_name"]); + assert.verifySteps(["get_views", "web_read", "method_name"]); assert.containsN(field_target, "option", 2); assert.containsOnce(field_target, "option[value='\"choice b\"']"); });