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 ##############################################################################
41 from mako.template import Template
42 from mako.lookup import TemplateLookup
43 from mako import exceptions
47 from report_helper import WebKitHelper
48 from report.report_sxw import *
51 from tools.translate import _
52 from 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.")
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 head_file = file( os.path.join(
122 str(time.time()) + '.head.html'
126 head_file.write(header)
128 file_to_del.append(head_file.name)
129 command.extend(['--header-html', head_file.name])
131 foot_file = file( os.path.join(
133 str(time.time()) + '.foot.html'
137 foot_file.write(footer)
139 file_to_del.append(foot_file.name)
140 command.extend(['--footer-html', foot_file.name])
142 if webkit_header.margin_top :
143 command.extend(['--margin-top', str(webkit_header.margin_top).replace(',', '.')])
144 if webkit_header.margin_bottom :
145 command.extend(['--margin-bottom', str(webkit_header.margin_bottom).replace(',', '.')])
146 if webkit_header.margin_left :
147 command.extend(['--margin-left', str(webkit_header.margin_left).replace(',', '.')])
148 if webkit_header.margin_right :
149 command.extend(['--margin-right', str(webkit_header.margin_right).replace(',', '.')])
150 if webkit_header.orientation :
151 command.extend(['--orientation', str(webkit_header.orientation).replace(',', '.')])
152 if webkit_header.format :
153 command.extend(['--page-size', str(webkit_header.format).replace(',', '.')])
155 for html in html_list :
156 html_file = file(os.path.join(tmp_dir, str(time.time()) + str(count) +'.body.html'), 'w')
158 html_file.write(html)
160 file_to_del.append(html_file.name)
161 command.append(html_file.name)
162 command.append(out_filename)
163 stderr_fd, stderr_path = tempfile.mkstemp(text=True)
164 file_to_del.append(stderr_path)
166 status = subprocess.call(command, stderr=stderr_fd)
167 os.close(stderr_fd) # ensure flush before reading
168 stderr_fd = None # avoid closing again in finally block
169 fobj = open(stderr_path, 'r')
170 error_message = fobj.read()
172 if not error_message:
173 error_message = _('No diagnosis message was provided')
175 error_message = _('The following diagnosis message was provided:\n') + error_message
177 raise except_osv(_('Webkit error' ),
178 _("The command 'wkhtmltopdf' failed with error code = %s. Message: %s") % (status, error_message))
179 pdf_file = open(out_filename, 'rb')
180 pdf = pdf_file.read()
183 if stderr_fd is not None:
185 for f_to_del in file_to_del:
188 except (OSError, IOError), exc:
189 _logger.error('cannot remove file %s: %s', f_to_del, exc)
192 def translate_call(self, src):
193 """Translate String."""
194 ir_translation = self.pool.get('ir.translation')
195 res = ir_translation._get_source(self.parser_instance.cr, self.parser_instance.uid,
196 None, 'report', self.parser_instance.localcontext.get('lang', 'en_US'), src)
201 # override needed to keep the attachments storing procedure
202 def create_single_pdf(self, cursor, uid, ids, data, report_xml, context=None):
203 """generate the PDF"""
208 if report_xml.report_type != 'webkit':
209 return super(WebKitParser,self).create_single_pdf(cursor, uid, ids, data, report_xml, context=context)
211 self.parser_instance = self.parser(cursor,
216 self.pool = pooler.get_pool(cursor.dbname)
217 objs = self.getObjects(cursor, uid, ids, context)
218 self.parser_instance.set_context(objs, data, ids, report_xml.report_type)
222 if report_xml.report_file :
223 path = addons.get_module_resource(report_xml.report_file)
224 if os.path.exists(path) :
225 template = file(path).read()
226 if not template and report_xml.report_webkit_data :
227 template = report_xml.report_webkit_data
229 raise except_osv(_('Error!'), _('Webkit report template not found!'))
230 header = report_xml.webkit_header.html
231 footer = report_xml.webkit_header.footer_html
232 if not header and report_xml.header:
234 _('No header defined for this Webkit report!'),
235 _('Please set a header in company settings.')
237 if not report_xml.header :
239 default_head = addons.get_module_resource('report_webkit', 'default_header.html')
240 with open(default_head,'r') as f:
242 css = report_xml.webkit_header.css
245 user = self.pool.get('res.users').browse(cursor, uid, uid)
246 company= user.company_id
248 #default_filters=['unicode', 'entity'] can be used to set global filter
249 body_mako_tpl = mako_template(template)
250 helper = WebKitHelper(cursor, uid, report_xml.id, context)
251 if report_xml.precise_mode:
253 self.parser_instance.localcontext['objects'] = [obj]
255 html = body_mako_tpl.render(helper=helper,
257 _=self.translate_call,
258 **self.parser_instance.localcontext)
261 msg = exceptions.text_error_template().render()
263 raise except_osv(_('Webkit render!'), msg)
266 html = body_mako_tpl.render(helper=helper,
268 _=self.translate_call,
269 **self.parser_instance.localcontext)
272 msg = exceptions.text_error_template().render()
274 raise except_osv(_('Webkit render!'), msg)
275 head_mako_tpl = mako_template(header)
277 head = head_mako_tpl.render(helper=helper,
279 _=self.translate_call,
281 **self.parser_instance.localcontext)
283 raise except_osv(_('Webkit render!'),
284 exceptions.text_error_template().render())
287 foot_mako_tpl = mako_template(footer)
289 foot = foot_mako_tpl.render(helper=helper,
291 _=self.translate_call,
292 **self.parser_instance.localcontext)
294 msg = exceptions.text_error_template().render()
296 raise except_osv(_('Webkit render!'), msg)
297 if report_xml.webkit_debug :
299 deb = head_mako_tpl.render(helper=helper,
301 _debug=tools.ustr("\n".join(htmls)),
302 _=self.translate_call,
303 **self.parser_instance.localcontext)
305 msg = exceptions.text_error_template().render()
307 raise except_osv(_('Webkit render!'), msg)
309 bin = self.get_lib(cursor, uid)
310 pdf = self.generate_pdf(bin, report_xml, head, foot, htmls)
314 def create(self, cursor, uid, ids, data, context=None):
315 """We override the create function in order to handle generator
316 Code taken from report openoffice. Thanks guys :) """
317 pool = pooler.get_pool(cursor.dbname)
318 ir_obj = pool.get('ir.actions.report.xml')
319 report_xml_ids = ir_obj.search(cursor, uid,
320 [('report_name', '=', self.name[7:])], context=context)
323 report_xml = ir_obj.browse(cursor,
327 report_xml.report_rml = None
328 report_xml.report_rml_content = None
329 report_xml.report_sxw_content_data = None
330 report_rml.report_sxw_content = None
331 report_rml.report_sxw = None
333 return super(WebKitParser, self).create(cursor, uid, ids, data, context)
334 if report_xml.report_type != 'webkit' :
335 return super(WebKitParser, self).create(cursor, uid, ids, data, context)
336 result = self.create_source_pdf(cursor, uid, ids, data, report_xml, context)
341 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: