11# Copyright 2017 Creu Blanca
22# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
33
4+ import io
45import logging
56from collections import OrderedDict
67
78from odoo import _ , api , fields , models
8- from odoo .exceptions import UserError
9+ from odoo .exceptions import AccessError , UserError
10+ from odoo .tools .safe_eval import safe_eval , time
911
1012_logger = logging .getLogger (__name__ )
1113
@@ -18,58 +20,127 @@ class ReportAction(models.Model):
1820 )
1921
2022 @api .model
21- def render_fillpdf (self , docids , data ):
22- # Here we add pdf attachment support
23- self_sudo = self . sudo ()
24- save_in_attachment = OrderedDict ()
25- # Maps the streams in `save_in_attachment` back to the records they came from
26- stream_record = dict ()
23+ def _render_fillpdf_stream (self , report_ref , docids , data ):
24+ report_sudo = self . _get_report ( report_ref )
25+ collected_streams = OrderedDict ()
26+
27+ # Fetch the existing attachments from the database for later use.
28+ # Reload the stream from the attachment in case of 'attachment_use'.
2729 if docids :
28- # Dispatch the records by ones having an attachment and ones requesting a call to
29- # fillpdf.
30- Model = self .env [self_sudo .model ]
31- record_ids = Model .browse (docids )
32- fp_record_ids = Model
33- if self_sudo .attachment :
34- for record_id in record_ids :
35- attachment = self_sudo .retrieve_attachment (record_id )
36- if attachment :
37- stream = self_sudo ._retrieve_stream_from_attachment (attachment )
38- save_in_attachment [record_id .id ] = stream
39- stream_record [stream ] = record_id
40- if not self_sudo .attachment_use or not attachment :
41- fp_record_ids += record_id
42-
43- else :
44- fp_record_ids = record_ids
45- docids = fp_record_ids .ids
46-
47- if save_in_attachment and not docids :
48- _logger .info ("The PDF report has been generated from attachments." )
49- self ._raise_on_unreadable_pdfs (save_in_attachment .values (), stream_record )
50- return self_sudo ._post_pdf (save_in_attachment ), "pdf"
51-
52- # We generate pdf with fillpdf
53- report_model_name = "report.%s" % self .report_name
54- report_model = self .env .get (report_model_name )
55- if report_model is None :
56- raise UserError (_ ("%s model was not found" % report_model_name ))
57-
58- pdf_content = report_model .with_context (
59- {"active_model" : self .model }
60- ).fill_report (docids , data )
30+ records = self .env [report_sudo .model ].browse (docids )
31+ for record in records :
32+ stream = None
33+ attachment = None
34+ if report_sudo .attachment :
35+ attachment = report_sudo .retrieve_attachment (record )
36+
37+ # If existing attachement, extract the stream from the attachment.
38+ if attachment and report_sudo .attachment_use :
39+ stream = io .BytesIO (attachment .raw )
40+
41+ # If no attachment, generate the pdf
42+ else :
43+ report_model_name = "report.%s" % self .report_name
44+ report_model = self .env .get (report_model_name )
45+ if report_model is None :
46+ raise UserError (
47+ _ ("%s model was not found" ) % report_model_name
48+ )
49+
50+ stream = io .BytesIO (
51+ report_model .with_context (
52+ ** {"active_model" : self .model }
53+ ).fill_report (docids , data )
54+ )
55+ _logger .info (
56+ "The PDF report has been generated for model: %s, records %s."
57+ % (report_model , str (docids ))
58+ )
59+
60+ collected_streams [record .id ] = {
61+ "stream" : stream ,
62+ "attachment" : attachment ,
63+ }
64+ return collected_streams
65+
66+ @api .model
67+ def render_fillpdf (self , report_ref , docids , data ):
68+ report_sudo = self ._get_report (report_ref )
69+ collected_streams = self ._render_fillpdf_stream (report_ref , docids , data )
70+
71+ if report_sudo .attachment :
72+ attachment_vals_list = []
73+ for res_id , stream_data in collected_streams .items ():
74+ # An attachment already exists.
75+ if stream_data ["attachment" ]:
76+ continue
77+
78+ # if res_id is false
79+ # we are unable to fetch the record,
80+ # it won't be saved as we can't split the documents unambiguously
81+ if not res_id :
82+ _logger .warning (
83+ "These documents were not saved as an attachment\
84+ because the template of %s doesn't "
85+ "have any headers seperating different instances\
86+ of it. If you want it saved,"
87+ "please print the documents separately" ,
88+ report_sudo .report_name ,
89+ )
90+ continue
91+ record = self .env [report_sudo .model ].browse (res_id )
92+ attachment_name = safe_eval (
93+ report_sudo .attachment , {"object" : record , "time" : time }
94+ )
95+
96+ # Unable to compute a name for the attachment.
97+ if not attachment_name :
98+ continue
99+
100+ attachment_vals_list .append (
101+ {
102+ "name" : attachment_name ,
103+ "raw" : stream_data ["stream" ].getvalue (),
104+ "res_model" : report_sudo .model ,
105+ "res_id" : record .id ,
106+ "type" : "binary" ,
107+ }
108+ )
109+
110+ if attachment_vals_list :
111+ attachment_names = ", " .join (x ["name" ] for x in attachment_vals_list )
112+ try :
113+ self .env ["ir.attachment" ].create (attachment_vals_list )
114+ except AccessError :
115+ _logger .info (
116+ "Cannot save PDF report %r attachments for user %r" ,
117+ attachment_names ,
118+ self .env .user .display_name ,
119+ )
120+ else :
121+ _logger .info (
122+ "The PDF documents %r are now saved in the database" ,
123+ attachment_names ,
124+ )
125+
126+ # Merge all streams together for a single record.
127+ streams_to_merge = [
128+ x ["stream" ] for x in collected_streams .values () if x ["stream" ]
129+ ]
130+ if len (streams_to_merge ) == 1 :
131+ pdf_content = streams_to_merge [0 ].getvalue ()
132+ else :
133+ with self ._merge_pdfs (streams_to_merge ) as pdf_merged_stream :
134+ pdf_content = pdf_merged_stream .getvalue ()
135+
136+ for stream in streams_to_merge :
137+ stream .close ()
61138
62139 if docids :
63- self ._raise_on_unreadable_pdfs (save_in_attachment .values (), stream_record )
64140 _logger .info (
65- "The PDF report has been generated for model: %s, records %s."
66- % (self_sudo .model , str (docids ))
67- )
68- return (
69- self_sudo ._post_pdf (
70- save_in_attachment , pdf_content = pdf_content , res_ids = docids
71- ),
72- "pdf" ,
141+ "The PDF report has been generated for model: %s, records %s." ,
142+ report_sudo .model ,
143+ str (docids ),
73144 )
74145
75146 return pdf_content , "pdf"
@@ -86,4 +157,4 @@ def _get_report_from_name(self, report_name):
86157 ("report_name" , "=" , report_name ),
87158 ]
88159 context = self .env ["res.users" ].context_get ()
89- return report_obj .with_context (context ).search (conditions , limit = 1 )
160+ return report_obj .with_context (** context ).search (conditions , limit = 1 )
0 commit comments