diff --git a/report_xls/__init__.py b/report_xls/__init__.py new file mode 100644 index 00000000..aab5a45b --- /dev/null +++ b/report_xls/__init__.py @@ -0,0 +1,24 @@ +# -*- encoding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# +# Copyright (c) 2013 Noviat nv/sa (www.noviat.com). All rights reserved. +# +# 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 . +# +############################################################################## + +from . import report_xls +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/report_xls/__openerp__.py b/report_xls/__openerp__.py new file mode 100644 index 00000000..c8fea849 --- /dev/null +++ b/report_xls/__openerp__.py @@ -0,0 +1,44 @@ +# -*- encoding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# +# Copyright (c) 2013 Noviat nv/sa (www.noviat.com). All rights reserved. +# +# 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': 'XLS report engine', + 'version': '0.3', + 'license': 'AGPL-3', + 'author': 'Noviat', + 'website': 'http://www.noviat.com', + 'category': 'Reporting', + 'description': """ + +This module adds XLS export capabilities to the standard OpenERP reporting engine. + +In order to generate an XLS export you can define a report of type 'xls' or alternatively pass {'xls_export' : 1) via the context to create method of the report. + + """, + 'depends': ['base'], + 'external_dependencies': {'python': ['xlwt']}, + 'demo_xml': [], + 'init_xml': [], + 'update_xml' : [], + 'active': False, + 'installable': True, +} +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/report_xls/report_xls.py b/report_xls/report_xls.py new file mode 100644 index 00000000..e8263a2f --- /dev/null +++ b/report_xls/report_xls.py @@ -0,0 +1,224 @@ +# -*- encoding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# +#Copyright (c) 2013 Noviat nv/sa (www.noviat.com). All rights reserved. +# +# 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 . +# +############################################################################## + +import xlwt +from xlwt.Style import default_style +import cStringIO +import datetime, time +import inspect +from types import CodeType +from openerp.report.report_sxw import * +from openerp import pooler +from openerp.tools.translate import translate, _ +import logging +_logger = logging.getLogger(__name__) + +class AttrDict(dict): + def __init__(self, *args, **kwargs): + super(AttrDict, self).__init__(*args, **kwargs) + self.__dict__ = self + +class report_xls(report_sxw): + + xls_types = { + 'bool': xlwt.Row.set_cell_boolean, + 'date': xlwt.Row.set_cell_date, + 'text': xlwt.Row.set_cell_text, + 'number': xlwt.Row.set_cell_number, + } + xls_types_default = { + 'bool': False, + 'date': None, + 'text': '', + 'number': 0, + } + + # TO DO: move parameters infra to configurable data + + # header/footer + DT_FORMAT = '%Y-%m-%d %H:%M:%S' + hf_params = { + 'font_size': 8, + 'font_style': 'I', # B: Bold, I: Italic, U: Underline + } + xls_headers = { + 'standard': '' + } + xls_footers = { + 'standard': ('&L&%(font_size)s&%(font_style)s' + datetime.now().strftime(DT_FORMAT) + + '&R&%(font_size)s&%(font_style)s&P / &N') %hf_params + } + + # styles + _pfc = '26' # default pattern fore_color + _bc = '22' # borders color + decimal_format = '#,##0.00' + date_format = 'YYYY-MM-DD' + xls_styles = { + 'xls_title': 'font: bold true, height 240;', + 'bold': 'font: bold true;', + 'underline': 'font: underline true;', + 'italic': 'font: italic true;', + 'fill': 'pattern: pattern solid, fore_color %s;' %_pfc, + 'fill_blue' : 'pattern: pattern solid, fore_color 27;', + 'fill_grey' : 'pattern: pattern solid, fore_color 22;', + 'borders_all': 'borders: left thin, right thin, top thin, bottom thin, ' \ + 'left_colour %s, right_colour %s, top_colour %s, bottom_colour %s;' %(_bc,_bc,_bc,_bc), + 'left': 'align: horz left;', + 'center': 'align: horz center;', + 'right': 'align: horz right;', + 'wrap': 'align: wrap true;', + 'top': 'align: vert top;', + 'bottom': 'align: vert bottom;', + } + # TO DO: move parameters supra to configurable data + + def create(self, cr, uid, ids, data, context=None): + self.pool = pooler.get_pool(cr.dbname) + self.cr = cr + self.uid = uid + report_obj = self.pool.get('ir.actions.report.xml') + report_ids = report_obj.search(cr, uid, + [('report_name', '=', self.name[7:])], context=context) + if report_ids: + report_xml = report_obj.browse(cr, uid, report_ids[0], context=context) + self.title = report_xml.name + if report_xml.report_type == 'xls': + return self.create_source_xls(cr, uid, ids, data, context) + elif context.get('xls_export'): + return self.create_source_xls(cr, uid, ids, data, context) + return super(report_xls, self).create(cr, uid, ids, data, context) + + def create_source_xls(self, cr, uid, ids, data, context=None): + if not context: context = {} + parser_instance = self.parser(cr, uid, self.name2, context) + self.parser_instance = parser_instance + objs = self.getObjects(cr, uid, ids, context) + parser_instance.set_context(objs, data, ids, 'xls') + objs = parser_instance.localcontext['objects'] + n = cStringIO.StringIO() + wb = xlwt.Workbook(encoding='utf-8') + _p = AttrDict(parser_instance.localcontext) + _xs = self.xls_styles + self.generate_xls_report(_p, _xs, data, objs, wb) + wb.save(n) + n.seek(0) + return (n.read(), 'xls') + + def render(self, wanted, col_specs, rowtype, render_space='empty'): + """ + returns 'mako'-rendered col_specs + + Input: + - wanted: element from the wanted_list + - col_specs : cf. specs[1:] documented in xls_row_template method + - rowtype : 'header' or 'data' + - render_space : type dict, (caller_space + localcontext) if not specified + """ + if render_space == 'empty': + render_space = {} + caller_space = inspect.currentframe().f_back.f_back.f_locals + localcontext = self.parser_instance.localcontext + render_space.update(caller_space) + render_space.update(localcontext) + row = col_specs[wanted][rowtype][:] + for i in range(len(row)): + if isinstance(row[i], CodeType): + row[i] = eval(row[i], render_space) + row.insert(0, wanted) + #_logger.warn('row O = %s', row) + return row + + def generate_xls_report(self, parser, xls_styles, data, objects, wb): + """ override this method to create your excel file """ + raise NotImplementedError() + + def xls_row_template(self, specs, wanted_list): + """ + Returns a row template. + + Input : + - 'wanted_list': list of Columns that will be returned in the row_template + - 'specs': list with Column Characteristics + 0: Column Name (from wanted_list) + 1: Column Colspan + 2: Column Size (unit = the width of the character ’0′ as it appears in the sheet’s default font) + 3: Column Type + 4: Column Data + 5: Column Formula (or 'None' for Data) + 6: Column Style + """ + r = [] + col = 0 + for w in wanted_list: + found = False + for s in specs: + if s[0] == w: + found = True + s_len = len(s) + c = list(s[:5]) + # set write_cell_func or formula + if s_len > 5 and s[5] is not None: + c.append({'formula': s[5]}) + else: + c.append({'write_cell_func': report_xls.xls_types[c[3]]}) + # Set custom cell style + if s_len > 6 and s[6] is not None: + c.append(s[6]) + else: + c.append(None) + # Set cell formula + if s_len > 7 and s[7] is not None: + c.append(s[7]) + else: + c.append(None) + r.append((col, c[1], c)) + col += c[1] + break + if not found: + _logger.warn("report_xls.xls_row_template, column '%s' not found in specs", w) + return r + + def xls_write_row(self, ws, row_pos, row_data, row_style=default_style, set_column_size=False): + r = ws.row(row_pos) + for col, size, spec in row_data: + data = spec[4] + formula = spec[5].get('formula') and xlwt.Formula(spec[5]['formula']) or None + style = spec[6] and spec[6] or row_style + if not data: + # if no data, use default values + data = report_xls.xls_types_default[spec[3]] + if size != 1: + if formula: + ws.write_merge(row_pos, row_pos, col, col+size-1, data, style) + else: + ws.write_merge(row_pos, row_pos, col, col+size-1, data, style) + else: + if formula: + ws.write(row_pos, col, formula, style) + else: + spec[5]['write_cell_func'](r, col, data, style) + if set_column_size: + ws.col(col).width = spec[2] * 256 + return row_pos + 1 + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/report_xls/utils.py b/report_xls/utils.py new file mode 100644 index 00000000..30ce800d --- /dev/null +++ b/report_xls/utils.py @@ -0,0 +1,49 @@ +# -*- encoding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# +# Copyright (c) 2013 Noviat nv/sa (www.noviat.com). All rights reserved. +# +# 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 . +# +############################################################################## +# + +def _render(code): + return compile(code, '', 'eval') + +def rowcol_to_cell(row, col, row_abs=False, col_abs=False): + # Code based upon utils from xlwt distribution + """ + Convert numeric row/col notation to an Excel cell reference string in A1 notation. + """ + d = col // 26 + m = col % 26 + chr1 = "" # Most significant character in AA1 + if row_abs: + row_abs = '$' + else: + row_abs = '' + if col_abs: + col_abs = '$' + else: + col_abs = '' + if d > 0: + chr1 = chr(ord('A') + d - 1) + chr2 = chr(ord('A') + m) + # Zero index to 1-index + return col_abs + chr1 + chr2 + row_abs + str(row + 1) + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: