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