From 77e56eea05948e8bde8a42f6cdf1f5a2e10085ae Mon Sep 17 00:00:00 2001 From: Simon Lejeune Date: Thu, 13 Mar 2014 14:34:59 +0100 Subject: [PATCH 01/19] [REF] Changed seek(0) to flush when manipulating files and reordered the impors bzr revid: sle@openerp.com-20140313133459-y3zvhhjby8pysew1 --- addons/report/controllers/main.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/addons/report/controllers/main.py b/addons/report/controllers/main.py index 0dae5f41288..e057e80beb7 100644 --- a/addons/report/controllers/main.py +++ b/addons/report/controllers/main.py @@ -41,20 +41,20 @@ import signal import os from distutils.version import LooseVersion - from werkzeug import exceptions from werkzeug.test import Client from werkzeug.wrappers import BaseResponse from werkzeug.datastructures import Headers from reportlab.graphics.barcode import createBarcodeDrawing - - -_logger = logging.getLogger(__name__) try: from pyPdf import PdfFileWriter, PdfFileReader except ImportError: PdfFileWriter = PdfFileReader = None + +_logger = logging.getLogger(__name__) + + class Report(http.Controller): @http.route(['/report//'], type='http', auth='user', website=True, multilang=True) @@ -303,7 +303,7 @@ class Report(http.Controller): # Directly load the document if we have it if save_in_attachment and save_in_attachment['loaded_documents'].get(reporthtml[0]): pdfreport.write(save_in_attachment['loaded_documents'].get(reporthtml[0])) - pdfreport.seek(0) + pdfreport.flush() pdfdocuments.append(pdfreport) continue @@ -312,7 +312,7 @@ class Report(http.Controller): head_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.header.tmp.', dir=tmp_dir, mode='w+') head_file.write(headers[index]) - head_file.seek(0) + head_file.flush() command_arg_local.extend(['--header-html', head_file.name]) # Footer stuff @@ -320,14 +320,14 @@ class Report(http.Controller): foot_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.footer.tmp.', dir=tmp_dir, mode='w+') foot_file.write(footers[index]) - foot_file.seek(0) + foot_file.flush() command_arg_local.extend(['--footer-html', foot_file.name]) # Body stuff content_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.body.tmp.', dir=tmp_dir, mode='w+') content_file.write(reporthtml[1]) - content_file.seek(0) + content_file.flush() try: # If the server is running with only one worker, increase it to two to be able @@ -364,7 +364,7 @@ class Report(http.Controller): _logger.info('The PDF document %s is now saved in the ' 'database' % attachment['name']) - pdfreport.seek(0) + pdfreport.flush() pdfdocuments.append(pdfreport) if headers: @@ -461,7 +461,7 @@ class Report(http.Controller): document.close() merged = StringIO.StringIO() writer.write(merged) - merged.seek(0) + merged.flush() content = merged.read() merged.close() return content From 060a171a26ca296cc22232533510222cac6b5bbe Mon Sep 17 00:00:00 2001 From: Simon Lejeune Date: Wed, 19 Mar 2014 19:36:14 +0100 Subject: [PATCH 02/19] [WIP] Refactoring report module to avoid using request in OpenERP models : the generation of the html/pdf is now done on the model side and not on the controller side anymore => it is possible to generate a pdf python-side without making a request to an url, request that can be refused because of access right in the request object at this precise moment bzr revid: sle@openerp.com-20140319183614-vibnmm6kkh2h6piu --- addons/account/report/report_vat.py | 7 +- addons/report/controllers/main.py | 496 +----------------- addons/report/models/report.py | 453 ++++++++++++++-- .../report/static/src/js/qwebactionmanager.js | 4 +- 4 files changed, 442 insertions(+), 518 deletions(-) diff --git a/addons/account/report/report_vat.py b/addons/account/report/report_vat.py index 236e2ffaac2..5a5e212df9f 100644 --- a/addons/account/report/report_vat.py +++ b/addons/account/report/report_vat.py @@ -31,7 +31,7 @@ import xlwt class tax_report(http.Controller, common_report_header): - @http.route(['/report/account.report_vat'], type='http', auth='user', website=True, multilang=True) + @http.route(['/report/account.report_vat', '/report/pdf/report/account.report_vat'], type='http', auth='user', website=True, multilang=True) def report_account_tax(self, **data): report_obj = request.registry['report'] self.cr, self.uid, self.pool = request.cr, request.uid, request.registry @@ -56,6 +56,11 @@ class tax_report(http.Controller, common_report_header): 'period_to': self.get_end_period(data), 'taxlines': self._get_lines(self._get_basedon(data), company_id=data['form']['company_id']), } + if request.httprequest.path.startswith('/report/pdf/'): + html = request.registry['report'].render(self.cr, self.uid, [], 'account.report_vat', docargs) + pdf = request.registry['report'].get_pdf(self.cr, self.uid, [], 'account.report_vat', html=html) + pdfhttpheaders = [('Content-Type', 'application/pdf'), ('Content-Length', len(pdf))] + return request.make_response(pdf, headers=pdfhttpheaders) return request.registry['report'].render(self.cr, self.uid, [], 'account.report_vat', docargs) def _get_basedon(self, form): diff --git a/addons/report/controllers/main.py b/addons/report/controllers/main.py index e057e80beb7..a7350fdd4fe 100644 --- a/addons/report/controllers/main.py +++ b/addons/report/controllers/main.py @@ -19,455 +19,27 @@ # ############################################################################## -from openerp.osv.osv import except_osv -from openerp.addons.web import http -from openerp.tools.translate import _ -from openerp.addons.web.http import request -import openerp.tools.config as config +from openerp.addons.web.http import Controller, route, request -import time -import base64 -import logging -import tempfile -import lxml.html -import subprocess import simplejson -try: - import cStringIO as StringIO -except ImportError: - import StringIO -import psutil -import signal -import os -from distutils.version import LooseVersion - from werkzeug import exceptions -from werkzeug.test import Client -from werkzeug.wrappers import BaseResponse -from werkzeug.datastructures import Headers from reportlab.graphics.barcode import createBarcodeDrawing -try: - from pyPdf import PdfFileWriter, PdfFileReader -except ImportError: - PdfFileWriter = PdfFileReader = None -_logger = logging.getLogger(__name__) +class ReportController(Controller): + @route(['/report//'], type='http', auth='user', website=True, multilang=True) + def report_html(self, reportname, docids): + return request.registry['report'].get_html(request.cr, request.uid, docids, reportname) -class Report(http.Controller): - - @http.route(['/report//'], type='http', auth='user', website=True, multilang=True) - def report_html(self, reportname, docids, **kwargs): - """This is the generic route for QWeb reports. It is used for reports - which do not need to preprocess the data (i.e. reports that just display - fields of a record). - - It is given a ~fully qualified report name, for instance 'account.report_invoice'. - Based on it, we know the module concerned and the name of the template. With the - name of the template, we will make a search on the ir.actions.reports.xml table and - get the record associated to finally know the model this template refers to. - - There is a way to declare the report (in module_report(s).xml) that you must respect: - id="action_report_model" - model="module.model" # To know which model the report refers to - string="Invoices" - report_type="qweb-pdf" # or qweb-html - name="module.template_name" - file="module.template_name" - - If you don't want your report listed under the print button, just add - 'menu=False'. - """ - ids = [int(i) for i in docids.split(',')] - ids = list(set(ids)) - report = self._get_report_from_name(reportname) - report_obj = request.registry[report.model] - docs = report_obj.browse(request.cr, request.uid, ids, context=request.context) - - docargs = { - 'doc_ids': ids, - 'doc_model': report.model, - 'docs': docs, - } - - return request.registry['report'].render(request.cr, request.uid, [], report.report_name, - docargs, context=request.context) - - @http.route(['/report/pdf/'], type='http', auth="user", website=True) - def report_pdf(self, path=None, landscape=False, **post): - """Route converting any reports to pdf. It will get the html-rendered report, extract - header, page and footer in order to prepare minimal html pages that will be further passed - to wkhtmltopdf. - - :param path: URL of the report (e.g. /report/account.report_invoice/1) - :returns: a response with 'application/pdf' headers and the pdf as content - """ - cr, uid, context = request.cr, request.uid, request.context - - # Get the report we are working on. - # Pattern is /report/module.reportname(?a=1) - reportname_in_path = path.split('/')[1].split('?')[0] - report = self._get_report_from_name(reportname_in_path) - - # Check attachment_use field. If set to true and an existing pdf is already saved, load - # this one now. If not, mark save it. - save_in_attachment = {} - - if report.attachment_use is True: - # Get the record ids we are working on. - path_ids = [int(i) for i in path.split('/')[2].split('?')[0].split(',')] - - save_in_attachment['model'] = report.model - save_in_attachment['loaded_documents'] = {} - - for path_id in path_ids: - obj = request.registry[report.model].browse(cr, uid, path_id) - filename = eval(report.attachment, {'object': obj, 'time': time}) - - if filename is False: # May be false if, for instance, the record is in draft state - continue - else: - alreadyindb = [('datas_fname', '=', filename), - ('res_model', '=', report.model), - ('res_id', '=', path_id)] - - attach_ids = request.registry['ir.attachment'].search(cr, uid, alreadyindb) - if attach_ids: - # Add the loaded pdf in the loaded_documents list - pdf = request.registry['ir.attachment'].browse(cr, uid, attach_ids[0]).datas - pdf = base64.decodestring(pdf) - save_in_attachment['loaded_documents'][path_id] = pdf - _logger.info('The PDF document %s was loaded from the database' % filename) - else: - # Mark current document to be saved - save_in_attachment[path_id] = filename - - # Get the paperformat associated to the report. If there is not, get the one associated to - # the company. - if not report.paperformat_id: - user = request.registry['res.users'].browse(cr, uid, uid, context=context) - paperformat = user.company_id.paperformat_id - else: - paperformat = report.paperformat_id - - # Get the html report. - html = self._get_url_content('/' + path, post)[0] - subst = self._get_url_content('/report/static/src/js/subst.js')[0] # Used in age numbering - css = '' # Local css - - headerhtml = [] - contenthtml = [] - footerhtml = [] - base_url = request.registry['ir.config_parameter'].get_param(cr, uid, 'web.base.url') - - minimalhtml = """ - - - - - - - - - - - - - - - {2} - -""" - - # The retrieved html report must be simplified. We convert it into a xml tree - # via lxml in order to extract headers, footers and content. - try: - root = lxml.html.fromstring(html) - - for node in root.xpath("//html/head/style"): - css += node.text - - for node in root.xpath("//div[@class='header']"): - body = lxml.html.tostring(node) - header = minimalhtml.format(css, subst, body, 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) - footerhtml.append(footer) - - for node in root.xpath("//div[@class='page']"): - # Previously, we marked some reports to be saved in attachment via their ids, so we - # 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 + "']") - if oemodelnode is not None: - reportid = oemodelnode.get('data-oe-id', False) - if reportid is not False: - reportid = int(reportid) - else: - reportid = False - - body = lxml.html.tostring(node) - reportcontent = minimalhtml.format(css, '', body, base_url) - contenthtml.append(tuple([reportid, reportcontent])) - - except lxml.etree.XMLSyntaxError: - contenthtml = [] - contenthtml.append(html) - save_in_attachment = {} # Don't save this potentially malformed document - - # Get paperformat arguments set in the root html tag. They are prioritized over - # paperformat-record arguments. - specific_paperformat_args = {} - for attribute in root.items(): - if attribute[0].startswith('data-report-'): - specific_paperformat_args[attribute[0]] = attribute[1] - - # Execute wkhtmltopdf process. - pdf = self._generate_wkhtml_pdf(headerhtml, footerhtml, contenthtml, landscape, - paperformat, specific_paperformat_args, save_in_attachment) - - return self._make_pdf_response(pdf) - - def _get_url_content(self, url, post=None): - """Resolve an internal webpage url and return its content with the help of - werkzeug.test.client. - - :param url: string representing the url to resolve - :param post: a dict representing the query string - :returns: a tuple str(html), int(statuscode) - """ - # Rebuilding the query string. - if post: - url += '?' - url += '&'.join('%s=%s' % (k, v) for (k, v) in post.iteritems()) - - # We have to pass the current headers in order to see the report. - reqheaders = Headers(request.httprequest.headers) - response = Client(request.httprequest.app, BaseResponse).get(url, headers=reqheaders, - follow_redirects=True) - content = response.data - - try: - content = content.decode('utf-8') - except UnicodeDecodeError: - pass - - return tuple([content, response.headers]) - - def _generate_wkhtml_pdf(self, 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 - document. - - :param header: list of string containing the headers - :param footer: list of string containing the footers - :param bodies: list of string containing the reports - :param landscape: boolean to force the pdf to be rendered under a landscape format - :param paperformat: ir.actions.report.paperformat to generate the wkhtmltopf arguments - :param specific_paperformat_args: dict of prioritized paperformat arguments - :param save_in_attachment: dict of reports to save/load in/from the db - :returns: Content of the pdf as a string - """ - command = ['wkhtmltopdf'] - tmp_dir = tempfile.gettempdir() - - command_args = [] - # Passing the cookie in order to resolve URL. - command_args.extend(['--cookie', 'session_id', request.httprequest.cookies['session_id']]) - - # Display arguments - if paperformat: - command_args.extend(self._build_wkhtmltopdf_args(paperformat, spec_paperformat_args)) - - if landscape and '--orientation' in command_args: - command_args_copy = list(command_args) - for index, elem in enumerate(command_args_copy): - if elem == '--orientation': - del command_args[index] - del command_args[index] - command_args.extend(['--orientation', 'landscape']) - elif landscape and not '--orientation' in command_args: - command_args.extend(['--orientation', 'landscape']) - - pdfdocuments = [] - # HTML to PDF thanks to WKhtmltopdf - for index, reporthtml in enumerate(bodies): - command_arg_local = [] - pdfreport = tempfile.NamedTemporaryFile(suffix='.pdf', prefix='report.tmp.', - mode='w+b') - # Directly load the document if we have it - if save_in_attachment and save_in_attachment['loaded_documents'].get(reporthtml[0]): - pdfreport.write(save_in_attachment['loaded_documents'].get(reporthtml[0])) - pdfreport.flush() - pdfdocuments.append(pdfreport) - continue - - # Header stuff - if headers: - head_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.header.tmp.', - dir=tmp_dir, mode='w+') - head_file.write(headers[index]) - head_file.flush() - command_arg_local.extend(['--header-html', head_file.name]) - - # Footer stuff - if footers: - foot_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.footer.tmp.', - dir=tmp_dir, mode='w+') - foot_file.write(footers[index]) - foot_file.flush() - command_arg_local.extend(['--footer-html', foot_file.name]) - - # Body stuff - content_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.body.tmp.', - dir=tmp_dir, mode='w+') - content_file.write(reporthtml[1]) - 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 config['workers'] == 1: - ppid = psutil.Process(os.getpid()).ppid - os.kill(ppid, signal.SIGTTIN) - - wkhtmltopdf = command + command_args + command_arg_local - wkhtmltopdf += [content_file.name] + [pdfreport.name] - - process = subprocess.Popen(wkhtmltopdf, stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - out, err = process.communicate() - - if config['workers'] == 1: - os.kill(ppid, signal.SIGTTOU) - - if process.returncode != 0: - raise except_osv(_('Report (PDF)'), - _('wkhtmltopdf failed with error code = %s. ' - 'Message: %s') % (str(process.returncode), err)) - - # Save the pdf in attachment if marked - if reporthtml[0] is not False and save_in_attachment.get(reporthtml[0]): - attachment = { - 'name': save_in_attachment.get(reporthtml[0]), - 'datas': base64.encodestring(pdfreport.read()), - 'datas_fname': save_in_attachment.get(reporthtml[0]), - 'res_model': save_in_attachment.get('model'), - 'res_id': reporthtml[0], - } - request.registry['ir.attachment'].create(request.cr, request.uid, attachment) - _logger.info('The PDF document %s is now saved in the ' - 'database' % attachment['name']) - - pdfreport.flush() - pdfdocuments.append(pdfreport) - - if headers: - head_file.close() - if footers: - foot_file.close() - except: - raise - - # Get and return the full pdf - if len(pdfdocuments) == 1: - content = pdfdocuments[0].read() - pdfdocuments[0].close() - else: - content = self._merge_pdf(pdfdocuments) - - return content - - def _build_wkhtmltopdf_args(self, paperformat, specific_paperformat_args=None): - """Build arguments understandable by wkhtmltopdf from an ir.actions.report.paperformat - record. - - :paperformat: ir.actions.report.paperformat record associated to a document - :specific_paperformat_args: a dict containing prioritized wkhtmltopdf arguments - :returns: list of string containing the wkhtmltopdf arguments - """ - command_args = [] - if paperformat.format and paperformat.format != 'custom': - command_args.extend(['--page-size', paperformat.format]) - - if paperformat.page_height and paperformat.page_width and paperformat.format == 'custom': - command_args.extend(['--page-width', str(paperformat.page_width) + 'in']) - command_args.extend(['--page-height', str(paperformat.page_height) + 'in']) - - if specific_paperformat_args and specific_paperformat_args['data-report-margin-top']: - command_args.extend(['--margin-top', - str(specific_paperformat_args['data-report-margin-top'])]) - elif paperformat.margin_top: - command_args.extend(['--margin-top', str(paperformat.margin_top)]) - - if paperformat.margin_left: - command_args.extend(['--margin-left', str(paperformat.margin_left)]) - if paperformat.margin_bottom: - command_args.extend(['--margin-bottom', str(paperformat.margin_bottom)]) - if paperformat.margin_right: - command_args.extend(['--margin-right', str(paperformat.margin_right)]) - if paperformat.orientation: - command_args.extend(['--orientation', str(paperformat.orientation)]) - if paperformat.header_spacing: - command_args.extend(['--header-spacing', str(paperformat.header_spacing)]) - if paperformat.header_line: - command_args.extend(['--header-line']) - if paperformat.dpi: - command_args.extend(['--dpi', str(paperformat.dpi)]) - - return command_args - - def _get_report_from_name(self, report_name): - """Get the first record of ir.actions.report.xml having the argument as value for - the field report_name. - """ - report_obj = request.registry['ir.actions.report.xml'] - qwebtypes = ['qweb-pdf', 'qweb-html'] - - idreport = report_obj.search(request.cr, request.uid, - [('report_type', 'in', qwebtypes), - ('report_name', '=', report_name)]) - - report = report_obj.browse(request.cr, request.uid, idreport[0], - context=request.context) - return report - - def _make_pdf_response(self, pdf): - """Make a request response for a PDF file with correct http headers. - - :param pdf: content of a pdf in a string - :returns: request response for a pdf document - """ - pdfhttpheaders = [('Content-Type', 'application/pdf'), - ('Content-Length', len(pdf))] + @route(['/report/pdf/report//'], type='http', auth="user", website=True) + def report_pdf(self, reportname, docids): + pdf = request.registry['report'].get_pdf(request.cr, request.uid, docids, reportname) + pdfhttpheaders = [('Content-Type', 'application/pdf'), ('Content-Length', len(pdf))] return request.make_response(pdf, headers=pdfhttpheaders) - def _merge_pdf(self, documents): - """Merge PDF files into one. - - :param documents: list of pdf files - :returns: string containing the merged pdf - """ - writer = PdfFileWriter() - for document in documents: - reader = PdfFileReader(file(document.name, "rb")) - for page in range(0, reader.getNumPages()): - writer.addPage(reader.getPage(page)) - document.close() - merged = StringIO.StringIO() - writer.write(merged) - merged.flush() - content = merged.read() - merged.close() - return content - - @http.route(['/report/barcode', '/report/barcode//'], type='http', auth="user") - def barcode(self, type, value, width=300, height=50): + @route(['/report/barcode', '/report/barcode//'], type='http', auth="user") + def report_barcode(self, type, value, width=300, height=50): """Contoller able to render barcode images thanks to reportlab. Samples: @@ -488,52 +60,26 @@ class Report(http.Controller): return request.make_response(barcode, headers=[('Content-Type', 'image/png')]) - @http.route('/report/download', type='http', auth="user") - def report_attachment(self, data, token): + @route(['/report/download'], type='http', auth="user") + def report_download(self, data, token): """This function is used by 'qwebactionmanager.js' in order to trigger the download of - a report of any type. + a pdf report. - :param data: a javasscript array JSON.stringified containg report internal url ([0]) and + :param data: a javascript array JSON.stringified containg report internal url ([0]) and type [1] :returns: Response with a filetoken cookie and an attachment header """ requestcontent = simplejson.loads(data) url, type = requestcontent[0], requestcontent[1] - file, fileheaders = self._get_url_content(url) - if type == 'qweb-pdf': - response = self._make_pdf_response(file) + reportname, docids = url.split('/')[-2:] + response = self.report_pdf(reportname, docids) response.headers.add('Content-Disposition', 'attachment; filename=report.pdf;') - elif type == 'controller': - response = request.make_response(file) - response.headers.add('Content-Disposition', fileheaders['Content-Disposition']) - response.headers.add('Content-Type', fileheaders['Content-Type']) + response.set_cookie('fileToken', token) + return response else: return - response.headers.add('Content-Length', len(file)) - response.set_cookie('fileToken', token) - return response - - @http.route('/report/check_wkhtmltopdf', type='json', auth="user") + @route(['/report/check_wkhtmltopdf'], type='json', auth="user") 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 + return request.registry['report'].check_wkhtmltopdf() diff --git a/addons/report/models/report.py b/addons/report/models/report.py index 8a2bc21b3b0..ccb892d56d7 100644 --- a/addons/report/models/report.py +++ b/addons/report/models/report.py @@ -19,54 +19,63 @@ # ############################################################################## -from openerp.addons.web.http import request from openerp.osv import osv -from openerp.osv.fields import float as float_field, function as function_field, datetime as datetime_field -from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT from openerp.tools.translate import _ +from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT, config +from openerp.osv.fields import float as float_field, function as function_field, datetime as datetime_field +import os import time +import psutil +import signal +import base64 +import logging +import tempfile +import lxml.html +import cStringIO +import subprocess from datetime import datetime - -from werkzeug.datastructures import Headers -from werkzeug.wrappers import BaseResponse -from werkzeug.test import Client +from distutils.version import LooseVersion +from pyPdf import PdfFileWriter, PdfFileReader -def get_date_length(date_format=DEFAULT_SERVER_DATE_FORMAT): - return len((datetime.now()).strftime(date_format)) +_logger = logging.getLogger(__name__) -class report(osv.Model): +class Report(osv.Model): _name = "report" _description = "Report" public_user = None - def get_digits(self, obj=None, f=None, dp=None): + #-------------------------------------------------------------------------- + # Extension of ir_ui_view.render with arguments frequently used in reports + #-------------------------------------------------------------------------- + + 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'] - ids = decimal_precision_obj.search(request.cr, request.uid, [('name', '=', dp)]) + ids = decimal_precision_obj.search(cr, uid, [('name', '=', dp)]) if ids: - d = decimal_precision_obj.browse(request.cr, request.uid, ids)[0].digits + d = decimal_precision_obj.browse(cr, uid, ids)[0].digits elif obj and f: res_digits = getattr(obj._columns[f], 'digits', lambda x: ((16, DEFAULT_DIGITS))) if isinstance(res_digits, tuple): d = res_digits[1] else: - d = res_digits(request.cr)[1] + d = res_digits(cr)[1] elif (hasattr(obj, '_field') and isinstance(obj._field, (float_field, function_field)) and obj._field.digits): d = obj._field.digits[1] or DEFAULT_DIGITS return d - def _get_lang_dict(self): + def _get_lang_dict(self, cr, uid): pool_lang = self.pool['res.lang'] lang = self.localcontext.get('lang', 'en_US') or 'en_US' - lang_ids = pool_lang.search(request.cr, request.uid, [('code', '=', lang)])[0] - lang_obj = pool_lang.browse(request.cr, request.uid, lang_ids) + lang_ids = pool_lang.search(cr, uid, [('code', '=', lang)])[0] + lang_obj = pool_lang.browse(cr, uid, lang_ids) lang_dict = { 'lang_obj': lang_obj, 'date_format': lang_obj.date_format, @@ -84,17 +93,20 @@ class report(osv.Model): formatLang(value, dp='Account') -> digits=3 formatLang(value, digits=5, dp='Account') -> digits=5 """ + def get_date_length(date_format=DEFAULT_SERVER_DATE_FORMAT): + return len((datetime.now()).strftime(date_format)) + if digits is None: if dp: - digits = self.get_digits(dp=dp) + digits = self.get_digits(self.cr, self.uid, dp=dp) else: - digits = self.get_digits(value) + digits = self.get_digits(self.cr, self.uid, value) if isinstance(value, (str, unicode)) and not value: return '' if not self.lang_dict_called: - self._get_lang_dict() + self._get_lang_dict(self.cr, self.uid) self.lang_dict_called = True if date or date_time: @@ -117,9 +129,7 @@ class report(osv.Model): date = datetime(*value.timetuple()[:6]) if date_time: # Convert datetime values to the expected client/context timezone - date = datetime_field.context_timestamp(request.cr, request.uid, - timestamp=date, - context=self.localcontext) + date = datetime_field.context_timestamp(cr, uid, timestamp=date, context=self.localcontext) return date.strftime(date_format.encode('utf-8')) res = self.lang_dict['lang_obj'].format('%.' + str(digits) + 'f', value, grouping=grouping, monetary=monetary) @@ -143,6 +153,8 @@ class report(osv.Model): if context is None: context = {} + self.cr, self.uid = cr, uid + self.lang_dict = self.default_lang = {} self.lang_dict_called = False self.localcontext = { @@ -150,7 +162,7 @@ class report(osv.Model): 'tz': context.get('tz'), 'uid': context.get('uid'), } - self._get_lang_dict() + self._get_lang_dict(self.cr, self.uid) view_obj = self.pool['ir.ui.view'] @@ -184,8 +196,10 @@ class report(osv.Model): res_company = current_user.company_id try: - website = request.website - res_company = request.website.company_id + from openerp.addons.web.http import request + if request.website: + website = request.website + res_company = request.website.company_id except: pass @@ -202,26 +216,164 @@ class report(osv.Model): return view_obj.render(cr, uid, template, values, context=context) - def get_pdf(self, report, record_id, context=None): - """Used to return the content of a generated PDF. + #-------------------------------------------------------------------------- + # Public report API + #-------------------------------------------------------------------------- - :returns: pdf - """ - url = '/report/pdf/report/' + report.report_file + '/' + str(record_id) - reqheaders = Headers(request.httprequest.headers) - reqheaders.pop('Accept') - reqheaders.add('Accept', 'application/pdf') - reqheaders.pop('Content-Type') - reqheaders.add('Content-Type', 'text/plain') - response = Client(request.httprequest.app, BaseResponse).get(url, headers=reqheaders, - follow_redirects=True) - return response.data + def get_html(self, cr, uid, ids, report_name, context=None): + if context is None: + context = {} + + ids = [int(i) for i in ids.split(',')] + ids = list(set(ids)) + 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, context=None): + if context is None: + context = {} + + if html is None: + html = self.get_html(cr, uid, ids, report_name, context=context) + html = html.decode('utf-8') + + # Get the report we are working on. + # Pattern is /report/module.reportname(?a=1) + 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. + save_in_attachment = {} + + if report.attachment_use is True: + save_in_attachment['model'] = report.model + save_in_attachment['loaded_documents'] = {} + + for record_id in ids: + obj = self.pool[report.model].browse(cr, uid, record_id) + filename = eval(report.attachment, {'object': obj, 'time': time}) + + if filename is False: # May be false if, for instance, the record is in draft state + continue + else: + alreadyindb = [('datas_fname', '=', filename), + ('res_model', '=', report.model), + ('res_id', '=', record_id)] + + attach_ids = self.pool['ir.attachment'].search(cr, uid, alreadyindb) + if attach_ids: + # Add the loaded pdf in the loaded_documents list + pdf = self.pool['ir.attachment'].browse(cr, uid, attach_ids[0]).datas + pdf = base64.decodestring(pdf) + save_in_attachment['loaded_documents'][record_id] = pdf + _logger.info('The PDF document %s was loaded from the database' % filename) + else: + # Mark current document to be saved + save_in_attachment[id] = filename + + # Get the paperformat associated to the report. If there is not, get the one associated to + # the company. + if not report.paperformat_id: + user = self.pool['res.users'].browse(cr, uid, uid) + paperformat = user.company_id.paperformat_id + else: + paperformat = report.paperformat_id + + # Get the html report. + #subst = self._get_url_content('/report/static/src/js/subst.js')[0] # Used in age numbering + subst = '' + css = '' # Local css + + headerhtml = [] + contenthtml = [] + footerhtml = [] + base_url = self.pool['ir.config_parameter'].get_param(cr, uid, 'web.base.url') + + minimalhtml = """ + + + + + + + + + + + + + + + {2} + +""" + + # The retrieved html report must be simplified. We convert it into a xml tree + # via lxml in order to extract headers, footers and content. + try: + root = lxml.html.fromstring(html) + + for node in root.xpath("//html/head/style"): + css += node.text + + for node in root.xpath("//div[@class='header']"): + body = lxml.html.tostring(node) + header = minimalhtml.format(css, subst, body, 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) + footerhtml.append(footer) + + for node in root.xpath("//div[@class='page']"): + # Previously, we marked some reports to be saved in attachment via their ids, so we + # 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 + "']") + if oemodelnode is not None: + reportid = oemodelnode.get('data-oe-id', False) + if reportid is not False: + reportid = int(reportid) + else: + reportid = False + + body = lxml.html.tostring(node) + reportcontent = minimalhtml.format(css, '', body, base_url) + contenthtml.append(tuple([reportid, reportcontent])) + + except lxml.etree.XMLSyntaxError: + contenthtml = [] + contenthtml.append(html) + save_in_attachment = {} # Don't save this potentially malformed document + + # Get paperformat arguments set in the root html tag. They are prioritized over + # paperformat-record arguments. + specific_paperformat_args = {} + for attribute in root.items(): + if attribute[0].startswith('data-report-'): + specific_paperformat_args[attribute[0]] = attribute[1] + + # Execute wkhtmltopdf process. + pdf = self._generate_wkhtml_pdf(headerhtml, footerhtml, contenthtml, context.get('landscape'), paperformat, specific_paperformat_args, save_in_attachment) + + return pdf def get_action(self, cr, uid, ids, report_name, datas=None, context=None): - """Used to return an action of type ir.actions.report.xml. + """Return an action of type ir.actions.report.xml. :param report_name: Name of the template to generate an action for """ + # TODO: return the action for the ids passed in args if context is None: context = {} @@ -249,6 +401,227 @@ class report(osv.Model): return action + #-------------------------------------------------------------------------- + # 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 _generate_wkhtml_pdf(self, 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 + document. + + :param header: list of string containing the headers + :param footer: list of string containing the footers + :param bodies: list of string containing the reports + :param landscape: boolean to force the pdf to be rendered under a landscape format + :param paperformat: ir.actions.report.paperformat to generate the wkhtmltopf arguments + :param specific_paperformat_args: dict of prioritized paperformat arguments + :param save_in_attachment: dict of reports to save/load in/from the db + :returns: Content of the pdf as a string + """ + command = ['wkhtmltopdf'] + tmp_dir = tempfile.gettempdir() + + command_args = [] + # Passing the cookie 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: + pass + + # Display arguments + if paperformat: + command_args.extend(self._build_wkhtmltopdf_args(paperformat, spec_paperformat_args)) + + if landscape and '--orientation' in command_args: + command_args_copy = list(command_args) + for index, elem in enumerate(command_args_copy): + if elem == '--orientation': + del command_args[index] + del command_args[index] + command_args.extend(['--orientation', 'landscape']) + elif landscape and not '--orientation' in command_args: + command_args.extend(['--orientation', 'landscape']) + + pdfdocuments = [] + # HTML to PDF thanks to WKhtmltopdf + for index, reporthtml in enumerate(bodies): + command_arg_local = [] + pdfreport = tempfile.NamedTemporaryFile(suffix='.pdf', prefix='report.tmp.', + mode='w+b') + # Directly load the document if we have it + if save_in_attachment and save_in_attachment['loaded_documents'].get(reporthtml[0]): + pdfreport.write(save_in_attachment['loaded_documents'].get(reporthtml[0])) + pdfreport.flush() + pdfdocuments.append(pdfreport) + continue + + # Header stuff + if headers: + head_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.header.tmp.', + dir=tmp_dir, mode='w+') + head_file.write(headers[index]) + head_file.flush() + command_arg_local.extend(['--header-html', head_file.name]) + + # Footer stuff + if footers: + foot_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.footer.tmp.', + dir=tmp_dir, mode='w+') + foot_file.write(footers[index]) + foot_file.flush() + command_arg_local.extend(['--footer-html', foot_file.name]) + + # Body stuff + content_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.body.tmp.', + dir=tmp_dir, mode='w+') + content_file.write(reporthtml[1]) + 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 config['workers'] == 1: + ppid = psutil.Process(os.getpid()).ppid + os.kill(ppid, signal.SIGTTIN) + + wkhtmltopdf = command + command_args + command_arg_local + wkhtmltopdf += [content_file.name] + [pdfreport.name] + + process = subprocess.Popen(wkhtmltopdf, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + out, err = process.communicate() + + if config['workers'] == 1: + os.kill(ppid, signal.SIGTTOU) + + if process.returncode != 0: + raise osv.except_osv(_('Report (PDF)'), + _('wkhtmltopdf failed with error code = %s. ' + 'Message: %s') % (str(process.returncode), err)) + + # Save the pdf in attachment if marked + if reporthtml[0] is not False and save_in_attachment.get(reporthtml[0]): + attachment = { + 'name': save_in_attachment.get(reporthtml[0]), + 'datas': base64.encodestring(pdfreport.read()), + 'datas_fname': save_in_attachment.get(reporthtml[0]), + 'res_model': save_in_attachment.get('model'), + 'res_id': reporthtml[0], + } + request.registry['ir.attachment'].create(request.cr, request.uid, attachment) + _logger.info('The PDF document %s is now saved in the ' + 'database' % attachment['name']) + + pdfreport.flush() + pdfdocuments.append(pdfreport) + + if headers: + head_file.close() + if footers: + foot_file.close() + except: + raise + + # Get and return the full pdf + if len(pdfdocuments) == 1: + content = pdfdocuments[0].read() + pdfdocuments[0].close() + else: + content = self._merge_pdf(pdfdocuments) + + return content + + def _get_report_from_name(self, cr, uid, report_name): + """Get the first record of ir.actions.report.xml having the ``report_name`` as value for + the field report_name. + """ + report_obj = self.pool['ir.actions.report.xml'] + qwebtypes = ['qweb-pdf', 'qweb-html'] + conditions = [('report_type', 'in', qwebtypes), ('report_name', '=', report_name)] + idreport = report_obj.search(cr, uid, conditions)[0] + return report_obj.browse(cr, uid, idreport) + + def _build_wkhtmltopdf_args(self, paperformat, specific_paperformat_args=None): + """Build arguments understandable by wkhtmltopdf from an ir.actions.report.paperformat + record. + + :paperformat: ir.actions.report.paperformat record associated to a document + :specific_paperformat_args: a dict containing prioritized wkhtmltopdf arguments + :returns: list of string containing the wkhtmltopdf arguments + """ + command_args = [] + if paperformat.format and paperformat.format != 'custom': + command_args.extend(['--page-size', paperformat.format]) + + if paperformat.page_height and paperformat.page_width and paperformat.format == 'custom': + command_args.extend(['--page-width', str(paperformat.page_width) + 'in']) + command_args.extend(['--page-height', str(paperformat.page_height) + 'in']) + + if specific_paperformat_args and specific_paperformat_args['data-report-margin-top']: + command_args.extend(['--margin-top', + str(specific_paperformat_args['data-report-margin-top'])]) + elif paperformat.margin_top: + command_args.extend(['--margin-top', str(paperformat.margin_top)]) + + if paperformat.margin_left: + command_args.extend(['--margin-left', str(paperformat.margin_left)]) + if paperformat.margin_bottom: + command_args.extend(['--margin-bottom', str(paperformat.margin_bottom)]) + if paperformat.margin_right: + command_args.extend(['--margin-right', str(paperformat.margin_right)]) + if paperformat.orientation: + command_args.extend(['--orientation', str(paperformat.orientation)]) + if paperformat.header_spacing: + command_args.extend(['--header-spacing', str(paperformat.header_spacing)]) + if paperformat.header_line: + command_args.extend(['--header-line']) + if paperformat.dpi: + command_args.extend(['--dpi', str(paperformat.dpi)]) + + return command_args + + def _merge_pdf(self, documents): + """Merge PDF files into one. + + :param documents: list of pdf files + :returns: string containing the merged pdf + """ + writer = PdfFileWriter() + for document in documents: + reader = PdfFileReader(file(document.name, "rb")) + for page in range(0, reader.getNumPages()): + writer.addPage(reader.getPage(page)) + document.close() + merged = cStringIO.StringIO() + writer.write(merged) + merged.flush() + content = merged.read() + merged.close() + return content + def eval_params(self, dict_param): """Parse a dictionary generated by the webclient (javascript) into a dictionary understandable by a wizard controller (python). diff --git a/addons/report/static/src/js/qwebactionmanager.js b/addons/report/static/src/js/qwebactionmanager.js index 8d1550a6e2c..cb04b8dc641 100644 --- a/addons/report/static/src/js/qwebactionmanager.js +++ b/addons/report/static/src/js/qwebactionmanager.js @@ -47,13 +47,13 @@ openerp.report = function(instance) { }); report_url += "?" + $.param(action.datas.form); } - if (action.report_type == 'qweb-html') { + if (action.report_type == 'qweb-html' || action.report_type == 'controller') { // Open the html report in a popup window.open(report_url, '_blank', 'height=768,width=1024'); instance.web.unblockUI(); return; } else { - // Trigger the download of the pdf/custom controller report + // Trigger the download of the pdf report var c = openerp.webclient.crashmanager; var response = new Array() response[0] = report_url From 1c5a57a1296599a18303455f5e4aaa7657363e75 Mon Sep 17 00:00:00 2001 From: Simon Lejeune Date: Wed, 19 Mar 2014 19:53:50 +0100 Subject: [PATCH 03/19] [FIX] email_template now use the new get_pdf method on the report model in order to avoid the blocking bug of generating a pdf with a public request.session bzr revid: sle@openerp.com-20140319185350-5t8iuvnzikwfajei --- addons/email_template/email_template.py | 2 +- addons/report/models/report.py | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/addons/email_template/email_template.py b/addons/email_template/email_template.py index c1329eb5a4e..3f0fad1c8a0 100644 --- a/addons/email_template/email_template.py +++ b/addons/email_template/email_template.py @@ -449,7 +449,7 @@ class email_template(osv.osv): ctx['lang'] = self.render_template_batch(cr, uid, template.lang, template.model, [res_id], context)[res_id] # take 0 ? if report.report_type in ['qweb-html', 'qweb-pdf']: - result, format = self.pool['report'].get_pdf(report, res_id, context=ctx), 'pdf' + result, format = self.pool['report'].get_pdf(cr, uid, [res_id], report_service, context=ctx), 'pdf' else: result, format = openerp.report.render_report(cr, uid, [res_id], report_service, {'model': template.model}, ctx) diff --git a/addons/report/models/report.py b/addons/report/models/report.py index ccb892d56d7..2275a46ae93 100644 --- a/addons/report/models/report.py +++ b/addons/report/models/report.py @@ -224,8 +224,13 @@ class Report(osv.Model): if context is None: context = {} - ids = [int(i) for i in ids.split(',')] - ids = list(set(ids)) + if isinstance(ids, str): + ids = [int(i) for i in ids.split(',')] + if isinstance(ids, list): + ids = list(set(ids)) + if isinstance(ids, int): + ids = [ids] + 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) From 221d2a6eebe604c1c5ec6011533d914809bcfc3a Mon Sep 17 00:00:00 2001 From: Simon Lejeune Date: Thu, 20 Mar 2014 14:34:25 +0100 Subject: [PATCH 04/19] [FIX] Reports not using the generic controller should not declare a controller anymore. They are now openerp models and are availabel either trough openerp model 'report' with get_html, get_pdf or through a controller. Adapted report_vat this way. bzr revid: sle@openerp.com-20140320133425-z04ewof8ktjanmn9 --- addons/account/report/report_vat.py | 18 ++++------- addons/report/controllers/main.py | 48 ++++++++++++++++++++++++++--- addons/report/models/report.py | 26 ++++++++++++++-- 3 files changed, 74 insertions(+), 18 deletions(-) diff --git a/addons/account/report/report_vat.py b/addons/account/report/report_vat.py index 5a5e212df9f..81e3bb64415 100644 --- a/addons/account/report/report_vat.py +++ b/addons/account/report/report_vat.py @@ -19,6 +19,7 @@ # ############################################################################## +from openerp.osv import osv from openerp.addons.web import http from openerp.addons.web.http import request from common_report_header import common_report_header @@ -29,14 +30,12 @@ except ImportError: import xlwt -class tax_report(http.Controller, common_report_header): +class tax_report(osv.Model, common_report_header): + _name = 'report.account.report_vat' - @http.route(['/report/account.report_vat', '/report/pdf/report/account.report_vat'], type='http', auth='user', website=True, multilang=True) - def report_account_tax(self, **data): + def render_html(self, cr, uid, ids, data=None, context=None): report_obj = request.registry['report'] - self.cr, self.uid, self.pool = request.cr, request.uid, request.registry - - data = report_obj.eval_params(data) + self.cr, self.uid = cr, uid res = {} self.period_ids = [] @@ -56,12 +55,7 @@ class tax_report(http.Controller, common_report_header): 'period_to': self.get_end_period(data), 'taxlines': self._get_lines(self._get_basedon(data), company_id=data['form']['company_id']), } - if request.httprequest.path.startswith('/report/pdf/'): - html = request.registry['report'].render(self.cr, self.uid, [], 'account.report_vat', docargs) - pdf = request.registry['report'].get_pdf(self.cr, self.uid, [], 'account.report_vat', html=html) - pdfhttpheaders = [('Content-Type', 'application/pdf'), ('Content-Length', len(pdf))] - return request.make_response(pdf, headers=pdfhttpheaders) - return request.registry['report'].render(self.cr, self.uid, [], 'account.report_vat', docargs) + return report_obj.render(self.cr, self.uid, [], 'account.report_vat', docargs) def _get_basedon(self, form): return form['form']['based_on'] diff --git a/addons/report/controllers/main.py b/addons/report/controllers/main.py index a7350fdd4fe..4925374cba2 100644 --- a/addons/report/controllers/main.py +++ b/addons/report/controllers/main.py @@ -22,15 +22,20 @@ from openerp.addons.web.http import Controller, route, request import simplejson +import urlparse from werkzeug import exceptions from reportlab.graphics.barcode import createBarcodeDrawing class ReportController(Controller): + #------------------------------------------------------ + # Generic reports controller + #------------------------------------------------------ + @route(['/report//'], type='http', auth='user', website=True, multilang=True) def report_html(self, reportname, docids): - return request.registry['report'].get_html(request.cr, request.uid, docids, reportname) + return request.registry['report'].get_html(request.cr, request.uid, docids, reportname, context=request.context) @route(['/report/pdf/report//'], type='http', auth="user", website=True) def report_pdf(self, reportname, docids): @@ -38,6 +43,30 @@ class ReportController(Controller): pdfhttpheaders = [('Content-Type', 'application/pdf'), ('Content-Length', len(pdf))] return request.make_response(pdf, headers=pdfhttpheaders) + #------------------------------------------------------ + # Particular reports controller + #------------------------------------------------------ + + @route(['/report/'], type='http', auth='user', website=True, multilang=True) + def report_html_particular(self, reportname, **data): + report_obj = request.registry['report'] + data = report_obj.eval_params(data) + return report_obj.get_html(request.cr, request.uid, [], reportname, data=data, context=request.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) + html = report_obj.get_html(cr, uid, [], reportname, data=data, context=context) + pdf = report_obj.get_pdf(cr, uid, [], reportname, html=html, context=context) + pdfhttpheaders = [('Content-Type', 'application/pdf'), ('Content-Length', len(pdf))] + return request.make_response(pdf, headers=pdfhttpheaders) + + #------------------------------------------------------ + # Misc utils + #------------------------------------------------------ + @route(['/report/barcode', '/report/barcode//'], type='http', auth="user") def report_barcode(self, type, value, width=300, height=50): """Contoller able to render barcode images thanks to reportlab. @@ -72,9 +101,20 @@ class ReportController(Controller): requestcontent = simplejson.loads(data) url, type = requestcontent[0], requestcontent[1] if type == 'qweb-pdf': - reportname, docids = url.split('/')[-2:] - response = self.report_pdf(reportname, docids) - response.headers.add('Content-Disposition', 'attachment; filename=report.pdf;') + reportname = url.split('/report/pdf/report/')[1].split('?')[0].split('/')[0] + + if '?' not in url: + # Generic report: + docids = url.split('/')[-1] + response = self.report_pdf(reportname, docids) + else: + # Particular report: + querystring = url.split('?')[1] + querystring = urlparse.parse_qsl(querystring) + dict_querystring = dict(querystring) + response = self.report_pdf_particular(reportname, **dict_querystring) + + response.headers.add('Content-Disposition', 'attachment; filename=%s.pdf;' % reportname) response.set_cookie('fileToken', token) return response else: diff --git a/addons/report/models/report.py b/addons/report/models/report.py index 2275a46ae93..5c0dc8c7703 100644 --- a/addons/report/models/report.py +++ b/addons/report/models/report.py @@ -220,10 +220,22 @@ class Report(osv.Model): # Public report API #-------------------------------------------------------------------------- - def get_html(self, cr, uid, ids, report_name, context=None): + def get_html(self, cr, uid, ids, report_name, data=None, context=None): + """This method generates and returns html version of generic report. + """ if context is None: context = {} + # If the report is using a custom model to render its html, we must use it. + # Otherwise, fallback on the generic html rendering. + if data is not None: + try: + report_model_name = 'report.%s' % report_name + particularreport_obj = self.pool[report_model_name] + return particularreport_obj.render_html(cr, uid, [], data=data, context=context) + except: + pass + if isinstance(ids, str): ids = [int(i) for i in ids.split(',')] if isinstance(ids, list): @@ -243,11 +255,21 @@ class Report(osv.Model): return self.render(cr, uid, [], report.report_name, docargs, context=context) def get_pdf(self, cr, uid, ids, report_name, html=None, context=None): + """This method generates and returns pdf version of generic 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 html is None: html = self.get_html(cr, uid, ids, report_name, context=context) + html = html.decode('utf-8') # Get the report we are working on. @@ -628,7 +650,7 @@ class Report(osv.Model): return content def eval_params(self, dict_param): - """Parse a dictionary generated by the webclient (javascript) into a dictionary + """Parse a dict generated by the webclient (javascript) into a dictionary understandable by a wizard controller (python). """ for key, value in dict_param.iteritems(): From 52522bf0f91d65fda2b363643dd99ad74b75f43d Mon Sep 17 00:00:00 2001 From: Simon Lejeune Date: Thu, 20 Mar 2014 15:06:09 +0100 Subject: [PATCH 05/19] [FIX] get_pdf can be called on is own (no need to call get_html before on particular controller-report) and make the multiple ids work bzr revid: sle@openerp.com-20140320140609-7ozly6f55sgeow7k --- addons/report/controllers/main.py | 16 +++++++++------- addons/report/models/report.py | 8 ++++---- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/addons/report/controllers/main.py b/addons/report/controllers/main.py index 4925374cba2..e962bdf12e7 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): - return request.registry['report'].get_html(request.cr, request.uid, docids, reportname, context=request.context) + cr, uid, context = request.cr, request.uid, request.context + 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): - pdf = request.registry['report'].get_pdf(request.cr, request.uid, docids, reportname) + cr, uid, context = request.cr, request.uid, request.context + 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) @@ -49,17 +51,17 @@ class ReportController(Controller): @route(['/report/'], type='http', auth='user', website=True, multilang=True) 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) - return report_obj.get_html(request.cr, request.uid, [], reportname, data=data, context=request.context) + data = report_obj.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) - html = report_obj.get_html(cr, uid, [], reportname, data=data, context=context) - pdf = report_obj.get_pdf(cr, uid, [], reportname, html=html, context=context) + data = report_obj.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) diff --git a/addons/report/models/report.py b/addons/report/models/report.py index 5c0dc8c7703..174ec71fab2 100644 --- a/addons/report/models/report.py +++ b/addons/report/models/report.py @@ -236,7 +236,7 @@ class Report(osv.Model): except: pass - if isinstance(ids, str): + if isinstance(ids, (str, unicode)): ids = [int(i) for i in ids.split(',')] if isinstance(ids, list): ids = list(set(ids)) @@ -254,7 +254,7 @@ class Report(osv.Model): } return self.render(cr, uid, [], report.report_name, docargs, context=context) - def get_pdf(self, cr, uid, ids, report_name, html=None, context=None): + def get_pdf(self, cr, uid, ids, report_name, html=None, data=None, context=None): """This method generates and returns pdf version of generic report. """ if context is None: @@ -268,7 +268,7 @@ class Report(osv.Model): ids = [ids] if html is None: - html = self.get_html(cr, uid, ids, report_name, context=context) + html = self.get_html(cr, uid, ids, report_name, data=data, context=context) html = html.decode('utf-8') @@ -644,7 +644,7 @@ class Report(osv.Model): document.close() merged = cStringIO.StringIO() writer.write(merged) - merged.flush() + merged.seek(0) content = merged.read() merged.close() return content From d941bce0766c9ed2ca68a31b4da2abd106937022 Mon Sep 17 00:00:00 2001 From: Simon Lejeune Date: Thu, 20 Mar 2014 15:25:20 +0100 Subject: [PATCH 06/19] [FIX] Adapted mrp bom structure (exit controller, added model) bzr revid: sle@openerp.com-20140320142520-3qapc7039q46eg9k --- addons/mrp/report/bom_structure.py | 18 ++++++++---------- addons/report/models/report.py | 13 ++++++------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/addons/mrp/report/bom_structure.py b/addons/mrp/report/bom_structure.py index 9af4e91b8ce..f710cce74ed 100644 --- a/addons/mrp/report/bom_structure.py +++ b/addons/mrp/report/bom_structure.py @@ -19,24 +19,22 @@ # ############################################################################## -from openerp.addons.web import http -from openerp.addons.web.http import request +from openerp.osv import osv -class bom_structure(http.Controller): +class bom_structure(osv.Model): + _name = 'report.mrp.report_mrpbomstructure' - @http.route(['/report/mrp.report_mrpbomstructure/'], type='http', auth='user', website=True, multilang=True) - def report_mrpbomstructure(self, docids): - ids = [int(i) for i in docids.split(',')] - ids = list(set(ids)) - report_obj = request.registry['mrp.bom'] - docs = report_obj.browse(request.cr, request.uid, ids, context=request.context) + def render_html(self, cr, uid, ids, data=None, context=None): + mrpbom_obj = self.pool['mrp.bom'] + report_obj = self.pool['report'] + docs = mrpbom_obj.browse(cr, uid, ids, context=context) docargs = { 'docs': docs, 'get_children': self.get_children, } - return request.registry['report'].render(request.cr, request.uid, [], 'mrp.report_mrpbomstructure', docargs) + return report_obj.render(cr, uid, [], 'mrp.report_mrpbomstructure', docargs) def get_children(self, object, level=0): result = [] diff --git a/addons/report/models/report.py b/addons/report/models/report.py index 174ec71fab2..9a713a014eb 100644 --- a/addons/report/models/report.py +++ b/addons/report/models/report.py @@ -228,13 +228,12 @@ class Report(osv.Model): # If the report is using a custom model to render its html, we must use it. # Otherwise, fallback on the generic html rendering. - if data is not None: - try: - report_model_name = 'report.%s' % report_name - particularreport_obj = self.pool[report_model_name] - return particularreport_obj.render_html(cr, uid, [], data=data, context=context) - except: - pass + 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 if isinstance(ids, (str, unicode)): ids = [int(i) for i in ids.split(',')] From b6bd0e8b8a2193b156eecceff53e39a908ed761e Mon Sep 17 00:00:00 2001 From: Simon Lejeune Date: Thu, 20 Mar 2014 16:42:26 +0100 Subject: [PATCH 07/19] [FIX] fix the print purchase button bzr revid: sle@openerp.com-20140320154226-tpx3d8a0xrmuxwrr --- addons/purchase/purchase.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py index b47320b1e79..97a1b87cd07 100644 --- a/addons/purchase/purchase.py +++ b/addons/purchase/purchase.py @@ -446,12 +446,7 @@ class purchase_order(osv.osv): ''' assert len(ids) == 1, 'This option should only be used for a single id at a time' self.signal_send_rfq(cr, uid, ids) - datas = { - 'model': 'purchase.order', - 'ids': ids, - 'form': self.read(cr, uid, ids[0], context=context), - } - return {'type': 'ir.actions.report.xml', 'report_name': 'purchase.quotation', 'datas': datas, 'nodestroy': True} + return self.pool['report'].get_action(cr, uid, ids, 'purchase.report_purchasequotation', context=context) #TODO: implement messages system def wkf_confirm_order(self, cr, uid, ids, context=None): From ad50d5fcfea22b8e224ef5939edb7b5aa41f75dd Mon Sep 17 00:00:00 2001 From: Simon Lejeune Date: Thu, 20 Mar 2014 17:58:41 +0100 Subject: [PATCH 08/19] [FIX] Multiple improvements in report including giving formatlang a cursor and a uid via a lambda method, cleaning of some comments and restoring the page numbering bzr revid: sle@openerp.com-20140320165841-21clvch4ujak1y3m --- addons/report/controllers/main.py | 2 +- addons/report/models/report.py | 74 ++++++++++++++----------------- 2 files changed, 35 insertions(+), 41 deletions(-) diff --git a/addons/report/controllers/main.py b/addons/report/controllers/main.py index e962bdf12e7..672cae81f77 100644 --- a/addons/report/controllers/main.py +++ b/addons/report/controllers/main.py @@ -66,7 +66,7 @@ class ReportController(Controller): return request.make_response(pdf, headers=pdfhttpheaders) #------------------------------------------------------ - # Misc utils + # Misc. utils #------------------------------------------------------ @route(['/report/barcode', '/report/barcode//'], type='http', auth="user") diff --git a/addons/report/models/report.py b/addons/report/models/report.py index 9a713a014eb..c685484c4af 100644 --- a/addons/report/models/report.py +++ b/addons/report/models/report.py @@ -85,7 +85,7 @@ class Report(osv.Model): self.default_lang[lang] = self.lang_dict.copy() return True - def formatLang(self, value, digits=None, date=False, date_time=False, grouping=True, monetary=False, dp=False, currency_obj=False): + def formatLang(self, value, digits=None, date=False, date_time=False, grouping=True, monetary=False, dp=False, currency_obj=False, cr=None, uid=None): """ Assuming 'Account' decimal.precision=3: formatLang(value) -> digits=2 (default) @@ -98,15 +98,15 @@ class Report(osv.Model): if digits is None: if dp: - digits = self.get_digits(self.cr, self.uid, dp=dp) + digits = self.get_digits(cr, uid, dp=dp) else: - digits = self.get_digits(self.cr, self.uid, value) + digits = self.get_digits(cr, uid, value) if isinstance(value, (str, unicode)) and not value: return '' if not self.lang_dict_called: - self._get_lang_dict(self.cr, self.uid) + self._get_lang_dict(cr, uid) self.lang_dict_called = True if date or date_time: @@ -153,8 +153,6 @@ class Report(osv.Model): if context is None: context = {} - self.cr, self.uid = cr, uid - self.lang_dict = self.default_lang = {} self.lang_dict_called = False self.localcontext = { @@ -162,7 +160,7 @@ class Report(osv.Model): 'tz': context.get('tz'), 'uid': context.get('uid'), } - self._get_lang_dict(self.cr, self.uid) + self._get_lang_dict(cr, uid) view_obj = self.pool['ir.ui.view'] @@ -207,7 +205,7 @@ class Report(osv.Model): 'time': time, 'user': current_user, 'user_id': current_user.id, - 'formatLang': self.formatLang, + 'formatLang': lambda *args, **kwargs: self.formatLang(*args, cr=cr, uid=uid, **kwargs), 'get_digits': self.get_digits, 'render_doc': render_doc, 'website': website, @@ -217,15 +215,22 @@ class Report(osv.Model): return view_obj.render(cr, uid, template, values, context=context) #-------------------------------------------------------------------------- - # Public report API + # Main reports methods #-------------------------------------------------------------------------- def get_html(self, cr, uid, ids, report_name, data=None, context=None): - """This method generates and returns html version of generic report. + """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: @@ -235,13 +240,6 @@ class Report(osv.Model): except: pass - 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] - 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) @@ -254,7 +252,7 @@ class Report(osv.Model): 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 generic report. + """This method generates and returns pdf version of a report. """ if context is None: context = {} @@ -271,8 +269,7 @@ class Report(osv.Model): html = html.decode('utf-8') - # Get the report we are working on. - # Pattern is /report/module.reportname(?a=1) + # Get the ir.actions.report.xml record we are working on. 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 @@ -305,18 +302,17 @@ class Report(osv.Model): # Mark current document to be saved save_in_attachment[id] = filename - # Get the paperformat associated to the report. If there is not, get the one associated to - # the company. + # Get the paperformat associated to the report, otherwise fallback on the company one. if not report.paperformat_id: user = self.pool['res.users'].browse(cr, uid, uid) paperformat = user.company_id.paperformat_id else: paperformat = report.paperformat_id - # Get the html report. + # Preparing the minimal html pages #subst = self._get_url_content('/report/static/src/js/subst.js')[0] # Used in age numbering - subst = '' - css = '' # Local css + subst = " " + css = '' # Will contain local css headerhtml = [] contenthtml = [] @@ -332,10 +328,8 @@ class Report(osv.Model): - - - + {1} {2} @@ -389,9 +383,11 @@ class Report(osv.Model): if attribute[0].startswith('data-report-'): specific_paperformat_args[attribute[0]] = attribute[1] - # Execute wkhtmltopdf process. - pdf = self._generate_wkhtml_pdf(headerhtml, footerhtml, contenthtml, context.get('landscape'), paperformat, specific_paperformat_args, save_in_attachment) - + # Run wkhtmltopdf process + pdf = self._generate_wkhtml_pdf( + cr, uid, headerhtml, footerhtml, contenthtml, context.get('landscape'), + paperformat, specific_paperformat_args, save_in_attachment + ) return pdf def get_action(self, cr, uid, ids, report_name, datas=None, context=None): @@ -453,7 +449,7 @@ class Report(osv.Model): _logger.error('You need WKHTMLTOPDF to print a pdf version of this report.') return False - def _generate_wkhtml_pdf(self, headers, footers, bodies, landscape, paperformat, spec_paperformat_args=None, save_in_attachment=None): + 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 document. @@ -470,7 +466,7 @@ class Report(osv.Model): tmp_dir = tempfile.gettempdir() command_args = [] - # Passing the cookie in order to resolve URL. + # 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']]) @@ -557,7 +553,7 @@ class Report(osv.Model): 'res_model': save_in_attachment.get('model'), 'res_id': reporthtml[0], } - request.registry['ir.attachment'].create(request.cr, request.uid, attachment) + self.pool['ir.attachment'].create(cr, uid, attachment) _logger.info('The PDF document %s is now saved in the ' 'database' % attachment['name']) @@ -591,12 +587,11 @@ class Report(osv.Model): return report_obj.browse(cr, uid, idreport) def _build_wkhtmltopdf_args(self, paperformat, specific_paperformat_args=None): - """Build arguments understandable by wkhtmltopdf from an ir.actions.report.paperformat - record. + """Build arguments understandable by wkhtmltopdf from a report.paperformat record. - :paperformat: ir.actions.report.paperformat record associated to a document + :paperformat: report.paperformat record :specific_paperformat_args: a dict containing prioritized wkhtmltopdf arguments - :returns: list of string containing the wkhtmltopdf arguments + :returns: list of string representing the wkhtmltopdf arguments """ command_args = [] if paperformat.format and paperformat.format != 'custom': @@ -649,8 +644,7 @@ class Report(osv.Model): return content def eval_params(self, dict_param): - """Parse a dict generated by the webclient (javascript) into a dictionary - understandable by a wizard controller (python). + """Parse a dict generated by the webclient (javascript) into a python dict. """ for key, value in dict_param.iteritems(): if value.lower() == 'false': From 57e6e8fd282a385cba17ca9dcd8b77cd2bb6446b Mon Sep 17 00:00:00 2001 From: Simon Lejeune Date: Fri, 21 Mar 2014 11:58:06 +0100 Subject: [PATCH 09/19] [FIX] Multiples fixes: correct context passing, changed osv.Model to osv.AbstractModel to avoid creating tables in database when the model use for the reports contains only method bzr revid: sle@openerp.com-20140321105806-0bwo992995la91fe --- addons/account/report/report_vat.py | 6 +++--- addons/mrp/report/bom_structure.py | 2 +- addons/report/controllers/main.py | 21 ++++++++----------- addons/report/models/report.py | 19 +---------------- .../report/static/src/js/qwebactionmanager.js | 4 ++-- addons/website_report/views/layouts.xml | 4 +++- 6 files changed, 19 insertions(+), 37 deletions(-) diff --git a/addons/account/report/report_vat.py b/addons/account/report/report_vat.py index 81e3bb64415..1c06bdc4037 100644 --- a/addons/account/report/report_vat.py +++ b/addons/account/report/report_vat.py @@ -30,12 +30,12 @@ except ImportError: import xlwt -class tax_report(osv.Model, common_report_header): +class tax_report(osv.AbstractModel, common_report_header): _name = 'report.account.report_vat' def render_html(self, cr, uid, ids, data=None, context=None): report_obj = request.registry['report'] - self.cr, self.uid = cr, uid + self.cr, self.uid, self.context = cr, uid, context res = {} self.period_ids = [] @@ -55,7 +55,7 @@ class tax_report(osv.Model, common_report_header): 'period_to': self.get_end_period(data), 'taxlines': self._get_lines(self._get_basedon(data), company_id=data['form']['company_id']), } - return report_obj.render(self.cr, self.uid, [], 'account.report_vat', docargs) + return report_obj.render(self.cr, self.uid, [], 'account.report_vat', docargs, context=context) def _get_basedon(self, form): return form['form']['based_on'] diff --git a/addons/mrp/report/bom_structure.py b/addons/mrp/report/bom_structure.py index f710cce74ed..eb238374293 100644 --- a/addons/mrp/report/bom_structure.py +++ b/addons/mrp/report/bom_structure.py @@ -22,7 +22,7 @@ from openerp.osv import osv -class bom_structure(osv.Model): +class bom_structure(osv.AbstractModel): _name = 'report.mrp.report_mrpbomstructure' def render_html(self, cr, uid, ids, data=None, context=None): diff --git a/addons/report/controllers/main.py b/addons/report/controllers/main.py index 672cae81f77..c843d92044e 100644 --- a/addons/report/controllers/main.py +++ b/addons/report/controllers/main.py @@ -32,13 +32,12 @@ class ReportController(Controller): #------------------------------------------------------ # Generic reports controller #------------------------------------------------------ - - @route(['/report//'], type='http', auth='user', website=True, multilang=True) + @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 return request.registry['report'].get_html(cr, uid, docids, reportname, context=context) - @route(['/report/pdf/report//'], type='http', auth="user", website=True) + @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 pdf = request.registry['report'].get_pdf(cr, uid, docids, reportname, context=context) @@ -48,15 +47,14 @@ class ReportController(Controller): #------------------------------------------------------ # Particular reports controller #------------------------------------------------------ - - @route(['/report/'], type='http', auth='user', website=True, multilang=True) + @route('/report/', type='http', auth='user', website=True, multilang=True) 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 return report_obj.get_html(cr, uid, [], reportname, data=data, context=context) - @route(['/report/pdf/report/'], type='http', auth='user', website=True, multilang=True) + @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'] @@ -66,15 +64,15 @@ class ReportController(Controller): return request.make_response(pdf, headers=pdfhttpheaders) #------------------------------------------------------ - # Misc. utils + # Misc. route utils #------------------------------------------------------ - @route(['/report/barcode', '/report/barcode//'], type='http', auth="user") def report_barcode(self, type, value, width=300, height=50): """Contoller able to render barcode images thanks to reportlab. Samples: - + :param type: Accepted types: 'Codabar', 'Code11', 'Code128', 'EAN13', 'EAN8', 'Extended39', 'Extended93', 'FIM', 'I2of5', 'MSI', 'POSTNET', 'QR', 'Standard39', 'Standard93', @@ -112,9 +110,8 @@ class ReportController(Controller): else: # Particular report: querystring = url.split('?')[1] - querystring = urlparse.parse_qsl(querystring) - dict_querystring = dict(querystring) - response = self.report_pdf_particular(reportname, **dict_querystring) + querystring = dict(urlparse.parse_qsl(querystring)) + response = self.report_pdf_particular(reportname, **querystring) response.headers.add('Content-Disposition', 'attachment; filename=%s.pdf;' % reportname) response.set_cookie('fileToken', token) diff --git a/addons/report/models/report.py b/addons/report/models/report.py index c685484c4af..79bf41158c0 100644 --- a/addons/report/models/report.py +++ b/addons/report/models/report.py @@ -187,29 +187,12 @@ class Report(osv.Model): qcontext['o'] = self.pool[model].browse(cr, uid, doc_id, context=ctx) return view_obj.render(cr, uid, template, qcontext, context=ctx) - current_user = self.pool['res.users'].browse(cr, uid, uid, context=context) - - # Website independance code - website = False - res_company = current_user.company_id - - try: - from openerp.addons.web.http import request - if request.website: - website = request.website - res_company = request.website.company_id - except: - pass - values.update({ 'time': time, - 'user': current_user, - 'user_id': current_user.id, 'formatLang': lambda *args, **kwargs: self.formatLang(*args, cr=cr, uid=uid, **kwargs), 'get_digits': self.get_digits, 'render_doc': render_doc, - 'website': website, - 'res_company': res_company, + 'editable': True, # Will active inherit_branding }) return view_obj.render(cr, uid, template, values, context=context) diff --git a/addons/report/static/src/js/qwebactionmanager.js b/addons/report/static/src/js/qwebactionmanager.js index cb04b8dc641..57163d58b20 100644 --- a/addons/report/static/src/js/qwebactionmanager.js +++ b/addons/report/static/src/js/qwebactionmanager.js @@ -49,7 +49,7 @@ openerp.report = function(instance) { } if (action.report_type == 'qweb-html' || action.report_type == 'controller') { // Open the html report in a popup - window.open(report_url, '_blank', 'height=768,width=1024'); + window.open(report_url, '_blank', 'height=900,width=1280'); instance.web.unblockUI(); return; } else { @@ -63,7 +63,7 @@ openerp.report = function(instance) { // Fallback of qweb-pdf if wkhtmltopdf is not installed if (!presence && 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.

\ +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(); diff --git a/addons/website_report/views/layouts.xml b/addons/website_report/views/layouts.xml index 091bb8dd3db..d3f6d69dd9e 100644 --- a/addons/website_report/views/layouts.xml +++ b/addons/website_report/views/layouts.xml @@ -42,16 +42,18 @@ - + + + From e06cefb4be20bb930bd8c8460cf876d477f62189 Mon Sep 17 00:00:00 2001 From: Simon Lejeune Date: Fri, 21 Mar 2014 12:17:42 +0100 Subject: [PATCH 10/19] [FIX] Correctly separate website variable between report and website_report layouts bzr revid: sle@openerp.com-20140321111742-rzkykk00z6fbum8d --- addons/report/models/report.py | 1 + addons/report/views/layouts.xml | 2 -- addons/website_report/views/layouts.xml | 6 ++++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/addons/report/models/report.py b/addons/report/models/report.py index 79bf41158c0..af231bc12cf 100644 --- a/addons/report/models/report.py +++ b/addons/report/models/report.py @@ -193,6 +193,7 @@ class Report(osv.Model): '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 }) return view_obj.render(cr, uid, template, values, context=context) diff --git a/addons/report/views/layouts.xml b/addons/report/views/layouts.xml index 73c5ce03244..588775fc41e 100644 --- a/addons/report/views/layouts.xml +++ b/addons/report/views/layouts.xml @@ -4,9 +4,7 @@