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