diff --git a/web_compute_domain_x2many/__init__.py b/web_compute_domain_x2many/__init__.py new file mode 100644 index 00000000..9dd152f9 --- /dev/null +++ b/web_compute_domain_x2many/__init__.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# This module copyright (C) 2014 Therp BV (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## diff --git a/web_compute_domain_x2many/__openerp__.py b/web_compute_domain_x2many/__openerp__.py new file mode 100644 index 00000000..b5ec7e14 --- /dev/null +++ b/web_compute_domain_x2many/__openerp__.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# This module copyright (C) 2014 Therp BV (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## +{ + "name": "Compute client-side domains on x2many fields correctly", + "version": "1.0", + "author": "Therp BV", + "license": "AGPL-3", + "complexity": "normal", + "description": """ +When using ``attrs="..."``, evaluation of x2many fields nearly always goes +wrong in ways not to be expected when being used to server side domains in +Model.search(). + +This addon fixes those cases while keeping backwards compatibility for cases +where you might have checks in a somewhat hacky way, ie. ``attrs="{'invisible': +[('category_id', '=', [[6, False, []]])]}"``. + """, + "category": "Dependency", + "depends": [ + 'web', + ], + "data": [ + ], + "js": [ + 'static/src/js/web_compute_domain_x2many.js', + ], + "css": [ + ], + "qweb": [ + ], + "test": [ + 'static/test/web_compute_domain_x2many.js', + ], + "auto_install": False, + "installable": True, + "application": False, + "external_dependencies": { + 'python': [], + }, +} diff --git a/web_compute_domain_x2many/static/src/img/icon.png b/web_compute_domain_x2many/static/src/img/icon.png new file mode 100644 index 00000000..f1006195 Binary files /dev/null and b/web_compute_domain_x2many/static/src/img/icon.png differ diff --git a/web_compute_domain_x2many/static/src/js/web_compute_domain_x2many.js b/web_compute_domain_x2many/static/src/js/web_compute_domain_x2many.js new file mode 100644 index 00000000..27f14f34 --- /dev/null +++ b/web_compute_domain_x2many/static/src/js/web_compute_domain_x2many.js @@ -0,0 +1,167 @@ +//-*- coding: utf-8 -*- +//############################################################################ +// +// OpenERP, Open Source Management Solution +// This module copyright (C) 2014 Therp BV (). +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// +//############################################################################ + +openerp.web_compute_domain_x2many = function(instance) +{ + var _t = instance.web._t; + + function find_in_commands(commands, id) + //check if a list of commands contains an id in a way that it will be + //contained after the command list is evaluated + { + return _.reduce( + _.map( + commands, + function(command) + { + switch(command[0]) + { + case 1: + case 4: + return 1; + case 2: + case 3: + case 5: + return -1 + case 6: + return _(command[2]).contains(id); + default: + return 0; + } + } + ), + function(a, b) {return a + b}, + 0 + ) > 0; + } + var comparators = { + scalar: function(field_value, op, val, field) + { + switch (op.toLowerCase()) { + case '=': + case '==': + return _.isEqual(field_value, val); + case '!=': + case '<>': + return !_.isEqual(field_value, val); + case '<': + return field_value < val; + case '>': + return field_value > val; + case '<=': + return field_value <= val; + case '>=': + return field_value >= val; + case 'in': + if (!_.isArray(val)) val = [val]; + return _(val).contains(field_value); + case 'not in': + if (!_.isArray(val)) val = [val]; + return !_(val).contains(field_value); + default: + console.warn( + _t("Unsupported operator %s in domain %s"), + op, JSON.stringify(expr)); + return true; + } + }, + one2many: function(field_value, op, val, field) + { + switch(op.toLowerCase()) + { + case '=': + case '==': + if(!_.isArray(val)) + { + return find_in_commands(field_value, val); + } + else + { + return comparators.scalar(field_value, op, val, field); + } + case '!=': + case '<>': + return !comparators.one2many(field_value, '=', val, field); + case 'in': + var found = false; + _.each(val, function(v) + { + found |= find_in_commands(field_value, v); + }); + return found; + case 'not in': + return !comparators.one2many(field_value, 'in', val, field); + default: + return comparators.scalar(field_value, op, val, field); + } + }, + many2many: function() + { + return comparators.one2many.apply(this, arguments);; + }, + }; + //start OpenERP compute_domain from web/static/src/view_form.js + instance.web.form.compute_domain = function(expr, fields) { + if (! (expr instanceof Array)) + return !! expr; + var stack = []; + for (var i = expr.length - 1; i >= 0; i--) { + var ex = expr[i]; + if (ex.length == 1) { + var top = stack.pop(); + switch (ex) { + case '|': + stack.push(stack.pop() || top); + continue; + case '&': + stack.push(stack.pop() && top); + continue; + case '!': + stack.push(!top); + continue; + default: + throw new Error(_.str.sprintf( + _t("Unknown operator %s in domain %s"), + ex, JSON.stringify(expr))); + } + } + + var field = fields[ex[0]]; + if (!field) { + throw new Error(_.str.sprintf( + _t("Unknown field %s in domain %s"), + ex[0], JSON.stringify(expr))); + } + var field_value = field.get_value ? field.get_value() : field.value; + var op = ex[1]; + var val = ex[2]; + //begin local changes + var field_type = field.field ? field.field.type : 'scalar'; + var comparator = comparators[field_type] ? comparators[field_type] : comparators.scalar; + stack.push(comparator(field_value, op, val, field)); + //end local changes + } + return _.all(stack, _.identity); + }; + //end OpenERP compute_domain + + instance.web_compute_domain_x2many.comparators = comparators; +} diff --git a/web_compute_domain_x2many/static/test/web_compute_domain_x2many.js b/web_compute_domain_x2many/static/test/web_compute_domain_x2many.js new file mode 100644 index 00000000..50213f25 --- /dev/null +++ b/web_compute_domain_x2many/static/test/web_compute_domain_x2many.js @@ -0,0 +1,106 @@ +//-*- coding: utf-8 -*- +//############################################################################ +// +// OpenERP, Open Source Management Solution +// This module copyright (C) 2014 Therp BV (). +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// +//############################################################################ + +openerp.testing.section( + 'web_compute_domain_x2many', + function(test) + { + //start OpenERP core tests from web/static/test/form.js + test("basic", function (instance) { + var fields = { + 'a': {value: 3}, + 'group_method': {value: 'line'}, + 'select1': {value: 'day'}, + 'rrule_type': {value: 'monthly'} + }; + ok(instance.web.form.compute_domain( + [['a', '=', 3]], fields)); + ok(instance.web.form.compute_domain( + [['group_method','!=','count']], fields)); + ok(instance.web.form.compute_domain( + [['select1','=','day'], ['rrule_type','=','monthly']], fields)); + }); + test("or", function (instance) { + var web = { + 'section_id': {value: null}, + 'user_id': {value: null}, + 'member_ids': {value: null} + }; + + var domain = ['|', ['section_id', '=', 42], + '|', ['user_id','=',3], + ['member_ids', 'in', [3]]]; + + ok(instance.web.form.compute_domain(domain, _.extend( + {}, web, {'section_id': {value: 42}}))); + ok(instance.web.form.compute_domain(domain, _.extend( + {}, web, {'user_id': {value: 3}}))); + + ok(instance.web.form.compute_domain(domain, _.extend( + {}, web, {'member_ids': {value: 3}}))); + }); + test("not", function (instance) { + var fields = { + 'a': {value: 5}, + 'group_method': {value: 'line'} + }; + ok(instance.web.form.compute_domain( + ['!', ['a', '=', 3]], fields)); + ok(instance.web.form.compute_domain( + ['!', ['group_method','=','count']], fields)); + }); + //end OpenERP core tests + var fields = { + one2many_empty: {value: [], field: {type: 'one2many'}}, + one2many_one_entry: {value: [[4, 42, false]], field: {type: 'one2many'}}, + one2many_multiple_entries: {value: [[4, 42, false], [4, 43, false]], field: {type: 'one2many'}}, + many2many_empty: {value: [[6, false, []]], field: {type: 'many2many'}}, + many2many_one_entry: {value: [[6, false, [42]]], field: {type: 'many2many'}}, + many2many_multiple_entries: {value: [[6, false, [42, 43]]], field: {type: 'many2many'}}, + }; + test('legacy behavior', function(instance) + { + var eval = function(expression, fields) + { + expression = instance.web.pyeval.eval('domain', expression, {}, {}); + return instance.web.form.compute_domain(expression, fields) + } + ok(eval("[('one2many_empty', '=', [])]", fields), 'empty one2many'); + ok(eval("[('many2many_empty', '=', [[6, False, []]])]", fields), 'empty many2many'); + }); + test('x2many tests', function(instance) + { + var eval = function(expression, fields) + { + expression = instance.web.pyeval.eval('domain', expression, {}, {}); + return instance.web.form.compute_domain(expression, fields) + } + ok(!eval("[('one2many_empty', '=', 42)]", fields), 'empty one2many == value'); + ok(eval("[('one2many_one_entry', '=', 42)]", fields), 'one2many with one entry == value'); + ok(eval("[('one2many_multiple_entries', '=', 42)]", fields), 'one2many with multiple entries == value'); + ok(eval("[('one2many_multiple_entries', 'in', [42])]", fields), 'one2many with multiple entries in [value]'); + ok(!eval("[('many2many_empty', '=', 42)]", fields), 'empty many2many == value'); + ok(eval("[('many2many_one_entry', '=', 42)]", fields), 'many2many with one entry == value'); + ok(eval("[('many2many_multiple_entries', '=', 42)]", fields), 'many2many with multiple entries == value'); + ok(eval("[('many2many_multiple_entries', 'in', [42])]", fields), 'many2many with multiple entries in [value]'); + ok(eval("[('many2many_multiple_entries', 'not in', [44])]", fields), 'many2many with multiple entries not in [value]'); + }); + });