88import json
99import logging
1010import os
11+ from contextlib import closing
12+
1113import pkg_resources
1214import requests
1315import sys
14- from tempfile import NamedTemporaryFile
16+ import tempfile
1517from zipfile import ZipFile , ZIP_DEFLATED
1618
19+ from openerp .exceptions import AccessError
1720from openerp .exceptions import UserError
1821from openerp .report .report_sxw import rml_parse
1922from openerp import api , fields , models , _
@@ -149,49 +152,72 @@ 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 (
214+ expressions )
215+ convertor = Py3oConvertor ()
216+ data_struct = convertor (py_expression )
217+ datadict = data_struct .render (localcontext )
218+
219+ if not is_native or not report_xml .py3o_is_local_fusion :
220+ # Call py3o.server to render the template in the desired format
195221 files = {
196222 'tmpl_file' : in_stream ,
197223 }
@@ -210,21 +236,13 @@ def _create_single_report(self, model_instance, data, save_in_attachment):
210236 _ ('Fusion server error %s' ) % r .text ,
211237 )
212238
213- # Here is a little joke about Odoo
214- # we do nice chunked reading from the network...
215239 chunk_size = 1024
216- with NamedTemporaryFile (
217- suffix = filetype ,
218- prefix = 'py3o-template-'
219- ) as fd :
240+ with open (result_path , 'w+' ) as fd :
220241 for chunk in r .iter_content (chunk_size ):
221242 fd .write (chunk )
222- fd .seek (0 )
223- # ... but odoo wants the whole data in memory anyways :)
224- res = fd .read ()
225243 self ._postprocess_report (
226- res , model_instance .id , save_in_attachment )
227- return res , "." + self . ir_actions_report_xml_id . py3o_filetype
244+ result_path , model_instance .id , save_in_attachment )
245+ return result_path
228246
229247 @api .multi
230248 def _get_or_create_single_report (self , model_instance , data ,
@@ -239,43 +257,42 @@ def _get_or_create_single_report(self, model_instance, data,
239257 model_instance , data , save_in_attachment )
240258
241259 @api .multi
242- def _zip_results (self , results ):
260+ def _zip_results (self , reports_path ):
243261 self .ensure_one ()
244262 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'
263+ result_path = tempfile .mktemp (suffix = "zip" , prefix = 'py3o-zip-result' )
264+ with ZipFile (result_path , 'w' , ZIP_DEFLATED ) as zf :
265+ cpt = 0
266+ for report in reports_path :
267+ fname = "%s_%d.%s" % (
268+ zfname_prefix , cpt , report .split ('.' )[- 1 ])
269+ zf .write (report , fname )
254270
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
271+ cpt += 1
272+ return result_path
266273
267274 @api .multi
268- def _merge_results (self , results ):
275+ def _merge_results (self , reports_path ):
269276 self .ensure_one ()
270- if not results :
271- return False , False
272- if len (results ) == 1 :
273- return results [0 ]
274277 filetype = self .ir_actions_report_xml_id .py3o_filetype
278+ if not reports_path :
279+ return False , False
280+ if len (reports_path ) == 1 :
281+ return reports_path [0 ], filetype
275282 if filetype == formats .FORMAT_PDF :
276- return self ._merge_pdfs ( results )
283+ return self ._merge_pdf ( reports_path ), formats . FORMAT_PDF
277284 else :
278- return self ._zip_results (results )
285+ return self ._zip_results (reports_path ), 'zip'
286+
287+ @api .model
288+ def _cleanup_tempfiles (self , temporary_files ):
289+ # Manual cleanup of the temporary files
290+ for temporary_file in temporary_files :
291+ try :
292+ os .unlink (temporary_file )
293+ except (OSError , IOError ):
294+ logger .error (
295+ 'Error when trying to remove file %s' % temporary_file )
279296
280297 @api .multi
281298 def create_report (self , res_ids , data ):
@@ -285,8 +302,21 @@ def create_report(self, res_ids, data):
285302 res_ids )
286303 save_in_attachment = self ._check_attachment_use (
287304 model_instances , self .ir_actions_report_xml_id ) or {}
288- results = []
305+ reports_path = []
289306 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 )
307+ reports_path .append (
308+ self ._get_or_create_single_report (
309+ model_instance , data , save_in_attachment ))
310+
311+ result_path , filetype = self ._merge_results (reports_path )
312+ reports_path .append (result_path )
313+
314+ # Here is a little joke about Odoo
315+ # we do all the generation process using files to avoid memory
316+ # consumption...
317+ # ... but odoo wants the whole data in memory anyways :)
318+
319+ with open (result_path , 'r+b' ) as fd :
320+ res = fd .read ()
321+ self ._cleanup_tempfiles (set (reports_path ))
322+ return res , filetype
0 commit comments