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)
64 class WebKitParser(report_sxw):
65 """Custom class that use webkit to render HTML reports
66 Code partially taken from report openoffice. Thanks guys :)
68 def __init__(self, name, table, rml=False, parser=False,
69 header=True, store=False):
70 self.parser_instance = False
71 self.localcontext = {}
72 report_sxw.__init__(self, name, table, rml, parser,
75 def get_lib(self, cursor, uid):
76 """Return the lib wkhtml path"""
77 proxy = self.pool.get('ir.config_parameter')
78 webkit_path = proxy.get_param(cursor, uid, 'webkit_path')
82 defpath = os.environ.get('PATH', os.defpath).split(os.pathsep)
83 if hasattr(sys, 'frozen'):
84 defpath.append(os.getcwd())
85 if tools.config['root_path']:
86 defpath.append(os.path.dirname(tools.config['root_path']))
87 webkit_path = tools.which('wkhtmltopdf', path=os.pathsep.join(defpath))
95 _('Wkhtmltopdf library path is not set'),
96 _('Please install executable on your system' \
97 ' (sudo apt-get install wkhtmltopdf) or download it from here:' \
98 ' http://code.google.com/p/wkhtmltopdf/downloads/list and set the' \
99 ' path in the ir.config_parameter with the webkit_path key.' \
100 'Minimal version is 0.9.9')
103 def generate_pdf(self, comm_path, report_xml, header, footer, html_list, webkit_header=False):
104 """Call webkit in order to generate pdf"""
105 if not webkit_header:
106 webkit_header = report_xml.webkit_header
107 tmp_dir = tempfile.gettempdir()
108 out_filename = tempfile.mktemp(suffix=".pdf", prefix="webkit.tmp.")
109 file_to_del = [out_filename]
111 command = [comm_path]
113 command = ['wkhtmltopdf']
115 command.append('--quiet')
116 # default to UTF-8 encoding. Use <meta charset="latin-1"> to override.
117 command.extend(['--encoding', 'utf-8'])
119 head_file = file( os.path.join(
121 str(time.time()) + '.head.html'
125 head_file.write(header)
127 file_to_del.append(head_file.name)
128 command.extend(['--header-html', head_file.name])
130 foot_file = file( os.path.join(
132 str(time.time()) + '.foot.html'
136 foot_file.write(footer)
138 file_to_del.append(foot_file.name)
139 command.extend(['--footer-html', foot_file.name])
141 if webkit_header.margin_top :
142 command.extend(['--margin-top', str(webkit_header.margin_top).replace(',', '.')])
143 if webkit_header.margin_bottom :
144 command.extend(['--margin-bottom', str(webkit_header.margin_bottom).replace(',', '.')])
145 if webkit_header.margin_left :
146 command.extend(['--margin-left', str(webkit_header.margin_left).replace(',', '.')])
147 if webkit_header.margin_right :
148 command.extend(['--margin-right', str(webkit_header.margin_right).replace(',', '.')])
149 if webkit_header.orientation :
150 command.extend(['--orientation', str(webkit_header.orientation).replace(',', '.')])
151 if webkit_header.format :
152 command.extend(['--page-size', str(webkit_header.format).replace(',', '.')])
154 for html in html_list :
155 html_file = file(os.path.join(tmp_dir, str(time.time()) + str(count) +'.body.html'), 'w')
157 html_file.write(html)
159 file_to_del.append(html_file.name)
160 command.append(html_file.name)
161 command.append(out_filename)
162 stderr_fd, stderr_path = tempfile.mkstemp(text=True)
163 file_to_del.append(stderr_path)
165 status = subprocess.call(command, stderr=stderr_fd)
166 os.close(stderr_fd) # ensure flush before reading
167 stderr_fd = None # avoid closing again in finally block
168 fobj = open(stderr_path, 'r')
169 error_message = fobj.read()
171 if not error_message:
172 error_message = _('No diagnosis message was provided')
174 error_message = _('The following diagnosis message was provided:\n') + error_message
176 raise except_osv(_('Webkit error' ),
177 _("The command 'wkhtmltopdf' failed with error code = %s. Message: %s") % (status, error_message))
178 pdf_file = open(out_filename, 'rb')
179 pdf = pdf_file.read()
182 if stderr_fd is not None:
184 for f_to_del in file_to_del:
187 except (OSError, IOError), exc:
188 _logger.error('cannot remove file %s: %s', f_to_del, exc)
191 def translate_call(self, src):
192 """Translate String."""
193 ir_translation = self.pool.get('ir.translation')
194 name = self.tmpl and 'addons/' + self.tmpl or None
195 res = ir_translation._get_source(self.parser_instance.cr, self.parser_instance.uid,
196 name, 'report', self.parser_instance.localcontext.get('lang', 'en_US'), src)
198 # no translation defined, fallback on None (backward compatibility)
199 res = ir_translation._get_source(self.parser_instance.cr, self.parser_instance.uid,
200 None, 'report', self.parser_instance.localcontext.get('lang', 'en_US'), src)
205 # override needed to keep the attachments storing procedure
206 def create_single_pdf(self, cursor, uid, ids, data, report_xml, context=None):
207 """generate the PDF"""
212 if report_xml.report_type != 'webkit':
213 return super(WebKitParser,self).create_single_pdf(cursor, uid, ids, data, report_xml, context=context)
215 self.parser_instance = self.parser(cursor,
220 self.pool = pooler.get_pool(cursor.dbname)
221 objs = self.getObjects(cursor, uid, ids, context)
222 self.parser_instance.set_context(objs, data, ids, report_xml.report_type)
226 if report_xml.report_file :
227 # backward-compatible if path in Windows format
228 report_path = report_xml.report_file.replace("\\", "/")
229 path = addons.get_module_resource(*report_path.split('/'))
230 if path and os.path.exists(path) :
231 template = file(path).read()
232 if not template and report_xml.report_webkit_data :
233 template = report_xml.report_webkit_data
235 raise except_osv(_('Error!'), _('Webkit report template not found!'))
236 header = report_xml.webkit_header.html
237 footer = report_xml.webkit_header.footer_html
238 if not header and report_xml.header:
240 _('No header defined for this Webkit report!'),
241 _('Please set a header in company settings.')
243 if not report_xml.header :
245 default_head = addons.get_module_resource('report_webkit', 'default_header.html')
246 with open(default_head,'r') as f:
248 css = report_xml.webkit_header.css
252 #default_filters=['unicode', 'entity'] can be used to set global filter
253 body_mako_tpl = mako_template(template)
254 helper = WebKitHelper(cursor, uid, report_xml.id, context)
255 if report_xml.precise_mode:
257 self.parser_instance.localcontext['objects'] = [obj]
259 html = body_mako_tpl.render(helper=helper,
261 _=self.translate_call,
262 **self.parser_instance.localcontext)
265 msg = exceptions.text_error_template().render()
267 raise except_osv(_('Webkit render!'), msg)
270 html = body_mako_tpl.render(helper=helper,
272 _=self.translate_call,
273 **self.parser_instance.localcontext)
276 msg = exceptions.text_error_template().render()
278 raise except_osv(_('Webkit render!'), msg)
279 head_mako_tpl = mako_template(header)
281 head = head_mako_tpl.render(helper=helper,
283 _=self.translate_call,
285 **self.parser_instance.localcontext)
287 raise except_osv(_('Webkit render!'),
288 exceptions.text_error_template().render())
291 foot_mako_tpl = mako_template(footer)
293 foot = foot_mako_tpl.render(helper=helper,
295 _=self.translate_call,
296 **self.parser_instance.localcontext)
298 msg = exceptions.text_error_template().render()
300 raise except_osv(_('Webkit render!'), msg)
301 if report_xml.webkit_debug :
303 deb = head_mako_tpl.render(helper=helper,
305 _debug=tools.ustr("\n".join(htmls)),
306 _=self.translate_call,
307 **self.parser_instance.localcontext)
309 msg = exceptions.text_error_template().render()
311 raise except_osv(_('Webkit render!'), msg)
313 bin = self.get_lib(cursor, uid)
314 pdf = self.generate_pdf(bin, report_xml, head, foot, htmls)
318 def create(self, cursor, uid, ids, data, context=None):
319 """We override the create function in order to handle generator
320 Code taken from report openoffice. Thanks guys :) """
321 pool = pooler.get_pool(cursor.dbname)
322 ir_obj = pool.get('ir.actions.report.xml')
323 report_xml_ids = ir_obj.search(cursor, uid,
324 [('report_name', '=', self.name[7:])], context=context)
327 report_xml = ir_obj.browse(cursor,
331 report_xml.report_rml = None
332 report_xml.report_rml_content = None
333 report_xml.report_sxw_content_data = None
334 report_xml.report_sxw_content = None
335 report_xml.report_sxw = None
337 return super(WebKitParser, self).create(cursor, uid, ids, data, context)
338 if report_xml.report_type != 'webkit' :
339 return super(WebKitParser, self).create(cursor, uid, ids, data, context)
340 result = self.create_source_pdf(cursor, uid, ids, data, report_xml, context)
345 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: