[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
This commit is contained in:
parent
874658f4b0
commit
060a171a26
|
@ -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):
|
||||
|
|
|
@ -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/<reportname>/<docids>'], 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/<reportname>/<docids>'], 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/<path:path>'], 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 = """
|
||||
<base href="{3}">
|
||||
<!DOCTYPE html>
|
||||
<html style="height: 0;">
|
||||
<head>
|
||||
<link href="/report/static/src/css/reset.min.css" rel="stylesheet"/>
|
||||
<link href="/web/static/lib/bootstrap/css/bootstrap.css" rel="stylesheet"/>
|
||||
<link href="/website/static/src/css/website.css" rel="stylesheet"/>
|
||||
<link href="/web/static/lib/fontawesome/css/font-awesome.css" rel="stylesheet"/>
|
||||
|
||||
<style type='text/css'>{0}</style>
|
||||
|
||||
<script type='text/javascript'>{1}</script>
|
||||
</head>
|
||||
<body class="container" onload='subst()'>
|
||||
{2}
|
||||
</body>
|
||||
</html>"""
|
||||
|
||||
# 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/<reportname>/<docids>'], 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>/<path:value>'], type='http', auth="user")
|
||||
def barcode(self, type, value, width=300, height=50):
|
||||
@route(['/report/barcode', '/report/barcode/<type>/<path:value>'], type='http', auth="user")
|
||||
def report_barcode(self, type, value, width=300, height=50):
|
||||
"""Contoller able to render barcode images thanks to reportlab.
|
||||
Samples:
|
||||
<img t-att-src="'/report/barcode/QR/%s' % o.name"/>
|
||||
|
@ -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()
|
||||
|
|
|
@ -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 = """
|
||||
<base href="{3}">
|
||||
<!DOCTYPE html>
|
||||
<html style="height: 0;">
|
||||
<head>
|
||||
<link href="/report/static/src/css/reset.min.css" rel="stylesheet"/>
|
||||
<link href="/web/static/lib/bootstrap/css/bootstrap.css" rel="stylesheet"/>
|
||||
<link href="/website/static/src/css/website.css" rel="stylesheet"/>
|
||||
<link href="/web/static/lib/fontawesome/css/font-awesome.css" rel="stylesheet"/>
|
||||
|
||||
<style type='text/css'>{0}</style>
|
||||
|
||||
<script type='text/javascript'>{1}</script>
|
||||
</head>
|
||||
<body class="container" onload='subst()'>
|
||||
{2}
|
||||
</body>
|
||||
</html>"""
|
||||
|
||||
# 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).
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue