[FIX] account_check_writing: display real currency name on check print
[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 class WebKitParser(report_sxw):
65     """Custom class that use webkit to render HTML reports
66        Code partially taken from report openoffice. Thanks guys :)
67     """
68     def __init__(self, name, table, rml=False, parser=False,
69         header=True, store=False):
70         self.parser_instance = False
71         self.localcontext = {}
72         report_sxw.__init__(self, name, table, rml, parser,
73             header, store)
74
75     def get_lib(self, cursor, uid):
76         """Return the lib wkhtml path"""
77         proxy = self.pool.get('ir.config_parameter')
78         webkit_path = proxy.get_param(cursor, uid, 'webkit_path')
79
80         if not webkit_path:
81             try:
82                 defpath = os.environ.get('PATH', os.defpath).split(os.pathsep)
83                 if hasattr(sys, 'frozen'):
84                     defpath.append(os.getcwd())
85                     if tools.config['root_path']:
86                         defpath.append(os.path.dirname(tools.config['root_path']))
87                 webkit_path = tools.which('wkhtmltopdf', path=os.pathsep.join(defpath))
88             except IOError:
89                 webkit_path = None
90
91         if webkit_path:
92             return webkit_path
93
94         raise except_osv(
95                          _('Wkhtmltopdf library path is not set'),
96                          _('Please install executable on your system' \
97                          ' (sudo apt-get install wkhtmltopdf) or download it from here:' \
98                          ' http://code.google.com/p/wkhtmltopdf/downloads/list and set the' \
99                          ' path in the ir.config_parameter with the webkit_path key.' \
100                          'Minimal version is 0.9.9')
101                         )
102
103     def generate_pdf(self, comm_path, report_xml, header, footer, html_list, webkit_header=False):
104         """Call webkit in order to generate pdf"""
105         if not webkit_header:
106             webkit_header = report_xml.webkit_header
107         tmp_dir = tempfile.gettempdir()
108         out_filename = tempfile.mktemp(suffix=".pdf", prefix="webkit.tmp.")
109         file_to_del = [out_filename]
110         if comm_path:
111             command = [comm_path]
112         else:
113             command = ['wkhtmltopdf']
114
115         command.append('--quiet')
116         # default to UTF-8 encoding.  Use <meta charset="latin-1"> to override.
117         command.extend(['--encoding', 'utf-8'])
118         if header :
119             head_file = file( os.path.join(
120                                   tmp_dir,
121                                   str(time.time()) + '.head.html'
122                                  ),
123                                 'w'
124                             )
125             head_file.write(header)
126             head_file.close()
127             file_to_del.append(head_file.name)
128             command.extend(['--header-html', head_file.name])
129         if footer :
130             foot_file = file(  os.path.join(
131                                   tmp_dir,
132                                   str(time.time()) + '.foot.html'
133                                  ),
134                                 'w'
135                             )
136             foot_file.write(footer)
137             foot_file.close()
138             file_to_del.append(foot_file.name)
139             command.extend(['--footer-html', foot_file.name])
140
141         if webkit_header.margin_top :
142             command.extend(['--margin-top', str(webkit_header.margin_top).replace(',', '.')])
143         if webkit_header.margin_bottom :
144             command.extend(['--margin-bottom', str(webkit_header.margin_bottom).replace(',', '.')])
145         if webkit_header.margin_left :
146             command.extend(['--margin-left', str(webkit_header.margin_left).replace(',', '.')])
147         if webkit_header.margin_right :
148             command.extend(['--margin-right', str(webkit_header.margin_right).replace(',', '.')])
149         if webkit_header.orientation :
150             command.extend(['--orientation', str(webkit_header.orientation).replace(',', '.')])
151         if webkit_header.format :
152             command.extend(['--page-size', str(webkit_header.format).replace(',', '.')])
153         count = 0
154         for html in html_list :
155             html_file = file(os.path.join(tmp_dir, str(time.time()) + str(count) +'.body.html'), 'w')
156             count += 1
157             html_file.write(html)
158             html_file.close()
159             file_to_del.append(html_file.name)
160             command.append(html_file.name)
161         command.append(out_filename)
162         stderr_fd, stderr_path = tempfile.mkstemp(text=True)
163         file_to_del.append(stderr_path)
164         try:
165             status = subprocess.call(command, stderr=stderr_fd)
166             os.close(stderr_fd) # ensure flush before reading
167             stderr_fd = None # avoid closing again in finally block
168             fobj = open(stderr_path, 'r')
169             error_message = fobj.read()
170             fobj.close()
171             if not error_message:
172                 error_message = _('No diagnosis message was provided')
173             else:
174                 error_message = _('The following diagnosis message was provided:\n') + error_message
175             if status :
176                 raise except_osv(_('Webkit error' ),
177                                  _("The command 'wkhtmltopdf' failed with error code = %s. Message: %s") % (status, error_message))
178             pdf_file = open(out_filename, 'rb')
179             pdf = pdf_file.read()
180             pdf_file.close()
181         finally:
182             if stderr_fd is not None:
183                 os.close(stderr_fd)
184             for f_to_del in file_to_del:
185                 try:
186                     os.unlink(f_to_del)
187                 except (OSError, IOError), exc:
188                     _logger.error('cannot remove file %s: %s', f_to_del, exc)
189         return pdf
190
191     def translate_call(self, src):
192         """Translate String."""
193         ir_translation = self.pool.get('ir.translation')
194         name = self.tmpl and 'addons/' + self.tmpl or None
195         res = ir_translation._get_source(self.parser_instance.cr, self.parser_instance.uid,
196                                          name, 'report', self.parser_instance.localcontext.get('lang', 'en_US'), src)
197         if res == src:
198             # no translation defined, fallback on None (backward compatibility)
199             res = ir_translation._get_source(self.parser_instance.cr, self.parser_instance.uid,
200                                              None, 'report', self.parser_instance.localcontext.get('lang', 'en_US'), src)
201         if not res :
202             return src
203         return res
204
205     # override needed to keep the attachments storing procedure
206     def create_single_pdf(self, cursor, uid, ids, data, report_xml, context=None):
207         """generate the PDF"""
208
209         if context is None:
210             context={}
211         htmls = []
212         if report_xml.report_type != 'webkit':
213             return super(WebKitParser,self).create_single_pdf(cursor, uid, ids, data, report_xml, context=context)
214
215         self.parser_instance = self.parser(cursor,
216                                            uid,
217                                            self.name2,
218                                            context=context)
219
220         self.pool = pooler.get_pool(cursor.dbname)
221         objs = self.getObjects(cursor, uid, ids, context)
222         self.parser_instance.set_context(objs, data, ids, report_xml.report_type)
223
224         template =  False
225
226         if report_xml.report_file :
227             # backward-compatible if path in Windows format
228             report_path = report_xml.report_file.replace("\\", "/")
229             path = addons.get_module_resource(*report_path.split('/'))
230             if path and os.path.exists(path) :
231                 template = file(path).read()
232         if not template and report_xml.report_webkit_data :
233             template =  report_xml.report_webkit_data
234         if not template :
235             raise except_osv(_('Error!'), _('Webkit report template not found!'))
236         header = report_xml.webkit_header.html
237         footer = report_xml.webkit_header.footer_html
238         if not header and report_xml.header:
239             raise except_osv(
240                   _('No header defined for this Webkit report!'),
241                   _('Please set a header in company settings.')
242               )
243         if not report_xml.header :
244             header = ''
245             default_head = addons.get_module_resource('report_webkit', 'default_header.html')
246             with open(default_head,'r') as f:
247                 header = f.read()
248         css = report_xml.webkit_header.css
249         if not css :
250             css = ''
251
252         #default_filters=['unicode', 'entity'] can be used to set global filter
253         body_mako_tpl = mako_template(template)
254         helper = WebKitHelper(cursor, uid, report_xml.id, context)
255         if report_xml.precise_mode:
256             for obj in objs:
257                 self.parser_instance.localcontext['objects'] = [obj]
258                 try :
259                     html = body_mako_tpl.render(helper=helper,
260                                                 css=css,
261                                                 _=self.translate_call,
262                                                 **self.parser_instance.localcontext)
263                     htmls.append(html)
264                 except Exception:
265                     msg = exceptions.text_error_template().render()
266                     _logger.error(msg)
267                     raise except_osv(_('Webkit render!'), msg)
268         else:
269             try :
270                 html = body_mako_tpl.render(helper=helper,
271                                             css=css,
272                                             _=self.translate_call,
273                                             **self.parser_instance.localcontext)
274                 htmls.append(html)
275             except Exception:
276                 msg = exceptions.text_error_template().render()
277                 _logger.error(msg)
278                 raise except_osv(_('Webkit render!'), msg)
279         head_mako_tpl = mako_template(header)
280         try :
281             head = head_mako_tpl.render(helper=helper,
282                                         css=css,
283                                         _=self.translate_call,
284                                         _debug=False,
285                                         **self.parser_instance.localcontext)
286         except Exception:
287             raise except_osv(_('Webkit render!'),
288                 exceptions.text_error_template().render())
289         foot = False
290         if footer :
291             foot_mako_tpl = mako_template(footer)
292             try :
293                 foot = foot_mako_tpl.render(helper=helper,
294                                             css=css,
295                                             _=self.translate_call,
296                                             **self.parser_instance.localcontext)
297             except:
298                 msg = exceptions.text_error_template().render()
299                 _logger.error(msg)
300                 raise except_osv(_('Webkit render!'), msg)
301         if report_xml.webkit_debug :
302             try :
303                 deb = head_mako_tpl.render(helper=helper,
304                                            css=css,
305                                            _debug=tools.ustr("\n".join(htmls)),
306                                            _=self.translate_call,
307                                            **self.parser_instance.localcontext)
308             except Exception:
309                 msg = exceptions.text_error_template().render()
310                 _logger.error(msg)
311                 raise except_osv(_('Webkit render!'), msg)
312             return (deb, 'html')
313         bin = self.get_lib(cursor, uid)
314         pdf = self.generate_pdf(bin, report_xml, head, foot, htmls)
315         return (pdf, 'pdf')
316
317
318     def create(self, cursor, uid, ids, data, context=None):
319         """We override the create function in order to handle generator
320            Code taken from report openoffice. Thanks guys :) """
321         pool = pooler.get_pool(cursor.dbname)
322         ir_obj = pool.get('ir.actions.report.xml')
323         report_xml_ids = ir_obj.search(cursor, uid,
324                 [('report_name', '=', self.name[7:])], context=context)
325         if report_xml_ids:
326
327             report_xml = ir_obj.browse(cursor,
328                                        uid,
329                                        report_xml_ids[0],
330                                        context=context)
331             report_xml.report_rml = None
332             report_xml.report_rml_content = None
333             report_xml.report_sxw_content_data = None
334             report_xml.report_sxw_content = None
335             report_xml.report_sxw = None
336         else:
337             return super(WebKitParser, self).create(cursor, uid, ids, data, context)
338         if report_xml.report_type != 'webkit' :
339             return super(WebKitParser, self).create(cursor, uid, ids, data, context)
340         result = self.create_source_pdf(cursor, uid, ids, data, report_xml, context)
341         if not result:
342             return (False,False)
343         return result
344
345 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: