Skip to content

Commit 0aa7ea3

Browse files
committed
[IMP] Minimizes memory consumption
1 parent 92f74f3 commit 0aa7ea3

1 file changed

Lines changed: 104 additions & 76 deletions

File tree

report_py3o/models/py3o_report.py

Lines changed: 104 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,15 @@
88
import json
99
import logging
1010
import os
11+
from contextlib import closing
12+
1113
import pkg_resources
1214
import requests
1315
import sys
14-
from tempfile import NamedTemporaryFile
16+
import tempfile
1517
from zipfile import ZipFile, ZIP_DEFLATED
1618

19+
from openerp.exceptions import AccessError
1720
from openerp.exceptions import UserError
1821
from openerp.report.report_sxw import rml_parse
1922
from openerp import api, fields, models, _
@@ -149,49 +152,71 @@ def _get_parser_context(self, model_instance, data):
149152
self._extend_parser_context(context_instance, report_xml)
150153
return context_instance.localcontext
151154

152-
@api.multi
153-
def _postprocess_report(self, content, res_id, save_in_attachment):
155+
@api.model
156+
def _get_report_from_name(self, report_name):
157+
"""Get the first record of ir.actions.report.xml having the
158+
``report_name`` as value for the field report_name.
159+
"""
160+
res = super(Py3oReport, self)._get_report_from_name(report_name)
161+
if res:
162+
return res
163+
# maybe a py3o reprot
164+
report_obj = self.env['ir.actions.report.xml']
165+
return report_obj.search(
166+
[('report_type', '=', 'py3o'),
167+
('report_name', '=', report_name)])
168+
169+
@api.model
170+
def _postprocess_report(self, report_path, res_id, save_in_attachment):
154171
if save_in_attachment.get(res_id):
155-
attachment = {
156-
'name': save_in_attachment.get(res_id),
157-
'datas': base64.encodestring(content),
158-
'datas_fname': save_in_attachment.get(res_id),
159-
'res_model': save_in_attachment.get('model'),
160-
'res_id': res_id,
161-
}
162-
return self.env['ir.attachment'].create(attachment)
163-
return False
172+
with open(report_path, 'rb') as pdfreport:
173+
attachment = {
174+
'name': save_in_attachment.get(res_id),
175+
'datas': base64.encodestring(pdfreport.read()),
176+
'datas_fname': save_in_attachment.get(res_id),
177+
'res_model': save_in_attachment.get('model'),
178+
'res_id': res_id,
179+
}
180+
try:
181+
self.env['ir.attachment'].create(attachment)
182+
except AccessError:
183+
logger.info("Cannot save PDF report %r as attachment",
184+
attachment['name'])
185+
else:
186+
logger.info(
187+
'The PDF document %s is now saved in the database',
188+
attachment['name'])
164189

165190
@api.multi
166191
def _create_single_report(self, model_instance, data, save_in_attachment):
167192
""" This function to generate our py3o report
168193
"""
169194
self.ensure_one()
170195
report_xml = self.ir_actions_report_xml_id
171-
196+
filetype = report_xml.py3o_filetype
197+
result_fd, result_path = tempfile.mkstemp(
198+
suffix='.' + filetype, prefix='p3o.report.tmp.')
172199
tmpl_data = self.get_template()
173200

174201
in_stream = StringIO(tmpl_data)
175-
out_stream = StringIO()
176-
template = Template(in_stream, out_stream, escape_false=True)
177-
localcontext = self._get_parser_context(model_instance, data)
178-
if report_xml.py3o_is_local_fusion:
179-
template.render(localcontext)
180-
in_stream = out_stream
181-
datadict = {}
182-
else:
183-
expressions = template.get_all_user_python_expression()
184-
py_expression = template.convert_py3o_to_python_ast(expressions)
185-
convertor = Py3oConvertor()
186-
data_struct = convertor(py_expression)
187-
datadict = data_struct.render(localcontext)
188-
189-
filetype = report_xml.py3o_filetype
190-
is_native = Formats().get_format(filetype).native
191-
if is_native:
192-
res = out_stream.getvalue()
193-
else: # Call py3o.server to render the template in the desired format
194-
in_stream.seek(0)
202+
with closing(os.fdopen(result_fd, 'w+')) as out_stream:
203+
template = Template(in_stream, out_stream, escape_false=True)
204+
localcontext = self._get_parser_context(model_instance, data)
205+
is_native = Formats().get_format(filetype).native
206+
if report_xml.py3o_is_local_fusion:
207+
template.render(localcontext)
208+
out_stream.seek(0)
209+
in_stream = out_stream.read()
210+
datadict = {}
211+
else:
212+
expressions = template.get_all_user_python_expression()
213+
py_expression = template.convert_py3o_to_python_ast(expressions)
214+
convertor = Py3oConvertor()
215+
data_struct = convertor(py_expression)
216+
datadict = data_struct.render(localcontext)
217+
218+
if not is_native or not report_xml.py3o_is_local_fusion:
219+
# Call py3o.server to render the template in the desired format
195220
files = {
196221
'tmpl_file': in_stream,
197222
}
@@ -210,21 +235,13 @@ def _create_single_report(self, model_instance, data, save_in_attachment):
210235
_('Fusion server error %s') % r.text,
211236
)
212237

213-
# Here is a little joke about Odoo
214-
# we do nice chunked reading from the network...
215238
chunk_size = 1024
216-
with NamedTemporaryFile(
217-
suffix=filetype,
218-
prefix='py3o-template-'
219-
) as fd:
239+
with closing(os.fdopen(result_fd, 'w+')) as out_stream:
220240
for chunk in r.iter_content(chunk_size):
221-
fd.write(chunk)
222-
fd.seek(0)
223-
# ... but odoo wants the whole data in memory anyways :)
224-
res = fd.read()
241+
out_stream.write(chunk)
225242
self._postprocess_report(
226-
res, model_instance.id, save_in_attachment)
227-
return res, "." + self.ir_actions_report_xml_id.py3o_filetype
243+
result_path, model_instance.id, save_in_attachment)
244+
return result_path
228245

229246
@api.multi
230247
def _get_or_create_single_report(self, model_instance, data,
@@ -239,43 +256,42 @@ def _get_or_create_single_report(self, model_instance, data,
239256
model_instance, data, save_in_attachment)
240257

241258
@api.multi
242-
def _zip_results(self, results):
259+
def _zip_results(self, reports_path):
243260
self.ensure_one()
244261
zfname_prefix = self.ir_actions_report_xml_id.name
245-
with NamedTemporaryFile(suffix="zip", prefix='py3o-zip-result') as fd:
246-
with ZipFile(fd, 'w', ZIP_DEFLATED) as zf:
247-
cpt = 0
248-
for r, ext in results:
249-
fname = "%s_%d.%s" % (zfname_prefix, cpt, ext)
250-
zf.writestr(fname, r)
251-
cpt += 1
252-
fd.seek(0)
253-
return fd.read(), 'zip'
262+
result_path = tempfile.mktemp(suffix="zip", prefix='py3o-zip-result')
263+
with ZipFile(result_path, 'w', ZIP_DEFLATED) as zf:
264+
cpt = 0
265+
for report in reports_path:
266+
fname = "%s_%d.%s" % (
267+
zfname_prefix, cpt, report.split('.')[-1])
268+
zf.write(report, fname)
254269

255-
@api.multi
256-
def _merge_pdfs(self, results):
257-
from pyPdf import PdfFileWriter, PdfFileReader
258-
output = PdfFileWriter()
259-
for r in results:
260-
reader = PdfFileReader(StringIO(r[0]))
261-
for page in range(reader.getNumPages()):
262-
output.addPage(reader.getPage(page))
263-
s = StringIO()
264-
output.write(s)
265-
return s.getvalue(), formats.FORMAT_PDF
270+
cpt += 1
271+
return result_path
266272

267273
@api.multi
268-
def _merge_results(self, results):
274+
def _merge_results(self, reports_path):
269275
self.ensure_one()
270-
if not results:
276+
if not reports_path:
271277
return False, False
272-
if len(results) == 1:
273-
return results[0]
278+
if len(reports_path) == 1:
279+
return reports_path[0]
274280
filetype = self.ir_actions_report_xml_id.py3o_filetype
275281
if filetype == formats.FORMAT_PDF:
276-
return self._merge_pdfs(results)
282+
return self._merge_pdf(reports_path), formats.FORMAT_PDF
277283
else:
278-
return self._zip_results(results)
284+
return self._zip_results(reports_path), 'zip'
285+
286+
@api.model
287+
def _cleanup_tempfiles(self, temporary_files):
288+
# Manual cleanup of the temporary files
289+
for temporary_file in temporary_files:
290+
try:
291+
os.unlink(temporary_file)
292+
except (OSError, IOError):
293+
logger.error(
294+
'Error when trying to remove file %s' % temporary_file)
279295

280296
@api.multi
281297
def create_report(self, res_ids, data):
@@ -285,8 +301,20 @@ def create_report(self, res_ids, data):
285301
res_ids)
286302
save_in_attachment = self._check_attachment_use(
287303
model_instances, self.ir_actions_report_xml_id) or {}
288-
results = []
304+
reports_path = []
289305
for model_instance in model_instances:
290-
results.append(self._get_or_create_single_report(
291-
model_instance, data, save_in_attachment))
292-
return self._merge_results(results)
306+
reports_path.append(self._get_or_create_single_report(
307+
model_instance, data, save_in_attachment))
308+
309+
result_path, filtype = self._merge_results(reports_path)
310+
reports_path.append(result_path)
311+
312+
# Here is a little joke about Odoo
313+
# we do all the generation process using files to avoid memory
314+
# consumption...
315+
# ... but odoo wants the whole data in memory anyways :)
316+
317+
with open(result_path, 'r+b') as fd:
318+
res = fd.read()
319+
self._cleanup_tempfiles(reports_path)
320+
return res, filtype

0 commit comments

Comments
 (0)