diff --git a/setup/web_domain_field/odoo/addons/web_domain_field b/setup/web_domain_field/odoo/addons/web_domain_field new file mode 120000 index 000000000..8cd49d57b --- /dev/null +++ b/setup/web_domain_field/odoo/addons/web_domain_field @@ -0,0 +1 @@ +../../../../web_domain_field \ No newline at end of file diff --git a/setup/web_domain_field/setup.py b/setup/web_domain_field/setup.py new file mode 100644 index 000000000..28c57bb64 --- /dev/null +++ b/setup/web_domain_field/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/web_domain_field/README.rst b/web_domain_field/README.rst new file mode 100644 index 000000000..4195b46e6 --- /dev/null +++ b/web_domain_field/README.rst @@ -0,0 +1,133 @@ +================ +Web Domain Field +================ + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! 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/15.0/web_domain_field + :alt: OCA/web +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/web-15-0/web-15-0-web_domain_field + :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/15.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +When you define a view you can specify on the relational fields a domain +attribute. This attribute is evaluated as filter to apply when displaying +existing records for selection. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +When you define a view you can specify on the relational fields a domain +attribute. This attribute is evaluated as filter to apply when displaying +existing records for selection. + +.. code-block:: xml + + + +The value provided for the domain attribute must be a string representing a +valid Odoo domain. This string is evaluated on the client side in a +restricted context where we can reference as right operand the values of +fields present into the form and a limited set of functions. + +In this context it's hard to build complex domain and we are facing to some +limitations as: + + * The syntax to include in your domain a criteria involving values from a + x2many field is complex. + * The right side of domain in case of x2many can involve huge amount of ids + (performance problem). + * Domains computed by an onchange on an other field are not recomputed when + you modify the form and don't modify the field triggering the onchange. + * It's not possible to extend an existing domain. You must completely redefine + the domain in your specialized addon + * etc... + +In order to mitigate these limitations this new addon allows you to use the +value of a field as domain of an other field in the xml definition of your +view. + +.. code-block:: xml + + + + +The field used as domain must provide the domain as a JSON encoded string. + +.. code-block:: python + + product_id_domain = fields.Char( + compute="_compute_product_id_domain", + readonly=True, + store=False, + ) + + @api.multi + @api.depends('name') + def _compute_product_id_domain(self): + for rec in self: + rec.product_id_domain = json.dumps( + [('type', '=', 'product'), ('name', 'like', rec.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 +~~~~~~~ + +* ACSONE SA/NV + +Contributors +~~~~~~~~~~~~ + +* Laurent Mignon +* Denis Roussel +* Raf Ven + +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_domain_field/__init__.py b/web_domain_field/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/web_domain_field/__manifest__.py b/web_domain_field/__manifest__.py new file mode 100644 index 000000000..a120e074c --- /dev/null +++ b/web_domain_field/__manifest__.py @@ -0,0 +1,20 @@ +# Copyright 2017 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Web Domain Field", + "summary": """ + Use computed field as domain""", + "version": "15.0.1.0.0", + "license": "AGPL-3", + "author": "ACSONE SA/NV,Odoo Community Association (OCA)", + "website": "https://github.com/OCA/web", + "depends": ["web"], + "data": [], + "assets": { + "web.assets_backend": [ + "/web_domain_field/static/lib/js/*.js", + ], + }, + "installable": True, +} diff --git a/web_domain_field/i18n/web_domain_field.pot b/web_domain_field/i18n/web_domain_field.pot new file mode 100644 index 000000000..4d8b20f91 --- /dev/null +++ b/web_domain_field/i18n/web_domain_field.pot @@ -0,0 +1,13 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# +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" diff --git a/web_domain_field/readme/CONTRIBUTORS.rst b/web_domain_field/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..1294ec29e --- /dev/null +++ b/web_domain_field/readme/CONTRIBUTORS.rst @@ -0,0 +1,3 @@ +* Laurent Mignon +* Denis Roussel +* Raf Ven diff --git a/web_domain_field/readme/DESCRIPTION.rst b/web_domain_field/readme/DESCRIPTION.rst new file mode 100644 index 000000000..5aa1b96f3 --- /dev/null +++ b/web_domain_field/readme/DESCRIPTION.rst @@ -0,0 +1,3 @@ +When you define a view you can specify on the relational fields a domain +attribute. This attribute is evaluated as filter to apply when displaying +existing records for selection. diff --git a/web_domain_field/readme/USAGE.rst b/web_domain_field/readme/USAGE.rst new file mode 100644 index 000000000..b9c04da19 --- /dev/null +++ b/web_domain_field/readme/USAGE.rst @@ -0,0 +1,51 @@ +When you define a view you can specify on the relational fields a domain +attribute. This attribute is evaluated as filter to apply when displaying +existing records for selection. + +.. code-block:: xml + + + +The value provided for the domain attribute must be a string representing a +valid Odoo domain. This string is evaluated on the client side in a +restricted context where we can reference as right operand the values of +fields present into the form and a limited set of functions. + +In this context it's hard to build complex domain and we are facing to some +limitations as: + + * The syntax to include in your domain a criteria involving values from a + x2many field is complex. + * The right side of domain in case of x2many can involve huge amount of ids + (performance problem). + * Domains computed by an onchange on an other field are not recomputed when + you modify the form and don't modify the field triggering the onchange. + * It's not possible to extend an existing domain. You must completely redefine + the domain in your specialized addon + * etc... + +In order to mitigate these limitations this new addon allows you to use the +value of a field as domain of an other field in the xml definition of your +view. + +.. code-block:: xml + + + + +The field used as domain must provide the domain as a JSON encoded string. + +.. code-block:: python + + product_id_domain = fields.Char( + compute="_compute_product_id_domain", + readonly=True, + store=False, + ) + + @api.depends('name') + def _compute_product_id_domain(self): + for rec in self: + rec.product_id_domain = json.dumps( + [('type', '=', 'product'), ('name', 'like', rec.name)] + ) diff --git a/web_domain_field/static/description/icon.png b/web_domain_field/static/description/icon.png new file mode 100644 index 000000000..3a0328b51 Binary files /dev/null and b/web_domain_field/static/description/icon.png differ diff --git a/web_domain_field/static/description/index.html b/web_domain_field/static/description/index.html new file mode 100644 index 000000000..37ca8312d --- /dev/null +++ b/web_domain_field/static/description/index.html @@ -0,0 +1,475 @@ + + + + + + +Web Domain Field + + + +
+

Web Domain Field

+ + +

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

+

When you define a view you can specify on the relational fields a domain +attribute. This attribute is evaluated as filter to apply when displaying +existing records for selection.

+

Table of contents

+ +
+

Usage

+

When you define a view you can specify on the relational fields a domain +attribute. This attribute is evaluated as filter to apply when displaying +existing records for selection.

+
+<field name="product_id" domain="[('type','=','product')]"/>
+
+

The value provided for the domain attribute must be a string representing a +valid Odoo domain. This string is evaluated on the client side in a +restricted context where we can reference as right operand the values of +fields present into the form and a limited set of functions.

+

In this context it’s hard to build complex domain and we are facing to some +limitations as:

+
+
    +
  • The syntax to include in your domain a criteria involving values from a +x2many field is complex.
  • +
  • The right side of domain in case of x2many can involve huge amount of ids +(performance problem).
  • +
  • Domains computed by an onchange on an other field are not recomputed when +you modify the form and don’t modify the field triggering the onchange.
  • +
  • It’s not possible to extend an existing domain. You must completely redefine +the domain in your specialized addon
  • +
  • etc…
  • +
+
+

In order to mitigate these limitations this new addon allows you to use the +value of a field as domain of an other field in the xml definition of your +view.

+
+<field name="product_id_domain" invisible="1"/>
+<field name="product_id" domain="product_id_domain"/>
+
+

The field used as domain must provide the domain as a JSON encoded string.

+
+product_id_domain = fields.Char(
+    compute="_compute_product_id_domain",
+    readonly=True,
+    store=False,
+)
+
+@api.multi
+@api.depends('name')
+def _compute_product_id_domain(self):
+    for rec in self:
+        rec.product_id_domain = json.dumps(
+            [('type', '=', 'product'), ('name', 'like', rec.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

+
    +
  • ACSONE SA/NV
  • +
+
+
+

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_domain_field/static/lib/js/pyeval.js b/web_domain_field/static/lib/js/pyeval.js new file mode 100644 index 000000000..6f2b9f61b --- /dev/null +++ b/web_domain_field/static/lib/js/pyeval.js @@ -0,0 +1,212 @@ +odoo.define('web.domain_field', function (require) { + "use strict"; + + var py_utils = require('web.py_utils'); + var session = require('web.session'); + + + var original_pyeval = py_utils.eval; + var py = window.py; + + /** Copied from py_utils and not modified but required since not publicly + exposed by web.py_utils**/ + + // recursively wraps JS objects passed into the context to attributedicts + // which jsonify back to JS objects + function wrap(value) { + if (value === null) { return py.None; } + + switch (typeof value) { + case 'undefined': throw new Error("No conversion for undefined"); + case 'boolean': return py.bool.fromJSON(value); + case 'number': return py.float.fromJSON(value); + case 'string': return py.str.fromJSON(value); + } + + switch(value.constructor) { + case Object: return wrapping_dict.fromJSON(value); + case Array: return wrapping_list.fromJSON(value); + } + + throw new Error("ValueError: unable to wrap " + value); + } + + var wrapping_dict = py.type('wrapping_dict', null, { + __init__: function () { + this._store = {}; + }, + __getitem__: function (key) { + var k = key.toJSON(); + if (!(k in this._store)) { + throw new Error("KeyError: '" + k + "'"); + } + return wrap(this._store[k]); + }, + __getattr__: function (key) { + return this.__getitem__(py.str.fromJSON(key)); + }, + __len__: function () { + return Object.keys(this._store).length; + }, + __nonzero__: function () { + return py.PY_size(this) > 0 ? py.True : py.False; + }, + get: function () { + var args = py.PY_parseArgs(arguments, ['k', ['d', py.None]]); + + if (!(args.k.toJSON() in this._store)) { return args.d; } + return this.__getitem__(args.k); + }, + fromJSON: function (d) { + var instance = py.PY_call(wrapping_dict); + instance._store = d; + return instance; + }, + toJSON: function () { + return this._store; + }, + }); + + var wrapping_list = py.type('wrapping_list', null, { + __init__: function () { + this._store = []; + }, + __getitem__: function (index) { + return wrap(this._store[index.toJSON()]); + }, + __len__: function () { + return this._store.length; + }, + __nonzero__: function () { + return py.PY_size(this) > 0 ? py.True : py.False; + }, + fromJSON: function (ar) { + var instance = py.PY_call(wrapping_list); + instance._store = ar; + return instance; + }, + toJSON: function () { + return this._store; + }, + }); + + function wrap_context(context) { + for (var k in context) { + if (!context.hasOwnProperty(k)) { continue; } + var val = context[k]; + // Don't add a test case like ``val === undefined`` + // this is intended to prevent letting crap pass + // on the context without even knowing it. + // If you face an issue from here, try to sanitize + // the context upstream instead + if (val === null) { continue; } + if (val.constructor === Array) { + context[k] = wrapping_list.fromJSON(val); + } else if (val.constructor === Object + && !py.PY_isInstance(val, py.object)) { + context[k] = wrapping_dict.fromJSON(val); + } + } + return context; + } + + function ensure_evaluated(args, kwargs) { + for (var i=0; i 0 && + domains[0].length === 1 && + (domains[0][0] === "|" || domains[0][0] === "!") + ); + _(domains).each(function (domain) { + if (_.isString(domain)) { + // Modified part or the original method + if (domain in evaluation_context) { + result_domain.push.apply( + result_domain, $.parseJSON(evaluation_context[domain])); + return; + } + // End of modifications + + // wrap raw strings in domain + domain = { __ref: 'domain', __debug: domain }; + } + var domain_array_to_combine; + switch(domain.__ref) { + case 'domain': + evaluation_context.context = evaluation_context; + domain_array_to_combine = py.eval(domain.__debug, wrap_context(evaluation_context)); + break; + default: + domain_array_to_combine = domain; + } + if (need_normalization) { + domain_array_to_combine = get_normalized_domain(domain_array_to_combine); + } + result_domain.push.apply(result_domain, domain_array_to_combine); + }); + return result_domain; + } + + + // Override pyeval in order to call our specialized implementation of + // eval_domains + function domain_field_pyeval (type, object, context, options) { + switch (type) { + case 'domain': + case 'domains': + if (type === 'domain') { + object = [object]; + } + return eval_domains(object, context); + default: + return original_pyeval(type, object, context, options); + } + } + + function eval_domains_and_contexts(source) { + // see Session.eval_context in Python + return { + context: domain_field_pyeval('contexts', source.contexts || [], source.eval_context), + domain: domain_field_pyeval('domains', source.domains, source.eval_context), + group_by: domain_field_pyeval('groupbys', source.group_by_seq || [], source.eval_context), + }; + } + + + py_utils.eval = domain_field_pyeval; + py_utils.ensure_evaluated = ensure_evaluated; + py_utils.eval_domains_and_contexts = eval_domains_and_contexts; + +});