[FIX] report_webkit: don't display warning message about non-patched qt.
[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 #
9 # WARNING: This program as such is intended to be used by professional
10 # programmers who take the whole responsability of assessing all potential
11 # consequences resulting from its eventual inadequacies and bugs
12 # End users who are looking for a ready-to-use solution with commercial
13 # garantees and support are strongly adviced to contract a Free Software
14 # Service Company
15 #
16 # This program is Free Software; you can redistribute it and/or
17 # modify it under the terms of the GNU General Public License
18 # as published by the Free Software Foundation; either version 2
19 # of the License, or (at your option) any later version.
20 #
21 # This program is distributed in the hope that it will be useful,
22 # but WITHOUT ANY WARRANTY; without even the implied warranty of
23 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24 # GNU General Public License for more details.
25 #
26 # You should have received a copy of the GNU General Public License
27 # along with this program; if not, write to the Free Software
28 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
29 #
30 ##############################################################################
31
32 import commands
33 import subprocess
34 import os
35 import report
36 import tempfile
37 import time
38 from mako.template import Template
39 from mako import exceptions
40 import netsvc
41 import pooler
42 from report_helper import WebKitHelper
43 from report.report_sxw import *
44 import addons
45 from tools.translate import _
46 from osv.osv import except_osv
47
48 class WebKitParser(report_sxw):
49     """Custom class that use webkit to render HTML reports
50        Code partially taken from report openoffice. Thanks guys :)
51     """
52     
53     def __init__(self, name, table, rml=False, parser=False, 
54         header=True, store=False):
55         self.parser_instance = False
56         self.localcontext={}
57         report_sxw.__init__(self, name, table, rml, parser, 
58             header, store)
59
60     def get_lib(self, cursor, uid, company) :
61         """Return the lib wkhtml path"""
62         #TODO Detect lib in system first
63         path = self.pool.get('res.company').read(cursor, uid, company, ['lib_path',])
64         path = path['lib_path']
65         if not path:
66             raise except_osv(
67                              _('Wkhtmltopdf library path is not set in company'),
68                              _('Please install executable on your system'+
69                              ' (sudo apt-get install wkhtmltopdf) or download it from here:'+
70                              ' http://code.google.com/p/wkhtmltopdf/downloads/list and set the'+
71                              ' path to the executable on the Company form.')
72                             ) 
73         if os.path.isabs(path) :
74             if (os.path.exists(path) and os.access(path, os.X_OK)\
75                 and os.path.basename(path).startswith('wkhtmltopdf')):
76                 return path
77             else:
78                 raise except_osv(
79                                 _('Wrong Wkhtmltopdf path set in company'+
80                                 'Given path is not executable or path is wrong'),
81                                 'for path %s'%(path)
82                                 )
83         else :
84             raise except_osv(
85                             _('path to Wkhtmltopdf is not absolute'),
86                             'for path %s'%(path)
87                             )
88     def generate_pdf(self, comm_path, report_xml, header, footer, html_list, webkit_header=False):
89         """Call webkit in order to generate pdf"""
90         if not webkit_header:
91             webkit_header = report_xml.webkit_header
92         tmp_dir = tempfile.gettempdir()
93         out = report_xml.name+str(time.time())+'.pdf'
94         out = os.path.join(tmp_dir, out.replace(' ',''))
95         files = []
96         file_to_del = []
97         if comm_path:
98             command = [comm_path]
99         else:
100             command = ['wkhtmltopdf']
101
102         command.append('-q')
103         if header :
104             head_file = file( os.path.join(
105                                   tmp_dir,
106                                   str(time.time()) + '.head.html'
107                                  ), 
108                                 'w'
109                             )
110             head_file.write(header)
111             head_file.close()
112             file_to_del.append(head_file.name)
113             command.extend(['--header-html', head_file.name])
114         if footer :
115             foot_file = file(  os.path.join(
116                                   tmp_dir,
117                                   str(time.time()) + '.foot.html'
118                                  ), 
119                                 'w'
120                             )
121             foot_file.write(footer)
122             foot_file.close()
123             file_to_del.append(foot_file.name)
124             command.extend(['--footer-html', foot_file.name])
125             
126         if webkit_header.margin_top :
127             command.extend(['--margin-top', str(webkit_header.margin_top).replace(',', '.')])
128         if webkit_header.margin_bottom :
129             command.extend(['--margin-bottom', str(webkit_header.margin_bottom).replace(',', '.')])
130         if webkit_header.margin_left :
131             command.extend(['--margin-left', str(webkit_header.margin_left).replace(',', '.')])
132         if webkit_header.margin_right :
133             command.extend(['--margin-right', str(webkit_header.margin_right).replace(',', '.')])
134         if webkit_header.orientation :
135             command.extend(['--orientation', str(webkit_header.orientation).replace(',', '.')])
136         if webkit_header.format :
137             command.extend(['--page-size', str(webkit_header.format).replace(',', '.')])
138         count = 0
139         for html in html_list :
140             html_file = file(os.path.join(tmp_dir, str(time.time()) + str(count) +'.body.html'), 'w')
141             count += 1
142             html_file.write(html)
143             html_file.close()
144             file_to_del.append(html_file.name)
145             command.append(html_file.name)
146         command.append(out)
147         generate_command = ' '.join(command)
148         try:
149             status = subprocess.call(command, stderr=subprocess.PIPE) # ignore stderr
150             if status :
151                 raise except_osv(
152                                 _('Webkit raise an error' ), 
153                                 status
154                             )
155         except Exception:
156             for f_to_del in file_to_del :
157                 os.unlink(f_to_del)
158
159         pdf = file(out, 'rb').read()
160         for f_to_del in file_to_del :
161             os.unlink(f_to_del)
162
163         os.unlink(out)
164         return pdf
165     
166     
167     def setLang(self, lang):
168         if not lang:
169             lang = 'en_US'
170         self.localcontext['lang'] = lang
171
172     def translate_call(self, src):
173         """Translate String."""
174         ir_translation = self.pool.get('ir.translation')
175         res = ir_translation._get_source(self.parser_instance.cr, self.parser_instance.uid, self.name, 'report', self.localcontext.get('lang', 'en_US'), src)
176         if not res :
177             return src
178         return res 
179  
180     def formatLang(self, value, digits=None, date=False, date_time=False, grouping=True, monetary=False):
181         """format using the know cursor, language from localcontext"""
182         if digits is None:
183             digits = self.parser_instance.get_digits(value)
184         if isinstance(value, (str, unicode)) and not value:
185             return ''
186         pool_lang = self.pool.get('res.lang')
187         lang = self.localcontext['lang']
188         
189         lang_ids = pool_lang.search(self.parser_instance.cr, self.parser_instance.uid, [('code','=',lang)])[0]
190         lang_obj = pool_lang.browse(self.parser_instance.cr, self.parser_instance.uid, lang_ids)
191
192         if date or date_time:
193             if not str(value):
194                 return ''
195
196             date_format = lang_obj.date_format
197             parse_format = '%Y-%m-%d'
198             if date_time:
199                 value=value.split('.')[0]
200                 date_format = date_format + " " + lang_obj.time_format
201                 parse_format = '%Y-%m-%d %H:%M:%S'
202             if not isinstance(value, time.struct_time):
203                 return time.strftime(date_format, time.strptime(value, parse_format))
204
205             else:
206                 date = datetime(*value.timetuple()[:6])
207             return date.strftime(date_format)
208
209         return lang_obj.format('%.' + str(digits) + 'f', value, grouping=grouping, monetary=monetary)
210
211     # override needed to keep the attachments' storing procedure
212     def create_single_pdf(self, cursor, uid, ids, data, report_xml, context=None):
213         """generate the PDF"""
214         
215         if context is None:
216             context={}
217
218         if report_xml.report_type != 'webkit':
219             return super(WebKitParser,self).create_single_pdf(cursor, uid, ids, data, report_xml, context=context)
220
221         self.parser_instance = self.parser(
222                                             cursor,
223                                             uid,
224                                             self.name2,
225                                             context=context
226                                         )
227
228         self.pool = pooler.get_pool(cursor.dbname)
229         objs = self.getObjects(cursor, uid, ids, context)
230         self.parser_instance.set_context(objs, data, ids, report_xml.report_type)
231
232         template =  False
233
234         if report_xml.report_file :
235             path = addons.get_module_resource(report_xml.report_file)
236             if os.path.exists(path) :
237                 template = file(path).read()
238         if not template and report_xml.report_webkit_data :
239             template =  report_xml.report_webkit_data
240         if not template :
241             raise except_osv(_('Error!'), _('Webkit Report template not found !'))
242         header = report_xml.webkit_header.html
243         footer = report_xml.webkit_header.footer_html
244         if not header and report_xml.header:
245           raise except_osv(
246                 _('No header defined for this Webkit report!'),
247                 _('Please set a header in company settings')
248             )
249         if not report_xml.header :
250             #I know it could be cleaner ...
251             header = u"""
252 <html>
253     <head>
254         <style type="text/css"> 
255             ${css}
256         </style>
257         <script>
258         function subst() {
259            var vars={};
260            var x=document.location.search.substring(1).split('&');
261            for(var i in x) {var z=x[i].split('=',2);vars[z[0]] = unescape(z[1]);}
262            var x=['frompage','topage','page','webpage','section','subsection','subsubsection'];
263            for(var i in x) {
264              var y = document.getElementsByClassName(x[i]);
265              for(var j=0; j<y.length; ++j) y[j].textContent = vars[x[i]];
266            }
267          }
268         </script>
269     </head>
270 <body style="border:0; margin: 0;" onload="subst()">
271 </body>
272 </html>"""
273         css = report_xml.webkit_header.css
274         if not css :
275             css = ''
276         user = self.pool.get('res.users').browse(cursor, uid, uid)
277         company= user.company_id
278         
279         #default_filters=['unicode', 'entity'] can be used to set global filter
280         body_mako_tpl = Template(template ,input_encoding='utf-8')
281         helper = WebKitHelper(cursor, uid, report_xml.id, context)
282         try :
283             html = body_mako_tpl.render(     helper=helper,
284                                              css=css,
285                                              _=self.translate_call,
286                                              **self.parser_instance.localcontext
287                                         )
288         except Exception, e:
289             msg = exceptions.text_error_template().render()
290             netsvc.Logger().notifyChannel('Webkit render', netsvc.LOG_ERROR, msg)
291             raise except_osv(_('Webkit render'), msg)
292         head_mako_tpl = Template(header, input_encoding='utf-8')
293         try :
294             head = head_mako_tpl.render(
295                                         company=company,
296                                         time=time,
297                                         helper=helper,
298                                         css=css,
299                                         formatLang=self.formatLang,
300                                         setLang=self.setLang,
301                                         _=self.translate_call,
302                                         _debug=False
303                                     )
304         except Exception, e:
305             raise except_osv(_('Webkit render'),
306                 exceptions.text_error_template().render())
307         foot = False
308         if footer :
309             foot_mako_tpl = Template(footer ,input_encoding='utf-8')
310             try :
311                 foot = foot_mako_tpl.render(
312                                             company=company,
313                                             time=time,
314                                             helper=helper,
315                                             css=css,
316                                             formatLang=self.formatLang,
317                                             setLang=self.setLang,
318                                             _=self.translate_call,
319                                             )
320             except:
321                 msg = exceptions.text_error_template().render()
322                 netsvc.Logger().notifyChannel('Webkit render', netsvc.LOG_ERROR, msg)
323                 raise except_osv(_('Webkit render'), msg)
324         if report_xml.webkit_debug :
325             try :
326                 deb = head_mako_tpl.render(
327                                             company=company,
328                                             time=time,
329                                             helper=helper,
330                                             css=css,
331                                             _debug=html,
332                                             formatLang=self.formatLang,
333                                             setLang=self.setLang,
334                                             _=self.translate_call,
335                                             )
336             except Exception, e:
337                 msg = exceptions.text_error_template().render()
338                 netsvc.Logger().notifyChannel('Webkit render', netsvc.LOG_ERROR, msg)
339                 raise except_osv(_('Webkit render'), msg)
340             return (deb, 'html')
341         bin = self.get_lib(cursor, uid, company.id)
342         pdf = self.generate_pdf(bin, report_xml, head, foot, [html])
343         return (pdf, 'pdf')
344
345
346     def create(self, cursor, uid, ids, data, context=None):
347         """We override the create function in order to handle generator
348            Code taken from report openoffice. Thanks guys :) """
349         pool = pooler.get_pool(cursor.dbname)
350         ir_obj = pool.get('ir.actions.report.xml')
351         report_xml_ids = ir_obj.search(cursor, uid,
352                 [('report_name', '=', self.name[7:])], context=context)
353         if report_xml_ids:
354             
355             report_xml = ir_obj.browse(
356                                         cursor, 
357                                         uid, 
358                                         report_xml_ids[0], 
359                                         context=context
360                                     )
361             report_xml.report_rml = None
362             report_xml.report_rml_content = None
363             report_xml.report_sxw_content_data = None
364             report_rml.report_sxw_content = None
365             report_rml.report_sxw = None
366         else:
367             return super(WebKitParser, self).create(cursor, uid, ids, data, context)
368         if report_xml.report_type != 'webkit' :
369             return super(WebKitParser, self).create(cursor, uid, ids, data, context)
370         result = self.create_source_pdf(cursor, uid, ids, data, report_xml, context)
371         if not result:
372             return (False,False)
373         return result