From 421cb233ca67b549f136d3a4e18e6b8e74bcd7d5 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Wed, 11 Mar 2015 18:00:21 +0100 Subject: [PATCH 001/153] [ADD] web_widget_x2many_2d_matrix --- web_widget_x2many_2d_matrix/README.rst | 56 ++++ web_widget_x2many_2d_matrix/__init__.py | 20 ++ web_widget_x2many_2d_matrix/__openerp__.py | 45 +++ .../static/description/icon.png | Bin 0 -> 1142 bytes .../src/css/web_widget_x2many_2d_matrix.css | 0 .../src/js/web_widget_x2many_2d_matrix.js | 305 ++++++++++++++++++ .../src/xml/web_widget_x2many_2d_matrix.xml | 33 ++ .../views/templates.xml | 11 + 8 files changed, 470 insertions(+) create mode 100644 web_widget_x2many_2d_matrix/README.rst create mode 100644 web_widget_x2many_2d_matrix/__init__.py create mode 100644 web_widget_x2many_2d_matrix/__openerp__.py create mode 100644 web_widget_x2many_2d_matrix/static/description/icon.png create mode 100644 web_widget_x2many_2d_matrix/static/src/css/web_widget_x2many_2d_matrix.css create mode 100644 web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js create mode 100644 web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml create mode 100644 web_widget_x2many_2d_matrix/views/templates.xml diff --git a/web_widget_x2many_2d_matrix/README.rst b/web_widget_x2many_2d_matrix/README.rst new file mode 100644 index 000000000000..27a266b981d6 --- /dev/null +++ b/web_widget_x2many_2d_matrix/README.rst @@ -0,0 +1,56 @@ +2D matrix for x2many fields +=========================== + +This module allows to show an x2many field with 3-tuples +($x_value, $y_value, $value) in a table + ++-----------+-----------+-----------+ +| | $x_value1 | $x_value2 | ++===========+===========+===========+ +| $y_value1 | $value1/1 | $value2/1 | ++-----------+-----------+-----------+ +| $y_value2 | $value1/2 | $value2/2 | ++-----------+-----------+-----------+ + +where `valuen/n` is editable. + + +Usage +===== + +Use this widget by saying:: + + + +This assumes that my_field refers to a model with the fields `x`, `y` and +`value`. If your fields are named differently, pass the correct names as +attributes:: + + + +Known issues / Roadmap +====================== + +* ... + +Credits +======= + +Contributors +------------ + +* Holger Brunn + +Maintainer +---------- + +.. image:: http://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: http://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 http://odoo-community.org. diff --git a/web_widget_x2many_2d_matrix/__init__.py b/web_widget_x2many_2d_matrix/__init__.py new file mode 100644 index 000000000000..faef9dac007f --- /dev/null +++ b/web_widget_x2many_2d_matrix/__init__.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# This module copyright (C) 2015 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_widget_x2many_2d_matrix/__openerp__.py b/web_widget_x2many_2d_matrix/__openerp__.py new file mode 100644 index 000000000000..1cbc4aad73cb --- /dev/null +++ b/web_widget_x2many_2d_matrix/__openerp__.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# This module copyright (C) 2015 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": "2D matrix for x2many fields", + "version": "1.0", + "author": "Therp BV", + "license": "AGPL-3", + "category": "Hidden/Dependency", + "summary": "Show list fields as a matrix", + "depends": [ + 'web', + ], + "data": [ + 'views/templates.xml', + ], + "qweb": [ + 'static/src/xml/web_widget_x2many_2d_matrix.xml', + ], + "test": [ + ], + "auto_install": False, + "installable": True, + "application": False, + "external_dependencies": { + 'python': [], + }, +} diff --git a/web_widget_x2many_2d_matrix/static/description/icon.png b/web_widget_x2many_2d_matrix/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..4c7ab302908e114888446d84d3493fa726033c1f GIT binary patch literal 1142 zcmeAS@N?(olHy`uVBq!ia0vp^0U*r53?z4+XPOVBSkfJR9T^xl_H+M9WCijWi-X*q z7}lMWc?skwBzpw;GB8xBF)%c=FfjZA3N^f7U???UV0e|lz+g3lfkC`r&aOZkpuXS$ zpAc7|0+Vy!%`Sd3J@*~Rz=H@Xz@vBMNCCrhU++~OAXQKjgnPb5^?zLm6yRy4l)f7mx|d; zx?*(k%?4)UEmyi0Ecrc2=k&YFn|8nX@qd4)(saLN%zo##oL4V9SpH%8W(I{5_Kby- zneS~VhopAyb6lBS&$U5E`gKppbdV7NQIC+SE zKe2ts8LoR*Hw$jqcIEDHSU_mi4;HoUDLTgOJMIHx zO|`@|q9i4;B-JXpC>2OC7#SEE>l#?<8d`)H8e5qfSQ#5>8yHy`7. +// +// 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_widget_x2many_2d_matrix = function(instance) +{ + instance.web.form.widgets.add( + 'x2many_2d_matrix', + 'instance.web_widget_x2many_2d_matrix.FieldX2Many2dMatrix'); + instance.web_widget_x2many_2d_matrix.FieldX2Many2dMatrix = instance.web.form.FieldOne2Many.extend({ + template: 'FieldX2Many2dMatrix', + widget_class: 'oe_form_field_x2many_2d_matrix', + + // those will be filled with rows from the dataset + by_x_axis: {}, + by_y_axis: {}, + field_x_axis: 'x', + field_label_x_axis: 'x', + field_y_axis: 'y', + field_label_y_axis: 'y', + field_value: 'value', + // information about our datatype + is_numeric: false, + show_row_totals: true, + show_column_totals: true, + // this will be filled with the model's fields_get + fields: {}, + + // read parameters + init: function(field_manager, node) + { + this.field_x_axis = node.attrs.field_x_axis || this.field_x_axis; + this.field_y_axis = node.attrs.field_y_axis || this.field_y_axis; + this.field_label_x_axis = node.attrs.field_label_x_axis || this.field_x_axis; + this.field_label_y_axis = node.attrs.field_label_y_axis || this.field_y_axis; + this.field_value = node.attrs.field_value || this.field_value; + this.show_row_totals = node.attrs.show_row_totals || this.show_row_totals; + this.show_column_totals = node.attrs.show_column_totals || this.show_column_totals; + return this._super.apply(this, arguments); + }, + + // return a field's value, id in case it's a one2many field + get_field_value: function(row, field, many2one_as_name) + { + if(this.fields[field].type == 'many2one' && _.isArray(row[field])) + { + if(many2one_as_name) + { + return row[field][1]; + } + else + { + return row[field][0]; + } + } + return row[field]; + }, + + // setup our datastructure for simple access in the template + set_value: function() + { + var self = this, + result = this._super.apply(this, arguments); + + self.by_x_axis = {}; + self.by_y_axis = {}; + + return jQuery.when(result).then(function() + { + return self.dataset._model.call('fields_get').then(function(fields) + { + self.fields = fields; + self.is_numeric = fields[self.field_value].type == 'float'; + self.show_row_totals &= self.is_numeric; + self.show_column_totals &= self.is_numeric; + }).then(function() + { + return self.dataset.read_ids(self.dataset.ids).then(function(rows) + { + var read_many2one = {}, + many2one_fields = [ + self.field_x_axis, self.field_y_axis, + self.field_label_x_axis, self.field_label_y_axis + ]; + // prepare to read many2one names if necessary (we can get (id, name) or just id as value) + _.each(many2one_fields, function(field) + { + if(self.fields[field].type == 'many2one') + { + read_many2one[field] = {}; + } + }); + // setup data structure + _.each(rows, function(row) + { + var x = self.get_field_value(row, self.field_x_axis), + y = self.get_field_value(row, self.field_y_axis); + self.by_x_axis[x] = self.by_x_axis[x] || {}; + self.by_y_axis[y] = self.by_y_axis[y] || {}; + self.by_x_axis[x][y] = row; + self.by_y_axis[y][x] = row; + _.each(read_many2one, function(rows, field) + { + if(!_.isArray(row[field])) + { + rows[row[field]] = rows[row[field]] || [] + rows[row[field]].push(row); + } + }); + }); + // read many2one fields if necessary + var deferrends = []; + _.each(read_many2one, function(rows, field) + { + if(_.isEmpty(rows)) + { + return; + } + var model = new instance.web.Model(self.fields[field].relation); + deferrends.push(model.call( + 'name_get', + [_.map(_.keys(rows), function(key) {return parseInt(key)})]) + .then(function(names) + { + _.each(names, function(name) + { + _.each(rows[name[0]], function(row) + { + row[field] = name; + }); + }); + })); + }) + return jQuery.when.apply(jQuery, deferrends); + }); + }); + }); + }, + + // get x axis values in the correct order + get_x_axis_values: function() + { + return _.keys(this.by_x_axis); + }, + + // get y axis values in the correct order + get_y_axis_values: function() + { + return _.keys(this.by_y_axis); + }, + + // get x axis labels + get_x_axis_labels: function() + { + var self = this; + return _.map( + this.get_x_axis_values(), + function(val) + { + return self.get_field_value( + _.first(_.values(self.by_x_axis[val])), + self.field_label_x_axis, true); + }); + }, + + // get the label for a value on the y axis + get_y_axis_label: function(y) + { + return this.get_field_value( + _.first(_.values(this.by_y_axis[y])), + this.field_label_y_axis, true); + }, + + // return the class(es) the inputs should have + get_xy_value_class: function() + { + var classes = 'oe_form_field oe_form_required'; + if(this.is_numeric) + { + classes += ' oe_form_field_float'; + } + return classes; + }, + + // return row id of a coordinate + get_xy_id: function(x, y) + { + return this.by_x_axis[x][y]['id']; + }, + + // return the value of a coordinate + get_xy_value: function(x, y) + { + return this.get_field_value( + this.by_x_axis[x][y], this.field_value); + }, + + // validate a value + validate_xy_value: function(val) + { + return true; + }, + + // parse a value from user input + parse_xy_value: function(val) + { + if(this.is_numeric) + { + return parseFloat(val); + } + else + { + return val; + } + }, + + // format a value from the database for display + format_xy_value: function(val) + { + return instance.web.format_value( + val, {'type': this.fields[this.field_value].type}); + }, + + // compute totals + compute_totals: function() + { + var self = this, + totals_x = {}, + totals_y = {}; + return self.dataset.read_ids(self.dataset.ids).then(function(rows) + { + _.each(rows, function(row) + { + var key_x = self.get_field_value(row, self.field_x_axis), + key_y = self.get_field_value(row, self.field_y_axis); + totals_x[key_x] = (totals_x[key_x] || 0) + self.get_field_value(row, self.field_value); + totals_y[key_y] = (totals_y[key_y] || 0) + self.get_field_value(row, self.field_value); + }); + }).then(function() + { + _.each(totals_y, function(total, y) + { + self.$el.find( + _.str.sprintf('td.row_total[data-y="%s"]', y)).text( + self.format_xy_value(total)); + }); + _.each(totals_x, function(total, x) + { + self.$el.find( + _.str.sprintf('td.column_total[data-x="%s"]', x)).text( + self.format_xy_value(total)); + }); + }); + }, + + start: function() + { + var self = this; + this.$el.find('input').on( + 'change', + function() + { + var $this = jQuery(this), + val = $this.val() + if(self.validate_xy_value(val)) + { + data = {} + data[self.field_value] = self.parse_xy_value(val); + self.dataset.write($this.data('id'), data); + $this.parent().removeClass('oe_form_invalid'); + self.compute_totals(); + } + else + { + $this.parent().addClass('oe_form_invalid'); + } + + }); + this.compute_totals(); + return this._super.apply(this, arguments); + }, + + // deactivate view related functions + load_views: function() {}, + reload_current_view: function() {}, + get_active_view: function() {}, + }); +} diff --git a/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml b/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml new file mode 100644 index 000000000000..e29367a010ae --- /dev/null +++ b/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml @@ -0,0 +1,33 @@ + + +
+ + + + + + + + + + + + + + + + +
+ + + Total
+ + + + +
Total + +
+
+
+
diff --git a/web_widget_x2many_2d_matrix/views/templates.xml b/web_widget_x2many_2d_matrix/views/templates.xml new file mode 100644 index 000000000000..06934cc33dbc --- /dev/null +++ b/web_widget_x2many_2d_matrix/views/templates.xml @@ -0,0 +1,11 @@ + + + + + + From 744680a302df613c91446003a395b407d1ec1c5a Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Wed, 11 Mar 2015 18:05:40 +0100 Subject: [PATCH 002/153] [UPD] readme --- web_widget_x2many_2d_matrix/README.rst | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/web_widget_x2many_2d_matrix/README.rst b/web_widget_x2many_2d_matrix/README.rst index 27a266b981d6..6be504c44bf3 100644 --- a/web_widget_x2many_2d_matrix/README.rst +++ b/web_widget_x2many_2d_matrix/README.rst @@ -29,10 +29,28 @@ attributes:: +You can pass the following parameters: + +field_x_axis + The field that indicates the x value of a point +field_y_axis + The field that indicates the y value of a point +field_label_x_axis + Use another field to display in the table header +field_label_y_axis + Use another field to display in the table header +field_value + Show this field as value +show_row_totals + If field_value is a numeric field, calculate row totals +show_column_totals + If field_value is a numeric field, calculate column totals + Known issues / Roadmap ====================== -* ... +* no validation yet +* it would be better to instantiate the proper field widget and let it render the input Credits ======= From dfac5349780bbe9b0ce74931805b5bad2f518bcc Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Thu, 12 Mar 2015 12:22:37 +0100 Subject: [PATCH 003/153] [IMP] show column totals in table footer --- .../static/src/xml/web_widget_x2many_2d_matrix.xml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml b/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml index e29367a010ae..fe3f82d31c4c 100644 --- a/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml +++ b/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml @@ -14,19 +14,21 @@ - + - + + + Total - + - + From ba4f84b4626fd7788565b9c21ce135fc3d633f0e Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Thu, 12 Mar 2015 12:27:24 +0100 Subject: [PATCH 004/153] [FIX] use odoo's parse_value --- .../static/src/js/web_widget_x2many_2d_matrix.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index 3a754f42651d..b157a67994ea 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -221,14 +221,8 @@ openerp.web_widget_x2many_2d_matrix = function(instance) // parse a value from user input parse_xy_value: function(val) { - if(this.is_numeric) - { - return parseFloat(val); - } - else - { - return val; - } + return instance.web.parse_value( + val, {'type': this.fields[this.field_value].type}); }, // format a value from the database for display From cade81822c92dec0f8bd73c0f4646563cd86ca6f Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Thu, 12 Mar 2015 15:27:25 +0100 Subject: [PATCH 005/153] [ADD] allow to open linked record of one of the axes is a many2one field --- .../src/css/web_widget_x2many_2d_matrix.css | 4 ++ .../src/js/web_widget_x2many_2d_matrix.js | 47 ++++++++++++++----- .../src/xml/web_widget_x2many_2d_matrix.xml | 8 ++-- 3 files changed, 44 insertions(+), 15 deletions(-) diff --git a/web_widget_x2many_2d_matrix/static/src/css/web_widget_x2many_2d_matrix.css b/web_widget_x2many_2d_matrix/static/src/css/web_widget_x2many_2d_matrix.css index e69de29bb2d1..2992579de638 100644 --- a/web_widget_x2many_2d_matrix/static/src/css/web_widget_x2many_2d_matrix.css +++ b/web_widget_x2many_2d_matrix/static/src/css/web_widget_x2many_2d_matrix.css @@ -0,0 +1,4 @@ +.oe_form_field_x2many_2d_matrix th.oe_link +{ + cursor: pointer; +} diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index b157a67994ea..f9ebeb25d158 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -166,18 +166,12 @@ openerp.web_widget_x2many_2d_matrix = function(instance) return _.keys(this.by_y_axis); }, - // get x axis labels - get_x_axis_labels: function() + // get the label for a value on the x axis + get_x_axis_label: function(x) { - var self = this; - return _.map( - this.get_x_axis_values(), - function(val) - { - return self.get_field_value( - _.first(_.values(self.by_x_axis[val])), - self.field_label_x_axis, true); - }); + return this.get_field_value( + _.first(_.values(this.by_x_axis[x])), + this.field_label_x_axis, true); }, // get the label for a value on the y axis @@ -264,6 +258,36 @@ openerp.web_widget_x2many_2d_matrix = function(instance) }); }, + setup_many2one_axes: function() + { + if(this.fields[this.field_x_axis].type == 'many2one') + { + this.$el.find('th[data-x]').addClass('oe_link') + .click(_.partial( + this.proxy(this.many2one_axis_click), + this.field_x_axis, 'x')); + } + if(this.fields[this.field_y_axis].type == 'many2one') + { + this.$el.find('tr[data-y] th').addClass('oe_link') + .click(_.partial( + this.proxy(this.many2one_axis_click), + this.field_y_axis, 'y')); + } + }, + + many2one_axis_click: function(field, id_attribute, e) + { + this.do_action({ + type: 'ir.actions.act_window', + name: this.fields[field].string, + res_model: this.fields[field].relation, + res_id: jQuery(e.currentTarget).data(id_attribute), + views: [[false, 'form']], + target: 'current', + }) + }, + start: function() { var self = this; @@ -288,6 +312,7 @@ openerp.web_widget_x2many_2d_matrix = function(instance) }); this.compute_totals(); + this.setup_many2one_axes(); return this._super.apply(this, arguments); }, diff --git a/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml b/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml index fe3f82d31c4c..625a2d20fed4 100644 --- a/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml +++ b/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml @@ -1,18 +1,18 @@ -
+
- + - -
- - + + Total
From ac95064fe9cb508b5d335afba549bf33d767e1df Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Thu, 12 Mar 2015 15:44:57 +0100 Subject: [PATCH 006/153] [IMP] handle readonly flag [ADD] show grand total [IMP] classify floats as floats --- .../static/src/js/web_widget_x2many_2d_matrix.js | 4 ++++ .../static/src/xml/web_widget_x2many_2d_matrix.xml | 9 +++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index f9ebeb25d158..df8b4930803b 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -230,6 +230,7 @@ openerp.web_widget_x2many_2d_matrix = function(instance) compute_totals: function() { var self = this, + grand_total = 0, totals_x = {}, totals_y = {}; return self.dataset.read_ids(self.dataset.ids).then(function(rows) @@ -240,6 +241,7 @@ openerp.web_widget_x2many_2d_matrix = function(instance) key_y = self.get_field_value(row, self.field_y_axis); totals_x[key_x] = (totals_x[key_x] || 0) + self.get_field_value(row, self.field_value); totals_y[key_y] = (totals_y[key_y] || 0) + self.get_field_value(row, self.field_value); + grand_total += self.get_field_value(row, self.field_value); }); }).then(function() { @@ -255,6 +257,8 @@ openerp.web_widget_x2many_2d_matrix = function(instance) _.str.sprintf('td.column_total[data-x="%s"]', x)).text( self.format_xy_value(total)); }); + self.$el.find('.grand_total').text( + self.format_xy_value(grand_total)) }); }, diff --git a/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml b/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml index 625a2d20fed4..2950439c1e51 100644 --- a/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml +++ b/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml @@ -14,19 +14,20 @@
+ - + + +
Total +
From cf7049bb22f1f7407dd8913de678e3b18a1d4ade Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Fri, 13 Mar 2015 15:29:09 +0100 Subject: [PATCH 007/153] [IMP] collapse whitespace in rows --- .../static/src/css/web_widget_x2many_2d_matrix.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/web_widget_x2many_2d_matrix/static/src/css/web_widget_x2many_2d_matrix.css b/web_widget_x2many_2d_matrix/static/src/css/web_widget_x2many_2d_matrix.css index 2992579de638..d33d4f21bdfb 100644 --- a/web_widget_x2many_2d_matrix/static/src/css/web_widget_x2many_2d_matrix.css +++ b/web_widget_x2many_2d_matrix/static/src/css/web_widget_x2many_2d_matrix.css @@ -2,3 +2,7 @@ { cursor: pointer; } +.openerp .oe_form_field_x2many_2d_matrix .oe_list_content > tbody > tr > td.oe_list_field_cell +{ + white-space: normal; +} From 8c062594dbaec167a6f266d654e195630ed61303 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Fri, 13 Mar 2015 15:34:02 +0100 Subject: [PATCH 008/153] [IMP] support changing readonly state --- .../static/src/js/web_widget_x2many_2d_matrix.js | 13 +++++++++++++ .../static/src/xml/web_widget_x2many_2d_matrix.xml | 4 ++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index df8b4930803b..058a682e268b 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -317,9 +317,22 @@ openerp.web_widget_x2many_2d_matrix = function(instance) }); this.compute_totals(); this.setup_many2one_axes(); + this.on("change:effective_readonly", + this, this.proxy(this.effective_readonly_change)); + this.effective_readonly_change(); return this._super.apply(this, arguments); }, + effective_readonly_change: function() + { + this.$el + .find('tbody td.oe_list_field_cell span.oe_form_field>input') + .toggle(!this.get('effective_readonly')); + this.$el + .find('tbody td.oe_list_field_cell span.oe_form_field>span') + .toggle(this.get('effective_readonly')); + }, + // deactivate view related functions load_views: function() {}, reload_current_view: function() {}, diff --git a/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml b/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml index 2950439c1e51..4f587e3f08de 100644 --- a/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml +++ b/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml @@ -16,8 +16,8 @@ - - + + From 74642eb98a341206dbfe8ed6d560dd5525068b0a Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Fri, 13 Mar 2015 17:36:39 +0100 Subject: [PATCH 009/153] [FIX] update readonly value after editing --- .../static/src/js/web_widget_x2many_2d_matrix.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index 058a682e268b..9a7b8ca62ddf 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -305,6 +305,8 @@ openerp.web_widget_x2many_2d_matrix = function(instance) { data = {} data[self.field_value] = self.parse_xy_value(val); + $this.siblings('span').text( + self.format_xy_value(self.parse_xy_value(val))); self.dataset.write($this.data('id'), data); $this.parent().removeClass('oe_form_invalid'); self.compute_totals(); From 255a206960dbf37e3f27f61d4f90418ad4fa870d Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Fri, 13 Mar 2015 17:44:41 +0100 Subject: [PATCH 010/153] [IMP] pass computed totals to inheriting function --- .../static/src/js/web_widget_x2many_2d_matrix.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index 9a7b8ca62ddf..0425468bfe2a 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -259,6 +259,11 @@ openerp.web_widget_x2many_2d_matrix = function(instance) }); self.$el.find('.grand_total').text( self.format_xy_value(grand_total)) + return { + totals_x: totals_x, + totals_y: totals_y, + grand_total: grand_total, + }; }); }, From 777a19f2a55bb60b8f11ea94cca0e5c8874e4425 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Fri, 13 Mar 2015 18:19:42 +0100 Subject: [PATCH 011/153] [FIX] replace therp icon --- .../static/description/icon.png | Bin 1142 -> 12361 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/web_widget_x2many_2d_matrix/static/description/icon.png b/web_widget_x2many_2d_matrix/static/description/icon.png index 4c7ab302908e114888446d84d3493fa726033c1f..2c8e1ad59d7db69e97b2467c6fb0d4e3e460c9b4 100644 GIT binary patch literal 12361 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 YID`P*yI)_SqJKa}LQ%X*)WH9L08=sy2LJ#7 literal 1142 zcmeAS@N?(olHy`uVBq!ia0vp^0U*r53?z4+XPOVBSkfJR9T^xl_H+M9WCijWi-X*q z7}lMWc?skwBzpw;GB8xBF)%c=FfjZA3N^f7U???UV0e|lz+g3lfkC`r&aOZkpuXS$ zpAc7|0+Vy!%`Sd3J@*~Rz=H@Xz@vBMNCCrhU++~OAXQKjgnPb5^?zLm6yRy4l)f7mx|d; zx?*(k%?4)UEmyi0Ecrc2=k&YFn|8nX@qd4)(saLN%zo##oL4V9SpH%8W(I{5_Kby- zneS~VhopAyb6lBS&$U5E`gKppbdV7NQIC+SE zKe2ts8LoR*Hw$jqcIEDHSU_mi4;HoUDLTgOJMIHx zO|`@|q9i4;B-JXpC>2OC7#SEE>l#?<8d`)H8e5qfSQ#5>8yHy`7 Date: Fri, 13 Mar 2015 18:26:29 +0100 Subject: [PATCH 012/153] [FIX] setup focus --- .../static/src/js/web_widget_x2many_2d_matrix.js | 1 + 1 file changed, 1 insertion(+) diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index 0425468bfe2a..191ffad66b90 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -338,6 +338,7 @@ openerp.web_widget_x2many_2d_matrix = function(instance) this.$el .find('tbody td.oe_list_field_cell span.oe_form_field>span') .toggle(this.get('effective_readonly')); + this.$el.find('input').first().focus(); }, // deactivate view related functions From 1d8bfde79f1a0c265a452804e60e45aba91a2214 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Fri, 13 Mar 2015 18:47:15 +0100 Subject: [PATCH 013/153] [ADD] validation [IMP] write formatted value to back to input --- web_widget_x2many_2d_matrix/README.rst | 3 +-- .../src/js/web_widget_x2many_2d_matrix.js | 23 +++++++++++++++---- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/web_widget_x2many_2d_matrix/README.rst b/web_widget_x2many_2d_matrix/README.rst index 6be504c44bf3..5fb296beb0ed 100644 --- a/web_widget_x2many_2d_matrix/README.rst +++ b/web_widget_x2many_2d_matrix/README.rst @@ -49,8 +49,7 @@ show_column_totals Known issues / Roadmap ====================== -* no validation yet -* it would be better to instantiate the proper field widget and let it render the input +* it would be worth trying to instantiate the proper field widget and let it render the input Credits ======= diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index 191ffad66b90..0c818548d926 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -209,6 +209,14 @@ openerp.web_widget_x2many_2d_matrix = function(instance) // validate a value validate_xy_value: function(val) { + try + { + this.parse_xy_value(val); + } + catch(e) + { + return false; + } return true; }, @@ -308,10 +316,12 @@ openerp.web_widget_x2many_2d_matrix = function(instance) val = $this.val() if(self.validate_xy_value(val)) { - data = {} - data[self.field_value] = self.parse_xy_value(val); - $this.siblings('span').text( - self.format_xy_value(self.parse_xy_value(val))); + var data = {}, value = self.parse_xy_value(val); + data[self.field_value] = value; + + $this.siblings('span').text(self.format_xy_value(value)); + $this.val(self.format_xy_value(value)); + self.dataset.write($this.data('id'), data); $this.parent().removeClass('oe_form_invalid'); self.compute_totals(); @@ -341,6 +351,11 @@ openerp.web_widget_x2many_2d_matrix = function(instance) this.$el.find('input').first().focus(); }, + is_syntax_valid: function() + { + return this.$el.find('.oe_form_invalid').length == 0; + }, + // deactivate view related functions load_views: function() {}, reload_current_view: function() {}, From 47c518b28351a997273e0ca86122074118155d46 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Mon, 16 Mar 2015 16:35:17 +0100 Subject: [PATCH 014/153] [IMP] we don't need data-x and data-y on the input --- .../static/src/xml/web_widget_x2many_2d_matrix.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml b/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml index 4f587e3f08de..952a003f647d 100644 --- a/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml +++ b/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml @@ -14,9 +14,9 @@ - + - + From 52d27aa13ffe301500a744095125c2b9d7ecac97 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Mon, 16 Mar 2015 16:47:21 +0100 Subject: [PATCH 015/153] [IMP] use semantic css classes instead of element names [RFR] and being on it, make reacting to changes in overrides simple --- .../src/js/web_widget_x2many_2d_matrix.js | 55 ++++++++++--------- .../src/xml/web_widget_x2many_2d_matrix.xml | 4 +- 2 files changed, 30 insertions(+), 29 deletions(-) diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index 0c818548d926..12a56c8cb56a 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -308,30 +308,8 @@ openerp.web_widget_x2many_2d_matrix = function(instance) start: function() { var self = this; - this.$el.find('input').on( - 'change', - function() - { - var $this = jQuery(this), - val = $this.val() - if(self.validate_xy_value(val)) - { - var data = {}, value = self.parse_xy_value(val); - data[self.field_value] = value; - - $this.siblings('span').text(self.format_xy_value(value)); - $this.val(self.format_xy_value(value)); - - self.dataset.write($this.data('id'), data); - $this.parent().removeClass('oe_form_invalid'); - self.compute_totals(); - } - else - { - $this.parent().addClass('oe_form_invalid'); - } - - }); + this.$el.find('.edit').on( + 'change', self.proxy(this.xy_value_change)); this.compute_totals(); this.setup_many2one_axes(); this.on("change:effective_readonly", @@ -340,15 +318,38 @@ openerp.web_widget_x2many_2d_matrix = function(instance) return this._super.apply(this, arguments); }, + xy_value_change: function(e) + { + var $this = jQuery(e.currentTarget), + val = $this.val(); + if(this.validate_xy_value(val)) + { + var data = {}, value = this.parse_xy_value(val); + data[this.field_value] = value; + + $this.siblings('.read').text(this.format_xy_value(value)); + $this.val(this.format_xy_value(value)); + + this.dataset.write($this.data('id'), data); + $this.parent().removeClass('oe_form_invalid'); + this.compute_totals(); + } + else + { + $this.parent().addClass('oe_form_invalid'); + } + + }, + effective_readonly_change: function() { this.$el - .find('tbody td.oe_list_field_cell span.oe_form_field>input') + .find('tbody td.oe_list_field_cell span.oe_form_field .edit') .toggle(!this.get('effective_readonly')); this.$el - .find('tbody td.oe_list_field_cell span.oe_form_field>span') + .find('tbody td.oe_list_field_cell span.oe_form_field .read') .toggle(this.get('effective_readonly')); - this.$el.find('input').first().focus(); + this.$el.find('.edit').first().focus(); }, is_syntax_valid: function() diff --git a/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml b/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml index 952a003f647d..35f1669bc197 100644 --- a/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml +++ b/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml @@ -16,8 +16,8 @@ - - + + From 6a11abd8780a03ef6bbfe46b3490ab75872f5581 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Mon, 16 Mar 2015 17:20:43 +0100 Subject: [PATCH 016/153] [IMP] add screenshot, example in README --- web_widget_x2many_2d_matrix/README.rst | 25 +++++++++++------- .../static/description/screenshot.png | Bin 0 -> 19577 bytes 2 files changed, 15 insertions(+), 10 deletions(-) create mode 100644 web_widget_x2many_2d_matrix/static/description/screenshot.png diff --git a/web_widget_x2many_2d_matrix/README.rst b/web_widget_x2many_2d_matrix/README.rst index 5fb296beb0ed..0b145aaf6cab 100644 --- a/web_widget_x2many_2d_matrix/README.rst +++ b/web_widget_x2many_2d_matrix/README.rst @@ -4,16 +4,22 @@ This module allows to show an x2many field with 3-tuples ($x_value, $y_value, $value) in a table -+-----------+-----------+-----------+ -| | $x_value1 | $x_value2 | -+===========+===========+===========+ -| $y_value1 | $value1/1 | $value2/1 | -+-----------+-----------+-----------+ -| $y_value2 | $value1/2 | $value2/2 | -+-----------+-----------+-----------+ + $x_value1 $x_value2 +========= =========== =========== +$y_value1 $value(1/1) $value(2/1) +$y_value2 $value(1/2) $value(2/2) +========= =========== =========== -where `valuen/n` is editable. +where `value(n/n)` is editable. +An example use case would be: Select some projects and some employees so that +a manager can easily fill in the planned_hours for one task per employee. The +result could look like this: + +.. image:: /web_widget_x2many_2d_matrix/static/description/screenshot.png + :alt: Screenshot + +The beauty of this is that you have an arbitrary amount of columns with this widget, trying to get this in standard x2many lists involves some quite agly hacks. Usage ===== @@ -26,8 +32,7 @@ This assumes that my_field refers to a model with the fields `x`, `y` and `value`. If your fields are named differently, pass the correct names as attributes:: - + You can pass the following parameters: diff --git a/web_widget_x2many_2d_matrix/static/description/screenshot.png b/web_widget_x2many_2d_matrix/static/description/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..47c2a40d62b23f3b5c4b97dbc699f20dc5baa5ba GIT binary patch literal 19577 zcmdVC2{e^$`#!8vX%Y!#h!7%_%wx1ELp-K!rXqxGo->q)2xUm-h$J?Nz0D*cWS*yF z9=3U&{m+e__j#Z9dDri^{_FeJw?3;y>)!Wp-S>T6=XspRah&`49}2fl9j7`@KtOOx z<~Bl^fZ$Lt0l@)XqJywyo4foJ{5fPRDWgh6MAY4{_=|vmoaGSRCs-Pp1@#&m`gIq70YLVD)@G0D*G1@G7Ha`E?hJr$+m(d;%U zPY6h>P7^9IiC~pdpX;1VJaCzWk%Z_P{ndyc$AsNCQ5Fn8k3Vl_Nu?`=SL^w`qpA3IR^cgQx>#36k%KQ zx!9+&v{v-dTjJB$E{1|p{o{+*1$Ixif8MeYwX;&)v~6{I=%<2#gLXnOCRz2Seas9` zPuQn_KuP`UHVbZc%b-$GqNy#|USkXvz45sxfSTX;c9L?8z#Q!X{4Enc`Sz_K>6TvL zVB#$eZ!I*1lu{S_9R-Esi^uVwzbz1Un0z6d^m#f<_EGxrxxgD-r}2mV;xqG3#cZoJ zHZ;)tCCDb_yXJ{C)EvTpM9*MqV)9F%POICm7m43MB)yz`oK=-A41*=#-QnDA4Eofm zQ``}g4k-3s!BgxFX1hn`qRaBoOs&}Co^O>?(slX|<@4e9f83qeg$$53K%>I!S7~-% z)Z@pte{_FDrXH4Pz%3onqr&g@g_bQ$mN`UbBdsYm^`ti%zm3G)T(Mxn$pVKl%Q;vi zg5N~Izz{o{WUHMdsvaT#EQAohiGHj@JmzOQsyuJ+D3LS_8b(*yh5RbBdp#0)nrNoG zSI;GOwNdj&9x4>V9|dO`aMoLQy@HEPz0x4JyW<#@sW$$((T~}EK$Tmt<9G8q(t^c* zc%wlAwYP!kf8O9Nn7sYIJ{W#t^z(!(WB~ z@d0%h{yZk}w%s2?M_l(uqQzi(r`EQ%i6s{al7mGr=`U?@%HFsy;2KVGai-cF6gAC?;%McR z_wo^+&DzsXi*GiV^I3Ldr~J8!Nu;iEaGQwxdY6@z z)i*G3yVlbEy6dWy{*}{Tej4{p5Jr4CX7h@Xfm_py&%eWHAhNrw`-fzfl7%^$cGuw$ zB_2&L$C0~_@Z;(;Jo5C(sB@U?aJj#1fn9BFEh#B!fx{F-LT`a1oru#_Vc|z`?eg++ zZ*T9mdw1^Kk$z`g6?{pg-m)iGiNq^OE_}i+pFeEot)H!yv)fA(J<~w)t z$kS5c0yLI9z0LDLR;P&dz_;(;Z!@rQa9m0V4Lut;Xk%-8npTL%aa!l$oAUcI3JOTJ zFhom3!^_vNnK8JDiNeo(v#;O3kHWQaq!&)dv`EUNokn?u%jscOP3)?c;$a5o2jb$7MJidu?m+-?sF z2q2ewS&%;2@b+lvGmsLIs1cp#qTU#SqnwpwgS`Y}TfQn>&O4s

q$zofJg&#hQSr2BbXSC%$N*Ni65_QQ{ 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 Date: Mon, 16 Mar 2015 17:20:59 +0100 Subject: [PATCH 017/153] [IMP] icon --- .../static/description/icon.png | Bin 12361 -> 5139 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/web_widget_x2many_2d_matrix/static/description/icon.png b/web_widget_x2many_2d_matrix/static/description/icon.png index 2c8e1ad59d7db69e97b2467c6fb0d4e3e460c9b4..d7cdcec3b4f3db5e2af2745392b116e16a2e40b4 100644 GIT binary patch literal 5139 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 literal 12361 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 YID`P*yI)_SqJKa}LQ%X*)WH9L08=sy2LJ#7 From 26a37dc4de9efc4a0238850967a825f61d3e7355 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Wed, 18 Mar 2015 17:10:29 +0100 Subject: [PATCH 018/153] [IMP] better modularity --- .../src/js/web_widget_x2many_2d_matrix.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index 12a56c8cb56a..d4828b47c19e 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -110,12 +110,7 @@ openerp.web_widget_x2many_2d_matrix = function(instance) // setup data structure _.each(rows, function(row) { - var x = self.get_field_value(row, self.field_x_axis), - y = self.get_field_value(row, self.field_y_axis); - self.by_x_axis[x] = self.by_x_axis[x] || {}; - self.by_y_axis[y] = self.by_y_axis[y] || {}; - self.by_x_axis[x][y] = row; - self.by_y_axis[y][x] = row; + self.add_xy_row(row); _.each(read_many2one, function(rows, field) { if(!_.isArray(row[field])) @@ -154,6 +149,17 @@ openerp.web_widget_x2many_2d_matrix = function(instance) }); }, + // to whatever needed to setup internal data structure + add_xy_row: function(row) + { + var x = this.get_field_value(row, this.field_x_axis), + y = this.get_field_value(row, this.field_y_axis); + this.by_x_axis[x] = this.by_x_axis[x] || {}; + this.by_y_axis[y] = this.by_y_axis[y] || {}; + this.by_x_axis[x][y] = row; + this.by_y_axis[y][x] = row; + }, + // get x axis values in the correct order get_x_axis_values: function() { From d03132b1e2ba5d954e1d8a01f0c47516e8c2104c Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Thu, 16 Apr 2015 09:59:47 +0200 Subject: [PATCH 019/153] [FIX] support rerendering after virtual ids change this is necessary for correct operation after creating new records --- .../static/src/js/web_widget_x2many_2d_matrix.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index d4828b47c19e..e1021457aba9 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -143,6 +143,13 @@ openerp.web_widget_x2many_2d_matrix = function(instance) }); })); }) + if(self.is_started && !self.no_rerender) + { + self.renderElement(); + self.$el.find('.edit').on( + 'change', self.proxy(self.xy_value_change)); + self.effective_readonly_change(); + } return jQuery.when.apply(jQuery, deferrends); }); }); From 7897d745de0f81021c1115bd11869929ac90affd Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Thu, 16 Apr 2015 10:15:39 +0200 Subject: [PATCH 020/153] [FIX] also reinitialize totals --- .../static/src/js/web_widget_x2many_2d_matrix.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index e1021457aba9..5d4ce7854587 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -146,6 +146,8 @@ openerp.web_widget_x2many_2d_matrix = function(instance) if(self.is_started && !self.no_rerender) { self.renderElement(); + self.compute_totals(); + self.setup_many2one_axes(); self.$el.find('.edit').on( 'change', self.proxy(self.xy_value_change)); self.effective_readonly_change(); From 2836d04f4c90557c5d4a02c6da1c883c4f59c727 Mon Sep 17 00:00:00 2001 From: Yannick Vaucher Date: Fri, 22 May 2015 19:45:36 +0200 Subject: [PATCH 021/153] Add bug tracker link on README.rst --- web_widget_x2many_2d_matrix/README.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/web_widget_x2many_2d_matrix/README.rst b/web_widget_x2many_2d_matrix/README.rst index 0b145aaf6cab..a6b436e17cf9 100644 --- a/web_widget_x2many_2d_matrix/README.rst +++ b/web_widget_x2many_2d_matrix/README.rst @@ -56,6 +56,16 @@ Known issues / Roadmap * it would be worth trying to instantiate the proper field widget and let it render the input + +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 +`here `_. + + Credits ======= From ff4189ba17f3d7050249c29edbb8515ac493845b Mon Sep 17 00:00:00 2001 From: Markus Schneider Date: Thu, 4 Jun 2015 14:30:25 +0200 Subject: [PATCH 022/153] add OCA to author --- web_widget_x2many_2d_matrix/__openerp__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web_widget_x2many_2d_matrix/__openerp__.py b/web_widget_x2many_2d_matrix/__openerp__.py index 1cbc4aad73cb..95a3299b2ec2 100644 --- a/web_widget_x2many_2d_matrix/__openerp__.py +++ b/web_widget_x2many_2d_matrix/__openerp__.py @@ -21,7 +21,8 @@ { "name": "2D matrix for x2many fields", "version": "1.0", - "author": "Therp BV", + "author": "Therp BV, " + "Odoo Community Association (OCA)",, "license": "AGPL-3", "category": "Hidden/Dependency", "summary": "Show list fields as a matrix", From 31f56a4be374d7fcf5472dae1764424e5adae507 Mon Sep 17 00:00:00 2001 From: Markus Schneider Date: Fri, 5 Jun 2015 00:33:22 +0200 Subject: [PATCH 023/153] remove comma --- web_widget_x2many_2d_matrix/__openerp__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_widget_x2many_2d_matrix/__openerp__.py b/web_widget_x2many_2d_matrix/__openerp__.py index 95a3299b2ec2..2e43203a8bd5 100644 --- a/web_widget_x2many_2d_matrix/__openerp__.py +++ b/web_widget_x2many_2d_matrix/__openerp__.py @@ -22,7 +22,7 @@ "name": "2D matrix for x2many fields", "version": "1.0", "author": "Therp BV, " - "Odoo Community Association (OCA)",, + "Odoo Community Association (OCA)", "license": "AGPL-3", "category": "Hidden/Dependency", "summary": "Show list fields as a matrix", From cc03b03464ac30e9b56e1cb2287347e6aa2f5341 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Fri, 9 Oct 2015 10:03:39 +0200 Subject: [PATCH 024/153] [UPD] prefix versions with 8.0 --- web_widget_x2many_2d_matrix/__openerp__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_widget_x2many_2d_matrix/__openerp__.py b/web_widget_x2many_2d_matrix/__openerp__.py index 2e43203a8bd5..0b652e3cd18f 100644 --- a/web_widget_x2many_2d_matrix/__openerp__.py +++ b/web_widget_x2many_2d_matrix/__openerp__.py @@ -20,7 +20,7 @@ ############################################################################## { "name": "2D matrix for x2many fields", - "version": "1.0", + "version": "8.0.1.0.0", "author": "Therp BV, " "Odoo Community Association (OCA)", "license": "AGPL-3", From bec7bff2224be36fca9ad7d98934b404c9b7794f Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Wed, 14 Oct 2015 02:57:05 +0200 Subject: [PATCH 025/153] [MIG] Make modules uninstallable --- web_widget_x2many_2d_matrix/__openerp__.py | 2 +- web_widget_x2many_2d_matrix/i18n/ar.po | 27 ++++++++++++++++++++++ web_widget_x2many_2d_matrix/i18n/de.po | 27 ++++++++++++++++++++++ web_widget_x2many_2d_matrix/i18n/es.po | 26 +++++++++++++++++++++ web_widget_x2many_2d_matrix/i18n/fi.po | 27 ++++++++++++++++++++++ web_widget_x2many_2d_matrix/i18n/fr.po | 26 +++++++++++++++++++++ web_widget_x2many_2d_matrix/i18n/hr.po | 27 ++++++++++++++++++++++ web_widget_x2many_2d_matrix/i18n/it.po | 26 +++++++++++++++++++++ web_widget_x2many_2d_matrix/i18n/pt_BR.po | 26 +++++++++++++++++++++ web_widget_x2many_2d_matrix/i18n/sl.po | 26 +++++++++++++++++++++ web_widget_x2many_2d_matrix/i18n/tr.po | 27 ++++++++++++++++++++++ 11 files changed, 266 insertions(+), 1 deletion(-) create mode 100644 web_widget_x2many_2d_matrix/i18n/ar.po create mode 100644 web_widget_x2many_2d_matrix/i18n/de.po create mode 100644 web_widget_x2many_2d_matrix/i18n/es.po create mode 100644 web_widget_x2many_2d_matrix/i18n/fi.po create mode 100644 web_widget_x2many_2d_matrix/i18n/fr.po create mode 100644 web_widget_x2many_2d_matrix/i18n/hr.po create mode 100644 web_widget_x2many_2d_matrix/i18n/it.po create mode 100644 web_widget_x2many_2d_matrix/i18n/pt_BR.po create mode 100644 web_widget_x2many_2d_matrix/i18n/sl.po create mode 100644 web_widget_x2many_2d_matrix/i18n/tr.po diff --git a/web_widget_x2many_2d_matrix/__openerp__.py b/web_widget_x2many_2d_matrix/__openerp__.py index 0b652e3cd18f..e48c3a6e6d6a 100644 --- a/web_widget_x2many_2d_matrix/__openerp__.py +++ b/web_widget_x2many_2d_matrix/__openerp__.py @@ -38,7 +38,7 @@ "test": [ ], "auto_install": False, - "installable": True, + 'installable': False, "application": False, "external_dependencies": { 'python': [], diff --git a/web_widget_x2many_2d_matrix/i18n/ar.po b/web_widget_x2many_2d_matrix/i18n/ar.po new file mode 100644 index 000000000000..7a85d2bde813 --- /dev/null +++ b/web_widget_x2many_2d_matrix/i18n/ar.po @@ -0,0 +1,27 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_widget_x2many_2d_matrix +# +# Translators: +# SaFi J. , 2015 +msgid "" +msgstr "" +"Project-Id-Version: web (8.0)\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2015-12-16 07:41+0000\n" +"PO-Revision-Date: 2015-12-16 17:24+0000\n" +"Last-Translator: SaFi J. \n" +"Language-Team: Arabic (http://www.transifex.com/oca/OCA-web-8-0/language/ar/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: ar\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" + +#. module: web_widget_x2many_2d_matrix +#. openerp-web +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:11 +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:28 +#, python-format +msgid "Total" +msgstr "المجموع الاجمالي" diff --git a/web_widget_x2many_2d_matrix/i18n/de.po b/web_widget_x2many_2d_matrix/i18n/de.po new file mode 100644 index 000000000000..337d2b944ef1 --- /dev/null +++ b/web_widget_x2many_2d_matrix/i18n/de.po @@ -0,0 +1,27 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_widget_x2many_2d_matrix +# +# Translators: +# Rudolf Schnapka , 2016 +msgid "" +msgstr "" +"Project-Id-Version: web (8.0)\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-01-10 07:31+0000\n" +"PO-Revision-Date: 2016-01-18 20:15+0000\n" +"Last-Translator: Rudolf Schnapka \n" +"Language-Team: German (http://www.transifex.com/oca/OCA-web-8-0/language/de/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: de\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: web_widget_x2many_2d_matrix +#. openerp-web +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:11 +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:28 +#, python-format +msgid "Total" +msgstr "Gesamt" diff --git a/web_widget_x2many_2d_matrix/i18n/es.po b/web_widget_x2many_2d_matrix/i18n/es.po new file mode 100644 index 000000000000..10ba2f9f8b08 --- /dev/null +++ b/web_widget_x2many_2d_matrix/i18n/es.po @@ -0,0 +1,26 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_widget_x2many_2d_matrix +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: web (8.0)\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2015-11-23 13:46+0000\n" +"PO-Revision-Date: 2015-11-07 11:29+0000\n" +"Last-Translator: Pedro M. Baeza \n" +"Language-Team: Spanish (http://www.transifex.com/oca/OCA-web-8-0/language/es/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: es\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: web_widget_x2many_2d_matrix +#. openerp-web +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:11 +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:28 +#, python-format +msgid "Total" +msgstr "Total" diff --git a/web_widget_x2many_2d_matrix/i18n/fi.po b/web_widget_x2many_2d_matrix/i18n/fi.po new file mode 100644 index 000000000000..df37d34a7088 --- /dev/null +++ b/web_widget_x2many_2d_matrix/i18n/fi.po @@ -0,0 +1,27 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_widget_x2many_2d_matrix +# +# Translators: +# Jarmo Kortetjärvi , 2016 +msgid "" +msgstr "" +"Project-Id-Version: web (8.0)\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-01-10 07:31+0000\n" +"PO-Revision-Date: 2016-02-01 09:54+0000\n" +"Last-Translator: Jarmo Kortetjärvi \n" +"Language-Team: Finnish (http://www.transifex.com/oca/OCA-web-8-0/language/fi/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: fi\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: web_widget_x2many_2d_matrix +#. openerp-web +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:11 +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:28 +#, python-format +msgid "Total" +msgstr "Yhteensä" diff --git a/web_widget_x2many_2d_matrix/i18n/fr.po b/web_widget_x2many_2d_matrix/i18n/fr.po new file mode 100644 index 000000000000..7ed8bc355a41 --- /dev/null +++ b/web_widget_x2many_2d_matrix/i18n/fr.po @@ -0,0 +1,26 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_widget_x2many_2d_matrix +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: web (8.0)\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-05-06 15:50+0000\n" +"PO-Revision-Date: 2015-11-07 11:22+0000\n" +"Last-Translator: <>\n" +"Language-Team: French (http://www.transifex.com/oca/OCA-web-8-0/language/fr/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: fr\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#. module: web_widget_x2many_2d_matrix +#. openerp-web +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:11 +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:28 +#, python-format +msgid "Total" +msgstr "Total" diff --git a/web_widget_x2many_2d_matrix/i18n/hr.po b/web_widget_x2many_2d_matrix/i18n/hr.po new file mode 100644 index 000000000000..f209e294170b --- /dev/null +++ b/web_widget_x2many_2d_matrix/i18n/hr.po @@ -0,0 +1,27 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_widget_x2many_2d_matrix +# +# Translators: +# Ana-Maria Olujić , 2016 +msgid "" +msgstr "" +"Project-Id-Version: web (8.0)\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-25 00:51+0000\n" +"PO-Revision-Date: 2016-08-19 11:47+0000\n" +"Last-Translator: Ana-Maria Olujić \n" +"Language-Team: Croatian (http://www.transifex.com/oca/OCA-web-8-0/language/hr/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: hr\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" + +#. module: web_widget_x2many_2d_matrix +#. openerp-web +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:11 +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:28 +#, python-format +msgid "Total" +msgstr "Ukupno" diff --git a/web_widget_x2many_2d_matrix/i18n/it.po b/web_widget_x2many_2d_matrix/i18n/it.po new file mode 100644 index 000000000000..5b5d0bf31c36 --- /dev/null +++ b/web_widget_x2many_2d_matrix/i18n/it.po @@ -0,0 +1,26 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_widget_x2many_2d_matrix +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: web (8.0)\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-03-17 07:30+0000\n" +"PO-Revision-Date: 2015-11-07 11:22+0000\n" +"Last-Translator: <>\n" +"Language-Team: Italian (http://www.transifex.com/oca/OCA-web-8-0/language/it/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: it\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: web_widget_x2many_2d_matrix +#. openerp-web +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:11 +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:28 +#, python-format +msgid "Total" +msgstr "Totale" diff --git a/web_widget_x2many_2d_matrix/i18n/pt_BR.po b/web_widget_x2many_2d_matrix/i18n/pt_BR.po new file mode 100644 index 000000000000..c56e07fa61ce --- /dev/null +++ b/web_widget_x2many_2d_matrix/i18n/pt_BR.po @@ -0,0 +1,26 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_widget_x2many_2d_matrix +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: web (8.0)\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-03-11 02:18+0000\n" +"PO-Revision-Date: 2016-03-05 16:20+0000\n" +"Last-Translator: danimaribeiro \n" +"Language-Team: Portuguese (Brazil) (http://www.transifex.com/oca/OCA-web-8-0/language/pt_BR/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: pt_BR\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#. module: web_widget_x2many_2d_matrix +#. openerp-web +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:11 +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:28 +#, python-format +msgid "Total" +msgstr "Total" diff --git a/web_widget_x2many_2d_matrix/i18n/sl.po b/web_widget_x2many_2d_matrix/i18n/sl.po new file mode 100644 index 000000000000..07ae09c5d338 --- /dev/null +++ b/web_widget_x2many_2d_matrix/i18n/sl.po @@ -0,0 +1,26 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_widget_x2many_2d_matrix +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: web (8.0)\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2015-11-23 13:46+0000\n" +"PO-Revision-Date: 2015-11-08 05:48+0000\n" +"Last-Translator: Matjaž Mozetič \n" +"Language-Team: Slovenian (http://www.transifex.com/oca/OCA-web-8-0/language/sl/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: sl\n" +"Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);\n" + +#. module: web_widget_x2many_2d_matrix +#. openerp-web +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:11 +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:28 +#, python-format +msgid "Total" +msgstr "Skupaj" diff --git a/web_widget_x2many_2d_matrix/i18n/tr.po b/web_widget_x2many_2d_matrix/i18n/tr.po new file mode 100644 index 000000000000..635773bda8e4 --- /dev/null +++ b/web_widget_x2many_2d_matrix/i18n/tr.po @@ -0,0 +1,27 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_widget_x2many_2d_matrix +# +# Translators: +# Ahmet Altınışık , 2015 +msgid "" +msgstr "" +"Project-Id-Version: web (8.0)\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-01-08 21:34+0000\n" +"PO-Revision-Date: 2015-12-30 22:00+0000\n" +"Last-Translator: Ahmet Altınışık \n" +"Language-Team: Turkish (http://www.transifex.com/oca/OCA-web-8-0/language/tr/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: tr\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#. module: web_widget_x2many_2d_matrix +#. openerp-web +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:11 +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:28 +#, python-format +msgid "Total" +msgstr "Toplam" From 134002c0d9e51acd253a076c0a5fb1b17ed6ab23 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Mon, 18 Jan 2016 16:41:25 +0100 Subject: [PATCH 026/153] [IMP] web_widget_x2many_2d_matrix: Several improvements * README update to newest OCA template * Example in README * Massive performance boost for big matrices, specially on Firefox * Assign id on row in order to find it back in all cases * Fix #321, choked on cached writes --- web_widget_x2many_2d_matrix/README.rst | 48 ++++++-- .../src/js/web_widget_x2many_2d_matrix.js | 112 ++++++++++++------ 2 files changed, 120 insertions(+), 40 deletions(-) diff --git a/web_widget_x2many_2d_matrix/README.rst b/web_widget_x2many_2d_matrix/README.rst index a6b436e17cf9..7c880b1316aa 100644 --- a/web_widget_x2many_2d_matrix/README.rst +++ b/web_widget_x2many_2d_matrix/README.rst @@ -1,3 +1,7 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :alt: License: AGPL-3 + +=========================== 2D matrix for x2many fields =========================== @@ -51,12 +55,41 @@ show_row_totals show_column_totals If field_value is a numeric field, calculate column totals +Example +======= + +You need a data structure already filled with values. Let's assume we want to use this widget in a wizard that lets the user fill in planned hours for one task per project per user. In this case, we can use ``project.task`` as our data model and point to it from our wizard. The crucial part is that we fill the field in the default function:: + + class MyWizard(models.TransientModel): + _name = 'my.wizard' + + def _default_task_ids(self): + # your list of project should come from the context, some selection + # in a previous wizard or wherever else + projects = self.env['project.project'].browse([1, 2, 3]) + # same with users + users = self.env['res.users'].browse([1, 2, 3]) + return [ + (0, 0, {'project_id': p.id, 'user_id': u.id, 'planned_hours': 0}) + # if the project doesn't have a task for the user, create a new one + if not p.task_ids.filtered(lambda x: x.user_id == u) else + # otherwise, return the task + (4, p.task_ids.filtered(lambda x: x.user_id == u)[0].id) + for p in projects + for u in users + ] + + task_ids = fields.Many2many('project.task', default=_default_task_ids) + +Now in our wizard, we can use:: + + + Known issues / Roadmap ====================== * it would be worth trying to instantiate the proper field widget and let it render the input - Bug Tracker =========== @@ -65,7 +98,6 @@ 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 `here `_. - Credits ======= @@ -77,12 +109,14 @@ Contributors Maintainer ---------- -.. image:: http://odoo-community.org/logo.png - :alt: Odoo Community Association - :target: http://odoo-community.org +.. 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. +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 http://odoo-community.org. +To contribute to this module, please visit https://odoo-community.org. diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index 5d4ce7854587..4dbcb4cc9c71 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -31,6 +31,8 @@ openerp.web_widget_x2many_2d_matrix = function(instance) // those will be filled with rows from the dataset by_x_axis: {}, by_y_axis: {}, + by_id: {}, + // configuration values field_x_axis: 'x', field_label_x_axis: 'x', field_y_axis: 'y', @@ -81,7 +83,8 @@ openerp.web_widget_x2many_2d_matrix = function(instance) self.by_x_axis = {}; self.by_y_axis = {}; - + self.by_id = {}; + return jQuery.when(result).then(function() { return self.dataset._model.call('fields_get').then(function(fields) @@ -90,7 +93,35 @@ openerp.web_widget_x2many_2d_matrix = function(instance) self.is_numeric = fields[self.field_value].type == 'float'; self.show_row_totals &= self.is_numeric; self.show_column_totals &= self.is_numeric; - }).then(function() + }) + // if there are cached writes on the parent dataset, read below + // only returns the written data, which is not enough to properly + // set up our data structure. Read those ids here and patch the + // cache + .then(function() + { + var ids_written = _.map( + self.dataset.to_write, function(x) { return x.id }); + if(!ids_written.length) + { + return; + } + return (new instance.web.Query(self.dataset._model)) + .filter([['id', 'in', ids_written]]) + .all() + .then(function(rows) + { + _.each(rows, function(row) + { + var cache = _.find( + self.dataset.cache, + function(x) { return x.id == row.id } + ); + _.extend(cache.values, row, _.clone(cache.values)); + }) + }) + }) + .then(function() { return self.dataset.read_ids(self.dataset.ids).then(function(rows) { @@ -158,15 +189,31 @@ openerp.web_widget_x2many_2d_matrix = function(instance) }); }, - // to whatever needed to setup internal data structure + // do whatever needed to setup internal data structure add_xy_row: function(row) { var x = this.get_field_value(row, this.field_x_axis), y = this.get_field_value(row, this.field_y_axis); + // row is a *copy* of a row in dataset.cache, fetch + // a reference to this row in order to have the + // internal data structure point to the same data + // the dataset manipulates + _.every(this.dataset.cache, function(cached_row) + { + if(cached_row.id == row.id) + { + row = cached_row.values; + // new rows don't have that + row.id = cached_row.id; + return false; + } + return true; + }); this.by_x_axis[x] = this.by_x_axis[x] || {}; this.by_y_axis[y] = this.by_y_axis[y] || {}; this.by_x_axis[x][y] = row; this.by_y_axis[y][x] = row; + this.by_id[row.id] = row; }, // get x axis values in the correct order @@ -255,39 +302,38 @@ openerp.web_widget_x2many_2d_matrix = function(instance) var self = this, grand_total = 0, totals_x = {}, - totals_y = {}; - return self.dataset.read_ids(self.dataset.ids).then(function(rows) + totals_y = {}, + rows = this.by_id, + deferred = jQuery.Deferred(); + _.each(rows, function(row) { - _.each(rows, function(row) - { - var key_x = self.get_field_value(row, self.field_x_axis), - key_y = self.get_field_value(row, self.field_y_axis); - totals_x[key_x] = (totals_x[key_x] || 0) + self.get_field_value(row, self.field_value); - totals_y[key_y] = (totals_y[key_y] || 0) + self.get_field_value(row, self.field_value); - grand_total += self.get_field_value(row, self.field_value); - }); - }).then(function() + var key_x = self.get_field_value(row, self.field_x_axis), + key_y = self.get_field_value(row, self.field_y_axis); + totals_x[key_x] = (totals_x[key_x] || 0) + self.get_field_value(row, self.field_value); + totals_y[key_y] = (totals_y[key_y] || 0) + self.get_field_value(row, self.field_value); + grand_total += self.get_field_value(row, self.field_value); + }); + _.each(totals_y, function(total, y) { - _.each(totals_y, function(total, y) - { - self.$el.find( - _.str.sprintf('td.row_total[data-y="%s"]', y)).text( - self.format_xy_value(total)); - }); - _.each(totals_x, function(total, x) - { - self.$el.find( - _.str.sprintf('td.column_total[data-x="%s"]', x)).text( - self.format_xy_value(total)); - }); - self.$el.find('.grand_total').text( - self.format_xy_value(grand_total)) - return { - totals_x: totals_x, - totals_y: totals_y, - grand_total: grand_total, - }; + self.$el.find( + _.str.sprintf('td.row_total[data-y="%s"]', y)).text( + self.format_xy_value(total)); + }); + _.each(totals_x, function(total, x) + { + self.$el.find( + _.str.sprintf('td.column_total[data-x="%s"]', x)).text( + self.format_xy_value(total)); + }); + self.$el.find('.grand_total').text( + self.format_xy_value(grand_total)) + deferred.resolve({ + totals_x: totals_x, + totals_y: totals_y, + grand_total: grand_total, + rows: rows, }); + return deferred; }, setup_many2one_axes: function() From cc1a141498845d32afbeac65052a2cf89905a70b Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Wed, 14 Sep 2016 09:32:56 +0200 Subject: [PATCH 027/153] [IMP] web_widget_x2many_2d_matrix: New option field_att_ Declare as many options prefixed with this string as you need for binding a field value with an HTML node attribute (disabled, class, style...) called as the `` passed in the option. NOTE: This doesn't prevent to require to fill the full matrix with all the combination records. --- web_widget_x2many_2d_matrix/README.rst | 38 ++++++++++++---- web_widget_x2many_2d_matrix/__openerp__.py | 35 +++------------ .../src/js/web_widget_x2many_2d_matrix.js | 45 ++++++++++--------- .../src/xml/web_widget_x2many_2d_matrix.xml | 2 +- 4 files changed, 63 insertions(+), 57 deletions(-) diff --git a/web_widget_x2many_2d_matrix/README.rst b/web_widget_x2many_2d_matrix/README.rst index 7c880b1316aa..83c29328c1d1 100644 --- a/web_widget_x2many_2d_matrix/README.rst +++ b/web_widget_x2many_2d_matrix/README.rst @@ -1,5 +1,6 @@ .. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg - :alt: License: AGPL-3 + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 =========================== 2D matrix for x2many fields @@ -8,7 +9,8 @@ This module allows to show an x2many field with 3-tuples ($x_value, $y_value, $value) in a table - $x_value1 $x_value2 +========= =========== =========== +\ $x_value1 $x_value2 ========= =========== =========== $y_value1 $value(1/1) $value(2/1) $y_value2 $value(1/2) $value(2/2) @@ -23,7 +25,9 @@ result could look like this: .. image:: /web_widget_x2many_2d_matrix/static/description/screenshot.png :alt: Screenshot -The beauty of this is that you have an arbitrary amount of columns with this widget, trying to get this in standard x2many lists involves some quite agly hacks. +The beauty of this is that you have an arbitrary amount of columns with this +widget, trying to get this in standard x2many lists involves some quite ugly +hacks. Usage ===== @@ -54,11 +58,23 @@ show_row_totals If field_value is a numeric field, calculate row totals show_column_totals If field_value is a numeric field, calculate column totals +field_att_ + Declare as many options prefixed with this string as you need for binding + a field value with an HTML node attribute (disabled, class, style...) + called as the `` passed in the option. + +.. 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/8.0 Example ======= -You need a data structure already filled with values. Let's assume we want to use this widget in a wizard that lets the user fill in planned hours for one task per project per user. In this case, we can use ``project.task`` as our data model and point to it from our wizard. The crucial part is that we fill the field in the default function:: +You need a data structure already filled with values. Let's assume we want to +use this widget in a wizard that lets the user fill in planned hours for one +task per project per user. In this case, we can use ``project.task`` as our +data model and point to it from our wizard. The crucial part is that we fill +the field in the default function:: class MyWizard(models.TransientModel): _name = 'my.wizard' @@ -85,6 +101,11 @@ Now in our wizard, we can use:: +Note that all values in the matrix must exist, so you need to create them +previously if not present, but you can control visually the editability of +the fields in the matrix through `field_att_disabled` option with a control +field. + Known issues / Roadmap ====================== @@ -93,10 +114,10 @@ Known issues / Roadmap 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 -`here `_. +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 ======= @@ -105,6 +126,7 @@ Contributors ------------ * Holger Brunn +* Pedro M. Baeza Maintainer ---------- diff --git a/web_widget_x2many_2d_matrix/__openerp__.py b/web_widget_x2many_2d_matrix/__openerp__.py index e48c3a6e6d6a..87dc3541c5b9 100644 --- a/web_widget_x2many_2d_matrix/__openerp__.py +++ b/web_widget_x2many_2d_matrix/__openerp__.py @@ -1,27 +1,13 @@ # -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# This module copyright (C) 2015 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 . -# -############################################################################## +# Copyright 2015 Holger Brunn +# Copyright 2016 Pedro M. Baeza +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + { "name": "2D matrix for x2many fields", - "version": "8.0.1.0.0", + "version": "8.0.1.1.0", "author": "Therp BV, " + "Tecnativa," "Odoo Community Association (OCA)", "license": "AGPL-3", "category": "Hidden/Dependency", @@ -35,12 +21,5 @@ "qweb": [ 'static/src/xml/web_widget_x2many_2d_matrix.xml', ], - "test": [ - ], - "auto_install": False, - 'installable': False, - "application": False, - "external_dependencies": { - 'python': [], - }, + "installable": True, } diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index 4dbcb4cc9c71..5f6147f4d09e 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -1,23 +1,6 @@ -//-*- coding: utf-8 -*- -//############################################################################ -// -// OpenERP, Open Source Management Solution -// This module copyright (C) 2015 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 . -// -//############################################################################ +/* Copyright 2015 Holger Brunn + * Copyright 2016 Pedro M. Baeza + * License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). */ openerp.web_widget_x2many_2d_matrix = function(instance) { @@ -44,6 +27,8 @@ openerp.web_widget_x2many_2d_matrix = function(instance) show_column_totals: true, // this will be filled with the model's fields_get fields: {}, + // Store fields used to fill HTML attributes + fields_att: {}, // read parameters init: function(field_manager, node) @@ -53,6 +38,12 @@ openerp.web_widget_x2many_2d_matrix = function(instance) this.field_label_x_axis = node.attrs.field_label_x_axis || this.field_x_axis; this.field_label_y_axis = node.attrs.field_label_y_axis || this.field_y_axis; this.field_value = node.attrs.field_value || this.field_value; + for (var property in node.attrs) { + if (property.startsWith("field_att_")) { + this.fields_att[property.substring(10)] = node.attrs[property]; + } + } + this.field_editability = node.attrs.field_editability || this.field_editability; this.show_row_totals = node.attrs.show_row_totals || this.show_row_totals; this.show_column_totals = node.attrs.show_column_totals || this.show_column_totals; return this._super.apply(this, arguments); @@ -261,6 +252,20 @@ openerp.web_widget_x2many_2d_matrix = function(instance) return this.by_x_axis[x][y]['id']; }, + get_xy_att: function(x, y) + { + var vals = {}; + for (var att in this.fields_att) { + var val = this.get_field_value( + this.by_x_axis[x][y], this.fields_att[att]); + // Discard empty values + if (val) { + vals[att] = val; + } + } + return vals; + }, + // return the value of a coordinate get_xy_value: function(x, y) { diff --git a/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml b/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml index 35f1669bc197..ca6b687f53b2 100644 --- a/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml +++ b/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml @@ -16,7 +16,7 @@ - + From d0af0d5da58e77bca900cbc05814425607299568 Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Fri, 16 Sep 2016 14:35:54 +0200 Subject: [PATCH 028/153] [MIG] web_widget_x2many_2d_matrix: Migration to 9.0 --- web_widget_x2many_2d_matrix/README.rst | 9 ++++ web_widget_x2many_2d_matrix/__openerp__.py | 2 +- .../src/js/web_widget_x2many_2d_matrix.js | 53 ++++++++++++------- 3 files changed, 44 insertions(+), 20 deletions(-) diff --git a/web_widget_x2many_2d_matrix/README.rst b/web_widget_x2many_2d_matrix/README.rst index 83c29328c1d1..dc8a480f1eae 100644 --- a/web_widget_x2many_2d_matrix/README.rst +++ b/web_widget_x2many_2d_matrix/README.rst @@ -110,6 +110,15 @@ Known issues / Roadmap ====================== * it would be worth trying to instantiate the proper field widget and let it render the input +* If you pass values with an onchange, you need to overwrite the model's method + `onchange` for making the widget work:: + + @api.multi + def onchange(self, values, field_name, field_onchange): + if "one2many_field" in field_onchange: + for sub in []: + field_onchange.setdefault("one2many_field." + sub, u"") + return super(model, self).onchange(values, field_name, field_onchange) Bug Tracker =========== diff --git a/web_widget_x2many_2d_matrix/__openerp__.py b/web_widget_x2many_2d_matrix/__openerp__.py index 87dc3541c5b9..a8f4e8cf07f7 100644 --- a/web_widget_x2many_2d_matrix/__openerp__.py +++ b/web_widget_x2many_2d_matrix/__openerp__.py @@ -5,7 +5,7 @@ { "name": "2D matrix for x2many fields", - "version": "8.0.1.1.0", + "version": "9.0.1.0.0", "author": "Therp BV, " "Tecnativa," "Odoo Community Association (OCA)", diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index 5f6147f4d09e..4087e88e379b 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -2,12 +2,16 @@ * Copyright 2016 Pedro M. Baeza * License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). */ -openerp.web_widget_x2many_2d_matrix = function(instance) -{ - instance.web.form.widgets.add( - 'x2many_2d_matrix', - 'instance.web_widget_x2many_2d_matrix.FieldX2Many2dMatrix'); - instance.web_widget_x2many_2d_matrix.FieldX2Many2dMatrix = instance.web.form.FieldOne2Many.extend({ +odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { + "use strict"; + + var core = require('web.core'); + var formats = require('web.formats'); + var FieldOne2Many = core.form_widget_registry.get('one2many'); + var Model = require('web.Model'); + var data = require('web.data'); + + var WidgetX2Many2dMatrix = FieldOne2Many.extend({ template: 'FieldX2Many2dMatrix', widget_class: 'oe_form_field_x2many_2d_matrix', @@ -46,7 +50,7 @@ openerp.web_widget_x2many_2d_matrix = function(instance) this.field_editability = node.attrs.field_editability || this.field_editability; this.show_row_totals = node.attrs.show_row_totals || this.show_row_totals; this.show_column_totals = node.attrs.show_column_totals || this.show_column_totals; - return this._super.apply(this, arguments); + return this._super(field_manager, node); }, // return a field's value, id in case it's a one2many field @@ -67,10 +71,10 @@ openerp.web_widget_x2many_2d_matrix = function(instance) }, // setup our datastructure for simple access in the template - set_value: function() + set_value: function(value_) { var self = this, - result = this._super.apply(this, arguments); + result = this._super(value_); self.by_x_axis = {}; self.by_y_axis = {}; @@ -150,7 +154,7 @@ openerp.web_widget_x2many_2d_matrix = function(instance) { return; } - var model = new instance.web.Model(self.fields[field].relation); + var model = new Model(self.fields[field].relation); deferrends.push(model.call( 'name_get', [_.map(_.keys(rows), function(key) {return parseInt(key)})]) @@ -171,7 +175,7 @@ openerp.web_widget_x2many_2d_matrix = function(instance) self.compute_totals(); self.setup_many2one_axes(); self.$el.find('.edit').on( - 'change', self.proxy(self.xy_value_change)); + 'change', self.proxy(self.xy_value_change)); self.effective_readonly_change(); } return jQuery.when.apply(jQuery, deferrends); @@ -290,14 +294,14 @@ openerp.web_widget_x2many_2d_matrix = function(instance) // parse a value from user input parse_xy_value: function(val) { - return instance.web.parse_value( + return formats.parse_value( val, {'type': this.fields[this.field_value].type}); }, // format a value from the database for display format_xy_value: function(val) { - return instance.web.format_value( + return formats.format_value( val, {'type': this.fields[this.field_value].type}); }, @@ -381,7 +385,7 @@ openerp.web_widget_x2many_2d_matrix = function(instance) this.on("change:effective_readonly", this, this.proxy(this.effective_readonly_change)); this.effective_readonly_change(); - return this._super.apply(this, arguments); + return this._super(); }, xy_value_change: function(e) @@ -423,9 +427,20 @@ openerp.web_widget_x2many_2d_matrix = function(instance) return this.$el.find('.oe_form_invalid').length == 0; }, - // deactivate view related functions - load_views: function() {}, - reload_current_view: function() {}, - get_active_view: function() {}, + load_views: function() { + // Needed for removing the initial empty tree view when the widget + // is loaded + var self = this, + result = this._super(); + + return $.when(result).then(function() + { + self.set_value(false); + }); + }, }); -} + + core.form_widget_registry.add('x2many_2d_matrix', WidgetX2Many2dMatrix); + + return WidgetX2Many2dMatrix; +}); From 5c0e4f5a4a10109415804f9a7d9090fc56c947a9 Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Fri, 16 Sep 2016 17:56:53 +0200 Subject: [PATCH 029/153] [IMP] web_widget_x2many_2d_matrix: Use new JS modularized API. --- .../static/src/js/web_widget_x2many_2d_matrix.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index 4087e88e379b..e570949d85da 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -10,6 +10,8 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { var FieldOne2Many = core.form_widget_registry.get('one2many'); var Model = require('web.Model'); var data = require('web.data'); + var _ = require('_'); + var $ = require('$'); var WidgetX2Many2dMatrix = FieldOne2Many.extend({ template: 'FieldX2Many2dMatrix', @@ -80,7 +82,7 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { self.by_y_axis = {}; self.by_id = {}; - return jQuery.when(result).then(function() + return $.when(result).then(function() { return self.dataset._model.call('fields_get').then(function(fields) { @@ -101,7 +103,7 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { { return; } - return (new instance.web.Query(self.dataset._model)) + return (new data.Query(self.dataset._model)) .filter([['id', 'in', ids_written]]) .all() .then(function(rows) @@ -178,7 +180,7 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { 'change', self.proxy(self.xy_value_change)); self.effective_readonly_change(); } - return jQuery.when.apply(jQuery, deferrends); + return $.when.apply($, deferrends); }); }); }); @@ -313,7 +315,7 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { totals_x = {}, totals_y = {}, rows = this.by_id, - deferred = jQuery.Deferred(); + deferred = $.Deferred(); _.each(rows, function(row) { var key_x = self.get_field_value(row, self.field_x_axis), @@ -369,7 +371,7 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { type: 'ir.actions.act_window', name: this.fields[field].string, res_model: this.fields[field].relation, - res_id: jQuery(e.currentTarget).data(id_attribute), + res_id: $(e.currentTarget).data(id_attribute), views: [[false, 'form']], target: 'current', }) @@ -390,7 +392,7 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { xy_value_change: function(e) { - var $this = jQuery(e.currentTarget), + var $this = $(e.currentTarget), val = $this.val(); if(this.validate_xy_value(val)) { From bb3bb29ad19b809bb57ed206f1e2086a59ea0347 Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Wed, 21 Sep 2016 18:24:51 +0200 Subject: [PATCH 030/153] [IMP] web_widget_x2many_2d_matrix: Include x_axis_clickable and y_axis_clickable attrs XML attributes for the widget that allows to configure if the axis will be clickable or not in case the source field is a many2one field. --- web_widget_x2many_2d_matrix/README.rst | 12 +++++++++-- .../src/js/web_widget_x2many_2d_matrix.js | 20 +++++++++++++++---- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/web_widget_x2many_2d_matrix/README.rst b/web_widget_x2many_2d_matrix/README.rst index dc8a480f1eae..6628a81ea877 100644 --- a/web_widget_x2many_2d_matrix/README.rst +++ b/web_widget_x2many_2d_matrix/README.rst @@ -52,12 +52,20 @@ field_label_x_axis Use another field to display in the table header field_label_y_axis Use another field to display in the table header +x_axis_clickable + It indicates if the X axis allows to be clicked for navigating to the field + (if it's a many2one field). True by default +y_axis_clickable + It indicates if the Y axis allows to be clicked for navigating to the field + (if it's a many2one field). True by default field_value Show this field as value show_row_totals - If field_value is a numeric field, calculate row totals + If field_value is a numeric field, it indicates if you want to calculate + row totals. True by default show_column_totals - If field_value is a numeric field, calculate column totals + If field_value is a numeric field, it indicates if you want to calculate + column totals. True by default field_att_ Declare as many options prefixed with this string as you need for binding a field value with an HTML node attribute (disabled, class, style...) diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index e570949d85da..c5631593097b 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -27,6 +27,8 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { field_y_axis: 'y', field_label_y_axis: 'y', field_value: 'value', + x_axis_clickable: true, + y_axis_clickable: true, // information about our datatype is_numeric: false, show_row_totals: true, @@ -36,6 +38,14 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { // Store fields used to fill HTML attributes fields_att: {}, + parse_boolean: function(val) + { + if (val.toLowerCase() === 'true' || val === '1') { + return true; + } + return false; + }, + // read parameters init: function(field_manager, node) { @@ -43,6 +53,8 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { this.field_y_axis = node.attrs.field_y_axis || this.field_y_axis; this.field_label_x_axis = node.attrs.field_label_x_axis || this.field_x_axis; this.field_label_y_axis = node.attrs.field_label_y_axis || this.field_y_axis; + this.x_axis_clickable = node.attrs.x_axis_clickable != undefined ? this.parse_boolean(node.attrs.x_axis_clickable) : this.x_axis_clickable; + this.y_axis_clickable = node.attrs.y_axis_clickable != undefined ? this.parse_boolean(node.attrs.y_axis_clickable) : this.y_axis_clickable; this.field_value = node.attrs.field_value || this.field_value; for (var property in node.attrs) { if (property.startsWith("field_att_")) { @@ -50,8 +62,8 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { } } this.field_editability = node.attrs.field_editability || this.field_editability; - this.show_row_totals = node.attrs.show_row_totals || this.show_row_totals; - this.show_column_totals = node.attrs.show_column_totals || this.show_column_totals; + this.show_row_totals = node.attrs.show_row_totals != undefined ? this.parse_boolean(node.attrs.show_row_totals) : this.show_row_totals; + this.show_column_totals = node.attrs.show_column_totals != undefined ? this.parse_boolean(node.attrs.show_column_totals) : this.show_column_totals; return this._super(field_manager, node); }, @@ -349,14 +361,14 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { setup_many2one_axes: function() { - if(this.fields[this.field_x_axis].type == 'many2one') + if(this.fields[this.field_x_axis].type == 'many2one' && this.x_axis_clickable) { this.$el.find('th[data-x]').addClass('oe_link') .click(_.partial( this.proxy(this.many2one_axis_click), this.field_x_axis, 'x')); } - if(this.fields[this.field_y_axis].type == 'many2one') + if(this.fields[this.field_y_axis].type == 'many2one' && this.y_axis_clickable) { this.$el.find('tr[data-y] th').addClass('oe_link') .click(_.partial( From 4f5d9c2cbcc08e3f53238c7039133dfcef717c4d Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Wed, 21 Sep 2016 20:39:15 +0200 Subject: [PATCH 031/153] [FIX] web_widget_x2many_2d_matrix: Use existing value in load_views --- .../static/src/js/web_widget_x2many_2d_matrix.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index c5631593097b..782ed21a8235 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -449,7 +449,7 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { return $.when(result).then(function() { - self.set_value(false); + self.set_value(self.get_value()); }); }, }); From 2e273f994e4fab4eefe6668bba313979c8727685 Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Thu, 22 Sep 2016 09:45:23 +0200 Subject: [PATCH 032/153] [IMP] web_widget_x2many_2d_matrix: Better options parsing --- .../static/src/js/web_widget_x2many_2d_matrix.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index 782ed21a8235..3e182047b807 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -53,8 +53,8 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { this.field_y_axis = node.attrs.field_y_axis || this.field_y_axis; this.field_label_x_axis = node.attrs.field_label_x_axis || this.field_x_axis; this.field_label_y_axis = node.attrs.field_label_y_axis || this.field_y_axis; - this.x_axis_clickable = node.attrs.x_axis_clickable != undefined ? this.parse_boolean(node.attrs.x_axis_clickable) : this.x_axis_clickable; - this.y_axis_clickable = node.attrs.y_axis_clickable != undefined ? this.parse_boolean(node.attrs.y_axis_clickable) : this.y_axis_clickable; + this.x_axis_clickable = this.parse_boolean(node.attrs.x_axis_clickable || '1'); + this.y_axis_clickable = this.parse_boolean(node.attrs.y_axis_clickable || '1'); this.field_value = node.attrs.field_value || this.field_value; for (var property in node.attrs) { if (property.startsWith("field_att_")) { @@ -62,8 +62,8 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { } } this.field_editability = node.attrs.field_editability || this.field_editability; - this.show_row_totals = node.attrs.show_row_totals != undefined ? this.parse_boolean(node.attrs.show_row_totals) : this.show_row_totals; - this.show_column_totals = node.attrs.show_column_totals != undefined ? this.parse_boolean(node.attrs.show_column_totals) : this.show_column_totals; + this.show_row_totals = this.parse_boolean(node.attrs.show_row_totals || '1'); + this.show_column_totals = this.parse_boolean(node.attrs.show_column_totals || '1'); return this._super(field_manager, node); }, From a32358e77f75a11021ab71dda2a91d7ae3fbaf39 Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Thu, 22 Sep 2016 09:48:03 +0200 Subject: [PATCH 033/153] [IMP+ web_widget_x2many_2d_matrix: Add roadmap --- web_widget_x2many_2d_matrix/README.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/web_widget_x2many_2d_matrix/README.rst b/web_widget_x2many_2d_matrix/README.rst index 6628a81ea877..85d5dd712515 100644 --- a/web_widget_x2many_2d_matrix/README.rst +++ b/web_widget_x2many_2d_matrix/README.rst @@ -117,7 +117,9 @@ field. Known issues / Roadmap ====================== -* it would be worth trying to instantiate the proper field widget and let it render the input +* It would be worth trying to instantiate the proper field widget and let it render the input +* Let the widget deal with the missing values of the full Cartesian product, + instead of being forced to pre-fill all the possible values. * If you pass values with an onchange, you need to overwrite the model's method `onchange` for making the widget work:: From 202efd9ffb326eae429dde6f53dc3770243a0e6b Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Thu, 22 Sep 2016 12:09:34 +0200 Subject: [PATCH 034/153] [IMP] web_widget_x2many_2d_matrix: Remove unneeded code --- .../src/js/web_widget_x2many_2d_matrix.js | 45 ------------------- 1 file changed, 45 deletions(-) diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index 3e182047b807..8d2e3fc680fd 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -134,55 +134,11 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { { return self.dataset.read_ids(self.dataset.ids).then(function(rows) { - var read_many2one = {}, - many2one_fields = [ - self.field_x_axis, self.field_y_axis, - self.field_label_x_axis, self.field_label_y_axis - ]; - // prepare to read many2one names if necessary (we can get (id, name) or just id as value) - _.each(many2one_fields, function(field) - { - if(self.fields[field].type == 'many2one') - { - read_many2one[field] = {}; - } - }); // setup data structure _.each(rows, function(row) { self.add_xy_row(row); - _.each(read_many2one, function(rows, field) - { - if(!_.isArray(row[field])) - { - rows[row[field]] = rows[row[field]] || [] - rows[row[field]].push(row); - } - }); }); - // read many2one fields if necessary - var deferrends = []; - _.each(read_many2one, function(rows, field) - { - if(_.isEmpty(rows)) - { - return; - } - var model = new Model(self.fields[field].relation); - deferrends.push(model.call( - 'name_get', - [_.map(_.keys(rows), function(key) {return parseInt(key)})]) - .then(function(names) - { - _.each(names, function(name) - { - _.each(rows[name[0]], function(row) - { - row[field] = name; - }); - }); - })); - }) if(self.is_started && !self.no_rerender) { self.renderElement(); @@ -192,7 +148,6 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { 'change', self.proxy(self.xy_value_change)); self.effective_readonly_change(); } - return $.when.apply($, deferrends); }); }); }); From f6f98d52572c8a8d23f7147f3bc57536d6fcf8a0 Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Thu, 22 Sep 2016 12:11:40 +0200 Subject: [PATCH 035/153] [FIX] web_widget_x2many_2d_matrix: Init correctly the view --- .../static/src/js/web_widget_x2many_2d_matrix.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index 8d2e3fc680fd..8a51b3a6b8b0 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -404,7 +404,7 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { return $.when(result).then(function() { - self.set_value(self.get_value()); + self.renderElement(); }); }, }); From 4947afb24a695f170ae8244309d5fe2febb44191 Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Thu, 6 Oct 2016 16:12:54 +0200 Subject: [PATCH 036/153] [MIG] Make modules uninstallable --- web_widget_x2many_2d_matrix/__openerp__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_widget_x2many_2d_matrix/__openerp__.py b/web_widget_x2many_2d_matrix/__openerp__.py index a8f4e8cf07f7..8b55c97d5e97 100644 --- a/web_widget_x2many_2d_matrix/__openerp__.py +++ b/web_widget_x2many_2d_matrix/__openerp__.py @@ -21,5 +21,5 @@ "qweb": [ 'static/src/xml/web_widget_x2many_2d_matrix.xml', ], - "installable": True, + 'installable': False, } From 40c7b3aa643f3e83d96d78ce1c36411128946832 Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Thu, 6 Oct 2016 16:13:01 +0200 Subject: [PATCH 037/153] [MIG] Rename manifest files --- web_widget_x2many_2d_matrix/{__openerp__.py => __manifest__.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename web_widget_x2many_2d_matrix/{__openerp__.py => __manifest__.py} (100%) diff --git a/web_widget_x2many_2d_matrix/__openerp__.py b/web_widget_x2many_2d_matrix/__manifest__.py similarity index 100% rename from web_widget_x2many_2d_matrix/__openerp__.py rename to web_widget_x2many_2d_matrix/__manifest__.py From 699539e8c858ee6a6149df82a68d66938f69eaf9 Mon Sep 17 00:00:00 2001 From: jesusVMayor Date: Mon, 24 Apr 2017 12:28:47 +0200 Subject: [PATCH 038/153] Migration of web_widget_x2many_2d_matrix to 10.0 --- web_widget_x2many_2d_matrix/__manifest__.py | 4 ++-- .../static/src/css/web_widget_x2many_2d_matrix.css | 2 +- .../static/src/js/web_widget_x2many_2d_matrix.js | 7 +++---- .../static/src/xml/web_widget_x2many_2d_matrix.xml | 6 +++--- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/web_widget_x2many_2d_matrix/__manifest__.py b/web_widget_x2many_2d_matrix/__manifest__.py index 8b55c97d5e97..b4651c0741ff 100644 --- a/web_widget_x2many_2d_matrix/__manifest__.py +++ b/web_widget_x2many_2d_matrix/__manifest__.py @@ -5,7 +5,7 @@ { "name": "2D matrix for x2many fields", - "version": "9.0.1.0.0", + "version": "10.0.1.0.0", "author": "Therp BV, " "Tecnativa," "Odoo Community Association (OCA)", @@ -21,5 +21,5 @@ "qweb": [ 'static/src/xml/web_widget_x2many_2d_matrix.xml', ], - 'installable': False, + "installable": True, } diff --git a/web_widget_x2many_2d_matrix/static/src/css/web_widget_x2many_2d_matrix.css b/web_widget_x2many_2d_matrix/static/src/css/web_widget_x2many_2d_matrix.css index d33d4f21bdfb..14ed1c5364fd 100644 --- a/web_widget_x2many_2d_matrix/static/src/css/web_widget_x2many_2d_matrix.css +++ b/web_widget_x2many_2d_matrix/static/src/css/web_widget_x2many_2d_matrix.css @@ -2,7 +2,7 @@ { cursor: pointer; } -.openerp .oe_form_field_x2many_2d_matrix .oe_list_content > tbody > tr > td.oe_list_field_cell +.oe_form_field_x2many_2d_matrix .oe_list_content > tbody > tr > td.oe_list_field_cell { white-space: normal; } diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index 8a51b3a6b8b0..43fa84bb4998 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -10,8 +10,7 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { var FieldOne2Many = core.form_widget_registry.get('one2many'); var Model = require('web.Model'); var data = require('web.data'); - var _ = require('_'); - var $ = require('$'); + var $ = require('jquery'); var WidgetX2Many2dMatrix = FieldOne2Many.extend({ template: 'FieldX2Many2dMatrix', @@ -383,10 +382,10 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { effective_readonly_change: function() { this.$el - .find('tbody td.oe_list_field_cell span.oe_form_field .edit') + .find('tbody .read') .toggle(!this.get('effective_readonly')); this.$el - .find('tbody td.oe_list_field_cell span.oe_form_field .read') + .find('tbody .read') .toggle(this.get('effective_readonly')); this.$el.find('.edit').first().focus(); }, diff --git a/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml b/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml index ca6b687f53b2..a1a0d52151ca 100644 --- a/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml +++ b/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml @@ -1,7 +1,7 @@

- +
- From 4cde1233af607a06933cea807bcaf2d22926f9bd Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Fri, 28 Apr 2017 20:01:00 +0200 Subject: [PATCH 039/153] [IMP] web_widget_x2many_2d_matrix: Update example There are now more required fields for a task. --- web_widget_x2many_2d_matrix/README.rst | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/web_widget_x2many_2d_matrix/README.rst b/web_widget_x2many_2d_matrix/README.rst index 85d5dd712515..40098bff14fe 100644 --- a/web_widget_x2many_2d_matrix/README.rst +++ b/web_widget_x2many_2d_matrix/README.rst @@ -84,6 +84,8 @@ task per project per user. In this case, we can use ``project.task`` as our data model and point to it from our wizard. The crucial part is that we fill the field in the default function:: + from odoo import fields, models + class MyWizard(models.TransientModel): _name = 'my.wizard' @@ -94,7 +96,13 @@ the field in the default function:: # same with users users = self.env['res.users'].browse([1, 2, 3]) return [ - (0, 0, {'project_id': p.id, 'user_id': u.id, 'planned_hours': 0}) + (0, 0, { + 'project_id': p.id, + 'user_id': u.id, + 'planned_hours': 0, + 'message_needaction': False, + 'date_deadline': fields.Date.today(), + }) # if the project doesn't have a task for the user, create a new one if not p.task_ids.filtered(lambda x: x.user_id == u) else # otherwise, return the task From 72fd5e464cad434e80a5e660d9cb21d72ad6e2ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Pigeon?= Date: Wed, 21 Jun 2017 16:56:59 +0200 Subject: [PATCH 040/153] [10.0] web_widget_x2many_2d_matrix: update README --- web_widget_x2many_2d_matrix/README.rst | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/web_widget_x2many_2d_matrix/README.rst b/web_widget_x2many_2d_matrix/README.rst index 40098bff14fe..d81d100cca64 100644 --- a/web_widget_x2many_2d_matrix/README.rst +++ b/web_widget_x2many_2d_matrix/README.rst @@ -40,7 +40,14 @@ This assumes that my_field refers to a model with the fields `x`, `y` and `value`. If your fields are named differently, pass the correct names as attributes:: - + + + + + + + + You can pass the following parameters: @@ -115,7 +122,14 @@ the field in the default function:: Now in our wizard, we can use:: - + + + + + + + + Note that all values in the matrix must exist, so you need to create them previously if not present, but you can control visually the editability of From ea175bd2eb3b15299cc574c26d1aed3e78d24d92 Mon Sep 17 00:00:00 2001 From: Richard deMeester Date: Thu, 31 Aug 2017 01:03:06 +1000 Subject: [PATCH 041/153] [FIX] web_widget_x2many_2d_matrix: fixes (#712) * Patches to make module operational. * Minor fix to Readonly Switch * Fix to render to set change attribute. * Totals recompute. Fixes #697 --- web_widget_x2many_2d_matrix/__manifest__.py | 2 +- web_widget_x2many_2d_matrix/i18n/lt.po | 27 +++++++++++++++++++ web_widget_x2many_2d_matrix/i18n/nl_NL.po | 27 +++++++++++++++++++ .../src/js/web_widget_x2many_2d_matrix.js | 8 ++++-- .../src/xml/web_widget_x2many_2d_matrix.xml | 4 +-- 5 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 web_widget_x2many_2d_matrix/i18n/lt.po create mode 100644 web_widget_x2many_2d_matrix/i18n/nl_NL.po diff --git a/web_widget_x2many_2d_matrix/__manifest__.py b/web_widget_x2many_2d_matrix/__manifest__.py index b4651c0741ff..69b703cd5678 100644 --- a/web_widget_x2many_2d_matrix/__manifest__.py +++ b/web_widget_x2many_2d_matrix/__manifest__.py @@ -5,7 +5,7 @@ { "name": "2D matrix for x2many fields", - "version": "10.0.1.0.0", + "version": "10.0.1.0.1", "author": "Therp BV, " "Tecnativa," "Odoo Community Association (OCA)", diff --git a/web_widget_x2many_2d_matrix/i18n/lt.po b/web_widget_x2many_2d_matrix/i18n/lt.po new file mode 100644 index 000000000000..d9620d989bed --- /dev/null +++ b/web_widget_x2many_2d_matrix/i18n/lt.po @@ -0,0 +1,27 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_widget_x2many_2d_matrix +# +# Translators: +# Viktoras Norkus , 2018 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-01-25 01:58+0000\n" +"PO-Revision-Date: 2018-01-25 01:58+0000\n" +"Last-Translator: Viktoras Norkus , 2018\n" +"Language-Team: Lithuanian (https://www.transifex.com/oca/teams/23907/lt/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: lt\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + +#. module: web_widget_x2many_2d_matrix +#. openerp-web +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:11 +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:28 +#, python-format +msgid "Total" +msgstr "Suma" diff --git a/web_widget_x2many_2d_matrix/i18n/nl_NL.po b/web_widget_x2many_2d_matrix/i18n/nl_NL.po new file mode 100644 index 000000000000..27efab7f526e --- /dev/null +++ b/web_widget_x2many_2d_matrix/i18n/nl_NL.po @@ -0,0 +1,27 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_widget_x2many_2d_matrix +# +# Translators: +# Peter Hageman , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-01-03 03:50+0000\n" +"PO-Revision-Date: 2018-01-03 03:50+0000\n" +"Last-Translator: Peter Hageman , 2017\n" +"Language-Team: Dutch (Netherlands) (https://www.transifex.com/oca/teams/23907/nl_NL/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: nl_NL\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: web_widget_x2many_2d_matrix +#. openerp-web +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:11 +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:28 +#, python-format +msgid "Total" +msgstr "Totaal" diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index 43fa84bb4998..ec88f1eafd1a 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -131,7 +131,7 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { }) .then(function() { - return self.dataset.read_ids(self.dataset.ids).then(function(rows) + return self.dataset.read_ids(self.dataset.ids, self.fields).then(function(rows) { // setup data structure _.each(rows, function(row) @@ -369,6 +369,7 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { $this.val(this.format_xy_value(value)); this.dataset.write($this.data('id'), data); + this.by_id[$this.data('id')][this.field_value] = value; $this.parent().removeClass('oe_form_invalid'); this.compute_totals(); } @@ -382,7 +383,7 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { effective_readonly_change: function() { this.$el - .find('tbody .read') + .find('tbody .edit') .toggle(!this.get('effective_readonly')); this.$el .find('tbody .read') @@ -404,6 +405,9 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { return $.when(result).then(function() { self.renderElement(); + self.compute_totals(); + self.$el.find('.edit').on( + 'change', self.proxy(self.xy_value_change)); }); }, }); diff --git a/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml b/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml index a1a0d52151ca..b7aaaefe1891 100644 --- a/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml +++ b/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml @@ -16,8 +16,8 @@
@@ -14,9 +14,9 @@
+ - + - - + + From d14334ff2d9435d4776bda579170b4ce16bbbc24 Mon Sep 17 00:00:00 2001 From: Artem Kostyuk Date: Thu, 15 Feb 2018 09:45:16 +0200 Subject: [PATCH 042/153] [11][MIG] web_widget_x2many_2d_matrix WIP --- web_widget_x2many_2d_matrix/README.rst | 6 +- web_widget_x2many_2d_matrix/__init__.py | 1 - web_widget_x2many_2d_matrix/__manifest__.py | 7 +- web_widget_x2many_2d_matrix/i18n/lt.po | 4 +- web_widget_x2many_2d_matrix/i18n/nl_NL.po | 4 +- .../src/js/web_widget_x2many_2d_matrix.js | 65 ++++++++++++------- 6 files changed, 51 insertions(+), 36 deletions(-) diff --git a/web_widget_x2many_2d_matrix/README.rst b/web_widget_x2many_2d_matrix/README.rst index d81d100cca64..6fb555b94582 100644 --- a/web_widget_x2many_2d_matrix/README.rst +++ b/web_widget_x2many_2d_matrix/README.rst @@ -80,7 +80,7 @@ field_att_ .. 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/8.0 + :target: https://runbot.odoo-community.org/runbot/162/11.0 Example ======= @@ -104,6 +104,7 @@ the field in the default function:: users = self.env['res.users'].browse([1, 2, 3]) return [ (0, 0, { + 'name': 'Sample task name', 'project_id': p.id, 'user_id': u.id, 'planned_hours': 0, @@ -158,7 +159,7 @@ 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. +help us smash it by providing a detailed and welcomed feedback. Credits ======= @@ -168,6 +169,7 @@ Contributors * Holger Brunn * Pedro M. Baeza +* Artem Kostyuk Maintainer ---------- diff --git a/web_widget_x2many_2d_matrix/__init__.py b/web_widget_x2many_2d_matrix/__init__.py index faef9dac007f..919541c6cab7 100644 --- a/web_widget_x2many_2d_matrix/__init__.py +++ b/web_widget_x2many_2d_matrix/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ############################################################################## # # OpenERP, Open Source Management Solution diff --git a/web_widget_x2many_2d_matrix/__manifest__.py b/web_widget_x2many_2d_matrix/__manifest__.py index 69b703cd5678..41f69a75ebb0 100644 --- a/web_widget_x2many_2d_matrix/__manifest__.py +++ b/web_widget_x2many_2d_matrix/__manifest__.py @@ -1,14 +1,13 @@ -# -*- coding: utf-8 -*- # Copyright 2015 Holger Brunn # Copyright 2016 Pedro M. Baeza # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - { "name": "2D matrix for x2many fields", - "version": "10.0.1.0.1", + "version": "11.0.1.0.0", "author": "Therp BV, " - "Tecnativa," + "Tecnativa, " "Odoo Community Association (OCA)", + "website": "https://github.com/OCA/web", "license": "AGPL-3", "category": "Hidden/Dependency", "summary": "Show list fields as a matrix", diff --git a/web_widget_x2many_2d_matrix/i18n/lt.po b/web_widget_x2many_2d_matrix/i18n/lt.po index d9620d989bed..57a65fc55590 100644 --- a/web_widget_x2many_2d_matrix/i18n/lt.po +++ b/web_widget_x2many_2d_matrix/i18n/lt.po @@ -6,10 +6,10 @@ # Viktoras Norkus , 2018 msgid "" msgstr "" -"Project-Id-Version: Odoo Server 10.0\n" +"Project-Id-Version: Odoo Server 11.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-01-25 01:58+0000\n" -"PO-Revision-Date: 2018-01-25 01:58+0000\n" +"PO-Revision-Date: 2018-02-15 12:40+0200\n" "Last-Translator: Viktoras Norkus , 2018\n" "Language-Team: Lithuanian (https://www.transifex.com/oca/teams/23907/lt/)\n" "MIME-Version: 1.0\n" diff --git a/web_widget_x2many_2d_matrix/i18n/nl_NL.po b/web_widget_x2many_2d_matrix/i18n/nl_NL.po index 27efab7f526e..e1fde063c22b 100644 --- a/web_widget_x2many_2d_matrix/i18n/nl_NL.po +++ b/web_widget_x2many_2d_matrix/i18n/nl_NL.po @@ -6,10 +6,10 @@ # Peter Hageman , 2017 msgid "" msgstr "" -"Project-Id-Version: Odoo Server 10.0\n" +"Project-Id-Version: Odoo Server 11.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-01-03 03:50+0000\n" -"PO-Revision-Date: 2018-01-03 03:50+0000\n" +"PO-Revision-Date: 2018-02-15 12:39+0200\n" "Last-Translator: Peter Hageman , 2017\n" "Language-Team: Dutch (Netherlands) (https://www.transifex.com/oca/teams/23907/nl_NL/)\n" "MIME-Version: 1.0\n" diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index ec88f1eafd1a..2c0a0cd92493 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -6,13 +6,15 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { "use strict"; var core = require('web.core'); - var formats = require('web.formats'); - var FieldOne2Many = core.form_widget_registry.get('one2many'); - var Model = require('web.Model'); + var FieldManagerMixin = require('web.FieldManagerMixin'); + var Widget = require('web.Widget'); + var fieldRegistry = require('web.field_registry'); + var widgetRegistry = require('web.widget_registry'); + var widgetOne2many = widgetRegistry.get('one2many'); var data = require('web.data'); var $ = require('jquery'); - var WidgetX2Many2dMatrix = FieldOne2Many.extend({ + var WidgetX2Many2dMatrix = widgetOne2Many.extend(FieldManagerMixin, { template: 'FieldX2Many2dMatrix', widget_class: 'oe_form_field_x2many_2d_matrix', @@ -46,28 +48,39 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { }, // read parameters - init: function(field_manager, node) - { - this.field_x_axis = node.attrs.field_x_axis || this.field_x_axis; - this.field_y_axis = node.attrs.field_y_axis || this.field_y_axis; - this.field_label_x_axis = node.attrs.field_label_x_axis || this.field_x_axis; - this.field_label_y_axis = node.attrs.field_label_y_axis || this.field_y_axis; - this.x_axis_clickable = this.parse_boolean(node.attrs.x_axis_clickable || '1'); - this.y_axis_clickable = this.parse_boolean(node.attrs.y_axis_clickable || '1'); - this.field_value = node.attrs.field_value || this.field_value; - for (var property in node.attrs) { + init: function (parent, fieldname, record, therest) { + var res = this._super(parent, fieldname, record, therest); + FieldManagerMixin.init.call(this); + var node = record.fieldsInfo[therest.viewType][fieldname]; + + this.field_x_axis = node.field_x_axis || this.field_x_axis; + this.field_y_axis = node.field_y_axis || this.field_y_axis; + this.field_label_x_axis = node.field_label_x_axis || this.field_x_axis; + this.field_label_y_axis = node.field_label_y_axis || this.field_y_axis; + this.x_axis_clickable = this.parse_boolean(node.x_axis_clickable || '1'); + this.y_axis_clickable = this.parse_boolean(node.y_axis_clickable || '1'); + this.field_value = node.field_value || this.field_value; + for (var property in node) { if (property.startsWith("field_att_")) { - this.fields_att[property.substring(10)] = node.attrs[property]; + this.fields_att[property.substring(10)] = node[property]; } } - this.field_editability = node.attrs.field_editability || this.field_editability; - this.show_row_totals = this.parse_boolean(node.attrs.show_row_totals || '1'); - this.show_column_totals = this.parse_boolean(node.attrs.show_column_totals || '1'); - return this._super(field_manager, node); + this.field_editability = node.field_editability || this.field_editability; + this.show_row_totals = this.parse_boolean(node.show_row_totals || '1'); + this.show_column_totals = this.parse_boolean(node.show_column_totals || '1'); + this.init_fields(); + // this.set_value(undefined); + + return res; + }, + + init_fields: function() { + return; }, // return a field's value, id in case it's a one2many field get_field_value: function(row, field, many2one_as_name) + // FIXME looks silly { if(this.fields[field].type == 'many2one' && _.isArray(row[field])) { @@ -262,15 +275,13 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { // parse a value from user input parse_xy_value: function(val) { - return formats.parse_value( - val, {'type': this.fields[this.field_value].type}); + return val; }, // format a value from the database for display format_xy_value: function(val) { - return formats.format_value( - val, {'type': this.fields[this.field_value].type}); + return val; }, // compute totals @@ -412,7 +423,11 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { }, }); - core.form_widget_registry.add('x2many_2d_matrix', WidgetX2Many2dMatrix); + fieldRegistry.add( + 'x2many_2d_matrix', WidgetX2Many2dMatrix + ); - return WidgetX2Many2dMatrix; + return { + WidgetX2Many2dMatrix: WidgetX2Many2dMatrix + }; }); From c9bdcab80a2e49dc4fa90cc95e4d1762f918620f Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Mon, 19 Feb 2018 17:48:06 +0100 Subject: [PATCH 043/153] [MIG+REF][11] web_widget_x2many_2d_matrix The widget has been completely refactored to benefit from the new MVC paradigm introduced in v11. --- web_widget_x2many_2d_matrix/README.rst | 53 +-- web_widget_x2many_2d_matrix/__manifest__.py | 7 +- .../static/description/icon.png | Bin 5139 -> 2477 bytes .../static/description/screenshot.png | Bin 19577 -> 22639 bytes .../src/css/web_widget_x2many_2d_matrix.css | 9 +- .../static/src/js/2d_matrix_renderer.js | 416 +++++++++++++++++ .../src/js/web_widget_x2many_2d_matrix.js | 433 ------------------ .../static/src/js/widget_x2many_2d_matrix.js | 172 +++++++ .../src/xml/web_widget_x2many_2d_matrix.xml | 36 -- .../views/{templates.xml => assets.xml} | 3 +- 10 files changed, 614 insertions(+), 515 deletions(-) create mode 100644 web_widget_x2many_2d_matrix/static/src/js/2d_matrix_renderer.js delete mode 100644 web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js create mode 100644 web_widget_x2many_2d_matrix/static/src/js/widget_x2many_2d_matrix.js delete mode 100644 web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml rename web_widget_x2many_2d_matrix/views/{templates.xml => assets.xml} (72%) diff --git a/web_widget_x2many_2d_matrix/README.rst b/web_widget_x2many_2d_matrix/README.rst index 6fb555b94582..52eb81b1336f 100644 --- a/web_widget_x2many_2d_matrix/README.rst +++ b/web_widget_x2many_2d_matrix/README.rst @@ -9,12 +9,13 @@ This module allows to show an x2many field with 3-tuples ($x_value, $y_value, $value) in a table -========= =========== =========== -\ $x_value1 $x_value2 -========= =========== =========== -$y_value1 $value(1/1) $value(2/1) -$y_value2 $value(1/2) $value(2/2) -========= =========== =========== ++-----------+-------------+-------------+ +| | $x_value1 | $x_value2 | ++===========+=============+=============+ +| $y_value1 | $value(1/1) | $value(2/1) | ++-----------+-------------+-------------+ +| $y_value2 | $value(1/2) | $value(2/2) | ++-----------+-------------+-------------+ where `value(n/n)` is editable. @@ -59,12 +60,6 @@ field_label_x_axis Use another field to display in the table header field_label_y_axis Use another field to display in the table header -x_axis_clickable - It indicates if the X axis allows to be clicked for navigating to the field - (if it's a many2one field). True by default -y_axis_clickable - It indicates if the Y axis allows to be clicked for navigating to the field - (if it's a many2one field). True by default field_value Show this field as value show_row_totals @@ -73,10 +68,6 @@ show_row_totals show_column_totals If field_value is a numeric field, it indicates if you want to calculate column totals. True by default -field_att_ - Declare as many options prefixed with this string as you need for binding - a field value with an HTML node attribute (disabled, class, style...) - called as the `` passed in the option. .. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas :alt: Try me on Runbot @@ -92,7 +83,7 @@ data model and point to it from our wizard. The crucial part is that we fill the field in the default function:: from odoo import fields, models - + class MyWizard(models.TransientModel): _name = 'my.wizard' @@ -105,8 +96,8 @@ the field in the default function:: return [ (0, 0, { 'name': 'Sample task name', - 'project_id': p.id, - 'user_id': u.id, + 'project_id': p.id, + 'user_id': u.id, 'planned_hours': 0, 'message_needaction': False, 'date_deadline': fields.Date.today(), @@ -132,26 +123,17 @@ Now in our wizard, we can use:: -Note that all values in the matrix must exist, so you need to create them -previously if not present, but you can control visually the editability of -the fields in the matrix through `field_att_disabled` option with a control -field. Known issues / Roadmap ====================== -* It would be worth trying to instantiate the proper field widget and let it render the input -* Let the widget deal with the missing values of the full Cartesian product, - instead of being forced to pre-fill all the possible values. -* If you pass values with an onchange, you need to overwrite the model's method - `onchange` for making the widget work:: +* Support extra attributes on each field cell via `field_extra_attrs` param. + We could set a cell as not editable, required or readonly for instance. + The `readonly` case will also give the ability + to click on m2o to open related records. + +* Support limit total records in the matrix. Ref: https://github.com/OCA/web/issues/901 - @api.multi - def onchange(self, values, field_name, field_onchange): - if "one2many_field" in field_onchange: - for sub in []: - field_onchange.setdefault("one2many_field." + sub, u"") - return super(model, self).onchange(values, field_name, field_onchange) Bug Tracker =========== @@ -170,6 +152,9 @@ Contributors * Holger Brunn * Pedro M. Baeza * Artem Kostyuk +* Simone Orsi +* Timon Tschanz + Maintainer ---------- diff --git a/web_widget_x2many_2d_matrix/__manifest__.py b/web_widget_x2many_2d_matrix/__manifest__.py index 41f69a75ebb0..31fa2d5a97e4 100644 --- a/web_widget_x2many_2d_matrix/__manifest__.py +++ b/web_widget_x2many_2d_matrix/__manifest__.py @@ -1,11 +1,13 @@ # Copyright 2015 Holger Brunn # Copyright 2016 Pedro M. Baeza +# Copyright 2018 Simone Orsi # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). { "name": "2D matrix for x2many fields", "version": "11.0.1.0.0", "author": "Therp BV, " "Tecnativa, " + "Camptocamp, " "Odoo Community Association (OCA)", "website": "https://github.com/OCA/web", "license": "AGPL-3", @@ -15,10 +17,7 @@ 'web', ], "data": [ - 'views/templates.xml', - ], - "qweb": [ - 'static/src/xml/web_widget_x2many_2d_matrix.xml', + 'views/assets.xml', ], "installable": True, } diff --git a/web_widget_x2many_2d_matrix/static/description/icon.png b/web_widget_x2many_2d_matrix/static/description/icon.png index d7cdcec3b4f3db5e2af2745392b116e16a2e40b4..a501fbf835ea6ee937588af1c15a104e6a97bf27 100644 GIT binary patch literal 2477 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 diff --git a/web_widget_x2many_2d_matrix/static/description/screenshot.png b/web_widget_x2many_2d_matrix/static/description/screenshot.png index 47c2a40d62b23f3b5c4b97dbc699f20dc5baa5ba..922e2961351acf706cef3946526403daa48108ee 100644 GIT binary patch literal 22639 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

q$zofJg&#hQSr2BbXSC%$N*Ni65_QQ{ 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 tbody > tr > td.oe_list_field_cell -{ - white-space: normal; +.o_field_x2many_2d_matrix .row-total { + font-weight: bold; } diff --git a/web_widget_x2many_2d_matrix/static/src/js/2d_matrix_renderer.js b/web_widget_x2many_2d_matrix/static/src/js/2d_matrix_renderer.js new file mode 100644 index 000000000000..898ac0d58133 --- /dev/null +++ b/web_widget_x2many_2d_matrix/static/src/js/2d_matrix_renderer.js @@ -0,0 +1,416 @@ +/* Copyright 2018 Simone Orsi + * License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). */ + +odoo.define('web_widget_x2many_2d_matrix.X2Many2dMatrixRenderer', function (require) { + "use strict"; + + // heavily inspired by Odoo's `ListRenderer` + var BasicRenderer = require('web.BasicRenderer'); + var config = require('web.config'); + var field_utils = require('web.field_utils'); + var utils = require('web.utils'); + var FIELD_CLASSES = { + // copied from ListRenderer + float: 'o_list_number', + integer: 'o_list_number', + monetary: 'o_list_number', + text: 'o_list_text', + }; + + var X2Many2dMatrixRenderer = BasicRenderer.extend({ + + init: function (parent, state, params) { + this._super.apply(this, arguments); + this.editable = params.editable; + this.columns = params.matrix_data.columns; + this.rows = params.matrix_data.rows; + this.matrix_data = params.matrix_data; + }, + /** + * Main render function for the matrix widget. It is rendered as a table. For now, + * this method does not wait for the field widgets to be ready. + * + * @override + * @private + * returns {Deferred} this deferred is resolved immediately + */ + _renderView: function () { + var self = this; + + this.$el + .removeClass('table-responsive') + .empty(); + + var $table = $('').addClass('o_list_view table table-condensed table-striped'); + this.$el + .addClass('table-responsive') + .append($table); + + this._computeColumnAggregates(); + this._computeRowAggregates(); + + $table + .append(this._renderHeader()) + .append(this._renderBody()); + if (self.matrix_data.show_column_totals) { + $table.append(this._renderFooter()); + } + return this._super(); + }, + /** + * Render the table body. Looks for the table body and renders the rows in it. + * Also it sets the tabindex on every input element. + * + * @private + * return {jQueryElement} The table body element that was just filled. + */ + _renderBody: function () { + var $body = $('').append(this._renderRows()); + _.each($body.find('input'), function (td, i) { + $(td).attr('tabindex', i); + }); + return $body; + }, + /** + * Render the table head of our matrix. Looks for the first table head + * and inserts the header into it. + * + * @private + * @return {jQueryElement} The thead element that was inserted into. + */ + _renderHeader: function () { + var $tr = $('').append('').append($tr); + }, + /** + * Render a single header cell. Creates a th and adds the description as text. + * + * @private + * @param {jQueryElement} node + * @returns {jQueryElement} the created . + * If aggregate is set on the row it also will generate the aggregate cell. + * + * @private + * @param {Object} row: The row that will be rendered. + * @returns {jQueryElement} the element that has been rendered. + */ + _renderRow: function (row) { + var self = this; + var $tr = $('', {class: 'o_data_row'}); + $tr = $tr.append(self._renderLabelCell(row.data[0])); + var $cells = _.map(this.columns, function (node, index) { + var record = row.data[index]; + // make the widget use our field value for each cell + node.attrs.name = self.matrix_data.field_value; + return self._renderBodyCell(record, node, index, {mode:''}); + }); + $tr = $tr.append($cells); + if (row.aggregate) { + $tr.append(self._renderAggregateRowCell(row)); + } + return $tr; + }, + /** + * Renders the label for a specific row. + * + * @private + * @params {Object} record: Contains the information about the record. + * @params {jQueryElement} the cell that was rendered. + */ + _renderLabelCell: function(record) { + var $td = $('').append($('').append('
'); + $tr= $tr.append(_.map(this.columns, this._renderHeaderCell.bind(this))); + if (this.matrix_data.show_row_totals) { + $tr.append($('', {class: 'total'})); + } + return $('
node. + */ + _renderHeaderCell: function (node) { + var name = node.attrs.name; + var field = this.state.fields[name]; + var $th = $(''); + if (!field) { + return $th; + } + var description; + if (node.attrs.widget) { + description = this.state.fieldsInfo.list[name].Widget.prototype.description; + } + if (description === undefined) { + description = node.attrs.string || field.string; + } + $th.text(description).data('name', name); + + if (field.type === 'float' || field.type === 'integer' || field.type === 'monetary') { + $th.addClass('text-right'); + } + + if (config.debug) { + var fieldDescr = { + field: field, + name: name, + string: description || name, + record: this.state, + attrs: node.attrs, + }; + this._addFieldTooltip(fieldDescr, $th); + } + return $th; + }, + /** + * Proxy call to function rendering single row. + * + * @private + * @returns {String} a string with the generated html. + * + */ + + _renderRows: function () { + return _.map(this.rows, this._renderRow.bind(this)); + }, + /** + * Render a single row with all its columns. Renders all the cells and then wraps them with a
'); + var value = record.data[this.matrix_data.field_y_axis]; + if (value.type == 'record') { + // we have a related record + value = value.data.display_name; + } + // get 1st column filled w/ Y label + $td.text(value); + return $td; + }, + /** + * Create a cell and fill it with the aggregate value. + * + * @private + * @param {Object} row: the row object to aggregate. + * @returns {jQueryElement} The rendered cell. + */ + _renderAggregateRowCell: function (row) { + var $cell = $('', {class: 'row-total text-right'}); + this._apply_aggregate_value($cell, row.aggregate); + return $cell; + }, + /** + * Render a single body Cell. + * Gets the field and renders the widget. We force the edit mode, since + * we always want the widget to be editable. + * + * @private + * @param {Object} record: Contains the data for this cell + * @param {jQueryElement} node: The HTML of the field. + * @param {int} colIndex: The index of the current column. + * @param {Object} options: The obtions used for the widget + * @returns {jQueryElement} the rendered cell. + */ + _renderBodyCell: function (record, node, colIndex, options) { + var tdClassName = 'o_data_cell'; + if (node.tag === 'button') { + tdClassName += ' o_list_button'; + } else if (node.tag === 'field') { + var typeClass = FIELD_CLASSES[this.state.fields[node.attrs.name].type]; + if (typeClass) { + tdClassName += (' ' + typeClass); + } + if (node.attrs.widget) { + tdClassName += (' o_' + node.attrs.widget + '_cell'); + } + } + // TODO roadmap: here we should collect possible extra params + // the user might want to attach to each single cell. + var $td = $('', { + 'class': tdClassName, + 'data-form-id': record.id, + 'data-id': record.data.id, + }); + // We register modifiers on the element so that it gets the correct + // modifiers classes (for styling) + var modifiers = this._registerModifiers(node, record, $td, _.pick(options, 'mode')); + // If the invisible modifiers is true, the element is left empty. + // Indeed, if the modifiers was to change the whole cell would be + // rerendered anyway. + if (modifiers.invisible && !(options && options.renderInvisible)) { + return $td; + } + options.mode = 'edit'; // enforce edit mode + var widget = this._renderFieldWidget(node, record, _.pick(options, 'mode')); + this._handleAttributes(widget.$el, node); + return $td.append(widget.$el); + }, + /** + * Wraps the column aggregate with a tfoot element + * + * @private + * @returns {jQueryElement} The footer element with the cells in it. + */ + _renderFooter: function () { + var $cells = this._renderAggregateColCells(); + if ($cells) { + return $('
').append($cells)); + } + return; + }, + /** + * Render the Aggregate cells for the column. + * + * @private + * @returns {List} the rendered cells + */ + _renderAggregateColCells: function () { + var self = this; + return _.map(this.columns, function (column, index) { + var $cell = $('', {class: 'col-total text-right'}); + if (column.aggregate) { + self._apply_aggregate_value($cell, column.aggregate); + } + return $cell; + }); + }, + /** + * Compute the column aggregates. + * This function is called everytime the value is changed. + * + * @private + */ + _computeColumnAggregates: function () { + if (!this.matrix_data.show_column_totals) { + return; + } + var self = this, + fname = this.matrix_data.field_value, + field = this.state.fields[fname]; + if (!field) { return; } + var type = field.type; + if (type !== 'integer' && type !== 'float' && type !== 'monetary') { + return; + } + _.each(self.columns, function (column, index) { + column.aggregate = { + fname: fname, + ftype: type, + // TODO: translate + help: 'Sum', + value: 0 + }; + _.each(self.rows, function (row) { + // var record = _.findWhere(self.state.data, {id: col.data.id}); + column.aggregate.value += row.data[index].data[fname]; + }); + }); + }, + /** + * Compute the row aggregates. + * This function is called everytime the value is changed. + * + * @private + */ + _computeRowAggregates: function () { + if (!this.matrix_data.show_row_totals) { + return; + } + var self = this, + fname = this.matrix_data.field_value, + field = this.state.fields[fname]; + if (!field) { return; } + var type = field.type; + if (type !== 'integer' && type !== 'float' && type !== 'monetary') { + return; + } + _.each(self.rows, function (row) { + row.aggregate = { + fname: fname, + ftype: type, + // TODO: translate + help: 'Sum', + value: 0 + }; + _.each(row.data, function (col) { + row.aggregate.value += col.data[fname]; + }); + }); + }, + /** + * Takes the given Value, formats it and adds it to the given cell. + * + * @private + * @param {jQueryElement} $cell: The Cell where the aggregate should be added. + * @param {Object} aggregate: The object which contains the information about the aggregate value + */ + _apply_aggregate_value: function ($cell, aggregate) { + var field = this.state.fields[aggregate.fname], + formatter = field_utils.format[field.type]; + var formattedValue = formatter(aggregate.value, field, {escape: true, }); + $cell.addClass('total').attr('title', aggregate.help).html(formattedValue); + }, + /** + * Check if the change was successful and then update the grid. + * This function is required on relational fields. + * + * @params {Object} state: Contains the current state of the field & all the data + * @params {String} id: the id of the updated object. + * @params {Array} fields: The fields we have in the view. + * @params {Object} ev: The event object. + * @returns {Deferred} The deferred object thats gonna be resolved when the change is made. + */ + confirmUpdate: function (state, id, fields, ev) { + var self = this; + this.state = state; + return this.confirmChange(state, id, fields, ev).then(function () { + self._refresh(id); + }); + }, + /** + * Refresh our grid. + * + * @private + */ + _refresh: function (id) { + this._updateRow(id); + this._refreshColTotals(); + this._refreshRowTotals(); + }, + /** + *Update row data in our internal rows. + * + * @params {String} id: The id of the row that needs to be updated. + */ + _updateRow: function (id) { + var self = this, + record = _.findWhere(self.state.data, {id: id}); + _.each(self.rows, function(row) { + _.each(row.data, function(col, i) { + if (col.id == id) { + row.data[i] = record; + } + }); + }); + }, + /** + * Update the row total. + */ + _refreshColTotals: function () { + this._computeColumnAggregates(); + this.$('tfoot').replaceWith(this._renderFooter()); + }, + /** + * Update the column total. + */ + _refreshRowTotals: function () { + var self = this; + this._computeRowAggregates(); + var $rows = self.$el.find('tr.o_data_row'); + _.each(self.rows, function(row, i) { + if (row.aggregate) { + $($rows[i]).find('.row-total') + .replaceWith(self._renderAggregateRowCell(row)); + } + }); + }, + /* + x2m fields expect this + */ + getEditableRecordID: function (){ return false;} + + }); + + return X2Many2dMatrixRenderer; +}); diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js deleted file mode 100644 index 2c0a0cd92493..000000000000 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ /dev/null @@ -1,433 +0,0 @@ -/* Copyright 2015 Holger Brunn - * Copyright 2016 Pedro M. Baeza - * License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). */ - -odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { - "use strict"; - - var core = require('web.core'); - var FieldManagerMixin = require('web.FieldManagerMixin'); - var Widget = require('web.Widget'); - var fieldRegistry = require('web.field_registry'); - var widgetRegistry = require('web.widget_registry'); - var widgetOne2many = widgetRegistry.get('one2many'); - var data = require('web.data'); - var $ = require('jquery'); - - var WidgetX2Many2dMatrix = widgetOne2Many.extend(FieldManagerMixin, { - template: 'FieldX2Many2dMatrix', - widget_class: 'oe_form_field_x2many_2d_matrix', - - // those will be filled with rows from the dataset - by_x_axis: {}, - by_y_axis: {}, - by_id: {}, - // configuration values - field_x_axis: 'x', - field_label_x_axis: 'x', - field_y_axis: 'y', - field_label_y_axis: 'y', - field_value: 'value', - x_axis_clickable: true, - y_axis_clickable: true, - // information about our datatype - is_numeric: false, - show_row_totals: true, - show_column_totals: true, - // this will be filled with the model's fields_get - fields: {}, - // Store fields used to fill HTML attributes - fields_att: {}, - - parse_boolean: function(val) - { - if (val.toLowerCase() === 'true' || val === '1') { - return true; - } - return false; - }, - - // read parameters - init: function (parent, fieldname, record, therest) { - var res = this._super(parent, fieldname, record, therest); - FieldManagerMixin.init.call(this); - var node = record.fieldsInfo[therest.viewType][fieldname]; - - this.field_x_axis = node.field_x_axis || this.field_x_axis; - this.field_y_axis = node.field_y_axis || this.field_y_axis; - this.field_label_x_axis = node.field_label_x_axis || this.field_x_axis; - this.field_label_y_axis = node.field_label_y_axis || this.field_y_axis; - this.x_axis_clickable = this.parse_boolean(node.x_axis_clickable || '1'); - this.y_axis_clickable = this.parse_boolean(node.y_axis_clickable || '1'); - this.field_value = node.field_value || this.field_value; - for (var property in node) { - if (property.startsWith("field_att_")) { - this.fields_att[property.substring(10)] = node[property]; - } - } - this.field_editability = node.field_editability || this.field_editability; - this.show_row_totals = this.parse_boolean(node.show_row_totals || '1'); - this.show_column_totals = this.parse_boolean(node.show_column_totals || '1'); - this.init_fields(); - // this.set_value(undefined); - - return res; - }, - - init_fields: function() { - return; - }, - - // return a field's value, id in case it's a one2many field - get_field_value: function(row, field, many2one_as_name) - // FIXME looks silly - { - if(this.fields[field].type == 'many2one' && _.isArray(row[field])) - { - if(many2one_as_name) - { - return row[field][1]; - } - else - { - return row[field][0]; - } - } - return row[field]; - }, - - // setup our datastructure for simple access in the template - set_value: function(value_) - { - var self = this, - result = this._super(value_); - - self.by_x_axis = {}; - self.by_y_axis = {}; - self.by_id = {}; - - return $.when(result).then(function() - { - return self.dataset._model.call('fields_get').then(function(fields) - { - self.fields = fields; - self.is_numeric = fields[self.field_value].type == 'float'; - self.show_row_totals &= self.is_numeric; - self.show_column_totals &= self.is_numeric; - }) - // if there are cached writes on the parent dataset, read below - // only returns the written data, which is not enough to properly - // set up our data structure. Read those ids here and patch the - // cache - .then(function() - { - var ids_written = _.map( - self.dataset.to_write, function(x) { return x.id }); - if(!ids_written.length) - { - return; - } - return (new data.Query(self.dataset._model)) - .filter([['id', 'in', ids_written]]) - .all() - .then(function(rows) - { - _.each(rows, function(row) - { - var cache = _.find( - self.dataset.cache, - function(x) { return x.id == row.id } - ); - _.extend(cache.values, row, _.clone(cache.values)); - }) - }) - }) - .then(function() - { - return self.dataset.read_ids(self.dataset.ids, self.fields).then(function(rows) - { - // setup data structure - _.each(rows, function(row) - { - self.add_xy_row(row); - }); - if(self.is_started && !self.no_rerender) - { - self.renderElement(); - self.compute_totals(); - self.setup_many2one_axes(); - self.$el.find('.edit').on( - 'change', self.proxy(self.xy_value_change)); - self.effective_readonly_change(); - } - }); - }); - }); - }, - - // do whatever needed to setup internal data structure - add_xy_row: function(row) - { - var x = this.get_field_value(row, this.field_x_axis), - y = this.get_field_value(row, this.field_y_axis); - // row is a *copy* of a row in dataset.cache, fetch - // a reference to this row in order to have the - // internal data structure point to the same data - // the dataset manipulates - _.every(this.dataset.cache, function(cached_row) - { - if(cached_row.id == row.id) - { - row = cached_row.values; - // new rows don't have that - row.id = cached_row.id; - return false; - } - return true; - }); - this.by_x_axis[x] = this.by_x_axis[x] || {}; - this.by_y_axis[y] = this.by_y_axis[y] || {}; - this.by_x_axis[x][y] = row; - this.by_y_axis[y][x] = row; - this.by_id[row.id] = row; - }, - - // get x axis values in the correct order - get_x_axis_values: function() - { - return _.keys(this.by_x_axis); - }, - - // get y axis values in the correct order - get_y_axis_values: function() - { - return _.keys(this.by_y_axis); - }, - - // get the label for a value on the x axis - get_x_axis_label: function(x) - { - return this.get_field_value( - _.first(_.values(this.by_x_axis[x])), - this.field_label_x_axis, true); - }, - - // get the label for a value on the y axis - get_y_axis_label: function(y) - { - return this.get_field_value( - _.first(_.values(this.by_y_axis[y])), - this.field_label_y_axis, true); - }, - - // return the class(es) the inputs should have - get_xy_value_class: function() - { - var classes = 'oe_form_field oe_form_required'; - if(this.is_numeric) - { - classes += ' oe_form_field_float'; - } - return classes; - }, - - // return row id of a coordinate - get_xy_id: function(x, y) - { - return this.by_x_axis[x][y]['id']; - }, - - get_xy_att: function(x, y) - { - var vals = {}; - for (var att in this.fields_att) { - var val = this.get_field_value( - this.by_x_axis[x][y], this.fields_att[att]); - // Discard empty values - if (val) { - vals[att] = val; - } - } - return vals; - }, - - // return the value of a coordinate - get_xy_value: function(x, y) - { - return this.get_field_value( - this.by_x_axis[x][y], this.field_value); - }, - - // validate a value - validate_xy_value: function(val) - { - try - { - this.parse_xy_value(val); - } - catch(e) - { - return false; - } - return true; - }, - - // parse a value from user input - parse_xy_value: function(val) - { - return val; - }, - - // format a value from the database for display - format_xy_value: function(val) - { - return val; - }, - - // compute totals - compute_totals: function() - { - var self = this, - grand_total = 0, - totals_x = {}, - totals_y = {}, - rows = this.by_id, - deferred = $.Deferred(); - _.each(rows, function(row) - { - var key_x = self.get_field_value(row, self.field_x_axis), - key_y = self.get_field_value(row, self.field_y_axis); - totals_x[key_x] = (totals_x[key_x] || 0) + self.get_field_value(row, self.field_value); - totals_y[key_y] = (totals_y[key_y] || 0) + self.get_field_value(row, self.field_value); - grand_total += self.get_field_value(row, self.field_value); - }); - _.each(totals_y, function(total, y) - { - self.$el.find( - _.str.sprintf('td.row_total[data-y="%s"]', y)).text( - self.format_xy_value(total)); - }); - _.each(totals_x, function(total, x) - { - self.$el.find( - _.str.sprintf('td.column_total[data-x="%s"]', x)).text( - self.format_xy_value(total)); - }); - self.$el.find('.grand_total').text( - self.format_xy_value(grand_total)) - deferred.resolve({ - totals_x: totals_x, - totals_y: totals_y, - grand_total: grand_total, - rows: rows, - }); - return deferred; - }, - - setup_many2one_axes: function() - { - if(this.fields[this.field_x_axis].type == 'many2one' && this.x_axis_clickable) - { - this.$el.find('th[data-x]').addClass('oe_link') - .click(_.partial( - this.proxy(this.many2one_axis_click), - this.field_x_axis, 'x')); - } - if(this.fields[this.field_y_axis].type == 'many2one' && this.y_axis_clickable) - { - this.$el.find('tr[data-y] th').addClass('oe_link') - .click(_.partial( - this.proxy(this.many2one_axis_click), - this.field_y_axis, 'y')); - } - }, - - many2one_axis_click: function(field, id_attribute, e) - { - this.do_action({ - type: 'ir.actions.act_window', - name: this.fields[field].string, - res_model: this.fields[field].relation, - res_id: $(e.currentTarget).data(id_attribute), - views: [[false, 'form']], - target: 'current', - }) - }, - - start: function() - { - var self = this; - this.$el.find('.edit').on( - 'change', self.proxy(this.xy_value_change)); - this.compute_totals(); - this.setup_many2one_axes(); - this.on("change:effective_readonly", - this, this.proxy(this.effective_readonly_change)); - this.effective_readonly_change(); - return this._super(); - }, - - xy_value_change: function(e) - { - var $this = $(e.currentTarget), - val = $this.val(); - if(this.validate_xy_value(val)) - { - var data = {}, value = this.parse_xy_value(val); - data[this.field_value] = value; - - $this.siblings('.read').text(this.format_xy_value(value)); - $this.val(this.format_xy_value(value)); - - this.dataset.write($this.data('id'), data); - this.by_id[$this.data('id')][this.field_value] = value; - $this.parent().removeClass('oe_form_invalid'); - this.compute_totals(); - } - else - { - $this.parent().addClass('oe_form_invalid'); - } - - }, - - effective_readonly_change: function() - { - this.$el - .find('tbody .edit') - .toggle(!this.get('effective_readonly')); - this.$el - .find('tbody .read') - .toggle(this.get('effective_readonly')); - this.$el.find('.edit').first().focus(); - }, - - is_syntax_valid: function() - { - return this.$el.find('.oe_form_invalid').length == 0; - }, - - load_views: function() { - // Needed for removing the initial empty tree view when the widget - // is loaded - var self = this, - result = this._super(); - - return $.when(result).then(function() - { - self.renderElement(); - self.compute_totals(); - self.$el.find('.edit').on( - 'change', self.proxy(self.xy_value_change)); - }); - }, - }); - - fieldRegistry.add( - 'x2many_2d_matrix', WidgetX2Many2dMatrix - ); - - return { - WidgetX2Many2dMatrix: WidgetX2Many2dMatrix - }; -}); diff --git a/web_widget_x2many_2d_matrix/static/src/js/widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/widget_x2many_2d_matrix.js new file mode 100644 index 000000000000..4b1a73f9cd74 --- /dev/null +++ b/web_widget_x2many_2d_matrix/static/src/js/widget_x2many_2d_matrix.js @@ -0,0 +1,172 @@ +/* Copyright 2015 Holger Brunn + * Copyright 2016 Pedro M. Baeza + * Copyright 2018 Simone Orsi + * License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). */ + +odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { + "use strict"; + + var core = require('web.core'); + // var FieldManagerMixin = require('web.FieldManagerMixin'); + var field_registry = require('web.field_registry'); + var relational_fields = require('web.relational_fields'); + var weContext = require('web_editor.context'); + // var Helpers = require('web_widget_x2many_2d_matrix.helpers'); + var AbstractField = require('web.AbstractField'); + var X2Many2dMatrixRenderer = require('web_widget_x2many_2d_matrix.X2Many2dMatrixRenderer'); + + var WidgetX2Many2dMatrix = relational_fields.FieldOne2Many.extend({ + widget_class: 'o_form_field_x2many_2d_matrix', + /** + * Initialize the widget & parameters. + * + * @param {Object} parent: contains the form view. + * @param {String} name: the name of the field. + * @param {Object} record: Contains the information about the database records. + * @param {Object} options: Contains the view options. + */ + init: function (parent, name, record, options) { + this._super(parent, name, record, options); + this.init_params(); + }, + + /** + * Initialize the widget specific parameters. + * Sets the axis and the values. + */ + init_params: function () { + var node = this.attrs; + this.by_x_axis = {}; + this.by_y_axis = {}; + this.field_x_axis = node.field_x_axis || this.field_x_axis; + this.field_y_axis = node.field_y_axis || this.field_y_axis; + this.field_label_x_axis = node.field_label_x_axis || this.field_x_axis; + this.field_label_y_axis = node.field_label_y_axis || this.field_y_axis; + this.x_axis_clickable = this.parse_boolean(node.x_axis_clickable || '1'); + this.y_axis_clickable = this.parse_boolean(node.y_axis_clickable || '1'); + this.field_value = node.field_value || this.field_value; + // TODO: is this really needed? Holger? + for (var property in node) { + if (property.startsWith("field_att_")) { + this.fields_att[property.substring(10)] = node[property]; + } + } + // and this? + this.field_editability = node.field_editability || this.field_editability; + this.show_row_totals = this.parse_boolean(node.show_row_totals || '1'); + this.show_column_totals = this.parse_boolean(node.show_column_totals || '1'); + this.init_matrix(); + }, + /** + * Initializes the Value matrix. + * Puts the values in the grid. If we have related items we use the display name. + */ + init_matrix: function(){ + var self = this, + records = self.recordData[this.name].data; + _.each(records, function(record) { + var x = record.data[self.field_x_axis], + y = record.data[self.field_y_axis]; + if (x.type == 'record') { + // we have a related record + x = x.data.display_name; + } + if (y.type == 'record') { + // we have a related record + y = y.data.display_name; + } + self.by_x_axis[x] = self.by_x_axis[x] || {}; + self.by_y_axis[y] = self.by_y_axis[y] || {}; + self.by_x_axis[x][y] = record; + self.by_y_axis[y][x] = record; + }); + // init columns + self.columns = []; + $.each(self.by_x_axis, function(x){ + self.columns.push(self._make_column(x)); + }); + self.rows = []; + $.each(self.by_y_axis, function(y){ + self.rows.push(self._make_row(y)); + }); + self.matrix_data = { + 'field_value': self.field_value, + 'field_x_axis': self.field_x_axis, + 'field_y_axis': self.field_y_axis, + 'columns': self.columns, + 'rows': self.rows, + 'show_row_totals': self.show_row_totals, + 'show_column_totals': self.show_column_totals + }; + + }, + /** + * Create scaffold for a column. + * + * @params {String} x: The string used as a column title + */ + _make_column: function(x){ + return { + // simulate node parsed on xml arch + 'tag': 'field', + 'attrs': { + 'name': this.field_x_axis, + 'string': x + } + }; + }, + /** + * Create scaffold for a row. + * + * @params {String} x: The string used as a row title + */ + _make_row: function(y){ + var self = this; + // use object so that we can attach more data if needed + var row = {'data': []}; + $.each(self.by_x_axis, function(x) { + row.data.push(self.by_y_axis[y][x]); + }); + return row; + }, + /** + *Parse a String containing a Python bool or 1 and convert it to a proper bool. + * + * @params {String} val: the string to be parsed. + * @returns {Boolean} The parsed boolean. + */ + parse_boolean: function(val) { + if (val.toLowerCase() === 'true' || val === '1') { + return true; + } + return false; + }, + /** + *Create the matrix renderer and add its output to our element + * + * @returns {Deferred} A deferred object to be completed when it finished rendering. + */ + _render: function () { + if (!this.view) { + return this._super(); + } + var arch = this.view.arch, + viewType = 'list'; + this.renderer = new X2Many2dMatrixRenderer(this, this.value, { + arch: arch, + editable: true, + viewType: viewType, + matrix_data: this.matrix_data + }); + this.$el.addClass('o_field_x2many o_field_x2many_2d_matrix'); + return this.renderer.appendTo(this.$el); + } + + }); + + field_registry.add('x2many_2d_matrix', WidgetX2Many2dMatrix); + + return { + WidgetX2Many2dMatrix: WidgetX2Many2dMatrix + }; +}); diff --git a/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml b/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml deleted file mode 100644 index b7aaaefe1891..000000000000 --- a/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml +++ /dev/null @@ -1,36 +0,0 @@ - - -
- - - - - - - - - - - - - - - - - - -
- - - Total
- - - - - -
Total -
-
-
-
diff --git a/web_widget_x2many_2d_matrix/views/templates.xml b/web_widget_x2many_2d_matrix/views/assets.xml similarity index 72% rename from web_widget_x2many_2d_matrix/views/templates.xml rename to web_widget_x2many_2d_matrix/views/assets.xml index 06934cc33dbc..ba820435cb41 100644 --- a/web_widget_x2many_2d_matrix/views/templates.xml +++ b/web_widget_x2many_2d_matrix/views/assets.xml @@ -3,7 +3,8 @@