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,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