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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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 res = ir_translation._get_source(self.parser_instance.cr, self.parser_instance.uid,
195 None, 'report', self.parser_instance.localcontext.get('lang', 'en_US'), src)
200 # override needed to keep the attachments storing procedure
201 def create_single_pdf(self, cursor, uid, ids, data, report_xml, context=None):
202 """generate the PDF"""
207 if report_xml.report_type != 'webkit':
208 return super(WebKitParser,self).create_single_pdf(cursor, uid, ids, data, report_xml, context=context)
210 self.parser_instance = self.parser(cursor,
215 self.pool = pooler.get_pool(cursor.dbname)
216 objs = self.getObjects(cursor, uid, ids, context)
217 self.parser_instance.set_context(objs, data, ids, report_xml.report_type)
221 if report_xml.report_file :
222 path = addons.get_module_resource(*report_xml.report_file.split(os.path.sep))
223 if path and os.path.exists(path) :
224 template = file(path).read()
225 if not template and report_xml.report_webkit_data :
226 template = report_xml.report_webkit_data
228 raise except_osv(_('Error!'), _('Webkit report template not found!'))
229 header = report_xml.webkit_header.html
230 footer = report_xml.webkit_header.footer_html
231 if not header and report_xml.header:
233 _('No header defined for this Webkit report!'),
234 _('Please set a header in company settings.')
236 if not report_xml.header :
238 default_head = addons.get_module_resource('report_webkit', 'default_header.html')
239 with open(default_head,'r') as f:
241 css = report_xml.webkit_header.css
245 #default_filters=['unicode', 'entity'] can be used to set global filter
246 body_mako_tpl = mako_template(template)
247 helper = WebKitHelper(cursor, uid, report_xml.id, context)
248 if report_xml.precise_mode:
250 self.parser_instance.localcontext['objects'] = [obj]
252 html = body_mako_tpl.render(helper=helper,
254 _=self.translate_call,
255 **self.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,
265 _=self.translate_call,
266 **self.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,
276 _=self.translate_call,
278 **self.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,
288 _=self.translate_call,
289 **self.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)),
299 _=self.translate_call,
300 **self.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)
311 def create(self, cursor, uid, ids, data, context=None):
312 """We override the create function in order to handle generator
313 Code taken from report openoffice. Thanks guys :) """
314 pool = pooler.get_pool(cursor.dbname)
315 ir_obj = pool.get('ir.actions.report.xml')
316 report_xml_ids = ir_obj.search(cursor, uid,
317 [('report_name', '=', self.name[7:])], context=context)
320 report_xml = ir_obj.browse(cursor,
324 report_xml.report_rml = None
325 report_xml.report_rml_content = None
326 report_xml.report_sxw_content_data = None
327 report_xml.report_sxw_content = None
328 report_xml.report_sxw = None
330 return super(WebKitParser, self).create(cursor, uid, ids, data, context)
331 if report_xml.report_type != 'webkit' :
332 return super(WebKitParser, self).create(cursor, uid, ids, data, context)
333 result = self.create_source_pdf(cursor, uid, ids, data, report_xml, context)
338 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: