diff --git a/addons/web/tests/__init__.py b/addons/web/tests/__init__.py index b1196a50c20..8c20aed1ce5 100644 --- a/addons/web/tests/__init__.py +++ b/addons/web/tests/__init__.py @@ -1,8 +1,5 @@ # -*- coding: utf-8 -*- -from . import test_menu, test_serving_base, test_js - -fast_suite = [] -checks = [ - test_menu, - test_serving_base, -] +import test_js +import test_menu +import test_serving_base +import test_ui diff --git a/addons/web/tests/test_js.py b/addons/web/tests/test_js.py index b37c082739f..2228794af6f 100644 --- a/addons/web/tests/test_js.py +++ b/addons/web/tests/test_js.py @@ -21,10 +21,6 @@ class WebSuite(QUnitSuite): '' ]) super(WebSuite, self).__init__(url, 50000) - def run(self, result): - if sql_db._Pool is not None: - sql_db._Pool.close_all(sql_db.dsn(tools.config['db_name'])) - return super(WebSuite, self).run(result) def load_tests(loader, standard_tests, _): standard_tests.addTest(WebSuite()) diff --git a/addons/web/tests/test_ui.py b/addons/web/tests/test_ui.py new file mode 100644 index 00000000000..f78ca714d94 --- /dev/null +++ b/addons/web/tests/test_ui.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +import openerp.tests + +class TestUi(openerp.tests.HttpCase): + def test(self): + self.phantom_js("/", "console.log('ok')", "console") + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/web_graph/__init__.py b/addons/web_graph/__init__.py index e69de29bb2d..ee5959455ad 100644 --- a/addons/web_graph/__init__.py +++ b/addons/web_graph/__init__.py @@ -0,0 +1 @@ +import controllers diff --git a/addons/web_graph/controllers/__init__.py b/addons/web_graph/controllers/__init__.py new file mode 100644 index 00000000000..039d9715fab --- /dev/null +++ b/addons/web_graph/controllers/__init__.py @@ -0,0 +1 @@ +import main \ No newline at end of file diff --git a/addons/web_graph/controllers/main.py b/addons/web_graph/controllers/main.py new file mode 100644 index 00000000000..320758e0950 --- /dev/null +++ b/addons/web_graph/controllers/main.py @@ -0,0 +1,88 @@ +from openerp import http +import simplejson +from openerp.http import request, serialize_exception as _serialize_exception +from cStringIO import StringIO +from collections import deque + +try: + import xlwt +except ImportError: + xlwt = None + +class TableExporter(http.Controller): + + @http.route('/web_graph/check_xlwt', type='json', auth='none') + def check_xlwt(self): + return xlwt is not None + + + @http.route('/web_graph/export_xls', type='http', auth="user") + def export_xls(self, data, token): + jdata = simplejson.loads(data) + nbr_measures = jdata['nbr_measures'] + workbook = xlwt.Workbook() + worksheet = workbook.add_sheet(jdata['title']) + header_bold = xlwt.easyxf("font: bold on; pattern: pattern solid, fore_colour gray25;") + header_plain = xlwt.easyxf("pattern: pattern solid, fore_colour gray25;") + bold = xlwt.easyxf("font: bold on;") + + # Step 1: writing headers + headers = jdata['headers'] + + # x,y: current coordinates + # carry: queue containing cell information when a cell has a >= 2 height + # and the drawing code needs to add empty cells below + x, y, carry = 1, 0, deque() + for i, header_row in enumerate(headers): + worksheet.write(i,0, '', header_plain) + for header in header_row: + while (carry and carry[0]['x'] == x): + cell = carry.popleft() + for i in range(nbr_measures): + worksheet.write(y, x+i, '', header_plain) + if cell['height'] > 1: + carry.append({'x': x, 'height':cell['height'] - 1}) + x = x + nbr_measures + style = header_plain if 'expanded' in header else header_bold + for i in range(header['width']): + worksheet.write(y, x + i, header['title'] if i == 0 else '', style) + if header['height'] > 1: + carry.append({'x': x, 'height':header['height'] - 1}) + x = x + header['width']; + while (carry and carry[0]['x'] == x): + cell = carry.popleft() + for i in range(nbr_measures): + worksheet.write(y, x+i, '', header_plain) + if cell['height'] > 1: + carry.append({'x': x, 'height':cell['height'] - 1}) + x = x + nbr_measures + x, y = 1, y + 1 + + # Step 2: measure row + if nbr_measures > 1: + worksheet.write(y,0, '', header_plain) + for measure in jdata['measure_row']: + style = header_bold if measure['is_bold'] else header_plain + worksheet.write(y, x, measure['text'], style); + x = x + 1 + y = y + 1 + + # Step 3: writing data + x = 0 + for row in jdata['rows']: + worksheet.write(y, x, row['indent'] * ' ' + row['title'], header_plain) + for cell in row['cells']: + x = x + 1 + if cell.get('is_bold', False): + worksheet.write(y, x, cell['value'], bold) + else: + worksheet.write(y, x, cell['value']) + x, y = 0, y + 1 + + response = request.make_response(None, + headers=[('Content-Type', 'application/vnd.ms-excel'), + ('Content-Disposition', 'attachment; filename=table.xls;')], + cookies={'fileToken': token}) + workbook.save(response.stream) + + return response diff --git a/addons/web_graph/static/src/js/graph_widget.js b/addons/web_graph/static/src/js/graph_widget.js index ef547e22632..51baabaff5e 100644 --- a/addons/web_graph/static/src/js/graph_widget.js +++ b/addons/web_graph/static/src/js/graph_widget.js @@ -25,6 +25,7 @@ openerp.web_graph.Graph = openerp.web.Widget.extend({ this.bar_ui = options.bar_ui || 'group'; this.graph_view = options.graph_view || null; this.pivot_options = options; + this.title = options.title || 'Data'; }, start: function() { @@ -39,6 +40,10 @@ openerp.web_graph.Graph = openerp.web.Widget.extend({ this.$('.graph_heatmap label').addClass('disabled'); } + openerp.session.rpc('/web_graph/check_xlwt').then(function (result) { + self.$('.graph_options_selection label').toggle(result); + }); + return this.model.call('fields_get', []).then(function (f) { self.fields = f; self.fields.__count = {field:'__count', type: 'integer', string:_t('Quantity')}; @@ -85,8 +90,8 @@ openerp.web_graph.Graph = openerp.web.Widget.extend({ groupbys = _.flatten(_.map(filters, function (filter) { var groupby = py.eval(filter.attrs.context).group_by; if (!(groupby instanceof Array)) { groupby = [groupby]; } - return _.map(groupby, function(g) { - return {field: g, filter: filter}; + return _.map(groupby, function(g) { + return {field: g, filter: filter}; }); })); @@ -264,6 +269,9 @@ openerp.web_graph.Graph = openerp.web.Widget.extend({ case 'update_values': this.pivot.update_data().then(this.proxy('display_data')); break; + case 'export_data': + this.export_xls(); + break; } }, @@ -355,6 +363,110 @@ openerp.web_graph.Graph = openerp.web.Widget.extend({ this.graph_view.register_groupby(this.pivot.rows.groupby, this.pivot.cols.groupby); }, + // ---------------------------------------------------------------------- + // Convert Pivot data structure into table structure : + // compute rows, cols, colors, cell width, cell height, ... + // ---------------------------------------------------------------------- + build_table: function() { + return { + headers: this.build_headers(), + measure_row: this.build_measure_row(), + rows: this.build_rows(), + nbr_measures: this.pivot.measures.length, + title: this.title, + }; + }, + + build_headers: function () { + var pivot = this.pivot, + nbr_measures = pivot.measures.length, + height = _.max(_.map(pivot.cols.headers, function(g) {return g.path.length;})), + rows = []; + + _.each(pivot.cols.headers, function (col) { + if (col.path.length === 0) { return;} + var cell_width = nbr_measures * (col.expanded ? pivot.get_ancestor_leaves(col).length : 1), + cell_height = col.expanded ? 1 : height - col.path.length + 1, + cell = {width: cell_width, height: cell_height, title: col.title, id: col.id, expanded: col.expanded}; + if (rows[col.path.length - 1]) { + rows[col.path.length - 1].push(cell); + } else { + rows[col.path.length - 1] = [cell]; + } + }); + + if (pivot.get_cols_leaves().length > 1) { + rows[0].push({width: nbr_measures, height: height, title: _t('Total'), id: pivot.main_col().id }); + } + if (pivot.cols.headers.length === 1) { + rows = [[{width: nbr_measures, height: 1, title: _t('Total'), id: pivot.main_col().id, expanded: false}]]; + } + return rows; + }, + + build_measure_row: function () { + var nbr_leaves = this.pivot.get_cols_leaves().length, + nbr_cols = nbr_leaves + ((nbr_leaves > 1) ? 1 : 0), + result = [], + add_total = this.pivot.get_cols_leaves().length > 1, + i, m; + for (i = 0; i < nbr_cols; i++) { + for (m = 0; m < this.pivot.measures.length; m++) { + result.push({ + text:this.pivot.measures[m].string, + is_bold: add_total && (i === nbr_cols - 1) + }); + } + } + return result; + }, + + make_cell: function (row, col, value, index) { + var formatted_value = openerp.web.format_value(value, {type:this.pivot.measures[index].type}), + cell = {value:formatted_value}; + + if (this.heatmap_mode === 'none') { return cell; } + var total = (this.heatmap_mode === 'both') ? this.pivot.get_total()[index] + : (this.heatmap_mode === 'row') ? this.pivot.get_total(row)[index] + : this.pivot.get_total(col)[index]; + var color = Math.floor(90 + 165*(total - Math.abs(value))/total); + if (color < 255) { + cell.color = color; + } + return cell; + }, + + build_rows: function () { + var self = this, + pivot = this.pivot, + m, cell; + + return _.map(pivot.rows.headers, function (row) { + var cells = []; + _.each(pivot.get_cols_leaves(), function (col) { + var values = pivot.get_values(row.id,col.id); + for (m = 0; m < pivot.measures.length; m++) { + cells.push(self.make_cell(row,col,values[m], m)); + } + }); + if (pivot.get_cols_leaves().length > 1) { + var totals = pivot.get_total(row); + for (m = 0; m < pivot.measures.length; m++) { + cell = self.make_cell(row, pivot.main_col(), totals[m], m); + cell.is_bold = 'true'; + cells.push(cell); + } + } + return { + id: row.id, + indent: row.path.length, + title: row.title, + expanded: row.expanded, + cells: cells, + }; + }); + }, + // ---------------------------------------------------------------------- // Main display method // ---------------------------------------------------------------------- @@ -362,7 +474,7 @@ openerp.web_graph.Graph = openerp.web.Widget.extend({ this.$('.graph_main_content svg').remove(); this.$('.graph_main_content div').remove(); this.table.empty(); - this.table.toggleClass('heatmap', this.heatmap_mode !== 'none') + this.table.toggleClass('heatmap', this.heatmap_mode !== 'none'); this.width = this.$el.width(); this.height = Math.min(Math.max(document.documentElement.clientHeight - 116 - 60, 250), Math.round(0.8*this.$el.width())); @@ -384,159 +496,75 @@ openerp.web_graph.Graph = openerp.web.Widget.extend({ // Drawing the table // ---------------------------------------------------------------------- draw_table: function () { - this.draw_top_headers(); - _.each(this.pivot.rows.headers, this.proxy('draw_row')); + var table = this.build_table(); + this.draw_headers(table.headers); + this.draw_measure_row(table.measure_row); + this.draw_rows(table.rows); }, - make_border_cell: function (colspan, rowspan, headercell) { - var tag = (headercell) ? $('