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