Browse Source
Merge pull request #96 from hbrunn/8.0-web_widget_x2many_2d_matrix
Merge pull request #96 from hbrunn/8.0-web_widget_x2many_2d_matrix
8.0 web widget x2many 2d matrixpull/138/head
Sylvain LE GAL
10 years ago
9 changed files with 578 additions and 0 deletions
-
78web_widget_x2many_2d_matrix/README.rst
-
20web_widget_x2many_2d_matrix/__init__.py
-
45web_widget_x2many_2d_matrix/__openerp__.py
-
BINweb_widget_x2many_2d_matrix/static/description/icon.png
-
BINweb_widget_x2many_2d_matrix/static/description/screenshot.png
-
8web_widget_x2many_2d_matrix/static/src/css/web_widget_x2many_2d_matrix.css
-
380web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js
-
36web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml
-
11web_widget_x2many_2d_matrix/views/templates.xml
@ -0,0 +1,78 @@ |
|||||
|
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 $value(1/1) $value(2/1) |
||||
|
$y_value2 $value(1/2) $value(2/2) |
||||
|
========= =========== =========== |
||||
|
|
||||
|
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 |
||||
|
===== |
||||
|
|
||||
|
Use this widget by saying:: |
||||
|
|
||||
|
<field name="my_field" widget="x2many_2d_matrix" /> |
||||
|
|
||||
|
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:: |
||||
|
|
||||
|
<field name="my_field" widget="x2many_2d_matrix" field_x_axis="my_field1" field_y_axis="my_field2" field_value="my_field3" /> |
||||
|
|
||||
|
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 |
||||
|
====================== |
||||
|
|
||||
|
* it would be worth trying to instantiate the proper field widget and let it render the input |
||||
|
|
||||
|
Credits |
||||
|
======= |
||||
|
|
||||
|
Contributors |
||||
|
------------ |
||||
|
|
||||
|
* Holger Brunn <hbrunn@therp.nl> |
||||
|
|
||||
|
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. |
@ -0,0 +1,20 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################## |
||||
|
# |
||||
|
# OpenERP, Open Source Management Solution |
||||
|
# This module copyright (C) 2015 Therp BV <http://therp.nl>. |
||||
|
# |
||||
|
# 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 <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
############################################################################## |
@ -0,0 +1,45 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################## |
||||
|
# |
||||
|
# OpenERP, Open Source Management Solution |
||||
|
# This module copyright (C) 2015 Therp BV <http://therp.nl>. |
||||
|
# |
||||
|
# 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 <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
############################################################################## |
||||
|
{ |
||||
|
"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': [], |
||||
|
}, |
||||
|
} |
After Width: 80 | Height: 80 | Size: 5.0 KiB |
After Width: 914 | Height: 349 | Size: 19 KiB |
@ -0,0 +1,8 @@ |
|||||
|
.oe_form_field_x2many_2d_matrix th.oe_link |
||||
|
{ |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
.openerp .oe_form_field_x2many_2d_matrix .oe_list_content > tbody > tr > td.oe_list_field_cell |
||||
|
{ |
||||
|
white-space: normal; |
||||
|
} |
@ -0,0 +1,380 @@ |
|||||
|
//-*- coding: utf-8 -*-
|
||||
|
//############################################################################
|
||||
|
//
|
||||
|
// OpenERP, Open Source Management Solution
|
||||
|
// This module copyright (C) 2015 Therp BV <http://therp.nl>.
|
||||
|
//
|
||||
|
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
//
|
||||
|
//############################################################################
|
||||
|
|
||||
|
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) |
||||
|
{ |
||||
|
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 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; |
||||
|
}); |
||||
|
}); |
||||
|
})); |
||||
|
}) |
||||
|
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(); |
||||
|
} |
||||
|
return jQuery.when.apply(jQuery, deferrends); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 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() |
||||
|
{ |
||||
|
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']; |
||||
|
}, |
||||
|
|
||||
|
// 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 instance.web.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( |
||||
|
val, {'type': this.fields[this.field_value].type}); |
||||
|
}, |
||||
|
|
||||
|
// compute totals
|
||||
|
compute_totals: function() |
||||
|
{ |
||||
|
var self = this, |
||||
|
grand_total = 0, |
||||
|
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); |
||||
|
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, |
||||
|
}; |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
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; |
||||
|
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.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 .edit') |
||||
|
.toggle(!this.get('effective_readonly')); |
||||
|
this.$el |
||||
|
.find('tbody td.oe_list_field_cell span.oe_form_field .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; |
||||
|
}, |
||||
|
|
||||
|
// deactivate view related functions
|
||||
|
load_views: function() {}, |
||||
|
reload_current_view: function() {}, |
||||
|
get_active_view: function() {}, |
||||
|
}); |
||||
|
} |
@ -0,0 +1,36 @@ |
|||||
|
<templates> |
||||
|
<t t-name="FieldX2Many2dMatrix"> |
||||
|
<div t-att-class="widget.widget_class"> |
||||
|
<table class="oe_list_content"> |
||||
|
<thead> |
||||
|
<tr class="oe_list_header_columns"> |
||||
|
<th /> |
||||
|
<th t-foreach="widget.get_x_axis_values()" t-as="x" t-att-data-x="x"> |
||||
|
<t t-esc="widget.get_x_axis_label(x)" /> |
||||
|
</th> |
||||
|
<th t-if="widget.show_row_totals">Total</th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody> |
||||
|
<tr t-foreach="widget.get_y_axis_values()" t-as="y" t-att-data-y="y"> |
||||
|
<th><t t-esc="widget.get_y_axis_label(y)" /></th> |
||||
|
<td t-foreach="widget.get_x_axis_values()" t-as="x" t-att-class="'oe_list_field_cell' + (widget.is_numeric ? ' oe_number' : '')" t-att-data-x="x"> |
||||
|
<span t-att-class="widget.get_xy_value_class()"> |
||||
|
<input class="edit" t-att-data-id="widget.get_xy_id(x, y)" t-att-value="widget.format_xy_value(widget.get_xy_value(x, y))" /> |
||||
|
<span class="read"><t t-esc="widget.format_xy_value(widget.get_xy_value(x, y))" /></span> |
||||
|
</span> |
||||
|
</td> |
||||
|
<td t-if="widget.show_row_totals" class="row_total oe_number" t-att-data-y="y"/> |
||||
|
</tr> |
||||
|
</tbody> |
||||
|
<tfoot t-if="widget.show_column_totals"> |
||||
|
<tr> |
||||
|
<th>Total</th> |
||||
|
<td t-foreach="widget.get_x_axis_values()" t-as="x" class="oe_list_footer oe_number column_total" t-att-data-x="x" /> |
||||
|
<td class="grand_total oe_number" /> |
||||
|
</tr> |
||||
|
</tfoot> |
||||
|
</table> |
||||
|
</div> |
||||
|
</t> |
||||
|
</templates> |
@ -0,0 +1,11 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<openerp> |
||||
|
<data> |
||||
|
<template id="assets_backend" name="web_widget_x2many_2d_matrix assets" inherit_id="web.assets_backend"> |
||||
|
<xpath expr="." position="inside"> |
||||
|
<script type="text/javascript" src="/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js"></script> |
||||
|
<link rel="stylesheet" href="/web_widget_x2many_2d_matrix/static/src/css/web_widget_x2many_2d_matrix.css"/> |
||||
|
</xpath> |
||||
|
</template> |
||||
|
</data> |
||||
|
</openerp> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue