11# -*- coding: utf-8 -*-
22# Copyright 2013 XCG Consulting (http://odoo.consulting)
3+ # Copyright 2016 ACSONE SA/NV
34# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
5+ import base64
6+ from base64 import b64decode
47from cStringIO import StringIO
58import json
6- import pkg_resources
9+ import logging
710import os
8- import sys
9- from base64 import b64decode
11+ import pkg_resources
1012import requests
13+ import sys
1114from tempfile import NamedTemporaryFile
12- from openerp import _
13- from openerp import exceptions
14- from openerp .report . report_sxw import report_sxw
15- from openerp import registry
16- import logging
15+ from zipfile import ZipFile , ZIP_DEFLATED
16+
17+ from openerp .exceptions import UserError
18+ from openerp . report . report_sxw import rml_parse
19+ from openerp import api , fields , models , _
1720
1821logger = logging .getLogger (__name__ )
1922
2023try :
2124 from py3o .template .helpers import Py3oConvertor
2225 from py3o .template import Template
26+ from py3o import formats
2327except ImportError :
2428 logger .debug ('Cannot import py3o.template' )
2529try :
@@ -64,42 +68,46 @@ def defautl_extend(report_xml, localcontext):
6468 localcontext ['b64decode' ] = b64decode
6569
6670
67- class Py3oParser (report_sxw ):
68- """Custom class that use Py3o to render libroffice reports.
69- Code partially taken from CampToCamp's webkit_report."""
71+ class Py3oReport (models .TransientModel ):
72+ _name = "py3o.report"
73+ _inherit = 'report'
74+ _description = "Report Py30"
7075
71- def get_template (self , report_obj ):
76+ ir_actions_report_xml_id = fields .Many2one (
77+ comodel_name = "ir.actions.report.xml" ,
78+ required = True
79+ )
80+
81+ @api .multi
82+ def get_template (self ):
7283 """private helper to fetch the template data either from the database
7384 or from the default template file provided by the implementer.
7485
7586 ATM this method takes a report definition recordset
7687 to try and fetch the report template from database. If not found it
7788 will fallback to the template file referenced in the report definition.
7889
79- @param report_obj: a recordset representing the report defintion
80- @type report_obj: openerp.model.recordset instance
81-
8290 @returns: string or buffer containing the template data
8391
8492 @raises: TemplateNotFound which is a subclass of
8593 openerp.exceptions.DeferredException
8694 """
87-
95+ self . ensure_one ()
8896 tmpl_data = None
89-
90- if report_obj .py3o_template_id and report_obj .py3o_template_id .id :
97+ report_xml = self . ir_actions_report_xml_id
98+ if report_xml .py3o_template_id and report_xml .py3o_template_id .id :
9199 # if a user gave a report template
92100 tmpl_data = b64decode (
93- report_obj .py3o_template_id .py3o_template_data
101+ report_xml .py3o_template_id .py3o_template_data
94102 )
95103
96- elif report_obj .py3o_template_fallback :
97- tmpl_name = report_obj .py3o_template_fallback
104+ elif report_xml .py3o_template_fallback :
105+ tmpl_name = report_xml .py3o_template_fallback
98106 flbk_filename = None
99- if report_obj .module :
107+ if report_xml .module :
100108 # if the default is defined
101109 flbk_filename = pkg_resources .resource_filename (
102- "openerp.addons.%s" % report_obj .module ,
110+ "openerp.addons.%s" % report_xml .module ,
103111 tmpl_name ,
104112 )
105113 elif os .path .isabs (tmpl_name ):
@@ -119,37 +127,53 @@ def get_template(self, report_obj):
119127
120128 return tmpl_data
121129
122- def _extend_parser_context (self , parser_instance , report_xml ):
130+ @api .multi
131+ def _extend_parser_context (self , context_instance , report_xml ):
123132 # add default extenders
124133 for fct in _extender_functions .get (None , []):
125- fct (report_xml , parser_instance .localcontext )
134+ fct (report_xml , context_instance .localcontext )
126135 # add extenders for registered on the template
127136 xml_id = report_xml .get_external_id ().get (report_xml .id )
128137 if xml_id in _extender_functions :
129138 for fct in _extender_functions [xml_id ]:
130- fct (report_xml , parser_instance .localcontext )
139+ fct (report_xml , context_instance .localcontext )
140+
141+ @api .multi
142+ def _get_parser_context (self , model_instance , data ):
143+ report_xml = self .ir_actions_report_xml_id
144+ context_instance = rml_parse (self .env .cr , self .env .uid ,
145+ report_xml .name ,
146+ context = self .env .context )
147+ context_instance .set_context (model_instance , data , model_instance .ids ,
148+ report_xml .report_type )
149+ self ._extend_parser_context (context_instance , report_xml )
150+ return context_instance .localcontext
151+
152+ @api .multi
153+ def _postprocess_report (self , content , res_id , save_in_attachment ):
154+ 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 )
131163
132- def create_single_pdf (self , cr , uid , ids , data , report_xml , context = None ):
133- """ Overide this function to generate our py3o report
164+ @api .multi
165+ def _create_single_report (self , model_instance , data , save_in_attachment ):
166+ """ This function to generate our py3o report
134167 """
135- if report_xml .report_type != 'py3o' :
136- return super (Py3oParser , self ).create_single_pdf (
137- cr , uid , ids , data , report_xml , context = context
138- )
168+ self .ensure_one ()
169+ report_xml = self .ir_actions_report_xml_id
139170
140- parser_instance = self .parser (cr , uid , self .name2 , context = context )
141- parser_instance .set_context (
142- self .getObjects (cr , uid , ids , context ),
143- data , ids , report_xml .report_type
144- )
145- self ._extend_parser_context (parser_instance , report_xml )
146-
147- tmpl_data = self .get_template (report_xml )
171+ tmpl_data = self .get_template ()
148172
149173 in_stream = StringIO (tmpl_data )
150174 out_stream = StringIO ()
151175 template = Template (in_stream , out_stream , escape_false = True )
152- localcontext = parser_instance . localcontext
176+ localcontext = self . _get_parser_context ( model_instance , data )
153177 if report_xml .py3o_is_local_fusion :
154178 template .render (localcontext )
155179 in_stream = out_stream
@@ -181,46 +205,87 @@ def create_single_pdf(self, cr, uid, ids, data, report_xml, context=None):
181205 report_xml .py3o_server_id .url , data = fields , files = files )
182206 if r .status_code != 200 :
183207 # server says we have an issue... let's tell that to enduser
184- raise exceptions . Warning (
208+ raise UserError (
185209 _ ('Fusion server error %s' ) % r .text ,
186210 )
187211
188212 # Here is a little joke about Odoo
189213 # we do nice chunked reading from the network...
190214 chunk_size = 1024
191215 with NamedTemporaryFile (
192- suffix = filetype ,
193- prefix = 'py3o-template-'
216+ suffix = filetype ,
217+ prefix = 'py3o-template-'
194218 ) as fd :
195219 for chunk in r .iter_content (chunk_size ):
196220 fd .write (chunk )
197221 fd .seek (0 )
198222 # ... but odoo wants the whole data in memory anyways :)
199223 res = fd .read ()
224+ self ._postprocess_report (
225+ res , model_instance .id , save_in_attachment )
226+ return res , "." + self .ir_actions_report_xml_id .py3o_filetype
227+
228+ @api .multi
229+ def _get_or_create_single_report (self , model_instance , data ,
230+ save_in_attachment ):
231+ self .ensure_one ()
232+ if save_in_attachment and save_in_attachment [
233+ 'loaded_documents' ].get (model_instance .id ):
234+ d = save_in_attachment [
235+ 'loaded_documents' ].get (model_instance .id )
236+ return d , self .ir_actions_report_xml_id .py3o_filetype
237+ return self ._create_single_report (
238+ model_instance , data , save_in_attachment )
239+
240+ @api .multi
241+ def _zip_results (self , results ):
242+ self .ensure_one ()
243+ zfname_prefix = self .ir_actions_report_xml_id .name
244+ with NamedTemporaryFile (suffix = "zip" , prefix = 'py3o-zip-result' ) as fd :
245+ with ZipFile (fd , 'w' , ZIP_DEFLATED ) as zf :
246+ cpt = 0
247+ for r , ext in results :
248+ fname = "%s_%d.%s" % (zfname_prefix , cpt , ext )
249+ zf .writestr (fname , r )
250+ cpt += 1
251+ fd .seek (0 )
252+ return fd .read (), 'zip'
253+
254+ @api .multi
255+ def _merge_pdfs (self , results ):
256+ from pyPdf import PdfFileWriter , PdfFileReader
257+ output = PdfFileWriter ()
258+ for r in results :
259+ reader = PdfFileReader (StringIO (r [0 ]))
260+ for page in range (reader .getNumPages ()):
261+ output .addPage (reader .getPage (page ))
262+ s = StringIO ()
263+ output .write (s )
264+ return s .getvalue (), formats .FORMAT_PDF
265+
266+ @api .multi
267+ def _merge_results (self , results ):
268+ self .ensure_one ()
269+ if not results :
270+ return False , False
271+ if len (results ) == 1 :
272+ return results [0 ]
273+ filetype = self .ir_actions_report_xml_id .py3o_filetype
274+ if filetype == formats .FORMAT_PDF :
275+ return self ._merge_pdfs (results )
276+ else :
277+ return self ._zip_results (results )
200278
201- return res , "." + filetype
202-
203- def create (self , cr , uid , ids , data , context = None ):
279+ @api .multi
280+ def create_report (self , res_ids , data ):
204281 """ Override this function to handle our py3o report
205282 """
206- pool = registry (cr .dbname )
207- ir_action_report_obj = pool ['ir.actions.report.xml' ]
208- report_xml_ids = ir_action_report_obj .search (
209- cr , uid , [('report_name' , '=' , self .name [7 :])], context = context
210- )
211- if not report_xml_ids :
212- return super (Py3oParser , self ).create (
213- cr , uid , ids , data , context = context
214- )
215-
216- report_xml = ir_action_report_obj .browse (
217- cr , uid , report_xml_ids [0 ], context = context
218- )
219-
220- result = self .create_source_pdf (
221- cr , uid , ids , data , report_xml , context
222- )
223-
224- if not result :
225- return False , False
226- return result
283+ model_instances = self .env [self .ir_actions_report_xml_id .model ].browse (
284+ res_ids )
285+ save_in_attachment = self ._check_attachment_use (
286+ model_instances , self .ir_actions_report_xml_id ) or {}
287+ results = []
288+ for model_instance in model_instances :
289+ results .append (self ._get_or_create_single_report (
290+ model_instance , data , save_in_attachment ))
291+ return self ._merge_results (results )
0 commit comments