OCA reporting engine fork for dev and update.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

254 lines
9.5 KiB

  1. # -*- coding: utf-8 -*-
  2. ##############################################################################
  3. #
  4. # OpenERP, Open Source Management Solution
  5. #
  6. # Copyright (c) 2014 Noviat nv/sa (www.noviat.com). All rights reserved.
  7. #
  8. # This program is free software: you can redistribute it and/or modify
  9. # it under the terms of the GNU Affero General Public License as
  10. # published by the Free Software Foundation, either version 3 of the
  11. # License, or (at your option) any later version.
  12. #
  13. # This program is distributed in the hope that it will be useful,
  14. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. # GNU Affero General Public License for more details.
  17. #
  18. # You should have received a copy of the GNU Affero General Public License
  19. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  20. #
  21. ##############################################################################
  22. import cStringIO
  23. from datetime import datetime
  24. from openerp.osv.fields import datetime as datetime_field
  25. from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
  26. import inspect
  27. from types import CodeType
  28. from openerp.report.report_sxw import report_sxw
  29. from openerp import pooler
  30. import logging
  31. _logger = logging.getLogger(__name__)
  32. xls_types_default = {
  33. 'bool': False,
  34. 'date': None,
  35. 'text': '',
  36. 'number': 0,
  37. }
  38. try:
  39. import xlwt
  40. from xlwt.Style import default_style
  41. xls_types = {
  42. 'bool': xlwt.Row.set_cell_boolean,
  43. 'date': xlwt.Row.set_cell_date,
  44. 'text': xlwt.Row.set_cell_text,
  45. 'number': xlwt.Row.set_cell_number,
  46. }
  47. except ImportError: # pragma: no cover
  48. _logger.debug("Cannot import xlwt. This module will not be functional.")
  49. xls_types = xls_types_default
  50. class AttrDict(dict):
  51. def __init__(self, *args, **kwargs):
  52. super(AttrDict, self).__init__(*args, **kwargs)
  53. self.__dict__ = self
  54. class report_xls(report_sxw):
  55. # TO DO: move parameters infra to configurable data
  56. # header/footer
  57. hf_params = {
  58. 'font_size': 8,
  59. 'font_style': 'I', # B: Bold, I: Italic, U: Underline
  60. }
  61. # styles
  62. _pfc = '26' # default pattern fore_color
  63. _bc = '22' # borders color
  64. decimal_format = '#,##0.00'
  65. date_format = 'YYYY-MM-DD'
  66. xls_styles = {
  67. 'xls_title': 'font: bold true, height 240;',
  68. 'bold': 'font: bold true;',
  69. 'underline': 'font: underline true;',
  70. 'italic': 'font: italic true;',
  71. 'fill': 'pattern: pattern solid, fore_color %s;' % _pfc,
  72. 'fill_blue': 'pattern: pattern solid, fore_color 27;',
  73. 'fill_grey': 'pattern: pattern solid, fore_color 22;',
  74. 'borders_all':
  75. 'borders: '
  76. 'left thin, right thin, top thin, bottom thin, '
  77. 'left_colour %s, right_colour %s, '
  78. 'top_colour %s, bottom_colour %s;'
  79. % (_bc, _bc, _bc, _bc),
  80. 'left': 'align: horz left;',
  81. 'center': 'align: horz center;',
  82. 'right': 'align: horz right;',
  83. 'wrap': 'align: wrap true;',
  84. 'top': 'align: vert top;',
  85. 'bottom': 'align: vert bottom;',
  86. }
  87. # TO DO: move parameters supra to configurable data
  88. def create(self, cr, uid, ids, data, context=None):
  89. self.pool = pooler.get_pool(cr.dbname)
  90. self.cr = cr
  91. self.uid = uid
  92. report_obj = self.pool.get('ir.actions.report.xml')
  93. report_ids = report_obj.search(
  94. cr, uid, [('report_name', '=', self.name[7:])], context=context)
  95. if report_ids:
  96. report_xml = report_obj.browse(
  97. cr, uid, report_ids[0], context=context)
  98. self.title = report_xml.name
  99. if report_xml.report_type == 'xls':
  100. return self.create_source_xls(cr, uid, ids, data, context)
  101. elif context.get('xls_export'):
  102. # use model from 'data' when no ir.actions.report.xml entry
  103. self.table = data.get('model') or self.table
  104. return self.create_source_xls(cr, uid, ids, data, context)
  105. return super(report_xls, self).create(cr, uid, ids, data, context)
  106. def create_source_xls(self, cr, uid, ids, data, context=None):
  107. if not context:
  108. context = {}
  109. parser_instance = self.parser(cr, uid, self.name2, context)
  110. self.parser_instance = parser_instance
  111. self.context = context
  112. objs = self.getObjects(cr, uid, ids, context)
  113. parser_instance.set_context(objs, data, ids, 'xls')
  114. objs = parser_instance.localcontext['objects']
  115. n = cStringIO.StringIO()
  116. # prevent style make error
  117. # http://stackoverflow.com/questions/17130516/xlwt-set-style-making-error-more-than-4094-xfs-styles
  118. wb = xlwt.Workbook(encoding='utf-8', style_compression=2)
  119. _p = AttrDict(parser_instance.localcontext)
  120. _xs = self.xls_styles
  121. self.xls_headers = {
  122. 'standard': '',
  123. }
  124. report_date = datetime_field.context_timestamp(
  125. cr, uid, datetime.now(), context)
  126. report_date = report_date.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
  127. self.xls_footers = {
  128. 'standard': (
  129. '&L&%(font_size)s&%(font_style)s' + report_date +
  130. '&R&%(font_size)s&%(font_style)s&P / &N'
  131. ) % self.hf_params,
  132. }
  133. self.generate_xls_report(_p, _xs, data, objs, wb)
  134. wb.save(n)
  135. n.seek(0)
  136. return (n.read(), 'xls')
  137. def render(self, wanted, col_specs, rowtype, render_space='empty'):
  138. """
  139. returns 'evaluated' col_specs
  140. Input:
  141. - wanted: element from the wanted_list
  142. - col_specs : cf. specs[1:] documented in xls_row_template method
  143. - rowtype : 'header' or 'data'
  144. - render_space : type dict, (caller_space + localcontext)
  145. if not specified
  146. """
  147. if render_space == 'empty':
  148. render_space = {}
  149. caller_space = inspect.currentframe().f_back.f_back.f_locals
  150. localcontext = self.parser_instance.localcontext
  151. render_space.update(caller_space)
  152. render_space.update(localcontext)
  153. row = col_specs[wanted][rowtype][:]
  154. for i in range(len(row)):
  155. if isinstance(row[i], CodeType):
  156. # TODO Use safe_eval or document why not and remove pylint hack
  157. row[i] = eval(row[i], render_space) # pylint: disable=W0123
  158. row.insert(0, wanted)
  159. return row
  160. def generate_xls_report(self, parser, xls_styles, data, objects, wb):
  161. """ override this method to create your excel file """
  162. raise NotImplementedError()
  163. def xls_row_template(self, specs, wanted_list):
  164. """
  165. Returns a row template.
  166. Input :
  167. - 'wanted_list': list of Columns that will be returned in the
  168. row_template
  169. - 'specs': list with Column Characteristics
  170. 0: Column Name (from wanted_list)
  171. 1: Column Colspan
  172. 2: Column Size (unit = the width of the character 0
  173. as it appears in the sheets default font)
  174. 3: Column Type
  175. 4: Column Data
  176. 5: Column Formula (or 'None' for Data)
  177. 6: Column Style
  178. """
  179. r = []
  180. col = 0
  181. for w in wanted_list:
  182. found = False
  183. for s in specs:
  184. if s[0] == w:
  185. found = True
  186. s_len = len(s)
  187. c = list(s[:5])
  188. # set write_cell_func or formula
  189. if s_len > 5 and s[5] is not None:
  190. c.append({'formula': s[5]})
  191. else:
  192. c.append({
  193. 'write_cell_func': report_xls.xls_types[c[3]]})
  194. # Set custom cell style
  195. if s_len > 6 and s[6] is not None:
  196. c.append(s[6])
  197. else:
  198. c.append(None)
  199. # Set cell formula
  200. if s_len > 7 and s[7] is not None:
  201. c.append(s[7])
  202. else:
  203. c.append(None)
  204. r.append((col, c[1], c))
  205. col += c[1]
  206. break
  207. if not found:
  208. _logger.warn("report_xls.xls_row_template, "
  209. "column '%s' not found in specs", w)
  210. return r
  211. def xls_write_row(self, ws, row_pos, row_data,
  212. row_style=default_style, set_column_size=False):
  213. r = ws.row(row_pos)
  214. for col, size, spec in row_data:
  215. data = spec[4]
  216. formula = spec[5].get('formula') and \
  217. xlwt.Formula(spec[5]['formula']) or None
  218. style = spec[6] and spec[6] or row_style
  219. if not data:
  220. # if no data, use default values
  221. data = report_xls.xls_types_default[spec[3]]
  222. if size != 1:
  223. if formula:
  224. ws.write_merge(
  225. row_pos, row_pos, col, col + size - 1, data, style)
  226. else:
  227. ws.write_merge(
  228. row_pos, row_pos, col, col + size - 1, data, style)
  229. else:
  230. if formula:
  231. ws.write(row_pos, col, formula, style)
  232. else:
  233. spec[5]['write_cell_func'](r, col, data, style)
  234. if set_column_size:
  235. ws.col(col).width = spec[2] * 256
  236. return row_pos + 1