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..a14831b6f
--- /dev/null
+++ b/web_domain_field/README.rst
@@ -0,0 +1,107 @@
+.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
+ :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
+ :alt: License: AGPL-3
+
+================
+Web Domain Field
+================
+
+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)]
+ )
+
+
+Usage
+=====
+
+.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
+ :alt: Try me on Runbot
+ :target: https://runbot.odoo-community.org/runbot/162/10.0
+
+
+
+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.
+
+Credits
+=======
+
+Images
+------
+
+* Odoo Community Association: `Icon `_.
+
+Contributors
+------------
+
+* Laurent Mignon
+* Denis Roussel
+
+Maintainer
+----------
+
+.. image:: https://odoo-community.org/logo.png
+ :alt: Odoo Community Association
+ :target: https://odoo-community.org
+
+This module is maintained by the OCA.
+
+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.
+
+To contribute to this module, please visit https://odoo-community.org.
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..0bdf8547c
--- /dev/null
+++ b/web_domain_field/__manifest__.py
@@ -0,0 +1,15 @@
+# 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": "14.0.1.0.0",
+ "license": "AGPL-3",
+ "author": "ACSONE SA/NV,Odoo Community Association (OCA)",
+ "website": "https://github.com/OCA/web",
+ "depends": ["web"],
+ "data": ["views/web_domain_field.xml"],
+ "installable": True,
+}
diff --git a/web_domain_field/i18n/es.po b/web_domain_field/i18n/es.po
new file mode 100644
index 000000000..cdd0dca4a
--- /dev/null
+++ b/web_domain_field/i18n/es.po
@@ -0,0 +1,14 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 13.0\n"
+"Report-Msgid-Bugs-To: \n"
+"Last-Translator: Automatically generated\n"
+"Language-Team: none\n"
+"Language: es\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"
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..cc93d01ee
--- /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 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"
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/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;
+
+});
diff --git a/web_domain_field/views/web_domain_field.xml b/web_domain_field/views/web_domain_field.xml
new file mode 100644
index 000000000..7102c526b
--- /dev/null
+++ b/web_domain_field/views/web_domain_field.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+