|
@@ -0,0 +1,255 @@
|
|
|
|
+# -*- coding: utf-8 -*-
|
|
|
|
+##############################################################################
|
|
|
|
+#
|
|
|
|
+# OpenERP, Open Source Management Solution
|
|
|
|
+#
|
|
|
|
+# Copyright (c) 2014 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 <http://www.gnu.org/licenses/>.
|
|
|
|
+#
|
|
|
|
+##############################################################################
|
|
|
|
+
|
|
|
|
+import cStringIO
|
|
|
|
+from datetime import datetime
|
|
|
|
+from openerp.osv.fields import datetime as datetime_field
|
|
|
|
+from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
|
|
|
|
+import inspect
|
|
|
|
+from types import CodeType
|
|
|
|
+from openerp.report.report_sxw import report_sxw
|
|
|
|
+from openerp.modules.registry import RegistryManager
|
|
|
|
+import logging
|
|
|
|
+
|
|
|
|
+_logger = logging.getLogger(__name__)
|
|
|
|
+
|
|
|
|
+xls_types_default = {
|
|
|
|
+ 'bool': False,
|
|
|
|
+ 'date': None,
|
|
|
|
+ 'text': '',
|
|
|
|
+ 'number': 0,
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+try:
|
|
|
|
+ import xlwt
|
|
|
|
+ from xlwt.Style import default_style
|
|
|
|
+
|
|
|
|
+ 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,
|
|
|
|
+ }
|
|
|
|
+except ImportError: # pragma: no cover
|
|
|
|
+ _logger.debug("Cannot import xlwt. This module will not be functional.")
|
|
|
|
+ xls_types = xls_types_default
|
|
|
|
+ default_style = None
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+class AttrDict(dict):
|
|
|
|
+ def __init__(self, *args, **kwargs):
|
|
|
|
+ super(AttrDict, self).__init__(*args, **kwargs)
|
|
|
|
+ self.__dict__ = self
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+class report_xls(report_sxw):
|
|
|
|
+ # TO DO: move parameters infra to configurable data
|
|
|
|
+ # header/footer
|
|
|
|
+ hf_params = {
|
|
|
|
+ 'font_size': 8,
|
|
|
|
+ 'font_style': 'I', # B: Bold, I: Italic, U: Underline
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ # 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 = RegistryManager.get(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'):
|
|
|
|
+ # use model from 'data' when no ir.actions.report.xml entry
|
|
|
|
+ self.table = data.get('model') or self.table
|
|
|
|
+ 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
|
|
|
|
+ self.context = context
|
|
|
|
+ objs = self.getObjects(cr, uid, ids, context)
|
|
|
|
+ parser_instance.set_context(objs, data, ids, 'xls')
|
|
|
|
+ objs = parser_instance.localcontext['objects']
|
|
|
|
+ n = cStringIO.StringIO()
|
|
|
|
+ # prevent style make error
|
|
|
|
+ # http://stackoverflow.com/questions/17130516/xlwt-set-style-making-error-more-than-4094-xfs-styles
|
|
|
|
+ wb = xlwt.Workbook(encoding='utf-8', style_compression=2)
|
|
|
|
+ _p = AttrDict(parser_instance.localcontext)
|
|
|
|
+ _xs = self.xls_styles
|
|
|
|
+ self.xls_headers = {
|
|
|
|
+ 'standard': '',
|
|
|
|
+ }
|
|
|
|
+ report_date = datetime_field.context_timestamp(
|
|
|
|
+ cr, uid, datetime.now(), context)
|
|
|
|
+ report_date = report_date.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
|
|
|
+ self.xls_footers = {
|
|
|
|
+ 'standard': (
|
|
|
|
+ '&L&%(font_size)s&%(font_style)s' + report_date +
|
|
|
|
+ '&R&%(font_size)s&%(font_style)s&P / &N'
|
|
|
|
+ ) % self.hf_params,
|
|
|
|
+ }
|
|
|
|
+ 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 'evaluated' 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):
|
|
|
|
+ # TODO Use safe_eval or document why not and remove pylint hack
|
|
|
|
+ row[i] = eval(row[i], render_space) # pylint: disable=W0123
|
|
|
|
+ row.insert(0, wanted)
|
|
|
|
+ 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': 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 = 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
|