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
40 from functools import partial
42 from mako.template import Template
43 from mako.lookup import TemplateLookup
44 from mako import exceptions
46 from openerp import netsvc
47 from openerp import pooler
48 from report_helper import WebKitHelper
49 from openerp.report.report_sxw import *
50 from openerp import addons
51 from openerp import tools
52 from openerp.tools.translate import _
53 from openerp.osv.osv import except_osv
55 _logger = logging.getLogger(__name__)
57 def mako_template(text):
58 """Build a Mako template.
60 This template uses UTF-8 encoding
62 tmp_lookup = TemplateLookup() #we need it in order to allow inclusion and inheritance
63 return Template(text, input_encoding='utf-8', output_encoding='utf-8', lookup=tmp_lookup)
66 class WebKitParser(report_sxw):
67 """Custom class that use webkit to render HTML reports
68 Code partially taken from report openoffice. Thanks guys :)
70 def __init__(self, name, table, rml=False, parser=False,
71 header=True, store=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, parser_instance, src):
183 """Translate String."""
184 ir_translation = self.pool['ir.translation']
185 name = self.tmpl and 'addons/' + self.tmpl or None
186 res = ir_translation._get_source(parser_instance.cr, parser_instance.uid,
187 name, 'report', parser_instance.localcontext.get('lang', 'en_US'), src)
189 # no translation defined, fallback on None (backward compatibility)
190 res = ir_translation._get_source(parser_instance.cr, parser_instance.uid,
191 None, 'report', 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 parser_instance = self.parser(cursor,
211 self.pool = pooler.get_pool(cursor.dbname)
212 objs = self.getObjects(cursor, uid, ids, context)
213 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 translate_call = partial(self.translate_call, parser_instance)
244 #default_filters=['unicode', 'entity'] can be used to set global filter
245 body_mako_tpl = mako_template(template)
246 helper = WebKitHelper(cursor, uid, report_xml.id, context)
247 if report_xml.precise_mode:
248 objs = parser_instance.localcontext['objects']
250 parser_instance.localcontext['objects'] = [obj]
252 html = body_mako_tpl.render(helper=helper,
255 **parser_instance.localcontext)
258 msg = exceptions.text_error_template().render()
260 raise except_osv(_('Webkit render!'), msg)
263 html = body_mako_tpl.render(helper=helper,
266 **parser_instance.localcontext)
269 msg = exceptions.text_error_template().render()
271 raise except_osv(_('Webkit render!'), msg)
272 head_mako_tpl = mako_template(header)
274 head = head_mako_tpl.render(helper=helper,
278 **parser_instance.localcontext)
280 raise except_osv(_('Webkit render!'),
281 exceptions.text_error_template().render())
284 foot_mako_tpl = mako_template(footer)
286 foot = foot_mako_tpl.render(helper=helper,
289 **parser_instance.localcontext)
291 msg = exceptions.text_error_template().render()
293 raise except_osv(_('Webkit render!'), msg)
294 if report_xml.webkit_debug :
296 deb = head_mako_tpl.render(helper=helper,
298 _debug=tools.ustr("\n".join(htmls)),
300 **parser_instance.localcontext)
302 msg = exceptions.text_error_template().render()
304 raise except_osv(_('Webkit render!'), msg)
306 bin = self.get_lib(cursor, uid)
307 pdf = self.generate_pdf(bin, report_xml, head, foot, htmls)
310 def create(self, cursor, uid, ids, data, context=None):
311 """We override the create function in order to handle generator
312 Code taken from report openoffice. Thanks guys :) """
313 pool = pooler.get_pool(cursor.dbname)
314 ir_obj = pool.get('ir.actions.report.xml')
315 report_xml_ids = ir_obj.search(cursor, uid,
316 [('report_name', '=', self.name[7:])], context=context)
319 report_xml = ir_obj.browse(cursor,
323 report_xml.report_rml = None
324 report_xml.report_rml_content = None
325 report_xml.report_sxw_content_data = None
326 report_xml.report_sxw_content = None
327 report_xml.report_sxw = None
329 return super(WebKitParser, self).create(cursor, uid, ids, data, context)
330 if report_xml.report_type != 'webkit':
331 return super(WebKitParser, self).create(cursor, uid, ids, data, context)
332 result = self.create_source_pdf(cursor, uid, ids, data, report_xml, context)
337 def _sanitize_html(self, html):
338 """wkhtmltopdf expects the html page to declare a doctype.
340 if html and html[:9].upper() != "<!DOCTYPE":
341 html = "<!DOCTYPE html>\n" + html
344 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: