From 970cca94f377970b693fc270ee7b00da4c79c330 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Wed, 11 Mar 2015 18:00:21 +0100 Subject: [PATCH 01/95] [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 000000000..27a266b98 --- /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 000000000..faef9dac0 --- /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 000000000..1cbc4aad7 --- /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 000000000..e29367a01 --- /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 000000000..06934cc33 --- /dev/null +++ b/web_widget_x2many_2d_matrix/views/templates.xml @@ -0,0 +1,11 @@ + + + + + + From 900d12e019c740bf83b28a353178ede4e551ab74 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Wed, 11 Mar 2015 18:05:40 +0100 Subject: [PATCH 02/95] [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 27a266b98..6be504c44 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 d946e72e0abad974437cd05bc15a0024788d44ab Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Thu, 12 Mar 2015 12:22:37 +0100 Subject: [PATCH 03/95] [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 e29367a01..fe3f82d31 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 fcafa6268f8cbc0068287aa6009f3141a5cfeee9 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Thu, 12 Mar 2015 12:27:24 +0100 Subject: [PATCH 04/95] [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 3a754f426..b157a6799 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 4a4bf0b68f4bdef2077647b390c071dfb918d6f0 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Thu, 12 Mar 2015 15:27:25 +0100 Subject: [PATCH 05/95] [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 e69de29bb..2992579de 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 b157a6799..f9ebeb25d 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 fe3f82d31..625a2d20f 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 4203e2ab4c8ed67483dc6a615a9e064aeffd36a2 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Thu, 12 Mar 2015 15:44:57 +0100 Subject: [PATCH 06/95] [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 f9ebeb25d..df8b49308 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 625a2d20f..2950439c1 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 82b6dc199b74d4c5862b36f4d1347c953e9bedbc Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Fri, 13 Mar 2015 15:29:09 +0100 Subject: [PATCH 07/95] [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 2992579de..d33d4f21b 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 ea84a081f012283c644beed5840735529b62e7b6 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Fri, 13 Mar 2015 15:34:02 +0100 Subject: [PATCH 08/95] [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 df8b49308..058a682e2 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 2950439c1..4f587e3f0 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 2e2b2453d78d30b109609c0cd8e1fb93751c7fce Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Fri, 13 Mar 2015 17:36:39 +0100 Subject: [PATCH 09/95] [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 058a682e2..9a7b8ca62 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 08285bb319efca47a2471f5ba17946fb1c7ceb19 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Fri, 13 Mar 2015 17:44:41 +0100 Subject: [PATCH 10/95] [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 9a7b8ca62..0425468bf 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 fef7b3c4091570f23995c13967f53c6fbb29d874 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Fri, 13 Mar 2015 18:19:42 +0100 Subject: [PATCH 11/95] [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 12/95] [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 0425468bf..191ffad66 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 1eaf985999cd31c422f462f7d7ac049bf6d7e066 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Fri, 13 Mar 2015 18:47:15 +0100 Subject: [PATCH 13/95] [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 6be504c44..5fb296beb 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 191ffad66..0c818548d 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 0d805deb39a51cc07f93180d1c0997f5e00bc97b Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Mon, 16 Mar 2015 16:35:17 +0100 Subject: [PATCH 14/95] [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 4f587e3f0..952a003f6 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 569def98e248dcd3cb4822444be01b70f6c0708c Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Mon, 16 Mar 2015 16:47:21 +0100 Subject: [PATCH 15/95] [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 0c818548d..12a56c8cb 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 952a003f6..35f1669bc 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 1ad7523065e874f4f6d9d5f093dd00a46b07d3d1 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Mon, 16 Mar 2015 17:20:43 +0100 Subject: [PATCH 16/95] [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 5fb296beb..0b145aaf6 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 17/95] [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 zcmd5?1yfsHw7s|%D^Ms-DaGB3yL$^1_u}rfxVyVsaS!fRG{xN|xVyf5Z{Cl1lQ}ub znPl$G&X%?JT4BnHQtwbdq5=T$PDWZ>6}mV4w~!H`&jRC!Y|stSNKQ%|c>C{>+fkAL zJwdUT)^Y*>wDQvj(x3|kONT7dd zgMyBXi(K6!lf0@>mk}sTzKPx=l3X-7c0K0oHhcBTx_YUZJ?Zy(vpwhW_rIZ^lGAFd zH|1pzkFrBr;eyGZ93P`3{~v7QH57`|Y=nUPo7=};$UWAp-qjn$F$ZnPK}0Q&ufZr$ z?zIm`I`&B(0xGIyR&Ar9JjYuz}v+3O-2PhDLvou1JQP}e*|;9YLDO0 zUe{Ypt8H%IY%Z%GGT)xQDzOWRTz%NnOpaJQxKd;K7lbz!C@W+uR9(*YHJxq&67x`< z<@Hg@iKC=e?ID@Xa99M{S;)7wZe2jXFe#94_APq3Oo#jE?I}#q)a$R$%S&rIhuCh4 zY45QWS{t6{!EYZP9*6T6pcU{Y88V&%&a>!pjw+O^$R5rV1ha_EDYrU*y`@-gJ5kbg z#k^H}LQHb9=R|0|i_4JD<^kJluNlnr%rnjw5&x@=mq@bVd+oQ!Gn}!XU%4IT?omzu z++|I4nn`)U-ABExJ>abov4(~&gKR*JjanxsCv%&9Arg&URyH>88k?H5D=RBYvMgqn zmgtt2mn%Uau2GOpLMYt>Rb=Q`021a8aTDnGUL?}pDc;r_;jWvR;qGnIgr6K0ljfO` z`U30LyZD7Y1htEL5I~O|zeWToU;FdKLPSYONCr!F3R_xuXV%tUi^K9eI&`g<8V(Y( z)#T*R{=4`$3*gh~zm7@LAFw=d-Fa+I+c5yC6s`5$H89_NK~mb)dK)&l$NWa?WD=}? zsOzD{j|QMq)a-A}-j57b9j3obD6KVtUi8h*JZ}9o9=RfEeYiR2UduzAW`iYt3f;+k zF@Wmi4le74#zvHhmQ+5MGPT#o*8rq1-44ZY_f!tAB40Cpc`>E1JGBrK7)w*(gbr>q zOEDY?6(wTsIJW50Yg92@AM_{;rhb(zkR3Q%ay#qDQP1X1f+K5mYU!%AX=BP5^>d2; z$seuBksc;q&}AwspeT+68PB*A&q1`Do0}^;m@XNe*7_H)wVd)}3=wP}3adFzK1PBAhb)PEB(o@=eKx z#&z}K>qj#5s;@U^AM-Ka$~a?GbVfzQk9Sk__snyCx!OrIf*N#Ehj**Mx7YH?;-QiS z0^LqL&cR{o=Jzj1u6~T!ls{N`>!5 zoh`k>;Wk&X+T-H1SP?p~ZAo?XlLwweKJm|G-s`H`dS8ym{d`BqxDvi?T71G9{!qd; z0_5)~Y_m~f0)nHAEFu;y0dZs~y`ngB7*;NMggtV2Ak_4{#;ok9h=@{nhUw3(T2DeR zQF?KWXCJIbbhHMZ3AjY~-=41qm)BoineTrcfo!;!K`*8h@C3$xAy+i`5eAXG;{Bna z4imh_k%_9APF10yrBloz2pN+Z&xRx=#XTre!1)T^9zmjz9Hqa&EcDA5Pxw@j^CU<0Nw@-V4q@J1 zAY2chM7|lxWSz6BTj@0HYl4U9bI8AUWeV{Ytmc7`VyAt6sq4|>zUM~PP3J7d4K(z1 zdUYCQdChZ}q(lO4j~74TGich&MqL%9=;8GIomyFW&Mp{=exWkaLa~pwR>Jg#tQl0O zi$vD0vE8~UiNhD+!o-UAi{)X(3~it5e3-iGY<~@>?_8AfappsN41~4Kc^cPO*m=J; zttJ0Sd(6+zE0ew8ntwqi$zv;MWbKZbF!bv^I*hEJda9P>6l&l1fr&q%?KfM=japp_ zEL@S8$I)L;H&XO}x4`;(aoN3^^f-{#_8OXad2ZlGFa=_vZ|vgf9jpcab0-GA*A*iY z3wLTh&ns2y`@9Es9C=A224+giw$a3|k#xke5(Xnyj8o!hV;d~+muP8a;>VyBpKTkWebwS&^>0}i<&)rNzrwn2yK=Xy1U{4WVA z{!W53sEi-Rl*i*YcdTY>zDWgWHb~*?GB147W{y0MK*CA*hz|fTT z^sKAz)w zgSw*0cqqE#I}OL#zz6+6ETfB)qiL*&g9v=6QY@3iUX#WVO}4bmX9i#7;)e@i zfzlb3We^ghnUJEKEUgqR!Ze3RUGbpw>`ZE7W22ePuMRD3ZL7JN8G=pw)F(rM`7_q1 zDDwMSE2bi6VsL#AByB=wZ*6a1}p#DBA&sgT1p zevX-Q=79%WQD%%QP7{h8p78|IuNR0bAxCz4l1n0^s zRLs%KUKk)?g_&wSW$Z$_If5vHNo0u$baY>1k z<2uLeMtR*j7d&$uoVB^w$>710a*2>6}+*e@!F; z1pdKP?3f`#0z_i47z2)s)b_=jne2-mYx1_ZAxe8?D!7X|V_b0~Qw-V9neQECe5Mm? zDz=oY478|oIfW1SXtz2aG!`)Zd>sDdugjt($mVvW9j@-(!oH3G8|Q0~bcdBRffdS7_mV?AG&OzFdk-7Ni?7*61X_ecM`vqV-FY3VI2Yk8_5 zdw#*&{K|-E!N;9~tptdVqp)_R$K7dLmZ+hrlvD_=C_0TU4y+MM)M7lWX^CtOB_nv_iol7I#@oWIiFa zhD5ooJ;X)Y_x_&DozR0$6{a6_EgNQ_v@xBx$BIX3&-^Kli*FMk_vgZZ`*0> z(V1!Oy(SfLwA-$uz3v?qdr5`4m;!$^`}*UYn?9S((ivUlz(T#-8T*0rqwaxo>mjn* zZTgnzIs0IgSgodaEvR^}px$hpZ))h6mCSRWf;!MQ5$A)RJ4KRG#@%33*V_xy;M}}^ z=CFw}4K5AH(Bj%GLN&1g0e(Q(ykZhvO(xQ=(YT{QP{vs-GQA&%hedK?nE zT^gUwj!_No-DHXQf`b)V`2%FVxK}qw5H4A;Rw(l_gN?mjXByG+>&f*lPKuOPpzmb) zZEzHlLrHmU!q#!5Q6?ZqTU1e9{b5pir!6WfY99JWpVvaOd1mgYnEoC2ij06F1`{uy zLX;kR04~mVV*DoI$RV?a3J?sr&vv8P4m~%f2i_u?UHcq=`LPq`*jlWcOmN#}+F|~! z0%k%QgP{a#!Hwg0+C(XFMB_25wYH|=+LI_~+XRQ|7s}$WC4OBv-8NsPyS$IK*nC)4 zzC)A-Q{UD)_S2^F2rN0*rb>z@np<9uMq`ugt2!c2odAs<>dCLkRXPvP#wRCR`Lt># z9@VaQt8Qu&pv~4QqsWWbe&Q%eEh-$p>-PHe^mM&d%V^T4s zoK>BVmS<6lkVhjkMn*>Ofsv#vtMv4R+SAkZnVA{#B-yl5^^r))0{bF$M2MYMtK;q! zU>`?THwwAx%|ZWxG`PVNEpEZ8!l@P}numoJB8n9`kO4Kuug8OufXR(_vy=V^XeMfE z?@!j7z<}>;`FDkl;0sI2B!5-eZ*&oFA3ss1up_d7@BpUP{rotTW$e)Ej*?**pv_=? zVcVpq$9+JPou1bh(VX8>JQv*x9r&03{lZVGiX{2lcWbwRruqv(H~?y(2(6XC&kV)6 zf2y-lc7_IIuA8--Kd|Y;_;O}s5W_mW`Jn>MZyP;Y@yJ12ysjSAq+pj;NVBQi6@?e3gmcz2U8ED0xo1Gb}F0Izb!3bKrIJh|)sM{TVTWAu5 z^WrjJe#-_!%A5t1MsZ=G z#UD<2R@5_+wVCyhxc`-xx8TA#7#9~2%Z0aScGP)0)zfm zZ~bNs>_4kQP&!nUxSni^a0!g-mc%f79mN9&2M1%|Rf5#AcFS0HneL$73t%vj$-C;b zH?hiHotNivb2tlz>mEs>9~bP%1bknf?z+|;h|85}$Tb-?N~P3gYg=3S`fm~_(00t zZ|Tk#qvg1?&Ulwl@9x1|KU@8McLEEzo~{hDr;X_^gQ6^1)z@v!ZfJL#`Kj(+P?LITZS8mOnjo+Mt+puCe%p!OBRYBK;9x_b=yUFDaosltd^l_pt4yJ{ z48R_m0n6i>Lt305p3u0k9)5lRix*7VK*5}{?C@`5n@t`J$FwmQfW=~}aH|G2UHEyd z+V%n<-(Gr)y}C4b-qHCmmcv7#C@Gza9P$SSIL)Q2{}{%=DcdHc(^bObaSfx+K#Bx;{+mh2jse>}Q>rHA z9Wj$gtF{AOnb3L2*Q8z(z?}xy@z+;Ry*yW#7ImsXC;#$j0QF^)=V18NtDE?Oi_yc)-hBi_I^&$^VI(rtb`=83}OU) z15=absmn8nKDSF#`AhRWyR2u{N)|*8o2UZ9Hf$bHC;<-`=OzV*U$p=%HcK3w*8aDz zx2klrna_cNumyFMpLT7+g^5>=8`{qEbgw|L-KpTn3(0N6&$cyGfOoUU={6M#MIUZB z)wk=5q^)@V9khoSIAIblI^ge~qrI#EQ8~_!_&90!IO!b}VBh`4#i1<1A~?cenyxUD zxWf0y+#z;$dOARwreV?!yvt(83Lwy4Yq9|e3BJ8pIq)8^P8BKFT4QC8%s zB0)ed9$qSmf>4%lMZ7oil^giCZL{0fBbC`fFSn=A8~GeE3}2>Ti}j^gno^%8l+9_! zrr5$iwB2&@9T2R>02}!N4={CHPfhRLN95jpDq5o}5^sD#Qk6ALr_PU)e#q`rDKCHq zj?+5KTd5G5_6;Ep_Oqtvuu}7#p6iG|(;G;h6{!??qhe`E3`d^aY%m)ESfQ3|M9P3j z$aUZ@*b!G`}5eEwK5H&Q7D6SyYlZ&nwd6uz%bonm;#0NkGTMO3OMG1 zNPl2aWP%rk0!xnC=S)gGa+1Ns;3GCf>E?jpF`0cK#C=66R5GM*hv(IFCDuURFZW&s z8v|8G92Q9k(IBzz?>Fm3-It6nMV!*@*6{B|g9wE>1q((3462Td)}9|0r_2?JA_7u) zLtX?@wzYE$j}d*vLNGeC9_rhk=u6H516YISlJTAXVj3w|Mj)B-oD~*bp&xCYurwsu=bQgM$y~7S3h!5yw)JJIL$W@qbO^PE>XI-z1#F zRBGa7*B>rPGUzcIT$*U4E-S%RwUXxj-rbJO+@+uBS_YHeZ~zKeg}+rfQx52Sj&nM` zgB70ND_Hv}m`CWNR+dRT+NI>zP5719dI&&N^hcwryuYYJJBlgWk+1z7ENY>JAG+TG zu(%w~Oz$C|48B@pb<E-{*NDcDmrb z%`b9lU0r$|%!v-+#V30`?n%2k#3sn^Yn6Ap{O9o*)3Z>5da*>|TNdfo^lLT6S#$K^ z(QPKjq_6f1iWYSY34~7UG>{>g9@$dBzsqhoGDKf_YIZso=V=9Q6oAKmqgyNbC|?_^BDYD0tcRlL3+l) zpN$Zi90QSZ8wSW-#7s)grN*%stt%UpBsA^?*_g1Ug@s^{puz>{bFfdrvpQ7G#02EE zS1bF+g@hIw68tTho_~ysQ$Z2xd|EwYilUCbDstyQ9c*FGyuHdr5I8FP0}n{HGoF~h zbRU|BWG!x+#ob(L0NtkJXu7?j<__l5t_lu7?oe%)VVeibH^U;Wv&Z*!b-;thplOndSjtdnhL5lZ`7Dww1NDGfek{iD1IGnUsm)e6Fqk!}L6vW%e%)YD(k? zEM&aBE*NC{@Af37_R&KhPVaF5gUt!-lL68ni?%>_-or8xLO{(bKWmTTnm6E!tF9i1 z&Bf4e{&Xw^*IiZc8It5XuKvTuT)n;^N!1KGG{0h5o2%|;#9QH32H!F@kteoV<=lvmS@HNctmN&zrv`{1QbMwBLNC)Jh;@{qM? z3d>kcgMygff(HptUl0d|Bhn2E$NIB?^MDxJy5tG%9oX*7GDzFak!4qyFd`;@G6x| z|6RC+fNWRObvnIUy!X<~C8_>FQ5sV+gVq*|@Fb`!ZrH>h#%>`&48t=OC5@X6;Q7R{ zXNAZP6je<1@zRC@7!&*lG6);B9?L!$z$kM%r3L(@iqge=+VeWX@~Lqo#Tj2XyPkug z;=v|u1ajz{<~&?Cq$Q$KHI|wV zen+(b)U+bH{<3zrfBW?`_bZp}vrwldEtVm9rq;W62jY|$N8)TKMe3YBK&I20+Q{5n zGTG~SNSpuBs_U|iQR+t%I}*;bg$uZeho@(CM597gEX-l-T)U*m`*eFp-BfLI8okmK z47$|~SZ3EQSb}@&6D!y$!h!VoS&sbRZP zL!m}(d3rZhtdC+v`T1+>dN6IEFqCu{j3#QbznZIb1G@1vbSU(_P8!w93ZOH?Ipll^ zZIE48Hp)edhMq@}Wp)C$#J^*?qR-)Ht84#c76qhrf% zu-#hg#78jQX!<9F@hMNe8jy^xr;<;{tLv?+2)@5kRvzIb^S-`ip1}Lj@PriQzt2t7 zAd=~25q*2Q)82a{2J-d!9IQ0S5kKK;W1)lubcF4LF&J@C6GwN2#dY0QUFLgzPiaK`Va{U;u>$Ou&l65DAzdne!#+gPyO=a<*!CqN+Lr{w&n-GnF|$ zt~Yw!iFHm(yh?Aw3L6`5Cy0g$UC*QB z$F)>ghkY{AMVAyk;55&T%e73LnFVWsP*#>|{|0aqD_Z@&-pXjclH}%ZvaPMZ^adFk zcS~`Y6D!Gd-LF{#QS7Y|x;=|Pg(@NJm9KCGmrlTvz4c0AmK@*G?>XvXpN7X4kMXZ= zf`-eth0c5@Y=dKdQ$K@5DUxN2VjE9vG%B>4`0q24lkxE}LdKyObk> z&*6YkfvVMn- zLBLlAL4EVpnxNw&F^_Dhh9P0<;979HZsI@U3N;AUK~G@vD|YEmjuCJs%Vr;RX!=-v zd0YU~=614_NRO{UafFRZSU9Xq{9U(rQw@bAy#zS25 z^Ro&}xTqrdvucM9Qab;3c{fQj>*E}w-!0SA^dK-_<L_HsiIh z6Gol65k3tJxiakwET7wNXS4J?7 zT&=fKcUr-wR(^P_$(OZPsxE7@*51NO9Cl1hQ|-9UF3vDYM`F6?Q6rj{^ToJU!9?rC z$$X4QgrH4Zdnh$oj_o?W>{&B`MPeE@Sp&Axv@-TRWmzG; zo-rnH$o9C2NgT#d#KG?vN4qCSJMwb1KwvJNicK&kE`lHDf(9h^byr;0qtSmym)!~{ zFwABtNL{Nd=3o$7qb`2i-iXB#vym}qJpAC^KyYdei#=AR*sphwwbBCPQ`~A=j|;mY0P@99WA6ze>mtJg9I)c!+}&aUQ%$s~~>wMIIC_?PCUvuotcI+c}DCZak<9DEGPf z@gv(TBCWH#V)WbXcAd}%pV!?$;rynGFFz_N5E-Mes*fA_c6Pu!~Mc&7Ao)gUahxkf$U(YprBCGs_-pb z>Fy;vm*K%%&&9=scXf3&8{&bLDV9ndD3lL>_j4I^sqv_Vmp%N)UiRuP;%-6>U^(dy z|K-7B1&&nmMXBfRqdHo*hB5ZViX@8E4yM_ya!jJsXu%zuA?#(5J=4(ss!>~Kx=`az zmk5jW)n}x`>b1taN09VwE*7KgIIH33kH*7}>?1*LvoQiDD1H4M$2gKO*wB80QRo1z zrAn^K>-m+y7~gozKL@ushtc56iarf7R|^(Y*+F(?jzm9a8k=vuSZ5mJG`Kf2H`mL8 zzNRhp!ZE@2CDCbu(dQR(qUsJG2CdJmUXB;>n4xSS_J}!~HJU2Z;&KCPIcij69B!{< zDt)Y!Us_L@$83+%!Dg*0SeNf&gj?DFF0?GCq<7(h!$g%f33)(#B;1dDo&Bc!@QuHw zCW&!@-R>{_2!ZiTdwm0j>G%oFw?Jsxuw^!M~vvCJq}X9f%pLUnS(nuE*B3ARJZG9`8R$!(+Z_ z@17e=opmi#K>HI&ZSfhm9bE8W{p9~>KN3q$BSXhi;7%UVaVHI;`||S}Ta9c7KKW-{ zKNhARqX^qH&63CQa8|$T-ChN`%-#BkNbm5Oe^8yZ9x=fHNAp!L($EpB4|OCN*|p=E za`23!A7)+(reH-VvhiT#rokA!J?W1|&l@MPj+}3!$pS{GtdkrHGqbZOT?}ws6R#f+ zb4Qs!TOJ(-LVKd6+v(SA`H#}?H!1Et`{?%f9*`CrUdn%aSseYC8*lZw0qtnjjA5ma zm+oAU+VISEv%TTkeNMVeEYzgw2S?`k83&0TRd`h!yhGAhT%E^Xc<#7TTnvT!aeT+# z&`44aEe$6vPFOC~{cGGpY>3p(y*;x;i}-0jOXRBv+CIDEUOsruSL#|->36OiTsRH9 z3wkX@G}30_BKTW7##Tdcvf#Ou`^DvFsafrOmC>l?#dh> zNYWUa;%OFPv(+Tf5dyTT(dzNNW~nh@v+M}L`Imyt4JY;WFdG{4W9*i;7}G4MQU0uE zvr|PvTGKQAs6oBACTw9)7RF#};ADCE8;w^NC6my$vMlsQt!L)u*(oXA*O2C`_18DR zl$R!SoF?p+dt+&qvt{340VZgf>J=6A!8S*S2JcuM15*)|hbT!c#kv?4MVu;KAIpC- z1Y=(#afp7c?dmcNxeNLrd0uduS+BdSXKt1RW{S1F0Mq8w@(1>b4F@IU?|Q zvY*>bPb=qRKXuupQMiZ4y3{MnwiIoT^$>jN>2ZiJd%?v)MJ-H)5Aj#+gmLt*; zh-V4c6gwnlDj7k7%+|Jgd*Fc!~#E^tw!(3$=c=LCN>=}sI ztUjQaZ}#cPzyquHx5L;m)$hNG$cxkwz}oJ!B|;t5)_)--=_fQiw~Y5MNJ95LkIGbz z8CpM5gRHrTD#FpC8;}(P%w{VF>bz?5CuyK8A8L`jn$>)@Zd>hHh^zH_HhA}H)0s~f zOEhq_H%M9hH1J|uK`iu)@khfxTF(QwU3f0sZ=Y?lNVng+BppG`cEeSO^mabo5i43IK8SG`wF`xoQ+ z!l>4>LP^t-hin0_{+;23Z@Rj=)qELEwjI0)qrVof_{FyzXo@y?F!;d%5RV?~{L2?Q z(E}bsf?z+WQB;21V^E6n$GI=i;=Woy*^D$2*b6^Sio4nHk6^1#Y_1DxUU5AlwO+HWDgcu=SWVdjYmB!HY3*K>bI@UQQt6OAf#tPNMZ?-@L zv4%&(VNu|Ok$;SN6C}o1|Ke-o1etirxjb8FutVI)YWczb9gFEq zI)PRTtvO9B2?1I{2)~l}D)i@!e}8-`ukKuGe%Q;8==SkPq<^1L^kTw5cxv>|z>j=sHhot&C-oVv;R{b<1&{vaB>%CI>e zqKzQ1MD1C1Gq&|kqTU@YZB3)g;B-jDAsx-VTN(%ZBH-yRg4sP=W*}dS0bc=pH3kBK z$};qn+W6>DbpDgzLXV7Uf8xYF1TlN}JTHQOyN=vp=`@v;3_VBS)%|}^vHvHf2kw2O YID`P*yI)_SqJKa}LQ%X*)WH9L08=sy2LJ#7 From e4365fa24c021fb91be45f03f88c52dca62d8347 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Wed, 18 Mar 2015 17:10:29 +0100 Subject: [PATCH 18/95] [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 12a56c8cb..d4828b47c 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 7d5d0d130fa13130497828f1f313e941459744da Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Thu, 16 Apr 2015 09:59:47 +0200 Subject: [PATCH 19/95] [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 d4828b47c..e1021457a 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 c671d3525d4aa020b04b9aa1fa8d7c891987f8f9 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Thu, 16 Apr 2015 10:15:39 +0200 Subject: [PATCH 20/95] [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 e1021457a..5d4ce7854 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 462173a5459aa2dce81228539d6dd32ac8f2c69b Mon Sep 17 00:00:00 2001 From: Yannick Vaucher Date: Fri, 22 May 2015 19:45:36 +0200 Subject: [PATCH 21/95] 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 0b145aaf6..a6b436e17 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 d8749699a99bbaff24ab98eae71b201125ce5512 Mon Sep 17 00:00:00 2001 From: Markus Schneider Date: Thu, 4 Jun 2015 14:30:25 +0200 Subject: [PATCH 22/95] 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 1cbc4aad7..95a3299b2 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 dfc4bd1cba35125dd5dffae11212ef21de4963b1 Mon Sep 17 00:00:00 2001 From: Markus Schneider Date: Fri, 5 Jun 2015 00:33:22 +0200 Subject: [PATCH 23/95] 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 95a3299b2..2e43203a8 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 ea5424c48471718a928533a416495a3d1b848c5e 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 24/95] [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 2e43203a8..0b652e3cd 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 395c381b17b6ca2f2d4ba4796d3cd6a84711cd38 Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Wed, 14 Oct 2015 02:57:05 +0200 Subject: [PATCH 25/95] [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 0b652e3cd..e48c3a6e6 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': [], From d5af1023521d156df68070074d16eb488cd66c90 Mon Sep 17 00:00:00 2001 From: OCA Transbot Date: Mon, 23 Nov 2015 23:43:23 -0500 Subject: [PATCH 26/95] OCA Transbot updated translations from Transifex --- 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 +++++++++++++++++++++++ 10 files changed, 265 insertions(+) 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/i18n/ar.po b/web_widget_x2many_2d_matrix/i18n/ar.po new file mode 100644 index 000000000..7a85d2bde --- /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 000000000..337d2b944 --- /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 000000000..10ba2f9f8 --- /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 000000000..df37d34a7 --- /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 000000000..7ed8bc355 --- /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 000000000..f209e2941 --- /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 000000000..5b5d0bf31 --- /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 000000000..c56e07fa6 --- /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 000000000..07ae09c5d --- /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 000000000..635773bda --- /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 b981ecd72fa9fd2963e7d489b358d2609e5eeb11 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Mon, 18 Jan 2016 16:41:25 +0100 Subject: [PATCH 27/95] [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 | 114 ++++++++++++------ 2 files changed, 121 insertions(+), 41 deletions(-) diff --git a/web_widget_x2many_2d_matrix/README.rst b/web_widget_x2many_2d_matrix/README.rst index a6b436e17..7c880b131 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 5d4ce7854..4dbcb4cc9 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() - { - _.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, - }; + 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() From 07e12b3f3eb2002b0c43facb0b2ad809e24572b4 Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Wed, 14 Sep 2016 09:32:56 +0200 Subject: [PATCH 28/95] [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 7c880b131..83c29328c 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 e48c3a6e6..87dc3541c 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 4dbcb4cc9..5f6147f4d 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 35f1669bc..ca6b687f5 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 fe7b4c3a852498779f4bf77c66b3bc6c35f47a2a Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Fri, 16 Sep 2016 14:35:54 +0200 Subject: [PATCH 29/95] [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 83c29328c..dc8a480f1 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 87dc3541c..a8f4e8cf0 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 5f6147f4d..4087e88e3 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 7e25a97cef2cc87a258c00102cce6cf445465cf2 Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Fri, 16 Sep 2016 17:56:53 +0200 Subject: [PATCH 30/95] [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 4087e88e3..e570949d8 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 2655123d5bb7ef78b63152ce3ab8b6f4a151c211 Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Wed, 21 Sep 2016 18:24:51 +0200 Subject: [PATCH 31/95] [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 dc8a480f1..6628a81ea 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 e570949d8..c56315930 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 07518433d1b9fe2f000a301d006ed8d0afa36555 Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Wed, 21 Sep 2016 20:39:15 +0200 Subject: [PATCH 32/95] [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 c56315930..782ed21a8 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 9fb5f933f884ba5fdf816a52de9ec0b489a0cd14 Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Thu, 22 Sep 2016 09:45:23 +0200 Subject: [PATCH 33/95] [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 782ed21a8..3e182047b 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 b13cd24692d92471215031a495baba6ec69e7c6b Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Thu, 22 Sep 2016 09:48:03 +0200 Subject: [PATCH 34/95] [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 6628a81ea..85d5dd712 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 4feb82659e9eb8df43cb082eac7603407a569abb Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Thu, 22 Sep 2016 12:09:34 +0200 Subject: [PATCH 35/95] [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 3e182047b..8d2e3fc68 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 ccdf5710aba95866cb0c08e9456a3ee077778fbb Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Thu, 22 Sep 2016 12:11:40 +0200 Subject: [PATCH 36/95] [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 8d2e3fc68..8a51b3a6b 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 dd84edd3e1e2f897c67275be8e8539e833b47c8a Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Thu, 6 Oct 2016 16:12:54 +0200 Subject: [PATCH 37/95] [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 a8f4e8cf0..8b55c97d5 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 146252808d2a3f3c9e15df0e9e4d78c3ef0c6218 Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Thu, 6 Oct 2016 16:13:01 +0200 Subject: [PATCH 38/95] [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 976ea30892609df35066ad4fce32733b14ec9114 Mon Sep 17 00:00:00 2001 From: jesusVMayor Date: Mon, 24 Apr 2017 12:28:47 +0200 Subject: [PATCH 39/95] 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 8b55c97d5..b4651c074 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 d33d4f21b..14ed1c536 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 8a51b3a6b..43fa84bb4 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 ca6b687f5..a1a0d5215 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 0111e0ce6716713a7f0353de06a9447524c5cf9c Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Fri, 28 Apr 2017 20:01:00 +0200 Subject: [PATCH 40/95] [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 85d5dd712..40098bff1 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 8662743ae42a1ca83ca7461fb1a30a872debfaf0 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 41/95] [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 40098bff1..d81d100cc 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 307e93007e69cc86ff4880a5a80d6adae3ad0182 Mon Sep 17 00:00:00 2001 From: Richard deMeester Date: Thu, 31 Aug 2017 01:03:06 +1000 Subject: [PATCH 42/95] [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 +- .../static/src/js/web_widget_x2many_2d_matrix.js | 8 ++++++-- .../static/src/xml/web_widget_x2many_2d_matrix.xml | 4 ++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/web_widget_x2many_2d_matrix/__manifest__.py b/web_widget_x2many_2d_matrix/__manifest__.py index b4651c074..69b703cd5 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/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 43fa84bb4..ec88f1eaf 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 a1a0d5215..b7aaaefe1 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 c635192ba233805cdb2e4a897cfd62cd191936dd Mon Sep 17 00:00:00 2001 From: OCA Transbot Date: Wed, 17 Jan 2018 10:50:20 +0100 Subject: [PATCH 43/95] OCA Transbot updated translations from Transifex --- web_widget_x2many_2d_matrix/i18n/lt.po | 27 +++++++++++++++++++++++ web_widget_x2many_2d_matrix/i18n/nl_NL.po | 27 +++++++++++++++++++++++ 2 files changed, 54 insertions(+) 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/i18n/lt.po b/web_widget_x2many_2d_matrix/i18n/lt.po new file mode 100644 index 000000000..d9620d989 --- /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 000000000..27efab7f5 --- /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" From 130909c7c4a3e362917e689da919e256229f4465 Mon Sep 17 00:00:00 2001 From: Artem Kostyuk Date: Thu, 15 Feb 2018 09:45:16 +0200 Subject: [PATCH 44/95] [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 d81d100cc..6fb555b94 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 faef9dac0..919541c6c 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 69b703cd5..41f69a75e 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 d9620d989..57a65fc55 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 27efab7f5..e1fde063c 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 ec88f1eaf..2c0a0cd92 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 d2dafcbdeb9b16d3a59d20bab5bedad7fa112a86 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Mon, 19 Feb 2018 17:48:06 +0100 Subject: [PATCH 45/95] [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 6fb555b94..52eb81b13 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 41f69a75e..31fa2d5a9 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 000000000..898ac0d58 --- /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 2c0a0cd92..000000000 --- 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 000000000..4b1a73f9c --- /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 b7aaaefe1..000000000 --- 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 06934cc33..ba820435c 100644 --- a/web_widget_x2many_2d_matrix/views/templates.xml +++ b/web_widget_x2many_2d_matrix/views/assets.xml @@ -3,7 +3,8 @@