Browse Source

New web_domain_field addon

pull/617/head
Laurent Mignon (ACSONE) 7 years ago
committed by Denis Roussel
parent
commit
4742843acb
  1. 104
      web_domain_field/README.rst
  2. 0
      web_domain_field/__init__.py
  3. 21
      web_domain_field/__openerp__.py
  4. BIN
      web_domain_field/static/description/icon.png
  5. 233
      web_domain_field/static/src/js/pyeval.js
  6. 8
      web_domain_field/views/web_domain_field.xml

104
web_domain_field/README.rst

@ -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
web_domain_field/__init__.py

21
web_domain_field/__openerp__.py

@ -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': [
],
}

BIN
web_domain_field/static/description/icon.png

After

Width: 128  |  Height: 128  |  Size: 9.2 KiB

233
web_domain_field/static/src/js/pyeval.js

@ -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;
});

8
web_domain_field/views/web_domain_field.xml

@ -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>
Loading…
Cancel
Save