[MERGE] report_webkit move wkhtmltopdf PATH from company to ir config parameters
[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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
30 #
31 ##############################################################################
32
33 import subprocess
34 import os
35 import report
36 import tempfile
37 import time
38 import logging
39
40 from mako.template import Template
41 from mako.lookup import TemplateLookup
42 from mako import exceptions
43
44 import netsvc
45 import pooler
46 from report_helper import WebKitHelper
47 from report.report_sxw import *
48 import addons
49 import tools
50 from tools.translate import _
51 from osv.osv import except_osv
52
53 logger = logging.getLogger('report_webkit')
54
55 def mako_template(text):
56     """Build a Mako template.
57
58     This template uses UTF-8 encoding
59     """
60     tmp_lookup  = TemplateLookup() #we need it in order to allow inclusion and inheritance
61     return Template(text, input_encoding='utf-8', output_encoding='utf-8', lookup=tmp_lookup)
62
63 class WebKitParser(report_sxw):
64     """Custom class that use webkit to render HTML reports
65        Code partially taken from report openoffice. Thanks guys :)
66     """
67     def __init__(self, name, table, rml=False, parser=False,
68         header=True, store=False):
69         self.parser_instance = False
70         self.localcontext = {}
71         report_sxw.__init__(self, name, table, rml, parser,
72             header, store)
73
74     def get_lib(self, cursor, uid, company) :
75         """Return the lib wkhtml path"""
76         proxy = self.pool.get('ir.config_parameter')
77         webkit_path = proxy.get_param(cursor, uid, 'webkit_path')
78
79         if not webkit_path:
80             try:
81                 defpath = os.environ.get('PATH', os.defpath).split(os.pathsep)
82                 if hasattr(sys, 'frozen'):
83                     defpath.append(os.getcwd())
84                     if tools.config['root_path']:
85                         defpath.append(os.path.dirname(tools.config['root_path']))
86                 webkit_path = tools.which('wkhtmltopdf', path=os.pathsep.join(defpath))
87             except IOError:
88                 webkit_path = None
89
90         if webkit_path:
91             return webkit_path
92
93         raise except_osv(
94                          _('Wkhtmltopdf library path is not set'),
95                          _('Please install executable on your system' \
96                          ' (sudo apt-get install wkhtmltopdf) or download it from here:' \
97                          ' http://code.google.com/p/wkhtmltopdf/downloads/list and set the' \
98                          ' path in the ir.config_parameter with the webkit_path key.' \
99                          'Minimal version is 0.9.9')
100                         )
101
102     def generate_pdf(self, comm_path, report_xml, header, footer, html_list, webkit_header=False):
103         """Call webkit in order to generate pdf"""
104         if not webkit_header:
105             webkit_header = report_xml.webkit_header
106         tmp_dir = tempfile.gettempdir()
107         out_filename = tempfile.mktemp(suffix=".pdf", prefix="webkit.tmp.")
108         files = []
109         file_to_del = []
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         try:
163             status = subprocess.call(command, stderr=subprocess.PIPE) # ignore stderr
164             if status :
165                 raise except_osv(
166                                 _('Webkit raise an error' ),
167                                 status
168                             )
169         except Exception:
170             for f_to_del in file_to_del :
171                 os.unlink(f_to_del)
172
173         pdf = file(out_filename, 'rb').read()
174         for f_to_del in file_to_del :
175             os.unlink(f_to_del)
176
177         os.unlink(out_filename)
178         return pdf
179
180     def translate_call(self, src):
181         """Translate String."""
182         ir_translation = self.pool.get('ir.translation')
183         res = ir_translation._get_source(self.parser_instance.cr, self.parser_instance.uid,
184                                          None, 'report', self.parser_instance.localcontext.get('lang', 'en_US'), src)
185         if not res :
186             return src
187         return res
188
189     # override needed to keep the attachments storing procedure
190     def create_single_pdf(self, cursor, uid, ids, data, report_xml, context=None):
191         """generate the PDF"""
192
193         if context is None:
194             context={}
195         htmls = []
196         if report_xml.report_type != 'webkit':
197             return super(WebKitParser,self).create_single_pdf(cursor, uid, ids, data, report_xml, context=context)
198
199         self.parser_instance = self.parser(cursor,
200                                            uid,
201                                            self.name2,
202                                            context=context)
203
204         self.pool = pooler.get_pool(cursor.dbname)
205         objs = self.getObjects(cursor, uid, ids, context)
206         self.parser_instance.set_context(objs, data, ids, report_xml.report_type)
207
208         template =  False
209
210         if report_xml.report_file :
211             path = addons.get_module_resource(report_xml.report_file)
212             if os.path.exists(path) :
213                 template = file(path).read()
214         if not template and report_xml.report_webkit_data :
215             template =  report_xml.report_webkit_data
216         if not template :
217             raise except_osv(_('Error!'), _('Webkit Report template not found !'))
218         header = report_xml.webkit_header.html
219         footer = report_xml.webkit_header.footer_html
220         if not header and report_xml.header:
221             raise except_osv(
222                   _('No header defined for this Webkit report!'),
223                   _('Please set a header in company settings')
224               )
225         if not report_xml.header :
226             header = ''
227             default_head = addons.get_module_resource('report_webkit', 'default_header.html')
228             with open(default_head,'r') as f:
229                 header = f.read()
230         css = report_xml.webkit_header.css
231         if not css :
232             css = ''
233         user = self.pool.get('res.users').browse(cursor, uid, uid)
234         company= user.company_id
235
236         #default_filters=['unicode', 'entity'] can be used to set global filter
237         body_mako_tpl = mako_template(template)
238         helper = WebKitHelper(cursor, uid, report_xml.id, context)
239         if report_xml.precise_mode:
240             for obj in objs:
241                 self.parser_instance.localcontext['objects'] = [obj]
242                 try :
243                     html = body_mako_tpl.render(helper=helper,
244                                                 css=css,
245                                                 _=self.translate_call,
246                                                 **self.parser_instance.localcontext)
247                     htmls.append(html)
248                 except Exception, e:
249                     msg = exceptions.text_error_template().render()
250                     logger.error(msg)
251                     raise except_osv(_('Webkit render'), msg)
252         else:
253             try :
254                 html = body_mako_tpl.render(helper=helper,
255                                             css=css,
256                                             _=self.translate_call,
257                                             **self.parser_instance.localcontext)
258                 htmls.append(html)
259             except Exception, e:
260                 msg = exceptions.text_error_template().render()
261                 logger.error(msg)
262                 raise except_osv(_('Webkit render'), msg)
263         head_mako_tpl = mako_template(header)
264         try :
265             head = head_mako_tpl.render(helper=helper,
266                                         css=css,
267                                         _=self.translate_call,
268                                         _debug=False,
269                                         **self.parser_instance.localcontext)
270         except Exception, e:
271             raise except_osv(_('Webkit render'),
272                 exceptions.text_error_template().render())
273         foot = False
274         if footer :
275             foot_mako_tpl = mako_template(footer)
276             try :
277                 foot = foot_mako_tpl.render(helper=helper,
278                                             css=css,
279                                             _=self.translate_call,
280                                             **self.parser_instance.localcontext)
281             except:
282                 msg = exceptions.text_error_template().render()
283                 logger.error(msg)
284                 raise except_osv(_('Webkit render'), msg)
285         if report_xml.webkit_debug :
286             try :
287                 deb = head_mako_tpl.render(helper=helper,
288                                            css=css,
289                                            _debug=tools.ustr("\n".join(htmls)),
290                                            _=self.translate_call,
291                                            **self.parser_instance.localcontext)
292             except Exception, e:
293                 msg = exceptions.text_error_template().render()
294                 logger.error(msg)
295                 raise except_osv(_('Webkit render'), msg)
296             return (deb, 'html')
297         bin = self.get_lib(cursor, uid, company.id)
298         pdf = self.generate_pdf(bin, report_xml, head, foot, htmls)
299         return (pdf, 'pdf')
300
301
302     def create(self, cursor, uid, ids, data, context=None):
303         """We override the create function in order to handle generator
304            Code taken from report openoffice. Thanks guys :) """
305         pool = pooler.get_pool(cursor.dbname)
306         ir_obj = pool.get('ir.actions.report.xml')
307         report_xml_ids = ir_obj.search(cursor, uid,
308                 [('report_name', '=', self.name[7:])], context=context)
309         if report_xml_ids:
310
311             report_xml = ir_obj.browse(cursor,
312                                        uid,
313                                        report_xml_ids[0],
314                                        context=context)
315             report_xml.report_rml = None
316             report_xml.report_rml_content = None
317             report_xml.report_sxw_content_data = None
318             report_rml.report_sxw_content = None
319             report_rml.report_sxw = None
320         else:
321             return super(WebKitParser, self).create(cursor, uid, ids, data, context)
322         if report_xml.report_type != 'webkit' :
323             return super(WebKitParser, self).create(cursor, uid, ids, data, context)
324         result = self.create_source_pdf(cursor, uid, ids, data, report_xml, context)
325         if not result:
326             return (False,False)
327         return result
328
329 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: