Skip to content

Commit 2ed3672

Browse files
committed
[MIG] report_fillpdf: Migration to 16.0
1 parent eca6a64 commit 2ed3672

File tree

7 files changed

+265
-194
lines changed

7 files changed

+265
-194
lines changed

report_fillpdf/__manifest__.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"author": "Creu Blanca," "Odoo Community Association (OCA)",
88
"website": "https://github.com/OCA/reporting-engine",
99
"category": "Reporting",
10-
"version": "14.0.1.0.1",
10+
"version": "16.0.1.0.1",
1111
"license": "AGPL-3",
1212
"external_dependencies": {
1313
"python": [
@@ -21,11 +21,14 @@
2121
"base",
2222
"web",
2323
],
24-
"data": [
25-
"views/webclient_templates.xml",
26-
],
24+
"data": [],
2725
"demo": [
2826
"demo/report.xml",
2927
],
3028
"installable": True,
29+
"assets": {
30+
"web.assets_backend": [
31+
"report_fillpdf/static/src/js/report/action_manager_report.esm.js",
32+
],
33+
},
3134
}

report_fillpdf/controllers/main.py

Lines changed: 74 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,25 @@
22
# License AGPL-3.0 or later (https://www.gnuorg/licenses/agpl.html).
33

44
import json
5+
import logging
56

6-
from odoo.http import content_disposition, request, route
7+
from werkzeug.urls import url_decode
78

8-
from odoo.addons.web.controllers import main as report
9+
from odoo.http import (
10+
content_disposition,
11+
request,
12+
route,
13+
serialize_exception as _serialize_exception,
14+
)
15+
from odoo.tools import html_escape
16+
from odoo.tools.safe_eval import safe_eval, time
917

18+
from odoo.addons.web.controllers.report import ReportController
1019

11-
class ReportController(report.ReportController):
20+
_logger = logging.getLogger(__name__)
21+
22+
23+
class ReportController(ReportController):
1224
@route()
1325
def report_routes(self, reportname, docids=None, converter=None, **data):
1426
if converter == "fillpdf":
@@ -19,23 +31,69 @@ def report_routes(self, reportname, docids=None, converter=None, **data):
1931
if data.get("options"):
2032
data.update(json.loads(data.pop("options")))
2133
if data.get("context"):
22-
# Ignore 'lang' here, because the context in data is the one
23-
# from the webclient *but* if the user explicitely wants to
24-
# change the lang, this mechanism overwrites it.
2534
data["context"] = json.loads(data["context"])
26-
if data["context"].get("lang"):
27-
del data["context"]["lang"]
2835
context.update(data["context"])
29-
pdf = report.with_context(context).render_fillpdf(docids, data=data)[0]
36+
pdf = report.with_context(**context).render_fillpdf(
37+
reportname, docids, data=data
38+
)[0]
3039
pdfhttpheaders = [
3140
("Content-Type", "application/pdf"),
3241
("Content-Length", len(pdf)),
33-
(
34-
"Content-Disposition",
35-
content_disposition(report.report_file + ".pdf"),
36-
),
3742
]
3843
return request.make_response(pdf, headers=pdfhttpheaders)
39-
return super(ReportController, self).report_routes(
40-
reportname, docids, converter, **data
41-
)
44+
return super().report_routes(reportname, docids, converter, **data)
45+
46+
@route()
47+
def report_download(self, data, context=None, token=None):
48+
requestcontent = json.loads(data)
49+
url, report_type = requestcontent[0], requestcontent[1]
50+
if report_type == "fillpdf":
51+
try:
52+
reportname = url.split("/report/fillpdf/")[1].split("?")[0]
53+
docids = None
54+
if "/" in reportname:
55+
reportname, docids = reportname.split("/")
56+
if docids:
57+
# Generic report:
58+
response = self.report_routes(
59+
reportname, docids=docids, converter="fillpdf", context=context
60+
)
61+
else:
62+
# Particular report:
63+
data = dict(
64+
url_decode(url.split("?")[1]).items()
65+
) # decoding the args represented in JSON
66+
if "context" in data:
67+
context, data_context = json.loads(context or "{}"), json.loads(
68+
data.pop("context")
69+
)
70+
context = json.dumps({**context, **data_context})
71+
response = self.report_routes(
72+
reportname, converter="fillpdf", context=context, **data
73+
)
74+
75+
report = request.env["ir.actions.report"]._get_report_from_name(
76+
reportname
77+
)
78+
filename = "%s.%s" % (report.name, "pdf")
79+
80+
if docids:
81+
ids = [int(x) for x in docids.split(",")]
82+
obj = request.env[report.model].browse(ids)
83+
if report.print_report_name and not len(obj) > 1:
84+
report_name = safe_eval(
85+
report.print_report_name, {"object": obj, "time": time}
86+
)
87+
filename = "%s.%s" % (report_name, "pdf")
88+
if not response.headers.get("Content-Disposition"):
89+
response.headers.add(
90+
"Content-Disposition", content_disposition(filename)
91+
)
92+
return response
93+
except Exception as e:
94+
_logger.exception("Error while generating report %s", reportname)
95+
se = _serialize_exception(e)
96+
error = {"code": 200, "message": "Odoo Server Error", "data": se}
97+
return request.make_response(html_escape(json.dumps(error)))
98+
else:
99+
return super().report_download(data, context=context, token=token)

report_fillpdf/demo/report.xml

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
<?xml version="1.0" encoding="UTF-8" ?>
22
<odoo>
33
<!--
4-
© 2017 Creu Blanca
5-
License AGPL-3.0 or later (https://www.gnuorg/licenses/agpl.html).
4+
© 2017 Creu Blanca
5+
License AGPL-3.0 or later (https://www.gnuorg/licenses/agpl.html).
66
-->
7-
<report
8-
id="partner_fillpdf"
9-
model="res.partner"
10-
string="Fill PDF"
11-
report_type="fillpdf"
12-
name="report_fillpdf.partner_fillpdf"
13-
file="res_partner"
14-
attachment_use="False"
15-
/>
7+
<record model="ir.actions.report" id="partner_fillpdf">
8+
<field name="name">report_fillpdf.partner_fillpdf</field>
9+
<field name="model">res.partner</field>
10+
<field name="report_type">fillpdf</field>
11+
<field name="report_name">res_partner</field>
12+
<field name="report_file">res_partner</field>
13+
</record>
1614
</odoo>

report_fillpdf/models/ir_report.py

Lines changed: 121 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
# Copyright 2017 Creu Blanca
22
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
33

4+
import io
45
import logging
56
from collections import OrderedDict
67

78
from 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

Comments
 (0)