[FIX] edi: the final semicolon in an openerp-web module is not optional, and breaks...
[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
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
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, company) :
77         """Return the lib wkhtml path"""
78         #TODO Detect lib in system first
79         path = self.pool.get('res.company').read(cursor, uid, company, ['lib_path',])
80         path = path['lib_path']
81         if not path:
82             raise except_osv(
83                              _('Wkhtmltopdf library path is not set in company'),
84                              _('Please install executable on your system'+
85                              ' (sudo apt-get install wkhtmltopdf) or download it from here:'+
86                              ' http://code.google.com/p/wkhtmltopdf/downloads/list and set the'+
87                              ' path to the executable on the Company form.'+
88                              'Minimal version is 0.9.9')
89                             )
90         if os.path.isabs(path) :
91             if (os.path.exists(path) and os.access(path, os.X_OK)\
92                 and os.path.basename(path).startswith('wkhtmltopdf')):
93                 return path
94             else:
95                 raise except_osv(
96                                 _('Wrong Wkhtmltopdf path set in company'+
97                                 'Given path is not executable or path is wrong'),
98                                 'for path %s'%(path)
99                                 )
100         else :
101             raise except_osv(
102                             _('path to Wkhtmltopdf is not absolute'),
103                             'for path %s'%(path)
104                             )
105     def generate_pdf(self, comm_path, report_xml, header, footer, html_list, webkit_header=False):
106         """Call webkit in order to generate pdf"""
107         if not webkit_header:
108             webkit_header = report_xml.webkit_header
109         tmp_dir = tempfile.gettempdir()
110         out = report_xml.name+str(time.time())+'.pdf'
111         out = os.path.join(tmp_dir, out.replace(' ',''))
112         files = []
113         file_to_del = []
114         if comm_path:
115             command = [comm_path]
116         else:
117             command = ['wkhtmltopdf']
118
119         command.append('--quiet')
120         # default to UTF-8 encoding.  Use <meta charset="latin-1"> to override.
121         command.extend(['--encoding', 'utf-8'])
122         if header :
123             head_file = file( os.path.join(
124                                   tmp_dir,
125                                   str(time.time()) + '.head.html'
126                                  ),
127                                 'w'
128                             )
129             head_file.write(header)
130             head_file.close()
131             file_to_del.append(head_file.name)
132             command.extend(['--header-html', head_file.name])
133         if footer :
134             foot_file = file(  os.path.join(
135                                   tmp_dir,
136                                   str(time.time()) + '.foot.html'
137                                  ),
138                                 'w'
139                             )
140             foot_file.write(footer)
141             foot_file.close()
142             file_to_del.append(foot_file.name)
143             command.extend(['--footer-html', foot_file.name])
144
145         if webkit_header.margin_top :
146             command.extend(['--margin-top', str(webkit_header.margin_top).replace(',', '.')])
147         if webkit_header.margin_bottom :
148             command.extend(['--margin-bottom', str(webkit_header.margin_bottom).replace(',', '.')])
149         if webkit_header.margin_left :
150             command.extend(['--margin-left', str(webkit_header.margin_left).replace(',', '.')])
151         if webkit_header.margin_right :
152             command.extend(['--margin-right', str(webkit_header.margin_right).replace(',', '.')])
153         if webkit_header.orientation :
154             command.extend(['--orientation', str(webkit_header.orientation).replace(',', '.')])
155         if webkit_header.format :
156             command.extend(['--page-size', str(webkit_header.format).replace(',', '.')])
157         count = 0
158         for html in html_list :
159             html_file = file(os.path.join(tmp_dir, str(time.time()) + str(count) +'.body.html'), 'w')
160             count += 1
161             html_file.write(html)
162             html_file.close()
163             file_to_del.append(html_file.name)
164             command.append(html_file.name)
165         command.append(out)
166         generate_command = ' '.join(command)
167         try:
168             status = subprocess.call(command, stderr=subprocess.PIPE) # ignore stderr
169             if status :
170                 raise except_osv(
171                                 _('Webkit raise an error' ),
172                                 status
173                             )
174         except Exception:
175             for f_to_del in file_to_del :
176                 os.unlink(f_to_del)
177
178         pdf = file(out, 'rb').read()
179         for f_to_del in file_to_del :
180             os.unlink(f_to_del)
181
182         os.unlink(out)
183         return pdf
184
185     def translate_call(self, src):
186         """Translate String."""
187         ir_translation = self.pool.get('ir.translation')
188         res = ir_translation._get_source(self.parser_instance.cr, self.parser_instance.uid,
189                                          self.name, 'report', self.parser_instance.localcontext.get('lang', 'en_US'), src)
190         if not res :
191             return src
192         return res
193
194     # override needed to keep the attachments storing procedure
195     def create_single_pdf(self, cursor, uid, ids, data, report_xml, context=None):
196         """generate the PDF"""
197
198         if context is None:
199             context={}
200         htmls = []
201         if report_xml.report_type != 'webkit':
202             return super(WebKitParser,self).create_single_pdf(cursor, uid, ids, data, report_xml, context=context)
203
204         self.parser_instance = self.parser(cursor,
205                                            uid,
206                                            self.name2,
207                                            context=context)
208
209         self.pool = pooler.get_pool(cursor.dbname)
210         objs = self.getObjects(cursor, uid, ids, context)
211         self.parser_instance.set_context(objs, data, ids, report_xml.report_type)
212
213         template =  False
214
215         if report_xml.report_file :
216             path = addons.get_module_resource(report_xml.report_file)
217             if os.path.exists(path) :
218                 template = file(path).read()
219         if not template and report_xml.report_webkit_data :
220             template =  report_xml.report_webkit_data
221         if not template :
222             raise except_osv(_('Error!'), _('Webkit Report template not found !'))
223         header = report_xml.webkit_header.html
224         footer = report_xml.webkit_header.footer_html
225         if not header and report_xml.header:
226             raise except_osv(
227                   _('No header defined for this Webkit report!'),
228                   _('Please set a header in company settings')
229               )
230         if not report_xml.header :
231             header = ''
232             default_head = addons.get_module_resource('report_webkit', 'default_header.html')
233             with open(default_head,'r') as f:
234                 header = f.read()
235         css = report_xml.webkit_header.css
236         if not css :
237             css = ''
238         user = self.pool.get('res.users').browse(cursor, uid, uid)
239         company= user.company_id
240
241         #default_filters=['unicode', 'entity'] can be used to set global filter
242         body_mako_tpl = mako_template(template)
243         helper = WebKitHelper(cursor, uid, report_xml.id, context)
244         if report_xml.precise_mode:
245             for obj in objs:
246                 self.parser_instance.localcontext['objects'] = [obj]
247                 try :
248                     html = body_mako_tpl.render(helper=helper,
249                                                 css=css,
250                                                 _=self.translate_call,
251                                                 **self.parser_instance.localcontext)
252                     htmls.append(html)
253                 except Exception, e:
254                     msg = exceptions.text_error_template().render()
255                     logger.error(msg)
256                     raise except_osv(_('Webkit render'), msg)
257         else:
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, e:
265                 msg = exceptions.text_error_template().render()
266                 logger.error(msg)
267                 raise except_osv(_('Webkit render'), msg)
268         head_mako_tpl = mako_template(header)
269         try :
270             head = head_mako_tpl.render(helper=helper,
271                                         css=css,
272                                         _=self.translate_call,
273                                         _debug=False,
274                                         **self.parser_instance.localcontext)
275         except Exception, e:
276             raise except_osv(_('Webkit render'),
277                 exceptions.text_error_template().render())
278         foot = False
279         if footer :
280             foot_mako_tpl = mako_template(footer)
281             try :
282                 foot = foot_mako_tpl.render(helper=helper,
283                                             css=css,
284                                             _=self.translate_call,
285                                             **self.parser_instance.localcontext)
286             except:
287                 msg = exceptions.text_error_template().render()
288                 logger.error(msg)
289                 raise except_osv(_('Webkit render'), msg)
290         if report_xml.webkit_debug :
291             try :
292                 deb = head_mako_tpl.render(helper=helper,
293                                            css=css,
294                                            _debug=tools.ustr("\n".join(htmls)),
295                                            _=self.translate_call,
296                                            **self.parser_instance.localcontext)
297             except Exception, e:
298                 msg = exceptions.text_error_template().render()
299                 logger.error(msg)
300                 raise except_osv(_('Webkit render'), msg)
301             return (deb, 'html')
302         bin = self.get_lib(cursor, uid, company.id)
303         pdf = self.generate_pdf(bin, report_xml, head, foot, htmls)
304         return (pdf, 'pdf')
305
306
307     def create(self, cursor, uid, ids, data, context=None):
308         """We override the create function in order to handle generator
309            Code taken from report openoffice. Thanks guys :) """
310         pool = pooler.get_pool(cursor.dbname)
311         ir_obj = pool.get('ir.actions.report.xml')
312         report_xml_ids = ir_obj.search(cursor, uid,
313                 [('report_name', '=', self.name[7:])], context=context)
314         if report_xml_ids:
315
316             report_xml = ir_obj.browse(cursor,
317                                        uid,
318                                        report_xml_ids[0],
319                                        context=context)
320             report_xml.report_rml = None
321             report_xml.report_rml_content = None
322             report_xml.report_sxw_content_data = None
323             report_rml.report_sxw_content = None
324             report_rml.report_sxw = None
325         else:
326             return super(WebKitParser, self).create(cursor, uid, ids, data, context)
327         if report_xml.report_type != 'webkit' :
328             return super(WebKitParser, self).create(cursor, uid, ids, data, context)
329         result = self.create_source_pdf(cursor, uid, ids, data, report_xml, context)
330         if not result:
331             return (False,False)
332         return result
333
334 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: