From 8581b1847f8077e85ed6cded7de851a2537be447 Mon Sep 17 00:00:00 2001 From: Simon Lejeune Date: Fri, 21 Mar 2014 17:47:16 +0100 Subject: [PATCH] [IMP] Multiple improvements: eval_param is set on the controller, the subprocess to get the version of wkhtmltopdf is only open at OpenERP start, better exceptions handling (try to avoid exceptions shallowing) and the rpc call from the webclient to know the version of wkhtmltopdf is only done once bzr revid: sle@openerp.com-20140321164716-uksuu6hsjj7q3698 --- addons/report/controllers/main.py | 33 +++- addons/report/models/report.py | 149 +++++++----------- .../report/static/src/js/qwebactionmanager.js | 16 +- 3 files changed, 91 insertions(+), 107 deletions(-) diff --git a/addons/report/controllers/main.py b/addons/report/controllers/main.py index 97a2719e37b..b2a8f7ba4d6 100644 --- a/addons/report/controllers/main.py +++ b/addons/report/controllers/main.py @@ -35,11 +35,13 @@ class ReportController(Controller): @route('/report//', type='http', auth='user', website=True, multilang=True) def report_html(self, reportname, docids): cr, uid, context = request.cr, request.uid, request.context + docids = self._eval_params(docids) return request.registry['report'].get_html(cr, uid, docids, reportname, context=context) @route('/report/pdf/report//', type='http', auth="user", website=True) def report_pdf(self, reportname, docids): cr, uid, context = request.cr, request.uid, request.context + docids = self._eval_params(docids) pdf = request.registry['report'].get_pdf(cr, uid, docids, reportname, context=context) pdfhttpheaders = [('Content-Type', 'application/pdf'), ('Content-Length', len(pdf))] return request.make_response(pdf, headers=pdfhttpheaders) @@ -51,14 +53,14 @@ class ReportController(Controller): def report_html_particular(self, reportname, **data): cr, uid, context = request.cr, request.uid, request.context report_obj = request.registry['report'] - data = report_obj.eval_params(data) # Sanitizing + data = self._eval_params(data) # Sanitizing return report_obj.get_html(cr, uid, [], reportname, data=data, context=context) @route('/report/pdf/report/', type='http', auth='user', website=True, multilang=True) def report_pdf_particular(self, reportname, **data): cr, uid, context = request.cr, request.uid, request.context report_obj = request.registry['report'] - data = report_obj.eval_params(data) # Sanitizing + data = self._eval_params(data) # Sanitizing pdf = report_obj.get_pdf(cr, uid, [], reportname, data=data, context=context) pdfhttpheaders = [('Content-Type', 'application/pdf'), ('Content-Length', len(pdf))] return request.make_response(pdf, headers=pdfhttpheaders) @@ -121,4 +123,29 @@ class ReportController(Controller): @route(['/report/check_wkhtmltopdf'], type='json', auth="user") def check_wkhtmltopdf(self): - return request.registry['report'].check_wkhtmltopdf() + return request.registry['report']._check_wkhtmltopdf() + + def _eval_params(self, param): + """Parse a dict generated by the webclient (javascript) into a python dict. + """ + if isinstance(param, dict): + for key, value in param.iteritems(): + if value.lower() == 'false': + param[key] = False + elif value.lower() == 'true': + param[key] = True + elif ',' in value: + param[key] = [int(i) for i in value.split(',')] + else: + try: + param[key] = int(value) + except (ValueError, TypeError): + pass + else: + if isinstance(param, (str, unicode)): + param = [int(i) for i in param.split(',')] + if isinstance(param, list): + param = list(set(param)) + if isinstance(param, int): + param = [param] + return param diff --git a/addons/report/models/report.py b/addons/report/models/report.py index 979afff89c3..7204cf75ab9 100644 --- a/addons/report/models/report.py +++ b/addons/report/models/report.py @@ -35,6 +35,7 @@ import lxml.html import cStringIO import subprocess from datetime import datetime +from functools import partial from distutils.version import LooseVersion try: from pyPdf import PdfFileWriter, PdfFileReader @@ -45,6 +46,24 @@ except ImportError: _logger = logging.getLogger(__name__) +"""Check the presence of wkhtmltopdf and return its version.""" +wkhtmltopdf_state = 'install' +try: + process = subprocess.Popen( + ['wkhtmltopdf', '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) +except OSError: + _logger.error('You need wkhtmltopdf to print a pdf version of the reports.') +else: + out, err = process.communicate() + version = out.splitlines()[1].strip() + version = version.split(' ')[1] + if LooseVersion(version) < LooseVersion('0.12.0'): + _logger.warning('Upgrade wkhtmltopdf to (at least) 0.12.0') + wkhtmltopdf_state = 'upgrade' + wkhtmltopdf_state = 'ok' + + class Report(osv.Model): _name = "report" _description = "Report" @@ -55,7 +74,7 @@ class Report(osv.Model): # Extension of ir_ui_view.render with arguments frequently used in reports #-------------------------------------------------------------------------- - def get_digits(self, cr, uid, obj=None, f=None, dp=None): + def _get_digits(self, cr, uid, obj=None, f=None, dp=None): d = DEFAULT_DIGITS = 2 if dp: decimal_precision_obj = self.pool['decimal.precision'] @@ -101,9 +120,9 @@ class Report(osv.Model): if digits is None: if dp: - digits = self.get_digits(cr, uid, dp=dp) + digits = self._get_digits(cr, uid, dp=dp) else: - digits = self.get_digits(cr, uid, value) + digits = self._get_digits(cr, uid, value) if isinstance(value, (str, unicode)) and not value: return '' @@ -192,8 +211,8 @@ class Report(osv.Model): values.update({ 'time': time, - 'formatLang': lambda *args, **kwargs: self.formatLang(*args, cr=cr, uid=uid, **kwargs), - 'get_digits': self.get_digits, + 'formatLang': partial(self.formatLang, cr=cr, uid=uid), + 'get_digits': self._get_digits, 'render_doc': render_doc, 'editable': True, # Will active inherit_branding 'res_company': self.pool['res.users'].browse(cr, uid, uid).company_id @@ -208,35 +227,22 @@ class Report(osv.Model): def get_html(self, cr, uid, ids, report_name, data=None, context=None): """This method generates and returns html version of a report. """ - if context is None: - context = {} - - if isinstance(ids, (str, unicode)): - ids = [int(i) for i in ids.split(',')] - if isinstance(ids, list): - ids = list(set(ids)) - if isinstance(ids, int): - ids = [ids] - # If the report is using a custom model to render its html, we must use it. # Otherwise, fallback on the generic html rendering. try: report_model_name = 'report.%s' % report_name particularreport_obj = self.pool[report_model_name] - return particularreport_obj.render_html(cr, uid, ids, data=data, context=context) - except: - pass - - report = self._get_report_from_name(cr, uid, report_name) - report_obj = self.pool[report.model] - docs = report_obj.browse(cr, uid, ids, context=context) - - docargs = { - 'doc_ids': ids, - 'doc_model': report.model, - 'docs': docs, - } - return self.render(cr, uid, [], report.report_name, docargs, context=context) + return particularreport_obj.render_html(cr, uid, ids, data={'form': data}, context=context) + except KeyError: + report = self._get_report_from_name(cr, uid, report_name) + report_obj = self.pool[report.model] + docs = report_obj.browse(cr, uid, ids, context=context) + docargs = { + 'doc_ids': ids, + 'doc_model': report.model, + 'docs': docs, + } + return self.render(cr, uid, [], report.report_name, docargs, context=context) def get_pdf(self, cr, uid, ids, report_name, html=None, data=None, context=None): """This method generates and returns pdf version of a report. @@ -244,13 +250,6 @@ class Report(osv.Model): if context is None: context = {} - if isinstance(ids, (str, unicode)): - ids = [int(i) for i in ids.split(',')] - if isinstance(ids, list): - ids = list(set(ids)) - if isinstance(ids, int): - ids = [ids] - if html is None: html = self.get_html(cr, uid, ids, report_name, data=data, context=context) @@ -260,7 +259,7 @@ class Report(osv.Model): report = self._get_report_from_name(cr, uid, report_name) # Check attachment_use field. If set to true and an existing pdf is already saved, load - # this one now. If not, mark save it. + # this one now. Else, mark save it. save_in_attachment = {} if report.attachment_use is True: @@ -307,7 +306,7 @@ class Report(osv.Model): base_url = self.pool['ir.config_parameter'].get_param(cr, uid, 'web.base.url') minimalhtml = """ - + @@ -315,11 +314,11 @@ class Report(osv.Model): - - {1} + + {subst} - {2} + {body} """ @@ -333,12 +332,12 @@ class Report(osv.Model): for node in root.xpath("//div[@class='header']"): body = lxml.html.tostring(node) - header = minimalhtml.format(css, subst, body, base_url) + header = minimalhtml.format(css=css, subst=subst, body=body, base_url=base_url) headerhtml.append(header) for node in root.xpath("//div[@class='footer']"): body = lxml.html.tostring(node) - footer = minimalhtml.format(css, subst, body, base_url) + footer = minimalhtml.format(css=css, subst=subst, body=body, base_url=base_url) footerhtml.append(footer) for node in root.xpath("//div[@class='page']"): @@ -346,16 +345,16 @@ class Report(osv.Model): # must set a relation between report ids and report's content. We use the QWeb # branding in order to do so: searching after a node having a data-oe-model # attribute with the value of the current report model and read its oe-id attribute - oemodelnode = node.find(".//*[@data-oe-model='" + report.model + "']") + oemodelnode = node.find(".//*[@data-oe-model='%s']" % report.model) if oemodelnode is not None: - reportid = oemodelnode.get('data-oe-id', False) - if reportid is not False: + reportid = oemodelnode.get('data-oe-id') + if reportid: reportid = int(reportid) else: reportid = False body = lxml.html.tostring(node) - reportcontent = minimalhtml.format(css, '', body, base_url) + reportcontent = minimalhtml.format(css=css, subst='', body=body, base_url=base_url) contenthtml.append(tuple([reportid, reportcontent])) except lxml.etree.XMLSyntaxError: @@ -414,27 +413,8 @@ class Report(osv.Model): # Report generation helpers #-------------------------------------------------------------------------- - def check_wkhtmltopdf(self): - """Check the presence of wkhtmltopdf and return its version. If wkhtmltopdf - cannot be found, return False. - """ - try: - process = subprocess.Popen(['wkhtmltopdf', '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - out, err = process.communicate() - if err: - raise - - version = out.splitlines()[1].strip() - version = version.split(' ')[1] - - if LooseVersion(version) < LooseVersion('0.12.0'): - _logger.warning('Upgrade WKHTMLTOPDF to (at least) 0.12.0') - return 'upgrade' - - return True - except: - _logger.error('You need WKHTMLTOPDF to print a pdf version of this report.') - return False + def _check_wkhtmltopdf(self): + return wkhtmltopdf_state def _generate_wkhtml_pdf(self, cr, uid, headers, footers, bodies, landscape, paperformat, spec_paperformat_args=None, save_in_attachment=None): """Execute wkhtmltopdf as a subprocess in order to convert html given in input into a pdf @@ -450,14 +430,14 @@ class Report(osv.Model): :returns: Content of the pdf as a string """ command = ['wkhtmltopdf'] + command_args = [] tmp_dir = tempfile.gettempdir() - command_args = [] # Passing the cookie to wkhtmltopdf in order to resolve URL. try: from openerp.addons.web.http import request - command_args.extend(['--cookie', 'session_id', request.httprequest.cookies['session_id']]) - except: + command_args.extend(['--cookie', 'session_id', request.session.sid]) + except AttributeError: pass # Display arguments @@ -510,8 +490,8 @@ class Report(osv.Model): content_file.flush() try: - # If the server is running with only one worker, increase it to two to be able - # to serve the http request from wkhtmltopdf. + # If the server is running with only one worker, ask to create a secund to be able + # to serve the http request of wkhtmltopdf subprocess. if config['workers'] == 1: ppid = psutil.Process(os.getpid()).ppid os.kill(ppid, signal.SIGTTIN) @@ -629,26 +609,3 @@ class Report(osv.Model): content = merged.read() merged.close() return content - - def eval_params(self, dict_param): - """Parse a dict generated by the webclient (javascript) into a python dict. - """ - for key, value in dict_param.iteritems(): - if value.lower() == 'false': - dict_param[key] = False - elif value.lower() == 'true': - dict_param[key] = True - elif ',' in value: - dict_param[key] = [int(i) for i in value.split(',')] - elif '%2C' in value: - dict_param[key] = [int(i) for i in value.split('%2C')] - else: - try: - i = int(value) - dict_param[key] = i - except (ValueError, TypeError): - pass - - data = {} - data['form'] = dict_param - return data diff --git a/addons/report/static/src/js/qwebactionmanager.js b/addons/report/static/src/js/qwebactionmanager.js index 57163d58b20..7cb468ea4cc 100644 --- a/addons/report/static/src/js/qwebactionmanager.js +++ b/addons/report/static/src/js/qwebactionmanager.js @@ -1,4 +1,5 @@ openerp.report = function(instance) { + var wkhtmltopdf_state; instance.web.ActionManager = instance.web.ActionManager.extend({ ir_actions_report_xml: function(action, options) { @@ -12,7 +13,7 @@ openerp.report = function(instance) { // QWeb reports if ('report_type' in action && (action.report_type == 'qweb-html' || action.report_type == 'qweb-pdf' || action.report_type == 'controller')) { - var report_url = '' + var report_url = ''; switch (action.report_type) { case 'qweb-html': report_url = '/report/' + action.report_name; @@ -55,20 +56,19 @@ openerp.report = function(instance) { } else { // Trigger the download of the pdf report var c = openerp.webclient.crashmanager; - var response = new Array() - response[0] = report_url - response[1] = action.report_type + var response = new Array(); + response[0] = report_url; + response[1] = action.report_type; - openerp.session.rpc('/report/check_wkhtmltopdf').then(function (presence) { + (wkhtmltopdf_state = wkhtmltopdf_state || openerp.session.rpc('/report/check_wkhtmltopdf')).then(function (presence) { // Fallback of qweb-pdf if wkhtmltopdf is not installed - if (!presence && action.report_type == 'qweb-pdf') { + if (presence == 'install' && action.report_type == 'qweb-pdf') { self.do_notify(_t('Report'), _t('Unable to find Wkhtmltopdf on this \ system. The report will be shown in html.

\ wkhtmltopdf.org'), true); window.open(report_url.substring(12), '_blank', 'height=768,width=1024'); instance.web.unblockUI(); - } - else { + } else { if (presence == 'upgrade') { self.do_notify(_t('Report'), _t('You should upgrade your version of\ Wkhtmltopdf to at least 0.12.0 in order to get a correct display of headers and footers as well as\