[FIX] report: windows: lower level of file opening/closing, which to find wkhtmltpdf
* Use of 'which' to find the wkhtmltopdf binary (allow the win32 service to find it) * Use of mkstemp and manual close of the file descriptors
This commit is contained in:
parent
e458be4a14
commit
f299b40fe6
|
@ -21,7 +21,7 @@
|
||||||
|
|
||||||
from openerp import api
|
from openerp import api
|
||||||
from openerp.osv import osv
|
from openerp.osv import osv
|
||||||
from openerp.tools import config
|
from openerp.tools import config, which
|
||||||
from openerp.tools.translate import _
|
from openerp.tools.translate import _
|
||||||
from openerp.addons.web.http import request
|
from openerp.addons.web.http import request
|
||||||
from openerp.tools.safe_eval import safe_eval as eval
|
from openerp.tools.safe_eval import safe_eval as eval
|
||||||
|
@ -32,31 +32,41 @@ import base64
|
||||||
import logging
|
import logging
|
||||||
import tempfile
|
import tempfile
|
||||||
import lxml.html
|
import lxml.html
|
||||||
import cStringIO
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
|
from contextlib import closing
|
||||||
from distutils.version import LooseVersion
|
from distutils.version import LooseVersion
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from os import name as OsName
|
|
||||||
from pyPdf import PdfFileWriter, PdfFileReader
|
from pyPdf import PdfFileWriter, PdfFileReader
|
||||||
from shutil import rmtree
|
|
||||||
|
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
"""Check the presence of wkhtmltopdf and return its version at OpnerERP start-up."""
|
#--------------------------------------------------------------------------
|
||||||
|
# Helpers
|
||||||
|
#--------------------------------------------------------------------------
|
||||||
|
def _get_wkhtmltopdf_bin():
|
||||||
|
defpath = os.environ.get('PATH', os.defpath).split(os.pathsep)
|
||||||
|
return which('wkhtmltopdf', path=os.pathsep.join(defpath))
|
||||||
|
|
||||||
|
|
||||||
|
#--------------------------------------------------------------------------
|
||||||
|
# Check the presence of Wkhtmltopdf and return its version at Odoo start-up
|
||||||
|
#--------------------------------------------------------------------------
|
||||||
wkhtmltopdf_state = 'install'
|
wkhtmltopdf_state = 'install'
|
||||||
try:
|
try:
|
||||||
process = subprocess.Popen(
|
process = subprocess.Popen(
|
||||||
['wkhtmltopdf', '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
[_get_wkhtmltopdf_bin(), '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||||
)
|
)
|
||||||
except OSError:
|
except (OSError, IOError):
|
||||||
_logger.info('You need wkhtmltopdf to print a pdf version of the reports.')
|
_logger.info('You need Wkhtmltopdf to print a pdf version of the reports.')
|
||||||
else:
|
else:
|
||||||
|
_logger.info('Will use the Wkhtmltopdf binary at %s' % _get_wkhtmltopdf_bin())
|
||||||
out, err = process.communicate()
|
out, err = process.communicate()
|
||||||
version = re.search('([0-9.]+)', out).group(0)
|
version = re.search('([0-9.]+)', out).group(0)
|
||||||
if LooseVersion(version) < LooseVersion('0.12.0'):
|
if LooseVersion(version) < LooseVersion('0.12.0'):
|
||||||
_logger.info('Upgrade wkhtmltopdf to (at least) 0.12.0')
|
_logger.info('Upgrade Wkhtmltopdf to (at least) 0.12.0')
|
||||||
wkhtmltopdf_state = 'upgrade'
|
wkhtmltopdf_state = 'upgrade'
|
||||||
else:
|
else:
|
||||||
wkhtmltopdf_state = 'ok'
|
wkhtmltopdf_state = 'ok'
|
||||||
|
@ -353,9 +363,7 @@ class Report(osv.Model):
|
||||||
:param save_in_attachment: dict of reports to save/load in/from the db
|
:param save_in_attachment: dict of reports to save/load in/from the db
|
||||||
:returns: Content of the pdf as a string
|
:returns: Content of the pdf as a string
|
||||||
"""
|
"""
|
||||||
command = ['wkhtmltopdf']
|
|
||||||
command_args = []
|
command_args = []
|
||||||
tmp_dir = tempfile.mkdtemp(prefix='report.tmp.')
|
|
||||||
|
|
||||||
# Passing the cookie to wkhtmltopdf in order to resolve internal links.
|
# Passing the cookie to wkhtmltopdf in order to resolve internal links.
|
||||||
try:
|
try:
|
||||||
|
@ -383,44 +391,45 @@ class Report(osv.Model):
|
||||||
|
|
||||||
# Execute WKhtmltopdf
|
# Execute WKhtmltopdf
|
||||||
pdfdocuments = []
|
pdfdocuments = []
|
||||||
|
temporary_files = []
|
||||||
# Workaround the NamedTemporaryFile limitation on Windows, which requires the file to
|
|
||||||
# be closed before accessing it by its name.
|
|
||||||
delete_tempfile = True
|
|
||||||
if OsName == 'nt':
|
|
||||||
delete_tempfile = False
|
|
||||||
|
|
||||||
for index, reporthtml in enumerate(bodies):
|
for index, reporthtml in enumerate(bodies):
|
||||||
local_command_args = []
|
local_command_args = []
|
||||||
pdfreport = tempfile.NamedTemporaryFile(delete=delete_tempfile, suffix='.pdf', prefix='report.tmp.', dir=tmp_dir, mode='w+b')
|
pdfreport_fd, pdfreport_path = tempfile.mkstemp(suffix='.pdf', prefix='report.tmp.')
|
||||||
|
temporary_files.append(pdfreport_path)
|
||||||
|
|
||||||
# Directly load the document if we already have it
|
# Directly load the document if we already have it
|
||||||
if save_in_attachment and save_in_attachment['loaded_documents'].get(reporthtml[0]):
|
if save_in_attachment and save_in_attachment['loaded_documents'].get(reporthtml[0]):
|
||||||
pdfreport.write(save_in_attachment['loaded_documents'].get(reporthtml[0]))
|
with closing(os.fdopen(pdfreport_fd, 'w')) as pdfreport:
|
||||||
pdfreport.seek(0)
|
pdfreport.write(save_in_attachment['loaded_documents'][reporthtml[0]])
|
||||||
pdfdocuments.append(pdfreport)
|
pdfdocuments.append(pdfreport_path)
|
||||||
continue
|
continue
|
||||||
|
else:
|
||||||
|
os.close(pdfreport_fd)
|
||||||
|
|
||||||
# Wkhtmltopdf handles header/footer as separate pages. Create them if necessary.
|
# Wkhtmltopdf handles header/footer as separate pages. Create them if necessary.
|
||||||
if headers:
|
if headers:
|
||||||
head_file = tempfile.NamedTemporaryFile(delete=delete_tempfile, suffix='.html', prefix='report.header.tmp.', dir=tmp_dir, mode='w+')
|
head_file_fd, head_file_path = tempfile.mkstemp(suffix='.html', prefix='report.header.tmp.')
|
||||||
head_file.write(headers[index])
|
temporary_files.append(head_file_path)
|
||||||
head_file.seek(0)
|
with closing(os.fdopen(head_file_fd, 'w')) as head_file:
|
||||||
local_command_args.extend(['--header-html', head_file.name])
|
head_file.write(headers[index])
|
||||||
|
local_command_args.extend(['--header-html', head_file_path])
|
||||||
if footers:
|
if footers:
|
||||||
foot_file = tempfile.NamedTemporaryFile(delete=delete_tempfile, suffix='.html', prefix='report.footer.tmp.', dir=tmp_dir, mode='w+')
|
foot_file_fd, foot_file_path = tempfile.mkstemp(suffix='.html', prefix='report.footer.tmp.')
|
||||||
foot_file.write(footers[index])
|
temporary_files.append(foot_file_path)
|
||||||
foot_file.seek(0)
|
with closing(os.fdopen(foot_file_fd, 'w')) as foot_file:
|
||||||
local_command_args.extend(['--footer-html', foot_file.name])
|
foot_file.write(footers[index])
|
||||||
|
local_command_args.extend(['--footer-html', foot_file_path])
|
||||||
|
|
||||||
# Body stuff
|
# Body stuff
|
||||||
content_file = tempfile.NamedTemporaryFile(delete=delete_tempfile, suffix='.html', prefix='report.body.tmp.', dir=tmp_dir, mode='w+')
|
content_file_fd, content_file_path = tempfile.mkstemp(suffix='.html', prefix='report.body.tmp.')
|
||||||
content_file.write(reporthtml[1])
|
temporary_files.append(content_file_path)
|
||||||
content_file.seek(0)
|
with closing(os.fdopen(content_file_fd, 'w')) as content_file:
|
||||||
|
content_file.write(reporthtml[1])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
wkhtmltopdf = command + command_args + local_command_args
|
wkhtmltopdf = [_get_wkhtmltopdf_bin()] + command_args + local_command_args
|
||||||
wkhtmltopdf += [content_file.name] + [pdfreport.name]
|
wkhtmltopdf += [content_file_path] + [pdfreport_path]
|
||||||
|
|
||||||
process = subprocess.Popen(wkhtmltopdf, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
process = subprocess.Popen(wkhtmltopdf, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||||
out, err = process.communicate()
|
out, err = process.communicate()
|
||||||
|
@ -432,37 +441,38 @@ class Report(osv.Model):
|
||||||
|
|
||||||
# Save the pdf in attachment if marked
|
# Save the pdf in attachment if marked
|
||||||
if reporthtml[0] is not False and save_in_attachment.get(reporthtml[0]):
|
if reporthtml[0] is not False and save_in_attachment.get(reporthtml[0]):
|
||||||
attachment = {
|
with open(pdfreport_path, 'rb') as pdfreport:
|
||||||
'name': save_in_attachment.get(reporthtml[0]),
|
attachment = {
|
||||||
'datas': base64.encodestring(pdfreport.read()),
|
'name': save_in_attachment.get(reporthtml[0]),
|
||||||
'datas_fname': save_in_attachment.get(reporthtml[0]),
|
'datas': base64.encodestring(pdfreport.read()),
|
||||||
'res_model': save_in_attachment.get('model'),
|
'datas_fname': save_in_attachment.get(reporthtml[0]),
|
||||||
'res_id': reporthtml[0],
|
'res_model': save_in_attachment.get('model'),
|
||||||
}
|
'res_id': reporthtml[0],
|
||||||
self.pool['ir.attachment'].create(cr, uid, attachment)
|
}
|
||||||
|
self.pool['ir.attachment'].create(cr, uid, attachment)
|
||||||
_logger.info('The PDF document %s is now saved in the '
|
_logger.info('The PDF document %s is now saved in the '
|
||||||
'database' % attachment['name'])
|
'database' % attachment['name'])
|
||||||
|
|
||||||
pdfreport.seek(0)
|
pdfdocuments.append(pdfreport_path)
|
||||||
pdfdocuments.append(pdfreport)
|
|
||||||
|
|
||||||
content_file.close()
|
|
||||||
|
|
||||||
if headers:
|
|
||||||
head_file.close()
|
|
||||||
if footers:
|
|
||||||
foot_file.close()
|
|
||||||
except:
|
except:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
# Return the entire document
|
# Return the entire document
|
||||||
if len(pdfdocuments) == 1:
|
if len(pdfdocuments) == 1:
|
||||||
content = pdfdocuments[0].read()
|
entire_report_path = pdfdocuments[0]
|
||||||
pdfdocuments[0].close()
|
|
||||||
else:
|
else:
|
||||||
content = self._merge_pdf(pdfdocuments)
|
entire_report_path = self._merge_pdf(pdfdocuments)
|
||||||
|
temporary_files.append(entire_report_path)
|
||||||
|
|
||||||
rmtree(tmp_dir)
|
with open(entire_report_path, 'rb') as pdfdocument:
|
||||||
|
content = pdfdocument.read()
|
||||||
|
|
||||||
|
# Manual cleanup of the temporary files
|
||||||
|
for temporary_file in temporary_files:
|
||||||
|
try:
|
||||||
|
os.unlink(temporary_file)
|
||||||
|
except (OSError, IOError):
|
||||||
|
_logger.error('Error when trying to remove file %s' % temporary_file)
|
||||||
|
|
||||||
return content
|
return content
|
||||||
|
|
||||||
|
@ -499,7 +509,7 @@ class Report(osv.Model):
|
||||||
if specific_paperformat_args and specific_paperformat_args.get('data-report-dpi'):
|
if specific_paperformat_args and specific_paperformat_args.get('data-report-dpi'):
|
||||||
command_args.extend(['--dpi', str(specific_paperformat_args['data-report-dpi'])])
|
command_args.extend(['--dpi', str(specific_paperformat_args['data-report-dpi'])])
|
||||||
elif paperformat.dpi:
|
elif paperformat.dpi:
|
||||||
if OsName == 'nt' and int(paperformat.dpi) <= 95:
|
if os.name == 'nt' and int(paperformat.dpi) <= 95:
|
||||||
_logger.info("Generating PDF on Windows platform require DPI >= 96. Using 96 instead.")
|
_logger.info("Generating PDF on Windows platform require DPI >= 96. Using 96 instead.")
|
||||||
command_args.extend(['--dpi', '96'])
|
command_args.extend(['--dpi', '96'])
|
||||||
else:
|
else:
|
||||||
|
@ -526,18 +536,23 @@ class Report(osv.Model):
|
||||||
def _merge_pdf(self, documents):
|
def _merge_pdf(self, documents):
|
||||||
"""Merge PDF files into one.
|
"""Merge PDF files into one.
|
||||||
|
|
||||||
:param documents: list of pdf files
|
:param documents: list of path of pdf files
|
||||||
:returns: string containing the merged pdf
|
:returns: path of the merged pdf
|
||||||
"""
|
"""
|
||||||
writer = PdfFileWriter()
|
writer = PdfFileWriter()
|
||||||
|
streams = [] # We have to close the streams *after* PdfFilWriter's call to write()
|
||||||
for document in documents:
|
for document in documents:
|
||||||
reader = PdfFileReader(file(document.name, "rb"))
|
pdfreport = file(document, 'rb')
|
||||||
|
streams.append(pdfreport)
|
||||||
|
reader = PdfFileReader(pdfreport)
|
||||||
for page in range(0, reader.getNumPages()):
|
for page in range(0, reader.getNumPages()):
|
||||||
writer.addPage(reader.getPage(page))
|
writer.addPage(reader.getPage(page))
|
||||||
document.close()
|
|
||||||
merged = cStringIO.StringIO()
|
merged_file_fd, merged_file_path = tempfile.mkstemp(suffix='.html', prefix='report.merged.tmp.')
|
||||||
writer.write(merged)
|
with closing(os.fdopen(merged_file_fd, 'w')) as merged_file:
|
||||||
merged.seek(0)
|
writer.write(merged_file)
|
||||||
content = merged.read()
|
|
||||||
merged.close()
|
for stream in streams:
|
||||||
return content
|
stream.close()
|
||||||
|
|
||||||
|
return merged_file_path
|
||||||
|
|
Loading…
Reference in New Issue