88import json
99import logging
1010import os
11+ from contextlib import closing
12+
1113import pkg_resources
1214import requests
1315import sys
14- from tempfile import NamedTemporaryFile
15- import logging
16+ import tempfile
1617from zipfile import ZipFile , ZIP_DEFLATED
1718
19+ from odoo .exceptions import AccessError
1820from odoo .exceptions import UserError
1921from openerp import api , fields , models , _
2022from odoo .report .report_sxw import rml_parse
@@ -151,49 +153,72 @@ def _get_parser_context(self, model_instance, data):
151153 self ._extend_parser_context (context_instance , report_xml )
152154 return context_instance .localcontext
153155
154- @api .multi
155- def _postprocess_report (self , content , res_id , save_in_attachment ):
156+ @api .model
157+ def _get_report_from_name (self , report_name ):
158+ """Get the first record of ir.actions.report.xml having the
159+ ``report_name`` as value for the field report_name.
160+ """
161+ res = super (Py3oReport , self )._get_report_from_name (report_name )
162+ if res :
163+ return res
164+ # maybe a py3o reprot
165+ report_obj = self .env ['ir.actions.report.xml' ]
166+ return report_obj .search (
167+ [('report_type' , '=' , 'py3o' ),
168+ ('report_name' , '=' , report_name )])
169+
170+ @api .model
171+ def _postprocess_report (self , report_path , res_id , save_in_attachment ):
156172 if save_in_attachment .get (res_id ):
157- attachment = {
158- 'name' : save_in_attachment .get (res_id ),
159- 'datas' : base64 .encodestring (content ),
160- 'datas_fname' : save_in_attachment .get (res_id ),
161- 'res_model' : save_in_attachment .get ('model' ),
162- 'res_id' : res_id ,
163- }
164- return self .env ['ir.attachment' ].create (attachment )
165- return False
173+ with open (report_path , 'rb' ) as pdfreport :
174+ attachment = {
175+ 'name' : save_in_attachment .get (res_id ),
176+ 'datas' : base64 .encodestring (pdfreport .read ()),
177+ 'datas_fname' : save_in_attachment .get (res_id ),
178+ 'res_model' : save_in_attachment .get ('model' ),
179+ 'res_id' : res_id ,
180+ }
181+ try :
182+ self .env ['ir.attachment' ].create (attachment )
183+ except AccessError :
184+ logger .info ("Cannot save PDF report %r as attachment" ,
185+ attachment ['name' ])
186+ else :
187+ logger .info (
188+ 'The PDF document %s is now saved in the database' ,
189+ attachment ['name' ])
166190
167191 @api .multi
168192 def _create_single_report (self , model_instance , data , save_in_attachment ):
169193 """ This function to generate our py3o report
170194 """
171195 self .ensure_one ()
172196 report_xml = self .ir_actions_report_xml_id
173-
197+ filetype = report_xml .py3o_filetype
198+ result_fd , result_path = tempfile .mkstemp (
199+ suffix = '.' + filetype , prefix = 'p3o.report.tmp.' )
174200 tmpl_data = self .get_template ()
175201
176202 in_stream = StringIO (tmpl_data )
177- out_stream = StringIO ()
178- template = Template (in_stream , out_stream , escape_false = True )
179- localcontext = self ._get_parser_context (model_instance , data )
180- if report_xml .py3o_is_local_fusion :
181- template .render (localcontext )
182- in_stream = out_stream
183- datadict = {}
184- else :
185- expressions = template .get_all_user_python_expression ()
186- py_expression = template .convert_py3o_to_python_ast (expressions )
187- convertor = Py3oConvertor ()
188- data_struct = convertor (py_expression )
189- datadict = data_struct .render (localcontext )
190-
191- filetype = report_xml .py3o_filetype
192- is_native = Formats ().get_format (filetype ).native
193- if is_native :
194- res = out_stream .getvalue ()
195- else : # Call py3o.server to render the template in the desired format
196- in_stream .seek (0 )
203+ with closing (os .fdopen (result_fd , 'w+' )) as out_stream :
204+ template = Template (in_stream , out_stream , escape_false = True )
205+ localcontext = self ._get_parser_context (model_instance , data )
206+ is_native = Formats ().get_format (filetype ).native
207+ if report_xml .py3o_is_local_fusion :
208+ template .render (localcontext )
209+ out_stream .seek (0 )
210+ in_stream = out_stream .read ()
211+ datadict = {}
212+ else :
213+ expressions = template .get_all_user_python_expression ()
214+ py_expression = template .convert_py3o_to_python_ast (
215+ expressions )
216+ convertor = Py3oConvertor ()
217+ data_struct = convertor (py_expression )
218+ datadict = data_struct .render (localcontext )
219+
220+ if not is_native or not report_xml .py3o_is_local_fusion :
221+ # Call py3o.server to render the template in the desired format
197222 files = {
198223 'tmpl_file' : in_stream ,
199224 }
@@ -212,21 +237,13 @@ def _create_single_report(self, model_instance, data, save_in_attachment):
212237 _ ('Fusion server error %s' ) % r .text ,
213238 )
214239
215- # Here is a little joke about Odoo
216- # we do nice chunked reading from the network...
217240 chunk_size = 1024
218- with NamedTemporaryFile (
219- suffix = filetype ,
220- prefix = 'py3o-template-'
221- ) as fd :
241+ with open (result_path , 'w+' ) as fd :
222242 for chunk in r .iter_content (chunk_size ):
223243 fd .write (chunk )
224- fd .seek (0 )
225- # ... but odoo wants the whole data in memory anyways :)
226- res = fd .read ()
227244 self ._postprocess_report (
228- res , model_instance .id , save_in_attachment )
229- return res , "." + self . ir_actions_report_xml_id . py3o_filetype
245+ result_path , model_instance .id , save_in_attachment )
246+ return result_path
230247
231248 @api .multi
232249 def _get_or_create_single_report (self , model_instance , data ,
@@ -241,43 +258,42 @@ def _get_or_create_single_report(self, model_instance, data,
241258 model_instance , data , save_in_attachment )
242259
243260 @api .multi
244- def _zip_results (self , results ):
261+ def _zip_results (self , reports_path ):
245262 self .ensure_one ()
246263 zfname_prefix = self .ir_actions_report_xml_id .name
247- with NamedTemporaryFile (suffix = "zip" , prefix = 'py3o-zip-result' ) as fd :
248- with ZipFile (fd , 'w' , ZIP_DEFLATED ) as zf :
249- cpt = 0
250- for r , ext in results :
251- fname = "%s_%d.%s" % (zfname_prefix , cpt , ext )
252- zf .writestr (fname , r )
253- cpt += 1
254- fd .seek (0 )
255- return fd .read (), 'zip'
264+ result_path = tempfile .mktemp (suffix = "zip" , prefix = 'py3o-zip-result' )
265+ with ZipFile (result_path , 'w' , ZIP_DEFLATED ) as zf :
266+ cpt = 0
267+ for report in reports_path :
268+ fname = "%s_%d.%s" % (
269+ zfname_prefix , cpt , report .split ('.' )[- 1 ])
270+ zf .write (report , fname )
256271
257- @api .multi
258- def _merge_pdfs (self , results ):
259- from pyPdf import PdfFileWriter , PdfFileReader
260- output = PdfFileWriter ()
261- for r in results :
262- reader = PdfFileReader (StringIO (r [0 ]))
263- for page in range (reader .getNumPages ()):
264- output .addPage (reader .getPage (page ))
265- s = StringIO ()
266- output .write (s )
267- return s .getvalue (), formats .FORMAT_PDF
272+ cpt += 1
273+ return result_path
268274
269275 @api .multi
270- def _merge_results (self , results ):
276+ def _merge_results (self , reports_path ):
271277 self .ensure_one ()
272- if not results :
273- return False , False
274- if len (results ) == 1 :
275- return results [0 ]
276278 filetype = self .ir_actions_report_xml_id .py3o_filetype
279+ if not reports_path :
280+ return False , False
281+ if len (reports_path ) == 1 :
282+ return reports_path [0 ], filetype
277283 if filetype == formats .FORMAT_PDF :
278- return self ._merge_pdfs ( results )
284+ return self ._merge_pdf ( reports_path ), formats . FORMAT_PDF
279285 else :
280- return self ._zip_results (results )
286+ return self ._zip_results (reports_path ), 'zip'
287+
288+ @api .model
289+ def _cleanup_tempfiles (self , temporary_files ):
290+ # Manual cleanup of the temporary files
291+ for temporary_file in temporary_files :
292+ try :
293+ os .unlink (temporary_file )
294+ except (OSError , IOError ):
295+ logger .error (
296+ 'Error when trying to remove file %s' % temporary_file )
281297
282298 @api .multi
283299 def create_report (self , res_ids , data ):
@@ -287,8 +303,21 @@ def create_report(self, res_ids, data):
287303 res_ids )
288304 save_in_attachment = self ._check_attachment_use (
289305 model_instances , self .ir_actions_report_xml_id ) or {}
290- results = []
306+ reports_path = []
291307 for model_instance in model_instances :
292- results .append (self ._get_or_create_single_report (
293- model_instance , data , save_in_attachment ))
294- return self ._merge_results (results )
308+ reports_path .append (
309+ self ._get_or_create_single_report (
310+ model_instance , data , save_in_attachment ))
311+
312+ result_path , filetype = self ._merge_results (reports_path )
313+ reports_path .append (result_path )
314+
315+ # Here is a little joke about Odoo
316+ # we do all the generation process using files to avoid memory
317+ # consumption...
318+ # ... but odoo wants the whole data in memory anyways :)
319+
320+ with open (result_path , 'r+b' ) as fd :
321+ res = fd .read ()
322+ self ._cleanup_tempfiles (set (reports_path ))
323+ return res , filetype
0 commit comments