Laurent Mignon (ACSONE)
8 years ago
committed by
Denis Roussel
6 changed files with 366 additions and 0 deletions
-
104web_domain_field/README.rst
-
0web_domain_field/__init__.py
-
21web_domain_field/__openerp__.py
-
BINweb_domain_field/static/description/icon.png
-
233web_domain_field/static/src/js/pyeval.js
-
8web_domain_field/views/web_domain_field.xml
@ -0,0 +1,104 @@ |
|||
.. 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 |
|||
|
|||
<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. |
|||
* 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 |
|||
* ... |
|||
|
|||
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 |
|||
|
|||
<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. |
|||
|
|||
.. 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/9.0 |
|||
|
|||
|
|||
|
|||
Bug Tracker |
|||
=========== |
|||
|
|||
Bugs are tracked on `GitHub Issues |
|||
<https://github.com/OCA/web/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 <https://github.com/OCA/maintainer-tools/blob/master/template/module/static/description/icon.svg>`_. |
|||
|
|||
Contributors |
|||
------------ |
|||
|
|||
* Laurent Mignon <laurent.mignon@acsone.eu> |
|||
|
|||
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. |
@ -0,0 +1,21 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# 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': '9.0.1.0.0', |
|||
'license': 'AGPL-3', |
|||
'author': 'ACSONE SA/NV,Odoo Community Association (OCA)', |
|||
'website': 'https://acsone.eu/', |
|||
'depends': [ |
|||
'web' |
|||
], |
|||
'data': [ |
|||
'views/web_domain_field.xml', |
|||
], |
|||
'demo': [ |
|||
], |
|||
} |
After Width: 128 | Height: 128 | Size: 9.2 KiB |
@ -0,0 +1,233 @@ |
|||
odoo.define('web.domain_field', function (require) { |
|||
"use strict"; |
|||
|
|||
var pyeval = require('web.pyeval'); |
|||
var session = require('web.session'); |
|||
|
|||
|
|||
var original_pyeval = pyeval.eval; |
|||
var original_ensure_evaluated = pyeval.ensure_evaluated; |
|||
var py = window.py; |
|||
|
|||
/** copied from pyeval and not modified but required since not publicly |
|||
exposed by web.pyeval**/ |
|||
|
|||
// 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]; |
|||
|
|||
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<args.length; ++i) { |
|||
args[i] = eval_arg(args[i]); |
|||
} |
|||
for (var k in kwargs) { |
|||
if (!kwargs.hasOwnProperty(k)) { continue; } |
|||
kwargs[k] = eval_arg(kwargs[k]); |
|||
} |
|||
} |
|||
function eval_contexts (contexts, evaluation_context) { |
|||
evaluation_context = _.extend(pyeval.context(), evaluation_context || {}); |
|||
return _(contexts).reduce(function (result_context, ctx) { |
|||
// __eval_context evaluations can lead to some of `contexts`'s
|
|||
// values being null, skip them as well as empty contexts
|
|||
if (_.isEmpty(ctx)) { return result_context; } |
|||
if (_.isString(ctx)) { |
|||
// wrap raw strings in context
|
|||
ctx = { __ref: 'context', __debug: ctx }; |
|||
} |
|||
var evaluated = ctx; |
|||
switch(ctx.__ref) { |
|||
case 'context': |
|||
evaluation_context.context = evaluation_context; |
|||
evaluated = py.eval(ctx.__debug, wrap_context(evaluation_context)); |
|||
break; |
|||
case 'compound_context': |
|||
var eval_context = eval_contexts([ctx.__eval_context]); |
|||
evaluated = eval_contexts( |
|||
ctx.__contexts, _.extend({}, evaluation_context, eval_context)); |
|||
break; |
|||
} |
|||
// add newly evaluated context to evaluation context for following
|
|||
// siblings
|
|||
_.extend(evaluation_context, evaluated); |
|||
return _.extend(result_context, evaluated); |
|||
}, {}); |
|||
} |
|||
|
|||
/** end of unmodified methods copied from pyeval **/ |
|||
|
|||
// We need to override the original method to be able to call our
|
|||
//specialized version of pyeval for domain fields
|
|||
function eval_arg (arg) { |
|||
if (typeof arg !== 'object' || !arg.__ref) { return arg; } |
|||
switch(arg.__ref) { |
|||
case 'domain': case 'compound_domain': |
|||
return domain_field_pyeval('domains', [arg]); |
|||
case 'context': case 'compound_context': |
|||
return original_pyeval('contexts', [arg]); |
|||
default: |
|||
throw new Error(_t("Unknown nonliteral type ") + ' ' + arg.__ref); |
|||
} |
|||
} |
|||
|
|||
// override eval_domains to add 3 lines in order to be able to use a field
|
|||
//value as domain
|
|||
function eval_domains (domains, evaluation_context) { |
|||
evaluation_context = _.extend(pyeval.context(), evaluation_context || |
|||
{}); |
|||
var result_domain = []; |
|||
_(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 }; |
|||
} |
|||
switch(domain.__ref) { |
|||
case 'domain': |
|||
evaluation_context.context = evaluation_context; |
|||
result_domain.push.apply( |
|||
result_domain, py.eval(domain.__debug, wrap_context(evaluation_context))); |
|||
break; |
|||
case 'compound_domain': |
|||
var eval_context = eval_contexts([domain.__eval_context]); |
|||
result_domain.push.apply( |
|||
result_domain, eval_domains( |
|||
domain.__domains, _.extend( |
|||
{}, evaluation_context, eval_context))); |
|||
break; |
|||
default: |
|||
result_domain.push.apply(result_domain, domain); |
|||
} |
|||
}); |
|||
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); |
|||
} |
|||
} |
|||
|
|||
// override sync_eval in order to call our specialized implementation of
|
|||
// eval_domains
|
|||
function sync_eval_domains_and_contexts (source) { |
|||
var contexts = ([session.user_context] || []).concat(source.contexts); |
|||
// see Session.eval_context in Python
|
|||
return { |
|||
context: domain_field_pyeval('contexts', contexts), |
|||
domain: domain_field_pyeval('domains', source.domains), |
|||
group_by: domain_field_pyeval('groupbys', source.group_by_seq || []) |
|||
}; |
|||
} |
|||
|
|||
pyeval.eval = domain_field_pyeval; |
|||
pyeval.ensure_evaluated = ensure_evaluated; |
|||
pyeval.sync_eval_domains_and_contexts = sync_eval_domains_and_contexts; |
|||
|
|||
}); |
@ -0,0 +1,8 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<odoo> |
|||
<template id="assets_backend" name="web_domain_field assets" inherit_id="web.assets_backend"> |
|||
<xpath expr="script[last()]" position="after"> |
|||
<script type="text/javascript" src="/web_domain_field/static/src/js/pyeval.js" /> |
|||
</xpath> |
|||
</template> |
|||
</odoo> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue