[IMP] demo data: project issues
[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 sys
36 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 import netsvc
46 import pooler
47 from report_helper import WebKitHelper
48 from report.report_sxw import *
49 import addons
50 import tools
51 from tools.translate import _
52 from 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         files = []
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             head_file = file( os.path.join(
121                                   tmp_dir,
122                                   str(time.time()) + '.head.html'
123                                  ),
124                                 'w'
125                             )
126             head_file.write(header)
127             head_file.close()
128             file_to_del.append(head_file.name)
129             command.extend(['--header-html', head_file.name])
130         if footer :
131             foot_file = file(  os.path.join(
132                                   tmp_dir,
133                                   str(time.time()) + '.foot.html'
134                                  ),
135                                 'w'
136                             )
137             foot_file.write(footer)
138             foot_file.close()
139             file_to_del.append(foot_file.name)
140             command.extend(['--footer-html', foot_file.name])
141
142         if webkit_header.margin_top :
143             command.extend(['--margin-top', str(webkit_header.margin_top).replace(',', '.')])
144         if webkit_header.margin_bottom :
145             command.extend(['--margin-bottom', str(webkit_header.margin_bottom).replace(',', '.')])
146         if webkit_header.margin_left :
147             command.extend(['--margin-left', str(webkit_header.margin_left).replace(',', '.')])
148         if webkit_header.margin_right :
149             command.extend(['--margin-right', str(webkit_header.margin_right).replace(',', '.')])
150         if webkit_header.orientation :
151             command.extend(['--orientation', str(webkit_header.orientation).replace(',', '.')])
152         if webkit_header.format :
153             command.extend(['--page-size', str(webkit_header.format).replace(',', '.')])
154         count = 0
155         for html in html_list :
156             html_file = file(os.path.join(tmp_dir, str(time.time()) + str(count) +'.body.html'), 'w')
157             count += 1
158             html_file.write(html)
159             html_file.close()
160             file_to_del.append(html_file.name)
161             command.append(html_file.name)
162         command.append(out_filename)
163         stderr_fd, stderr_path = tempfile.mkstemp(text=True)
164         file_to_del.append(stderr_path)
165         try:
166             status = subprocess.call(command, stderr=stderr_fd)
167             os.close(stderr_fd) # ensure flush before reading
168             stderr_fd = None # avoid closing again in finally block
169             fobj = open(stderr_path, 'r')
170             error_message = fobj.read()
171             fobj.close()
172             if not error_message:
173                 error_message = _('No diagnosis message was provided')
174             else:
175                 error_message = _('The following diagnosis message was provided:\n') + error_message
176             if status :
177                 raise except_osv(_('Webkit error' ),
178                                  _("The command 'wkhtmltopdf' failed with error code = %s. Message: %s") % (status, error_message))
179             pdf_file = open(out_filename, 'rb')
180             pdf = pdf_file.read()
181             pdf_file.close()
182         finally:
183             if stderr_fd is not None:
184                 os.close(stderr_fd)
185             for f_to_del in file_to_del:
186                 try:
187                     os.unlink(f_to_del)
188                 except (OSError, IOError), exc:
189                     _logger.error('cannot remove file %s: %s', f_to_del, exc)
190         return pdf
191
192     def translate_call(self, src):
193         """Translate String."""
194         ir_translation = self.pool.get('ir.translation')
195         res = ir_translation._get_source(self.parser_instance.cr, self.parser_instance.uid,
196                                          None, 'report', self.parser_instance.localcontext.get('lang', 'en_US'), src)
197         if not res :
198             return src
199         return res
200
201     # override needed to keep the attachments storing procedure
202     def create_single_pdf(self, cursor, uid, ids, data, report_xml, context=None):
203         """generate the PDF"""
204
205         if context is None:
206             context={}
207         htmls = []
208         if report_xml.report_type != 'webkit':
209             return super(WebKitParser,self).create_single_pdf(cursor, uid, ids, data, report_xml, context=context)
210
211         self.parser_instance = self.parser(cursor,
212                                            uid,
213                                            self.name2,
214                                            context=context)
215
216         self.pool = pooler.get_pool(cursor.dbname)
217         objs = self.getObjects(cursor, uid, ids, context)
218         self.parser_instance.set_context(objs, data, ids, report_xml.report_type)
219
220         template =  False
221
222         if report_xml.report_file :
223             path = addons.get_module_resource(report_xml.report_file)
224             if os.path.exists(path) :
225                 template = file(path).read()
226         if not template and report_xml.report_webkit_data :
227             template =  report_xml.report_webkit_data
228         if not template :
229             raise except_osv(_('Error!'), _('Webkit report template not found!'))
230         header = report_xml.webkit_header.html
231         footer = report_xml.webkit_header.footer_html
232         if not header and report_xml.header:
233             raise except_osv(
234                   _('No header defined for this Webkit report!'),
235                   _('Please set a header in company settings.')
236               )
237         if not report_xml.header :
238             header = ''
239             default_head = addons.get_module_resource('report_webkit', 'default_header.html')
240             with open(default_head,'r') as f:
241                 header = f.read()
242         css = report_xml.webkit_header.css
243         if not css :
244             css = ''
245         user = self.pool.get('res.users').browse(cursor, uid, uid)
246         company= user.company_id
247
248         #default_filters=['unicode', 'entity'] can be used to set global filter
249         body_mako_tpl = mako_template(template)
250         helper = WebKitHelper(cursor, uid, report_xml.id, context)
251         if report_xml.precise_mode:
252             for obj in objs:
253                 self.parser_instance.localcontext['objects'] = [obj]
254                 try :
255                     html = body_mako_tpl.render(helper=helper,
256                                                 css=css,
257                                                 _=self.translate_call,
258                                                 **self.parser_instance.localcontext)
259                     htmls.append(html)
260                 except Exception, e:
261                     msg = exceptions.text_error_template().render()
262                     _logger.error(msg)
263                     raise except_osv(_('Webkit render!'), msg)
264         else:
265             try :
266                 html = body_mako_tpl.render(helper=helper,
267                                             css=css,
268                                             _=self.translate_call,
269                                             **self.parser_instance.localcontext)
270                 htmls.append(html)
271             except Exception, e:
272                 msg = exceptions.text_error_template().render()
273                 _logger.error(msg)
274                 raise except_osv(_('Webkit render!'), msg)
275         head_mako_tpl = mako_template(header)
276         try :
277             head = head_mako_tpl.render(helper=helper,
278                                         css=css,
279                                         _=self.translate_call,
280                                         _debug=False,
281                                         **self.parser_instance.localcontext)
282         except Exception, e:
283             raise except_osv(_('Webkit render!'),
284                 exceptions.text_error_template().render())
285         foot = False
286         if footer :
287             foot_mako_tpl = mako_template(footer)
288             try :
289                 foot = foot_mako_tpl.render(helper=helper,
290                                             css=css,
291                                             _=self.translate_call,
292                                             **self.parser_instance.localcontext)
293             except:
294                 msg = exceptions.text_error_template().render()
295                 _logger.error(msg)
296                 raise except_osv(_('Webkit render!'), msg)
297         if report_xml.webkit_debug :
298             try :
299                 deb = head_mako_tpl.render(helper=helper,
300                                            css=css,
301                                            _debug=tools.ustr("\n".join(htmls)),
302                                            _=self.translate_call,
303                                            **self.parser_instance.localcontext)
304             except Exception, e:
305                 msg = exceptions.text_error_template().render()
306                 _logger.error(msg)
307                 raise except_osv(_('Webkit render!'), msg)
308             return (deb, 'html')
309         bin = self.get_lib(cursor, uid)
310         pdf = self.generate_pdf(bin, report_xml, head, foot, htmls)
311         return (pdf, 'pdf')
312
313
314     def create(self, cursor, uid, ids, data, context=None):
315         """We override the create function in order to handle generator
316            Code taken from report openoffice. Thanks guys :) """
317         pool = pooler.get_pool(cursor.dbname)
318         ir_obj = pool.get('ir.actions.report.xml')
319         report_xml_ids = ir_obj.search(cursor, uid,
320                 [('report_name', '=', self.name[7:])], context=context)
321         if report_xml_ids:
322
323             report_xml = ir_obj.browse(cursor,
324                                        uid,
325                                        report_xml_ids[0],
326                                        context=context)
327             report_xml.report_rml = None
328             report_xml.report_rml_content = None
329             report_xml.report_sxw_content_data = None
330             report_rml.report_sxw_content = None
331             report_rml.report_sxw = None
332         else:
333             return super(WebKitParser, self).create(cursor, uid, ids, data, context)
334         if report_xml.report_type != 'webkit' :
335             return super(WebKitParser, self).create(cursor, uid, ids, data, context)
336         result = self.create_source_pdf(cursor, uid, ids, data, report_xml, context)
337         if not result:
338             return (False,False)
339         return result
340
341 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: