From 0a62fce86eaf4c84d614a3ce884de4fa46dcc599 Mon Sep 17 00:00:00 2001 From: Raf Geens Date: Thu, 1 Apr 2021 15:39:20 +0000 Subject: [PATCH] [FIX] base: Report which PDFs can't be read PyPDF2 can't read some PDFs due to bugs, lack of fault-tolerance and not supporting some encryption schemes. When invoices are uploaded directly through the Invoices / Accounting app, that will fail for those files because it will try to read part of them. When invoices are uploaded through the Documents app, they're not read, which means the customer can end up in a situation where read and merge operations (like when printing a selection of original vendor bills) will fail. The structural solution for this is to replace PyPDF2 with a more capable library, like pikepdf: https://github.com/odoo/odoo/pull/64356 . This change is too impactful for stable, and there are compatibility concerns as well. A possible workaround is merging the PDFs we can and bundling the rest together in a zip file. This is possible on the level of just _merge_pdfs, but the PDF extension and mimetype are pretty hard-coded and the changes required to switch them to ZIP based on what happens in _merge_pdfs seem too impactful. Instead, this fix detects as early as possible if the PDFs can't be read and reports it to the user. The excluded PDFs can still be downloaded individually. In this way the user has a workaround and does not need to resort to trial and error to figure out which PDFs are causing the problem. Related tickets: 2359929, 2389679 closes odoo/odoo#69082 X-original-commit: e935cf55975e4bbffcfe29dcfe51b3a91dd0aeab Signed-off-by: Nicolas Lempereur (nle) --- odoo/addons/base/i18n/base.pot | 10 ++++++ odoo/addons/base/models/ir_actions_report.py | 32 +++++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/odoo/addons/base/i18n/base.pot b/odoo/addons/base/i18n/base.pot index 763f4b7e69e..8880195147a 100644 --- a/odoo/addons/base/i18n/base.pot +++ b/odoo/addons/base/i18n/base.pot @@ -18788,6 +18788,16 @@ msgid "" "Module operations are not possible at this time, please try again later or contact your system administrator." msgstr "" +#. module: base +#: code:addons/base/models/ir_actions_report.py:0 +#, python-format +msgid "" +"Odoo is unable to merge the PDFs attached to the following records:\n" +"%s\n" +"\n" +"Please exclude them from the selection to continue. It's possible to still retrieve those PDFs by selecting each of the affected records individually, which will avoid merging." +msgstr "" + #. module: base #: model:ir.model.fields,help:base.field_ir_sequence__padding msgid "" diff --git a/odoo/addons/base/models/ir_actions_report.py b/odoo/addons/base/models/ir_actions_report.py index f5072119ad3..5053d3279aa 100644 --- a/odoo/addons/base/models/ir_actions_report.py +++ b/odoo/addons/base/models/ir_actions_report.py @@ -692,6 +692,30 @@ class IrActionsReport(models.Model): close_streams(streams) return result + def _get_unreadable_pdfs(self, streams): + unreadable_streams = [] + + writer = PdfFileWriter() + for stream in streams: + try: + reader = PdfFileReader(stream) + writer.appendPagesFromReader(reader) + except utils.PdfReadError: + unreadable_streams.append(stream) + + return unreadable_streams + + def _raise_on_unreadable_pdfs(self, streams, stream_record): + unreadable_pdfs = self._get_unreadable_pdfs(streams) + if unreadable_pdfs: + records = [stream_record[s].name for s in unreadable_pdfs if s in stream_record] + raise UserError(_( + "Odoo is unable to merge the PDFs attached to the following records:\n" + "%s\n\n" + "Please exclude them from the selection to continue. It's possible to " + "still retrieve those PDFs by selecting each of the affected records " + "individually, which will avoid merging.") % "\n".join(records)) + def _merge_pdfs(self, streams): writer = PdfFileWriter() for stream in streams: @@ -743,6 +767,8 @@ class IrActionsReport(models.Model): return self_sudo.with_context(context)._render_qweb_html(res_ids, data=data)[0] save_in_attachment = OrderedDict() + # Maps the streams in `save_in_attachment` back to the records they came from + stream_record = dict() if res_ids: # Dispatch the records by ones having an attachment and ones requesting a call to # wkhtmltopdf. @@ -753,7 +779,9 @@ class IrActionsReport(models.Model): for record_id in record_ids: attachment = self_sudo.retrieve_attachment(record_id) if attachment: - save_in_attachment[record_id.id] = self_sudo._retrieve_stream_from_attachment(attachment) + stream = self_sudo._retrieve_stream_from_attachment(attachment) + save_in_attachment[record_id.id] = stream + stream_record[stream] = record_id if not self_sudo.attachment_use or not attachment: wk_record_ids += record_id else: @@ -765,6 +793,7 @@ class IrActionsReport(models.Model): # - The report is not fully present in attachments. if save_in_attachment and not res_ids: _logger.info('The PDF report has been generated from attachments.') + self._raise_on_unreadable_pdfs(save_in_attachment.values(), stream_record) return self_sudo._post_pdf(save_in_attachment), 'pdf' if self.get_wkhtmltopdf_state() == 'install': @@ -794,6 +823,7 @@ class IrActionsReport(models.Model): set_viewport_size=context.get('set_viewport_size'), ) if res_ids: + self._raise_on_unreadable_pdfs(save_in_attachment.values(), stream_record) _logger.info('The PDF report has been generated for model: %s, records %s.' % (self_sudo.model, str(res_ids))) return self_sudo._post_pdf(save_in_attachment, pdf_content=pdf_content, res_ids=html_ids), 'pdf' return pdf_content, 'pdf'