[MERGE] merge from trunk addons
[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
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):
88         """Call webkit in order to generate pdf"""
89         tmp_dir = tempfile.gettempdir()
90         out = report_xml.name+str(time.time())+'.pdf'
91         out = os.path.join(tmp_dir, out.replace(' ',''))
92         files = []
93         file_to_del = []
94         if comm_path:
95             command = [comm_path]
96         else:
97             command = ['wkhtmltopdf']
98                 
99         command.append('-q')
100         if header :
101             head_file = file( os.path.join(
102                                   tmp_dir,
103                                   str(time.time()) + '.head.html'
104                                  ), 
105                                 'w'
106                             )
107             head_file.write(header)
108             head_file.close()
109             file_to_del.append(head_file.name)
110             command.append("--header-html '%s'"%(head_file.name))
111         if footer :
112             foot_file = file(  os.path.join(
113                                   tmp_dir,
114                                   str(time.time()) + '.foot.html'
115                                  ), 
116                                 'w'
117                             )
118             foot_file.write(footer)
119             foot_file.close()
120             file_to_del.append(foot_file.name)
121             command.append("--footer-html '%s'"%(foot_file.name))
122             
123         if report_xml.webkit_header.margin_top :
124             command.append('--margin-top %s'%(report_xml.webkit_header.margin_top))
125         if report_xml.webkit_header.margin_bottom :
126             command.append('--margin-bottom %s'%(report_xml.webkit_header.margin_bottom))
127         if report_xml.webkit_header.margin_left :
128             command.append('--margin-left %s'%(report_xml.webkit_header.margin_left))
129         if report_xml.webkit_header.margin_right :
130             command.append('--margin-right %s'%(report_xml.webkit_header.margin_right))
131         if report_xml.webkit_header.orientation :
132             command.append("--orientation '%s'"%(report_xml.webkit_header.orientation))
133         if report_xml.webkit_header.format :
134             command.append(" --page-size '%s'"%(report_xml.webkit_header.format))
135         for html in html_list :
136             html_file = file(os.path.join(tmp_dir, str(time.time()) + '.body.html'), 'w')
137             html_file.write(html)
138             html_file.close()
139             file_to_del.append(html_file.name)
140             command.append(html_file.name)
141         command.append(out)
142         generate_command = ' '.join(command)
143         try:
144             status = commands.getstatusoutput(generate_command)
145             if status[0] :
146                 raise except_osv(
147                                 _('Webkit raise an error' ), 
148                                 status[1]
149                             )
150         except Exception:
151             for f_to_del in file_to_del :
152                 os.unlink(f_to_del)
153
154         pdf = file(out).read()
155         for f_to_del in file_to_del :
156             os.unlink(f_to_del)
157
158         os.unlink(out)
159         return pdf
160     
161     
162     def setLang(self, lang):
163         if not lang:
164             lang = 'en_US'
165         self.localcontext['lang'] = lang
166
167     def translate_call(self, src):
168         """Translate String."""
169         ir_translation = self.pool.get('ir.translation')
170         res = ir_translation._get_source(self.parser_instance.cr, self.parser_instance.uid, self.name, 'report', self.localcontext.get('lang', 'en_US'), src)
171         return res 
172  
173     def formatLang(self, value, digits=None, date=False, date_time=False, grouping=True, monetary=False):
174         """format using the know cursor, language from localcontext"""
175         if digits is None:
176             digits = self.parser_instance.get_digits(value)
177         if isinstance(value, (str, unicode)) and not value:
178             return ''
179         pool_lang = self.pool.get('res.lang')
180         lang = self.localcontext['lang']
181         
182         lang_ids = pool_lang.search(self.parser_instance.cr, self.parser_instance.uid, [('code','=',lang)])[0]
183         lang_obj = pool_lang.browse(self.parser_instance.cr, self.parser_instance.uid, lang_ids)
184
185         if date or date_time:
186             if not str(value):
187                 return ''
188
189             date_format = lang_obj.date_format
190             parse_format = '%Y-%m-%d'
191             if date_time:
192                 value=value.split('.')[0]
193                 date_format = date_format + " " + lang_obj.time_format
194                 parse_format = '%Y-%m-%d %H:%M:%S'
195             if not isinstance(value, time.struct_time):
196                 return time.strftime(date_format, time.strptime(value, parse_format))
197
198             else:
199                 date = datetime(*value.timetuple()[:6])
200             return date.strftime(date_format)
201
202         return lang_obj.format('%.' + str(digits) + 'f', value, grouping=grouping, monetary=monetary)
203
204     # override needed to keep the attachments' storing procedure
205     def create_single_pdf(self, cursor, uid, ids, data, report_xml, context=None):
206         """generate the PDF"""
207         
208         if context is None:
209             context={}
210             
211         if report_xml.report_type != 'webkit':
212             return super(WebKitParser,self).create_single_pdf(cursor, uid, ids, data, report_xml, context=context)
213
214         self.parser_instance = self.parser(
215                                             cursor, 
216                                             uid, 
217                                             self.name2, 
218                                             context=context
219                                         )
220
221         self.pool = pooler.get_pool(cursor.dbname)
222         objs = self.getObjects(cursor, uid, ids, context)
223         self.parser_instance.set_context(objs, data, ids, report_xml.report_type)
224
225         template =  False
226
227         if report_xml.report_file :
228             path = addons.get_module_resource(report_xml.report_file)
229             if os.path.exists(path) :
230                 template = file(path).read()
231         if not template and report_xml.report_webkit_data :
232             template =  report_xml.report_webkit_data
233         if not template :
234             raise except_osv(_('Webkit Report template not found !'), _(''))
235         header = report_xml.webkit_header.html
236         footer = report_xml.webkit_header.footer_html
237         if not header and report_xml.header:
238           raise except_osv(
239                 _('No header defined for this Webkit report!'),
240                 _('Please set a header in company settings')
241             )
242         if not report_xml.header :
243             #I know it could be cleaner ...
244             header = u"""
245 <html>
246     <head>
247         <style type="text/css"> 
248             ${css}
249         </style>
250         <script>
251         function subst() {
252            var vars={};
253            var x=document.location.search.substring(1).split('&');
254            for(var i in x) {var z=x[i].split('=',2);vars[z[0]] = unescape(z[1]);}
255            var x=['frompage','topage','page','webpage','section','subsection','subsubsection'];
256            for(var i in x) {
257              var y = document.getElementsByClassName(x[i]);
258              for(var j=0; j<y.length; ++j) y[j].textContent = vars[x[i]];
259            }
260          }
261         </script>
262     </head>
263 <body style="border:0; margin: 0;" onload="subst()">
264 </body>
265 </html>"""
266         css = report_xml.webkit_header.css
267         if not css :
268             css = ''
269         user = self.pool.get('res.users').browse(cursor, uid, uid)
270         company= user.company_id
271         
272         #default_filters=['unicode', 'entity'] can be used to set global filter
273         body_mako_tpl = Template(template ,input_encoding='utf-8')
274         helper = WebKitHelper(cursor, uid, report_xml.id, context)
275         html = body_mako_tpl.render(     helper=helper, 
276                                          css=css,
277                                          _=self.translate_call,
278                                          **self.parser_instance.localcontext
279                                          )
280         head_mako_tpl = Template(header, input_encoding='utf-8')
281         head = head_mako_tpl.render(
282                                     company=company, 
283                                     time=time, 
284                                     helper=helper, 
285                                     css=css,
286                                     formatLang=self.formatLang,
287                                     setLang=self.setLang,
288                                     _=self.translate_call,
289                                     _debug=False
290                                 )
291         foot = False
292         if footer :
293             foot_mako_tpl = Template(footer ,input_encoding='utf-8')
294             foot = foot_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                                         )
303         if report_xml.webkit_debug :
304             deb = head_mako_tpl.render(
305                                         company=company, 
306                                         time=time, 
307                                         helper=helper, 
308                                         css=css, 
309                                         _debug=html,
310                                         formatLang=self.formatLang,
311                                         setLang=self.setLang,
312                                         _=self.translate_call,
313                                         )
314             return (deb, 'html')
315         bin = self.get_lib(cursor, uid, company.id)
316         pdf = self.generate_pdf(bin, report_xml, head, foot, [html])
317         return (pdf, 'pdf')
318
319
320     def create(self, cursor, uid, ids, data, context=None):
321         """We override the create function in order to handle generator
322            Code taken from report openoffice. Thanks guys :) """
323         pool = pooler.get_pool(cursor.dbname)
324         ir_obj = pool.get('ir.actions.report.xml')
325         report_xml_ids = ir_obj.search(cursor, uid,
326                 [('report_name', '=', self.name[7:])], context=context)
327         if report_xml_ids:
328             
329             report_xml = ir_obj.browse(
330                                         cursor, 
331                                         uid, 
332                                         report_xml_ids[0], 
333                                         context=context
334                                     )
335             report_xml.report_rml = None
336             report_xml.report_rml_content = None
337             report_xml.report_sxw_content_data = None
338             report_rml.report_sxw_content = None
339             report_rml.report_sxw = None
340         else:
341             return super(WebKitParser, self).create(cursor, uid, ids, data, context)
342         if report_xml.report_type != 'webkit' :
343             return super(WebKitParser, self).create(cursor, uid, ids, data, context)
344         result = self.create_source_pdf(cursor, uid, ids, data, report_xml, context)
345         if not result:
346             return (False,False)
347         return result