[REVERT] b6a7402fdbddfe817fa36292e6f89e8659f9bdea, pagereset seems to be useful in...
[odoo/odoo.git] / addons / report_webkit / webkit_report.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 # Copyright (c) 2010 Camptocamp SA (http://www.camptocamp.com)
5 # All Right Reserved
6 #
7 # Author : Nicolas Bessi (Camptocamp)
8 # Contributor(s) : Florent Xicluna (Wingo SA)
9 #
10 # WARNING: This program as such is intended to be used by professional
11 # programmers who take the whole responsability of assessing all potential
12 # consequences resulting from its eventual inadequacies and bugs
13 # End users who are looking for a ready-to-use solution with commercial
14 # garantees and support are strongly adviced to contract a Free Software
15 # Service Company
16 #
17 # This program is Free Software; you can redistribute it and/or
18 # modify it under the terms of the GNU General Public License
19 # as published by the Free Software Foundation; either version 2
20 # of the License, or (at your option) any later version.
21 #
22 # This program is distributed in the hope that it will be useful,
23 # but WITHOUT ANY WARRANTY; without even the implied warranty of
24 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
25 # GNU General Public License for more details.
26 #
27 # You should have received a copy of the GNU General Public License
28 # along with this program; if not, write to the Free Software
29 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
30 #
31 ##############################################################################
32
33 import subprocess
34 import os
35 import sys
36 from openerp import report
37 import tempfile
38 import time
39 import logging
40
41 from mako.template import Template
42 from mako.lookup import TemplateLookup
43 from mako import exceptions
44
45 from openerp import netsvc
46 from openerp import pooler
47 from report_helper import WebKitHelper
48 from openerp.report.report_sxw import *
49 from openerp import addons
50 from openerp import tools
51 from openerp.tools.translate import _
52 from openerp.osv.osv import except_osv
53
54 _logger = logging.getLogger(__name__)
55
56 def mako_template(text):
57     """Build a Mako template.
58
59     This template uses UTF-8 encoding
60     """
61     tmp_lookup  = TemplateLookup() #we need it in order to allow inclusion and inheritance
62     return Template(text, input_encoding='utf-8', output_encoding='utf-8', lookup=tmp_lookup)
63
64
65 class WebKitParser(report_sxw):
66     """Custom class that use webkit to render HTML reports
67        Code partially taken from report openoffice. Thanks guys :)
68     """
69     def __init__(self, name, table, rml=False, parser=False,
70         header=True, store=False):
71         self.parser_instance = False
72         self.localcontext = {}
73         report_sxw.__init__(self, name, table, rml, parser,
74             header, store)
75
76     def get_lib(self, cursor, uid):
77         """Return the lib wkhtml path"""
78         proxy = self.pool.get('ir.config_parameter')
79         webkit_path = proxy.get_param(cursor, uid, 'webkit_path')
80
81         if not webkit_path:
82             try:
83                 defpath = os.environ.get('PATH', os.defpath).split(os.pathsep)
84                 if hasattr(sys, 'frozen'):
85                     defpath.append(os.getcwd())
86                     if tools.config['root_path']:
87                         defpath.append(os.path.dirname(tools.config['root_path']))
88                 webkit_path = tools.which('wkhtmltopdf', path=os.pathsep.join(defpath))
89             except IOError:
90                 webkit_path = None
91
92         if webkit_path:
93             return webkit_path
94
95         raise except_osv(
96                          _('Wkhtmltopdf library path is not set'),
97                          _('Please install executable on your system' \
98                          ' (sudo apt-get install wkhtmltopdf) or download it from here:' \
99                          ' http://code.google.com/p/wkhtmltopdf/downloads/list and set the' \
100                          ' path in the ir.config_parameter with the webkit_path key.' \
101                          'Minimal version is 0.9.9')
102                         )
103
104     def generate_pdf(self, comm_path, report_xml, header, footer, html_list, webkit_header=False):
105         """Call webkit in order to generate pdf"""
106         if not webkit_header:
107             webkit_header = report_xml.webkit_header
108         fd, out_filename = tempfile.mkstemp(suffix=".pdf",
109                                             prefix="webkit.tmp.")
110         file_to_del = [out_filename]
111         if comm_path:
112             command = [comm_path]
113         else:
114             command = ['wkhtmltopdf']
115
116         command.append('--quiet')
117         # default to UTF-8 encoding.  Use <meta charset="latin-1"> to override.
118         command.extend(['--encoding', 'utf-8'])
119         if header :
120             with tempfile.NamedTemporaryFile(suffix=".head.html",
121                                              delete=False) as head_file:
122                 head_file.write(self._sanitize_html(header))
123             file_to_del.append(head_file.name)
124             command.extend(['--header-html', head_file.name])
125         if footer :
126             with tempfile.NamedTemporaryFile(suffix=".foot.html",
127                                              delete=False) as foot_file:
128                 foot_file.write(self._sanitize_html(footer))
129             file_to_del.append(foot_file.name)
130             command.extend(['--footer-html', foot_file.name])
131
132         if webkit_header.margin_top :
133             command.extend(['--margin-top', str(webkit_header.margin_top).replace(',', '.')])
134         if webkit_header.margin_bottom :
135             command.extend(['--margin-bottom', str(webkit_header.margin_bottom).replace(',', '.')])
136         if webkit_header.margin_left :
137             command.extend(['--margin-left', str(webkit_header.margin_left).replace(',', '.')])
138         if webkit_header.margin_right :
139             command.extend(['--margin-right', str(webkit_header.margin_right).replace(',', '.')])
140         if webkit_header.orientation :
141             command.extend(['--orientation', str(webkit_header.orientation).replace(',', '.')])
142         if webkit_header.format :
143             command.extend(['--page-size', str(webkit_header.format).replace(',', '.')])
144         count = 0
145         for html in html_list :
146             with tempfile.NamedTemporaryFile(suffix="%d.body.html" %count,
147                                              delete=False) as html_file:
148                 count += 1
149                 html_file.write(self._sanitize_html(html))
150             file_to_del.append(html_file.name)
151             command.append(html_file.name)
152         command.append(out_filename)
153         stderr_fd, stderr_path = tempfile.mkstemp(text=True)
154         file_to_del.append(stderr_path)
155         try:
156             status = subprocess.call(command, stderr=stderr_fd)
157             os.close(stderr_fd) # ensure flush before reading
158             stderr_fd = None # avoid closing again in finally block
159             fobj = open(stderr_path, 'r')
160             error_message = fobj.read()
161             fobj.close()
162             if not error_message:
163                 error_message = _('No diagnosis message was provided')
164             else:
165                 error_message = _('The following diagnosis message was provided:\n') + error_message
166             if status :
167                 raise except_osv(_('Webkit error' ),
168                                  _("The command 'wkhtmltopdf' failed with error code = %s. Message: %s") % (status, error_message))
169             with open(out_filename, 'rb') as pdf_file:
170                 pdf = pdf_file.read()
171             os.close(fd)
172         finally:
173             if stderr_fd is not None:
174                 os.close(stderr_fd)
175             for f_to_del in file_to_del:
176                 try:
177                     os.unlink(f_to_del)
178                 except (OSError, IOError), exc:
179                     _logger.error('cannot remove file %s: %s', f_to_del, exc)
180         return pdf
181
182     def translate_call(self, src):
183         """Translate String."""
184         ir_translation = self.pool.get('ir.translation')
185         name = self.tmpl and 'addons/' + self.tmpl or None
186         res = ir_translation._get_source(self.parser_instance.cr, self.parser_instance.uid,
187                                          name, 'report', self.parser_instance.localcontext.get('lang', 'en_US'), src)
188         if res == src:
189             # no translation defined, fallback on None (backward compatibility)
190             res = ir_translation._get_source(self.parser_instance.cr, self.parser_instance.uid,
191                                              None, 'report', self.parser_instance.localcontext.get('lang', 'en_US'), src)
192         if not res :
193             return src
194         return res
195
196     # override needed to keep the attachments storing procedure
197     def create_single_pdf(self, cursor, uid, ids, data, report_xml, context=None):
198         """generate the PDF"""
199
200         if context is None:
201             context={}
202         htmls = []
203         if report_xml.report_type != 'webkit':
204             return super(WebKitParser,self).create_single_pdf(cursor, uid, ids, data, report_xml, context=context)
205
206         self.parser_instance = self.parser(cursor,
207                                            uid,
208                                            self.name2,
209                                            context=context)
210
211         self.pool = pooler.get_pool(cursor.dbname)
212         objs = self.getObjects(cursor, uid, ids, context)
213         self.parser_instance.set_context(objs, data, ids, report_xml.report_type)
214
215         template =  False
216
217         if report_xml.report_file :
218             # backward-compatible if path in Windows format
219             report_path = report_xml.report_file.replace("\\", "/")
220             path = addons.get_module_resource(*report_path.split('/'))
221             if path and os.path.exists(path) :
222                 template = file(path).read()
223         if not template and report_xml.report_webkit_data :
224             template =  report_xml.report_webkit_data
225         if not template :
226             raise except_osv(_('Error!'), _('Webkit report template not found!'))
227         header = report_xml.webkit_header.html
228         footer = report_xml.webkit_header.footer_html
229         if not header and report_xml.header:
230             raise except_osv(
231                   _('No header defined for this Webkit report!'),
232                   _('Please set a header in company settings.')
233               )
234         if not report_xml.header :
235             header = ''
236             default_head = addons.get_module_resource('report_webkit', 'default_header.html')
237             with open(default_head,'r') as f:
238                 header = f.read()
239         css = report_xml.webkit_header.css
240         if not css :
241             css = ''
242
243         #default_filters=['unicode', 'entity'] can be used to set global filter
244         body_mako_tpl = mako_template(template)
245         helper = WebKitHelper(cursor, uid, report_xml.id, context)
246         if report_xml.precise_mode:
247             for obj in objs:
248                 self.parser_instance.localcontext['objects'] = [obj]
249                 try :
250                     html = body_mako_tpl.render(helper=helper,
251                                                 css=css,
252                                                 _=self.translate_call,
253                                                 **self.parser_instance.localcontext)
254                     htmls.append(html)
255                 except Exception:
256                     msg = exceptions.text_error_template().render()
257                     _logger.error(msg)
258                     raise except_osv(_('Webkit render!'), msg)
259         else:
260             try :
261                 html = body_mako_tpl.render(helper=helper,
262                                             css=css,
263                                             _=self.translate_call,
264                                             **self.parser_instance.localcontext)
265                 htmls.append(html)
266             except Exception:
267                 msg = exceptions.text_error_template().render()
268                 _logger.error(msg)
269                 raise except_osv(_('Webkit render!'), msg)
270         head_mako_tpl = mako_template(header)
271         try :
272             head = head_mako_tpl.render(helper=helper,
273                                         css=css,
274                                         _=self.translate_call,
275                                         _debug=False,
276                                         **self.parser_instance.localcontext)
277         except Exception:
278             raise except_osv(_('Webkit render!'),
279                 exceptions.text_error_template().render())
280         foot = False
281         if footer :
282             foot_mako_tpl = mako_template(footer)
283             try :
284                 foot = foot_mako_tpl.render(helper=helper,
285                                             css=css,
286                                             _=self.translate_call,
287                                             **self.parser_instance.localcontext)
288             except:
289                 msg = exceptions.text_error_template().render()
290                 _logger.error(msg)
291                 raise except_osv(_('Webkit render!'), msg)
292         if report_xml.webkit_debug :
293             try :
294                 deb = head_mako_tpl.render(helper=helper,
295                                            css=css,
296                                            _debug=tools.ustr("\n".join(htmls)),
297                                            _=self.translate_call,
298                                            **self.parser_instance.localcontext)
299             except Exception:
300                 msg = exceptions.text_error_template().render()
301                 _logger.error(msg)
302                 raise except_osv(_('Webkit render!'), msg)
303             return (deb, 'html')
304         bin = self.get_lib(cursor, uid)
305         pdf = self.generate_pdf(bin, report_xml, head, foot, htmls)
306         return (pdf, 'pdf')
307
308     def create(self, cursor, uid, ids, data, context=None):
309         """We override the create function in order to handle generator
310            Code taken from report openoffice. Thanks guys :) """
311         pool = pooler.get_pool(cursor.dbname)
312         ir_obj = pool.get('ir.actions.report.xml')
313         report_xml_ids = ir_obj.search(cursor, uid,
314                 [('report_name', '=', self.name[7:])], context=context)
315         if report_xml_ids:
316
317             report_xml = ir_obj.browse(cursor,
318                                        uid,
319                                        report_xml_ids[0],
320                                        context=context)
321             report_xml.report_rml = None
322             report_xml.report_rml_content = None
323             report_xml.report_sxw_content_data = None
324             report_xml.report_sxw_content = None
325             report_xml.report_sxw = None
326         else:
327             return super(WebKitParser, self).create(cursor, uid, ids, data, context)
328         if report_xml.report_type != 'webkit':
329             return super(WebKitParser, self).create(cursor, uid, ids, data, context)
330         result = self.create_source_pdf(cursor, uid, ids, data, report_xml, context)
331         if not result:
332             return (False,False)
333         return result
334
335     def _sanitize_html(self, html):
336         """wkhtmltopdf expects the html page to declare a doctype.
337         """
338         if html and html[:9].upper() != "<!DOCTYPE":
339             html = "<!DOCTYPE html>\n" + html
340         return html
341
342 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: