[IMP]account_analytic_analysis: set ids intes of id for write bcoz in project there...
[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 sys
36 from openerp import report
37 import tempfile
38 import time
39 import logging
40
41 from openerp import netsvc
42 from openerp import pooler
43 from report_helper import WebKitHelper
44 from openerp.report.report_sxw import *
45 from openerp import addons
46 from openerp import tools
47 from openerp.tools.translate import _
48 from openerp.osv.osv import except_osv
49 from urllib import urlencode, quote as quote
50
51 _logger = logging.getLogger(__name__)
52
53 try:
54     # We use a jinja2 sandboxed environment to render mako templates.
55     # Note that the rendering does not cover all the mako syntax, in particular
56     # arbitrary Python statements are not accepted, and not all expressions are
57     # allowed: only "public" attributes (not starting with '_') of objects may
58     # be accessed.
59     # This is done on purpose: it prevents incidental or malicious execution of
60     # Python code that may break the security of the server.
61     from jinja2.sandbox import SandboxedEnvironment
62     mako_template_env = SandboxedEnvironment(
63         block_start_string="<%",
64         block_end_string="%>",
65         variable_start_string="${",
66         variable_end_string="}",
67         comment_start_string="<%doc>",
68         comment_end_string="</%doc>",
69         line_statement_prefix="%",
70         line_comment_prefix="##",
71         trim_blocks=True,               # do not output newline after blocks
72         autoescape=True,                # XML/HTML automatic escaping
73     )
74     mako_template_env.globals.update({
75         'str': str,
76         'quote': quote,
77         'urlencode': urlencode,
78     })
79 except ImportError:
80     _logger.warning("jinja2 not available, templating features will not work!")
81
82 def mako_template(text):
83     """Build a Mako template.
84
85     This template uses UTF-8 encoding
86     """
87
88     return mako_template_env.from_string(text)
89
90 _extender_functions = {}
91
92 def webkit_report_extender(report_name):
93     """
94     A decorator to define functions to extend the context used in a template rendering.
95     report_name must be the xml id of the desired report (it is mandatory to indicate the
96     module in that xml id).
97
98     The given function will be called at the creation of the report. The following arguments
99     will be passed to it (in this order):
100     - pool The model pool.
101     - cr The cursor.
102     - uid The user id.
103     - localcontext The context given to the template engine to render the templates for the
104         current report. This is the context that should be modified.
105     - context The OpenERP context.
106     """
107     def fct1(fct):
108         lst = _extender_functions.get(report_name)
109         if not lst:
110             lst = []
111             _extender_functions[report_name] = lst
112         lst.append(fct)
113         return fct
114     return fct1
115
116 class WebKitParser(report_sxw):
117     """Custom class that use webkit to render HTML reports
118        Code partially taken from report openoffice. Thanks guys :)
119     """
120     def __init__(self, name, table, rml=False, parser=False,
121         header=True, store=False):
122         self.parser_instance = False
123         self.localcontext = {}
124         report_sxw.__init__(self, name, table, rml, parser,
125             header, store)
126
127     def get_lib(self, cursor, uid):
128         """Return the lib wkhtml path"""
129         proxy = self.pool.get('ir.config_parameter')
130         webkit_path = proxy.get_param(cursor, uid, 'webkit_path')
131
132         if not webkit_path:
133             try:
134                 defpath = os.environ.get('PATH', os.defpath).split(os.pathsep)
135                 if hasattr(sys, 'frozen'):
136                     defpath.append(os.getcwd())
137                     if tools.config['root_path']:
138                         defpath.append(os.path.dirname(tools.config['root_path']))
139                 webkit_path = tools.which('wkhtmltopdf', path=os.pathsep.join(defpath))
140             except IOError:
141                 webkit_path = None
142
143         if webkit_path:
144             return webkit_path
145
146         raise except_osv(
147                          _('Wkhtmltopdf library path is not set'),
148                          _('Please install executable on your system' \
149                          ' (sudo apt-get install wkhtmltopdf) or download it from here:' \
150                          ' http://code.google.com/p/wkhtmltopdf/downloads/list and set the' \
151                          ' path in the ir.config_parameter with the webkit_path key.' \
152                          'Minimal version is 0.9.9')
153                         )
154
155     def generate_pdf(self, comm_path, report_xml, header, footer, html_list, webkit_header=False):
156         """Call webkit in order to generate pdf"""
157         if not webkit_header:
158             webkit_header = report_xml.webkit_header
159         tmp_dir = tempfile.gettempdir()
160         out_filename = tempfile.mktemp(suffix=".pdf", prefix="webkit.tmp.")
161         file_to_del = [out_filename]
162         if comm_path:
163             command = [comm_path]
164         else:
165             command = ['wkhtmltopdf']
166
167         command.append('--quiet')
168         # default to UTF-8 encoding.  Use <meta charset="latin-1"> to override.
169         command.extend(['--encoding', 'utf-8'])
170         if header :
171             head_file = file( os.path.join(
172                                   tmp_dir,
173                                   str(time.time()) + '.head.html'
174                                  ),
175                                 'w'
176                             )
177             head_file.write(header)
178             head_file.close()
179             file_to_del.append(head_file.name)
180             command.extend(['--header-html', head_file.name])
181         if footer :
182             foot_file = file(  os.path.join(
183                                   tmp_dir,
184                                   str(time.time()) + '.foot.html'
185                                  ),
186                                 'w'
187                             )
188             foot_file.write(footer)
189             foot_file.close()
190             file_to_del.append(foot_file.name)
191             command.extend(['--footer-html', foot_file.name])
192
193         if webkit_header.margin_top :
194             command.extend(['--margin-top', str(webkit_header.margin_top).replace(',', '.')])
195         if webkit_header.margin_bottom :
196             command.extend(['--margin-bottom', str(webkit_header.margin_bottom).replace(',', '.')])
197         if webkit_header.margin_left :
198             command.extend(['--margin-left', str(webkit_header.margin_left).replace(',', '.')])
199         if webkit_header.margin_right :
200             command.extend(['--margin-right', str(webkit_header.margin_right).replace(',', '.')])
201         if webkit_header.orientation :
202             command.extend(['--orientation', str(webkit_header.orientation).replace(',', '.')])
203         if webkit_header.format :
204             command.extend(['--page-size', str(webkit_header.format).replace(',', '.')])
205         count = 0
206         for html in html_list :
207             html_file = file(os.path.join(tmp_dir, str(time.time()) + str(count) +'.body.html'), 'w')
208             count += 1
209             html_file.write(html)
210             html_file.close()
211             file_to_del.append(html_file.name)
212             command.append(html_file.name)
213         command.append(out_filename)
214         stderr_fd, stderr_path = tempfile.mkstemp(text=True)
215         file_to_del.append(stderr_path)
216         try:
217             status = subprocess.call(command, stderr=stderr_fd)
218             os.close(stderr_fd) # ensure flush before reading
219             stderr_fd = None # avoid closing again in finally block
220             fobj = open(stderr_path, 'r')
221             error_message = fobj.read()
222             fobj.close()
223             if not error_message:
224                 error_message = _('No diagnosis message was provided')
225             else:
226                 error_message = _('The following diagnosis message was provided:\n') + error_message
227             if status :
228                 raise except_osv(_('Webkit error' ),
229                                  _("The command 'wkhtmltopdf' failed with error code = %s. Message: %s") % (status, error_message))
230             pdf_file = open(out_filename, 'rb')
231             pdf = pdf_file.read()
232             pdf_file.close()
233         finally:
234             if stderr_fd is not None:
235                 os.close(stderr_fd)
236             for f_to_del in file_to_del:
237                 try:
238                     os.unlink(f_to_del)
239                 except (OSError, IOError), exc:
240                     _logger.error('cannot remove file %s: %s', f_to_del, exc)
241         return pdf
242
243     def translate_call(self, src):
244         """Translate String."""
245         ir_translation = self.pool.get('ir.translation')
246         res = ir_translation._get_source(self.parser_instance.cr, self.parser_instance.uid,
247                                          None, 'report', self.parser_instance.localcontext.get('lang', 'en_US'), src)
248         if not res :
249             return src
250         return res
251
252     # override needed to keep the attachments storing procedure
253     def create_single_pdf(self, cursor, uid, ids, data, report_xml, context=None):
254         """generate the PDF"""
255
256         # just try to find an xml id for the report
257         cr = cursor
258         import openerp.pooler as pooler
259         pool = pooler.get_pool(cr.dbname)
260         found_xml_ids = pool.get("ir.model.data").search(cr, uid, [["model", "=", "ir.actions.report.xml"], \
261             ["res_id", "=", report_xml.id]], context=context)
262         xml_id = None
263         if found_xml_ids:
264             xml_id = pool.get("ir.model.data").read(cr, uid, found_xml_ids[0], ["module", "name"])
265             xml_id = "%s.%s" % (xml_id["module"], xml_id["name"])
266
267         if context is None:
268             context={}
269         htmls = []
270         if report_xml.report_type != 'webkit':
271             return super(WebKitParser,self).create_single_pdf(cursor, uid, ids, data, report_xml, context=context)
272
273         self.parser_instance = self.parser(cursor,
274                                            uid,
275                                            self.name2,
276                                            context=context)
277
278         self.pool = pooler.get_pool(cursor.dbname)
279         objs = self.getObjects(cursor, uid, ids, context)
280         self.parser_instance.set_context(objs, data, ids, report_xml.report_type)
281
282         template =  False
283
284         if report_xml.report_file :
285             path = addons.get_module_resource(*report_xml.report_file.split(os.path.sep))
286             if path and os.path.exists(path) :
287                 template = file(path).read()
288         if not template and report_xml.report_webkit_data :
289             template =  report_xml.report_webkit_data
290         if not template :
291             raise except_osv(_('Error!'), _('Webkit report template not found!'))
292         header = report_xml.webkit_header.html
293         footer = report_xml.webkit_header.footer_html
294         if not header and report_xml.header:
295             raise except_osv(
296                   _('No header defined for this Webkit report!'),
297                   _('Please set a header in company settings.')
298               )
299         if not report_xml.header :
300             header = ''
301             default_head = addons.get_module_resource('report_webkit', 'default_header.html')
302             with open(default_head,'r') as f:
303                 header = f.read()
304         css = report_xml.webkit_header.css
305         if not css :
306             css = ''
307
308         body_mako_tpl = mako_template(template)
309         helper = WebKitHelper(cursor, uid, report_xml.id, context)
310         self.parser_instance.localcontext['helper'] = helper
311         self.parser_instance.localcontext['css'] = css
312         self.parser_instance.localcontext['_'] = self.translate_call
313
314         # apply extender functions
315         additional = {}
316         if xml_id in _extender_functions:
317             for fct in _extender_functions[xml_id]:
318                 fct(pool, cr, uid, self.parser_instance.localcontext, context)
319
320         if report_xml.precise_mode:
321             ctx = dict(self.parser_instance.localcontext)
322             for obj in self.parser_instance.localcontext['objects']:
323                 ctx['objects'] = [obj]
324                 try :
325                     html = body_mako_tpl.render(dict(ctx))
326                     htmls.append(html)
327                 except Exception, e:
328                     msg = u"%s" % e
329                     _logger.error(msg)
330                     raise except_osv(_('Webkit render!'), msg)
331         else:
332             try :
333                 html = body_mako_tpl.render(dict(self.parser_instance.localcontext))
334                 htmls.append(html)
335             except Exception:
336                 msg = u"%s" % e
337                 _logger.error(msg)
338                 raise except_osv(_('Webkit render!'), msg)
339         head_mako_tpl = mako_template(header)
340         try :
341             head = head_mako_tpl.render(dict(self.parser_instance.localcontext, _debug=False))
342         except Exception, e:
343             raise except_osv(_('Webkit render!'), u"%s" % e)
344         foot = False
345         if footer :
346             foot_mako_tpl = mako_template(footer)
347             try :
348                 foot = foot_mako_tpl.render(dict({},
349                                             **self.parser_instance.localcontext))
350             except Exception, e:
351                 msg = u"%s" % e
352                 _logger.error(msg)
353                 raise except_osv(_('Webkit render!'), msg)
354         if report_xml.webkit_debug :
355             try :
356                 deb = head_mako_tpl.render(dict(self.parser_instance.localcontext, _debug=tools.ustr("\n".join(htmls))))
357             except Exception, e:
358                 msg = u"%s" % e
359                 _logger.error(msg)
360                 raise except_osv(_('Webkit render!'), msg)
361             return (deb, 'html')
362         bin = self.get_lib(cursor, uid)
363         pdf = self.generate_pdf(bin, report_xml, head, foot, htmls)
364         return (pdf, 'pdf')
365
366
367     def create(self, cursor, uid, ids, data, context=None):
368         """We override the create function in order to handle generator
369            Code taken from report openoffice. Thanks guys :) """
370         pool = pooler.get_pool(cursor.dbname)
371         ir_obj = pool.get('ir.actions.report.xml')
372         report_xml_ids = ir_obj.search(cursor, uid,
373                 [('report_name', '=', self.name[7:])], context=context)
374         if report_xml_ids:
375
376             report_xml = ir_obj.browse(cursor,
377                                        uid,
378                                        report_xml_ids[0],
379                                        context=context)
380             report_xml.report_rml = None
381             report_xml.report_rml_content = None
382             report_xml.report_sxw_content_data = None
383             report_xml.report_sxw_content = None
384             report_xml.report_sxw = None
385         else:
386             return super(WebKitParser, self).create(cursor, uid, ids, data, context)
387         if report_xml.report_type != 'webkit' :
388             return super(WebKitParser, self).create(cursor, uid, ids, data, context)
389         result = self.create_source_pdf(cursor, uid, ids, data, report_xml, context)
390         if not result:
391             return (False,False)
392         return result
393
394 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: