summaryrefslogtreecommitdiff
path: root/odoo
diff options
context:
space:
mode:
authorBenjamin Frantzen (bfr) <bfr@odoo.com>2020-06-10 17:03:10 +0000
committerBenjamin Frantzen (bfr) <bfr@odoo.com>2020-06-18 16:49:32 +0000
commitae15f89f73cffaebd20ba74b9c20ad641a1ea5e4 (patch)
treec2cab938806908dfbf7f0477976c3d7d289e91d7 /odoo
parent80379606dbc7d4afdb22476eea97a28ed88fe497 (diff)
[FIX] tools.pdf: do not crash when PDF has no attachments
In the PDF, `DictionaryObject` can be wrapped in `IndirectObject`. For nested dictionaries, using `get` on a `DictionaryObject` will unwrap the result if it's an `IndirectObject`, but not `__getitem__` so when trying to futher call `get` on the result will cause an error: we need to unwrap the object first. Instead, we patch `DictionaryObject.get()` for it to unwrap the object in case it's an `IndirectObject`. This is a follow-up commit of fece5ab1bf2fef043b131f1bd0886f0841116103 closes odoo/odoo#53269 X-original-commit: 189a0b28ec7fdb7472f936450cfd7b4bfd6912ed Signed-off-by: Olivier Dony (odo) <odo@openerp.com> Signed-off-by: bfr-o <bfr-o@users.noreply.github.com>
Diffstat (limited to 'odoo')
-rw-r--r--odoo/addons/base/tests/__init__.py1
-rw-r--r--odoo/addons/base/tests/minimal.pdf44
-rw-r--r--odoo/addons/base/tests/test_pdf.py75
-rw-r--r--odoo/tools/pdf.py17
4 files changed, 135 insertions, 2 deletions
diff --git a/odoo/addons/base/tests/__init__.py b/odoo/addons/base/tests/__init__.py
index b08e17ca0ae..af317988c81 100644
--- a/odoo/addons/base/tests/__init__.py
+++ b/odoo/addons/base/tests/__init__.py
@@ -45,3 +45,4 @@ from . import test_tests_tags
from . import test_base_document_layout
from . import test_form_create
from . import test_cloc
+from . import test_pdf
diff --git a/odoo/addons/base/tests/minimal.pdf b/odoo/addons/base/tests/minimal.pdf
new file mode 100644
index 00000000000..2a73de072da
--- /dev/null
+++ b/odoo/addons/base/tests/minimal.pdf
@@ -0,0 +1,44 @@
+%PDF-1.3
+%µ¶
+
+1 0 obj
+<</Type/Catalog/Pages 2 0 R/Names 5 0 R>>
+endobj
+
+2 0 obj
+<</Type/Pages/Kids[3 0 R]/Count 1>>
+endobj
+
+3 0 obj
+<</Type/Page/Parent 2 0 R/Resources<</Font<</F1<</Type/Font/Subtype/Type1/BaseFont/Arial>>>>>>/Contents 4 0 R>>
+endobj
+
+4 0 obj
+<</Length 44>>
+stream
+BT
+/F1 100 Tf
+10 400 Td
+(!Odoo Test!) Tj
+ET
+endstream
+endobj
+
+5 0 obj
+<</Foo/Bar>>
+endobj
+
+xref
+0 6
+0000000000 00001 f
+0000000016 00000 n
+0000000074 00000 n
+0000000126 00000 n
+0000000254 00000 n
+0000000346 00000 n
+
+trailer
+<</Size 6/Root 1 0 R>>
+startxref
+376
+%%EOF
diff --git a/odoo/addons/base/tests/test_pdf.py b/odoo/addons/base/tests/test_pdf.py
new file mode 100644
index 00000000000..6954ca4a689
--- /dev/null
+++ b/odoo/addons/base/tests/test_pdf.py
@@ -0,0 +1,75 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo.tests.common import TransactionCase
+from odoo.tools import pdf
+from odoo.modules.module import get_module_resource
+import io
+
+
+class TestPdf(TransactionCase):
+ """ Tests on pdf. """
+
+ def setUp(self):
+ super().setUp()
+ file_path = get_module_resource('base', 'tests', 'minimal.pdf')
+ self.file = open(file_path, 'rb').read()
+ self.minimal_reader_buffer = io.BytesIO(self.file)
+ self.minimal_pdf_reader = pdf.OdooPdfFileReader(self.minimal_reader_buffer)
+
+ def test_odoo_pdf_file_reader(self):
+ attachments = list(self.minimal_pdf_reader.getAttachments())
+ self.assertEqual(len(attachments), 0)
+
+ pdf_writer = pdf.PdfFileWriter()
+ pdf_writer.cloneReaderDocumentRoot(self.minimal_pdf_reader)
+ pdf_writer.addAttachment('test_attachment.txt', b'My awesome attachment')
+
+ attachments = list(self.minimal_pdf_reader.getAttachments())
+ self.assertEqual(len(attachments), 1)
+
+ def test_odoo_pdf_file_writer(self):
+ attachments = list(self.minimal_pdf_reader.getAttachments())
+ self.assertEqual(len(attachments), 0)
+
+ pdf_writer = pdf.OdooPdfFileWriter()
+ pdf_writer.cloneReaderDocumentRoot(self.minimal_pdf_reader)
+
+ pdf_writer.addAttachment('test_attachment.txt', b'My awesome attachment')
+ attachments = list(self.minimal_pdf_reader.getAttachments())
+ self.assertEqual(len(attachments), 1)
+
+ pdf_writer.addAttachment('another_attachment.txt', b'My awesome OTHER attachment')
+ attachments = list(self.minimal_pdf_reader.getAttachments())
+ self.assertEqual(len(attachments), 2)
+
+ def test_merge_pdf(self):
+ self.assertEqual(self.minimal_pdf_reader.getNumPages(), 1)
+ page = self.minimal_pdf_reader.getPage(0)
+
+ merged_pdf = pdf.merge_pdf([self.file, self.file])
+ merged_reader_buffer = io.BytesIO(merged_pdf)
+ merged_pdf_reader = pdf.OdooPdfFileReader(merged_reader_buffer)
+ self.assertEqual(merged_pdf_reader.getNumPages(), 2)
+ merged_reader_buffer.close()
+
+ def test_branded_file_writer(self):
+ # It's not easy to create a PDF with PyPDF2, so instead we copy minimal.pdf with our custom pdf writer
+ pdf_writer = pdf.PdfFileWriter() # BrandedFileWriter
+ pdf_writer.cloneReaderDocumentRoot(self.minimal_pdf_reader)
+ writer_buffer = io.BytesIO()
+ pdf_writer.write(writer_buffer)
+ branded_content = writer_buffer.getvalue()
+ writer_buffer.close()
+
+ # Read the metadata of the newly created pdf.
+ reader_buffer = io.BytesIO(branded_content)
+ pdf_reader = pdf.PdfFileReader(reader_buffer)
+ pdf_info = pdf_reader.getDocumentInfo()
+ self.assertEqual(pdf_info['/Producer'], 'Odoo')
+ self.assertEqual(pdf_info['/Creator'], 'Odoo')
+ reader_buffer.close()
+
+ def tearDown(self):
+ super().tearDown()
+ self.minimal_reader_buffer.close()
diff --git a/odoo/tools/pdf.py b/odoo/tools/pdf.py
index 8e317fa2bde..dadae1df522 100644
--- a/odoo/tools/pdf.py
+++ b/odoo/tools/pdf.py
@@ -12,6 +12,17 @@ import hashlib
DEFAULT_PDF_DATETIME_FORMAT = "D:%Y%m%d%H%M%S+00'00'"
+# make sure values are unwrapped by calling the specialized __getitem__
+def _unwrapping_get(self, key, default=None):
+ try:
+ return self[key]
+ except KeyError:
+ return default
+
+
+DictionaryObject.get = _unwrapping_get
+
+
class BrandedFileWriter(PdfFileWriter):
def __init__(self):
super().__init__()
@@ -25,7 +36,8 @@ PdfFileWriter = BrandedFileWriter
def merge_pdf(pdf_data):
- ''' Merge a collection of PDF documents in one
+ ''' Merge a collection of PDF documents in one.
+ Note that the attachments are not merged.
:param list pdf_data: a list of PDF datastrings
:return: a unique merged PDF datastring
'''
@@ -40,7 +52,8 @@ def merge_pdf(pdf_data):
def rotate_pdf(pdf):
- ''' Rotate clockwise PDF (90°)
+ ''' Rotate clockwise PDF (90°) into a new PDF.
+ Note that the attachments are not copied.
:param pdf: a PDF to rotate
:return: a PDF rotated
'''