1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # Copyright (c) 2010 Camptocamp SA (http://www.camptocamp.com)
7 # Author : Nicolas Bessi (Camptocamp)
8 # Contributor(s) : Florent Xicluna (Wingo SA)
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
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.
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.
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
31 ##############################################################################
36 from openerp import report
41 from mako.template import Template
42 from mako.lookup import TemplateLookup
43 from mako import exceptions
45 from openerp import netsvc
46 from openerp import pooler
47 from report_helper import WebKitHelper
48 from openerp.report.report_sxw import *
49 from openerp import addons
50 from openerp import tools
51 from openerp.tools.translate import _
52 from openerp.osv.osv import except_osv
54 _logger = logging.getLogger(__name__)
56 def mako_template(text):
57 """Build a Mako template.
59 This template uses UTF-8 encoding
61 tmp_lookup = TemplateLookup() #we need it in order to allow inclusion and inheritance
62 return Template(text, input_encoding='utf-8', output_encoding='utf-8', lookup=tmp_lookup)
65 class WebKitParser(report_sxw):
66 """Custom class that use webkit to render HTML reports
67 Code partially taken from report openoffice. Thanks guys :)
69 def __init__(self, name, table, rml=False, parser=False,
70 header=True, store=False):
71 self.parser_instance = False
72 self.localcontext = {}
73 report_sxw.__init__(self, name, table, rml, parser,
76 def get_lib(self, cursor, uid):
77 """Return the lib wkhtml path"""
78 proxy = self.pool.get('ir.config_parameter')
79 webkit_path = proxy.get_param(cursor, uid, 'webkit_path')
83 defpath = os.environ.get('PATH', os.defpath).split(os.pathsep)
84 if hasattr(sys, 'frozen'):
85 defpath.append(os.getcwd())
86 if tools.config['root_path']:
87 defpath.append(os.path.dirname(tools.config['root_path']))
88 webkit_path = tools.which('wkhtmltopdf', path=os.pathsep.join(defpath))
96 _('Wkhtmltopdf library path is not set'),
97 _('Please install executable on your system' \
98 ' (sudo apt-get install wkhtmltopdf) or download it from here:' \
99 ' http://code.google.com/p/wkhtmltopdf/downloads/list and set the' \
100 ' path in the ir.config_parameter with the webkit_path key.' \
101 'Minimal version is 0.9.9')
104 def generate_pdf(self, comm_path, report_xml, header, footer, html_list, webkit_header=False):
105 """Call webkit in order to generate pdf"""
106 if not webkit_header:
107 webkit_header = report_xml.webkit_header
108 fd, out_filename = tempfile.mkstemp(suffix=".pdf",
109 prefix="webkit.tmp.")
110 file_to_del = [out_filename]
112 command = [comm_path]
114 command = ['wkhtmltopdf']
116 command.append('--quiet')
117 # default to UTF-8 encoding. Use <meta charset="latin-1"> to override.
118 command.extend(['--encoding', 'utf-8'])
120 with tempfile.NamedTemporaryFile(suffix=".head.html",
121 delete=False) as head_file:
122 head_file.write(self._sanitize_html(header))
123 file_to_del.append(head_file.name)
124 command.extend(['--header-html', head_file.name])
126 with tempfile.NamedTemporaryFile(suffix=".foot.html",
127 delete=False) as foot_file:
128 foot_file.write(self._sanitize_html(footer))
129 file_to_del.append(foot_file.name)
130 command.extend(['--footer-html', foot_file.name])
132 if webkit_header.margin_top :
133 command.extend(['--margin-top', str(webkit_header.margin_top).replace(',', '.')])
134 if webkit_header.margin_bottom :
135 command.extend(['--margin-bottom', str(webkit_header.margin_bottom).replace(',', '.')])
136 if webkit_header.margin_left :
137 command.extend(['--margin-left', str(webkit_header.margin_left).replace(',', '.')])
138 if webkit_header.margin_right :
139 command.extend(['--margin-right', str(webkit_header.margin_right).replace(',', '.')])
140 if webkit_header.orientation :
141 command.extend(['--orientation', str(webkit_header.orientation).replace(',', '.')])
142 if webkit_header.format :
143 command.extend(['--page-size', str(webkit_header.format).replace(',', '.')])
145 for html in html_list :
146 with tempfile.NamedTemporaryFile(suffix="%d.body.html" %count,
147 delete=False) as html_file:
149 html_file.write(self._sanitize_html(html))
150 file_to_del.append(html_file.name)
151 command.append(html_file.name)
152 command.append(out_filename)
153 stderr_fd, stderr_path = tempfile.mkstemp(text=True)
154 file_to_del.append(stderr_path)
156 status = subprocess.call(command, stderr=stderr_fd)
157 os.close(stderr_fd) # ensure flush before reading
158 stderr_fd = None # avoid closing again in finally block
159 fobj = open(stderr_path, 'r')
160 error_message = fobj.read()
162 if not error_message:
163 error_message = _('No diagnosis message was provided')
165 error_message = _('The following diagnosis message was provided:\n') + error_message
167 raise except_osv(_('Webkit error' ),
168 _("The command 'wkhtmltopdf' failed with error code = %s. Message: %s") % (status, error_message))
169 with open(out_filename, 'rb') as pdf_file:
170 pdf = pdf_file.read()
173 if stderr_fd is not None:
175 for f_to_del in file_to_del:
178 except (OSError, IOError), exc:
179 _logger.error('cannot remove file %s: %s', f_to_del, exc)
182 def translate_call(self, src):
183 """Translate String."""
184 ir_translation = self.pool.get('ir.translation')
185 name = self.tmpl and 'addons/' + self.tmpl or None
186 res = ir_translation._get_source(self.parser_instance.cr, self.parser_instance.uid,
187 name, 'report', self.parser_instance.localcontext.get('lang', 'en_US'), src)
189 # no translation defined, fallback on None (backward compatibility)
190 res = ir_translation._get_source(self.parser_instance.cr, self.parser_instance.uid,
191 None, 'report', self.parser_instance.localcontext.get('lang', 'en_US'), src)
196 # override needed to keep the attachments storing procedure
197 def create_single_pdf(self, cursor, uid, ids, data, report_xml, context=None):
198 """generate the PDF"""
203 if report_xml.report_type != 'webkit':
204 return super(WebKitParser,self).create_single_pdf(cursor, uid, ids, data, report_xml, context=context)
206 self.parser_instance = self.parser(cursor,
211 self.pool = pooler.get_pool(cursor.dbname)
212 objs = self.getObjects(cursor, uid, ids, context)
213 self.parser_instance.set_context(objs, data, ids, report_xml.report_type)
217 if report_xml.report_file :
218 # backward-compatible if path in Windows format
219 report_path = report_xml.report_file.replace("\\", "/")
220 path = addons.get_module_resource(*report_path.split('/'))
221 if path and os.path.exists(path) :
222 template = file(path).read()
223 if not template and report_xml.report_webkit_data :
224 template = report_xml.report_webkit_data
226 raise except_osv(_('Error!'), _('Webkit report template not found!'))
227 header = report_xml.webkit_header.html
228 footer = report_xml.webkit_header.footer_html
229 if not header and report_xml.header:
231 _('No header defined for this Webkit report!'),
232 _('Please set a header in company settings.')
234 if not report_xml.header :
236 default_head = addons.get_module_resource('report_webkit', 'default_header.html')
237 with open(default_head,'r') as f:
239 css = report_xml.webkit_header.css
243 #default_filters=['unicode', 'entity'] can be used to set global filter
244 body_mako_tpl = mako_template(template)
245 helper = WebKitHelper(cursor, uid, report_xml.id, context)
246 if report_xml.precise_mode:
248 self.parser_instance.localcontext['objects'] = [obj]
250 html = body_mako_tpl.render(helper=helper,
252 _=self.translate_call,
253 **self.parser_instance.localcontext)
256 msg = exceptions.text_error_template().render()
258 raise except_osv(_('Webkit render!'), msg)
261 html = body_mako_tpl.render(helper=helper,
263 _=self.translate_call,
264 **self.parser_instance.localcontext)
267 msg = exceptions.text_error_template().render()
269 raise except_osv(_('Webkit render!'), msg)
270 head_mako_tpl = mako_template(header)
272 head = head_mako_tpl.render(helper=helper,
274 _=self.translate_call,
276 **self.parser_instance.localcontext)
278 raise except_osv(_('Webkit render!'),
279 exceptions.text_error_template().render())
282 foot_mako_tpl = mako_template(footer)
284 foot = foot_mako_tpl.render(helper=helper,
286 _=self.translate_call,
287 **self.parser_instance.localcontext)
289 msg = exceptions.text_error_template().render()
291 raise except_osv(_('Webkit render!'), msg)
292 if report_xml.webkit_debug :
294 deb = head_mako_tpl.render(helper=helper,
296 _debug=tools.ustr("\n".join(htmls)),
297 _=self.translate_call,
298 **self.parser_instance.localcontext)
300 msg = exceptions.text_error_template().render()
302 raise except_osv(_('Webkit render!'), msg)
304 bin = self.get_lib(cursor, uid)
305 pdf = self.generate_pdf(bin, report_xml, head, foot, htmls)
308 def create(self, cursor, uid, ids, data, context=None):
309 """We override the create function in order to handle generator
310 Code taken from report openoffice. Thanks guys :) """
311 pool = pooler.get_pool(cursor.dbname)
312 ir_obj = pool.get('ir.actions.report.xml')
313 report_xml_ids = ir_obj.search(cursor, uid,
314 [('report_name', '=', self.name[7:])], context=context)
317 report_xml = ir_obj.browse(cursor,
321 report_xml.report_rml = None
322 report_xml.report_rml_content = None
323 report_xml.report_sxw_content_data = None
324 report_xml.report_sxw_content = None
325 report_xml.report_sxw = None
327 return super(WebKitParser, self).create(cursor, uid, ids, data, context)
328 if report_xml.report_type != 'webkit':
329 return super(WebKitParser, self).create(cursor, uid, ids, data, context)
330 result = self.create_source_pdf(cursor, uid, ids, data, report_xml, context)
335 def _sanitize_html(self, html):
336 """wkhtmltopdf expects the html page to declare a doctype.
338 if html and html[:9].upper() != "<!DOCTYPE":
339 html = "<!DOCTYPE html>\n" + html
342 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: