Merge lp:~bcim/openerp-reporting-engines/7.0-report_xls into lp:openerp-reporting-engines
- 7.0-report_xls
- Merge into 7.0
Proposed by
Jacques-Etienne Baudoux
Status: | Merged |
---|---|
Merged at revision: | 2 |
Proposed branch: | lp:~bcim/openerp-reporting-engines/7.0-report_xls |
Merge into: | lp:openerp-reporting-engines |
Diff against target: |
361 lines (+341/-0) 4 files modified
report_xls/__init__.py (+24/-0) report_xls/__openerp__.py (+44/-0) report_xls/report_xls.py (+224/-0) report_xls/utils.py (+49/-0) |
To merge this branch: | bzr merge lp:~bcim/openerp-reporting-engines/7.0-report_xls |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Joël Grand-Guillaume @ camptocamp | code review, no tests | Approve | |
Jacques-Etienne Baudoux (community) | test | Approve | |
Review via email: mp+195412@code.launchpad.net |
Commit message
Description of the change
The report_xls module from Noviat
c.f. https:/
To post a comment you must log in.
Revision history for this message
Jacques-Etienne Baudoux (jbaudoux) wrote : | # |
review:
Approve
(test)
Revision history for this message
Pedro Manuel Baeza (pedro.baeza) wrote : | # |
The beginning of the discussion of this module starts here:
https:/
Revision history for this message
Joël Grand-Guillaume @ camptocamp (jgrandguillaume-c2c) wrote : | # |
LGTM, Thanks for this contribs
review:
Approve
(code review, no tests)
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === added directory 'report_xls' |
2 | === added file 'report_xls/__init__.py' |
3 | --- report_xls/__init__.py 1970-01-01 00:00:00 +0000 |
4 | +++ report_xls/__init__.py 2013-11-15 16:03:10 +0000 |
5 | @@ -0,0 +1,24 @@ |
6 | +# -*- encoding: utf-8 -*- |
7 | +############################################################################## |
8 | +# |
9 | +# OpenERP, Open Source Management Solution |
10 | +# |
11 | +# Copyright (c) 2013 Noviat nv/sa (www.noviat.com). All rights reserved. |
12 | +# |
13 | +# This program is free software: you can redistribute it and/or modify |
14 | +# it under the terms of the GNU Affero General Public License as |
15 | +# published by the Free Software Foundation, either version 3 of the |
16 | +# License, or (at your option) any later version. |
17 | +# |
18 | +# This program is distributed in the hope that it will be useful, |
19 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
20 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
21 | +# GNU Affero General Public License for more details. |
22 | +# |
23 | +# You should have received a copy of the GNU Affero General Public License |
24 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
25 | +# |
26 | +############################################################################## |
27 | + |
28 | +from . import report_xls |
29 | +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
30 | |
31 | === added file 'report_xls/__openerp__.py' |
32 | --- report_xls/__openerp__.py 1970-01-01 00:00:00 +0000 |
33 | +++ report_xls/__openerp__.py 2013-11-15 16:03:10 +0000 |
34 | @@ -0,0 +1,44 @@ |
35 | +# -*- encoding: utf-8 -*- |
36 | +############################################################################## |
37 | +# |
38 | +# OpenERP, Open Source Management Solution |
39 | +# |
40 | +# Copyright (c) 2013 Noviat nv/sa (www.noviat.com). All rights reserved. |
41 | +# |
42 | +# This program is free software: you can redistribute it and/or modify |
43 | +# it under the terms of the GNU Affero General Public License as |
44 | +# published by the Free Software Foundation, either version 3 of the |
45 | +# License, or (at your option) any later version. |
46 | +# |
47 | +# This program is distributed in the hope that it will be useful, |
48 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
49 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
50 | +# GNU Affero General Public License for more details. |
51 | +# |
52 | +# You should have received a copy of the GNU Affero General Public License |
53 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
54 | +# |
55 | +############################################################################## |
56 | +{ |
57 | + 'name': 'XLS report engine', |
58 | + 'version': '0.3', |
59 | + 'license': 'AGPL-3', |
60 | + 'author': 'Noviat', |
61 | + 'website': 'http://www.noviat.com', |
62 | + 'category': 'Reporting', |
63 | + 'description': """ |
64 | + |
65 | +This module adds XLS export capabilities to the standard OpenERP reporting engine. |
66 | + |
67 | +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. |
68 | + |
69 | + """, |
70 | + 'depends': ['base'], |
71 | + 'external_dependencies': {'python': ['xlwt']}, |
72 | + 'demo_xml': [], |
73 | + 'init_xml': [], |
74 | + 'update_xml' : [], |
75 | + 'active': False, |
76 | + 'installable': True, |
77 | +} |
78 | +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
79 | |
80 | === added file 'report_xls/report_xls.py' |
81 | --- report_xls/report_xls.py 1970-01-01 00:00:00 +0000 |
82 | +++ report_xls/report_xls.py 2013-11-15 16:03:10 +0000 |
83 | @@ -0,0 +1,224 @@ |
84 | +# -*- encoding: utf-8 -*- |
85 | +############################################################################## |
86 | +# |
87 | +# OpenERP, Open Source Management Solution |
88 | +# |
89 | +#Copyright (c) 2013 Noviat nv/sa (www.noviat.com). All rights reserved. |
90 | +# |
91 | +# This program is free software: you can redistribute it and/or modify |
92 | +# it under the terms of the GNU Affero General Public License as |
93 | +# published by the Free Software Foundation, either version 3 of the |
94 | +# License, or (at your option) any later version. |
95 | +# |
96 | +# This program is distributed in the hope that it will be useful, |
97 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
98 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
99 | +# GNU Affero General Public License for more details. |
100 | +# |
101 | +# You should have received a copy of the GNU Affero General Public License |
102 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
103 | +# |
104 | +############################################################################## |
105 | + |
106 | +import xlwt |
107 | +from xlwt.Style import default_style |
108 | +import cStringIO |
109 | +import datetime, time |
110 | +import inspect |
111 | +from types import CodeType |
112 | +from openerp.report.report_sxw import * |
113 | +from openerp import pooler |
114 | +from openerp.tools.translate import translate, _ |
115 | +import logging |
116 | +_logger = logging.getLogger(__name__) |
117 | + |
118 | +class AttrDict(dict): |
119 | + def __init__(self, *args, **kwargs): |
120 | + super(AttrDict, self).__init__(*args, **kwargs) |
121 | + self.__dict__ = self |
122 | + |
123 | +class report_xls(report_sxw): |
124 | + |
125 | + xls_types = { |
126 | + 'bool': xlwt.Row.set_cell_boolean, |
127 | + 'date': xlwt.Row.set_cell_date, |
128 | + 'text': xlwt.Row.set_cell_text, |
129 | + 'number': xlwt.Row.set_cell_number, |
130 | + } |
131 | + xls_types_default = { |
132 | + 'bool': False, |
133 | + 'date': None, |
134 | + 'text': '', |
135 | + 'number': 0, |
136 | + } |
137 | + |
138 | + # TO DO: move parameters infra to configurable data |
139 | + |
140 | + # header/footer |
141 | + DT_FORMAT = '%Y-%m-%d %H:%M:%S' |
142 | + hf_params = { |
143 | + 'font_size': 8, |
144 | + 'font_style': 'I', # B: Bold, I: Italic, U: Underline |
145 | + } |
146 | + xls_headers = { |
147 | + 'standard': '' |
148 | + } |
149 | + xls_footers = { |
150 | + 'standard': ('&L&%(font_size)s&%(font_style)s' + datetime.now().strftime(DT_FORMAT) + |
151 | + '&R&%(font_size)s&%(font_style)s&P / &N') %hf_params |
152 | + } |
153 | + |
154 | + # styles |
155 | + _pfc = '26' # default pattern fore_color |
156 | + _bc = '22' # borders color |
157 | + decimal_format = '#,##0.00' |
158 | + date_format = 'YYYY-MM-DD' |
159 | + xls_styles = { |
160 | + 'xls_title': 'font: bold true, height 240;', |
161 | + 'bold': 'font: bold true;', |
162 | + 'underline': 'font: underline true;', |
163 | + 'italic': 'font: italic true;', |
164 | + 'fill': 'pattern: pattern solid, fore_color %s;' %_pfc, |
165 | + 'fill_blue' : 'pattern: pattern solid, fore_color 27;', |
166 | + 'fill_grey' : 'pattern: pattern solid, fore_color 22;', |
167 | + 'borders_all': 'borders: left thin, right thin, top thin, bottom thin, ' \ |
168 | + 'left_colour %s, right_colour %s, top_colour %s, bottom_colour %s;' %(_bc,_bc,_bc,_bc), |
169 | + 'left': 'align: horz left;', |
170 | + 'center': 'align: horz center;', |
171 | + 'right': 'align: horz right;', |
172 | + 'wrap': 'align: wrap true;', |
173 | + 'top': 'align: vert top;', |
174 | + 'bottom': 'align: vert bottom;', |
175 | + } |
176 | + # TO DO: move parameters supra to configurable data |
177 | + |
178 | + def create(self, cr, uid, ids, data, context=None): |
179 | + self.pool = pooler.get_pool(cr.dbname) |
180 | + self.cr = cr |
181 | + self.uid = uid |
182 | + report_obj = self.pool.get('ir.actions.report.xml') |
183 | + report_ids = report_obj.search(cr, uid, |
184 | + [('report_name', '=', self.name[7:])], context=context) |
185 | + if report_ids: |
186 | + report_xml = report_obj.browse(cr, uid, report_ids[0], context=context) |
187 | + self.title = report_xml.name |
188 | + if report_xml.report_type == 'xls': |
189 | + return self.create_source_xls(cr, uid, ids, data, context) |
190 | + elif context.get('xls_export'): |
191 | + return self.create_source_xls(cr, uid, ids, data, context) |
192 | + return super(report_xls, self).create(cr, uid, ids, data, context) |
193 | + |
194 | + def create_source_xls(self, cr, uid, ids, data, context=None): |
195 | + if not context: context = {} |
196 | + parser_instance = self.parser(cr, uid, self.name2, context) |
197 | + self.parser_instance = parser_instance |
198 | + objs = self.getObjects(cr, uid, ids, context) |
199 | + parser_instance.set_context(objs, data, ids, 'xls') |
200 | + objs = parser_instance.localcontext['objects'] |
201 | + n = cStringIO.StringIO() |
202 | + wb = xlwt.Workbook(encoding='utf-8') |
203 | + _p = AttrDict(parser_instance.localcontext) |
204 | + _xs = self.xls_styles |
205 | + self.generate_xls_report(_p, _xs, data, objs, wb) |
206 | + wb.save(n) |
207 | + n.seek(0) |
208 | + return (n.read(), 'xls') |
209 | + |
210 | + def render(self, wanted, col_specs, rowtype, render_space='empty'): |
211 | + """ |
212 | + returns 'mako'-rendered col_specs |
213 | + |
214 | + Input: |
215 | + - wanted: element from the wanted_list |
216 | + - col_specs : cf. specs[1:] documented in xls_row_template method |
217 | + - rowtype : 'header' or 'data' |
218 | + - render_space : type dict, (caller_space + localcontext) if not specified |
219 | + """ |
220 | + if render_space == 'empty': |
221 | + render_space = {} |
222 | + caller_space = inspect.currentframe().f_back.f_back.f_locals |
223 | + localcontext = self.parser_instance.localcontext |
224 | + render_space.update(caller_space) |
225 | + render_space.update(localcontext) |
226 | + row = col_specs[wanted][rowtype][:] |
227 | + for i in range(len(row)): |
228 | + if isinstance(row[i], CodeType): |
229 | + row[i] = eval(row[i], render_space) |
230 | + row.insert(0, wanted) |
231 | + #_logger.warn('row O = %s', row) |
232 | + return row |
233 | + |
234 | + def generate_xls_report(self, parser, xls_styles, data, objects, wb): |
235 | + """ override this method to create your excel file """ |
236 | + raise NotImplementedError() |
237 | + |
238 | + def xls_row_template(self, specs, wanted_list): |
239 | + """ |
240 | + Returns a row template. |
241 | + |
242 | + Input : |
243 | + - 'wanted_list': list of Columns that will be returned in the row_template |
244 | + - 'specs': list with Column Characteristics |
245 | + 0: Column Name (from wanted_list) |
246 | + 1: Column Colspan |
247 | + 2: Column Size (unit = the width of the character ’0′ as it appears in the sheet’s default font) |
248 | + 3: Column Type |
249 | + 4: Column Data |
250 | + 5: Column Formula (or 'None' for Data) |
251 | + 6: Column Style |
252 | + """ |
253 | + r = [] |
254 | + col = 0 |
255 | + for w in wanted_list: |
256 | + found = False |
257 | + for s in specs: |
258 | + if s[0] == w: |
259 | + found = True |
260 | + s_len = len(s) |
261 | + c = list(s[:5]) |
262 | + # set write_cell_func or formula |
263 | + if s_len > 5 and s[5] is not None: |
264 | + c.append({'formula': s[5]}) |
265 | + else: |
266 | + c.append({'write_cell_func': report_xls.xls_types[c[3]]}) |
267 | + # Set custom cell style |
268 | + if s_len > 6 and s[6] is not None: |
269 | + c.append(s[6]) |
270 | + else: |
271 | + c.append(None) |
272 | + # Set cell formula |
273 | + if s_len > 7 and s[7] is not None: |
274 | + c.append(s[7]) |
275 | + else: |
276 | + c.append(None) |
277 | + r.append((col, c[1], c)) |
278 | + col += c[1] |
279 | + break |
280 | + if not found: |
281 | + _logger.warn("report_xls.xls_row_template, column '%s' not found in specs", w) |
282 | + return r |
283 | + |
284 | + def xls_write_row(self, ws, row_pos, row_data, row_style=default_style, set_column_size=False): |
285 | + r = ws.row(row_pos) |
286 | + for col, size, spec in row_data: |
287 | + data = spec[4] |
288 | + formula = spec[5].get('formula') and xlwt.Formula(spec[5]['formula']) or None |
289 | + style = spec[6] and spec[6] or row_style |
290 | + if not data: |
291 | + # if no data, use default values |
292 | + data = report_xls.xls_types_default[spec[3]] |
293 | + if size != 1: |
294 | + if formula: |
295 | + ws.write_merge(row_pos, row_pos, col, col+size-1, data, style) |
296 | + else: |
297 | + ws.write_merge(row_pos, row_pos, col, col+size-1, data, style) |
298 | + else: |
299 | + if formula: |
300 | + ws.write(row_pos, col, formula, style) |
301 | + else: |
302 | + spec[5]['write_cell_func'](r, col, data, style) |
303 | + if set_column_size: |
304 | + ws.col(col).width = spec[2] * 256 |
305 | + return row_pos + 1 |
306 | + |
307 | +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
308 | |
309 | === added file 'report_xls/utils.py' |
310 | --- report_xls/utils.py 1970-01-01 00:00:00 +0000 |
311 | +++ report_xls/utils.py 2013-11-15 16:03:10 +0000 |
312 | @@ -0,0 +1,49 @@ |
313 | +# -*- encoding: utf-8 -*- |
314 | +############################################################################## |
315 | +# |
316 | +# OpenERP, Open Source Management Solution |
317 | +# |
318 | +# Copyright (c) 2013 Noviat nv/sa (www.noviat.com). All rights reserved. |
319 | +# |
320 | +# This program is free software: you can redistribute it and/or modify |
321 | +# it under the terms of the GNU Affero General Public License as |
322 | +# published by the Free Software Foundation, either version 3 of the |
323 | +# License, or (at your option) any later version. |
324 | +# |
325 | +# This program is distributed in the hope that it will be useful, |
326 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
327 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
328 | +# GNU Affero General Public License for more details. |
329 | +# |
330 | +# You should have received a copy of the GNU Affero General Public License |
331 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
332 | +# |
333 | +############################################################################## |
334 | +# |
335 | + |
336 | +def _render(code): |
337 | + return compile(code, '<string>', 'eval') |
338 | + |
339 | +def rowcol_to_cell(row, col, row_abs=False, col_abs=False): |
340 | + # Code based upon utils from xlwt distribution |
341 | + """ |
342 | + Convert numeric row/col notation to an Excel cell reference string in A1 notation. |
343 | + """ |
344 | + d = col // 26 |
345 | + m = col % 26 |
346 | + chr1 = "" # Most significant character in AA1 |
347 | + if row_abs: |
348 | + row_abs = '$' |
349 | + else: |
350 | + row_abs = '' |
351 | + if col_abs: |
352 | + col_abs = '$' |
353 | + else: |
354 | + col_abs = '' |
355 | + if d > 0: |
356 | + chr1 = chr(ord('A') + d - 1) |
357 | + chr2 = chr(ord('A') + m) |
358 | + # Zero index to 1-index |
359 | + return col_abs + chr1 + chr2 + row_abs + str(row + 1) |
360 | + |
361 | +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
Works on saas1