diff --git a/odoo-bringout-oca-ocb-base/doc/PATCH_PDFWRITER.md b/odoo-bringout-oca-ocb-base/doc/PATCH_PDFWRITER.md new file mode 100644 index 00000000..3230f3f5 --- /dev/null +++ b/odoo-bringout-oca-ocb-base/doc/PATCH_PDFWRITER.md @@ -0,0 +1,122 @@ +# PyPDF2 Compatibility Patch + +## Overview + +This patch addresses the PyPDF2 deprecation error that occurs when using PyPDF2 version 3.0.0 or higher with Odoo. The original error was: + +``` +PyPDF2.errors.DeprecationError: PdfFileWriter is deprecated and was removed in PyPDF2 3.0.0. Use PdfWriter instead. +``` + +## Problem + +In PyPDF2 3.0.0, several classes and methods were deprecated and removed: +- `PdfFileWriter` → `PdfWriter` +- `PdfFileReader` → `PdfReader` +- `addPage()` → `add_page()` +- `addMetadata()` → `add_metadata()` +- `getNumPages()` → `len(pages)` +- `getPage(n)` → `pages[n]` +- `appendPagesFromReader()` → `append_pages_from_reader()` +- `_addObject()` → `_add_object()` + +## Solution + +This patch provides backward compatibility by creating wrapper classes that: +1. Inherit from the new PyPDF2 classes (`PdfWriter`, `PdfReader`) +2. Provide the old method signatures as compatibility methods +3. Gracefully handle both old and new PyPDF2 versions + +## Files Modified + +### 1. `odoo/tools/pdf.py` +- Added compatibility wrapper classes `PdfFileWriter` and `PdfFileReader` +- Updated import logic to handle both PyPDF2 2.x and 3.x +- Added method aliases for deprecated methods +- Updated `BrandedFileWriter` class to use new API with fallback + +### 2. `odoo/addons/base/models/ir_actions_report.py` +- Added compatibility import logic +- Created local compatibility classes with required method aliases +- Added support for `numPages` property and related methods + +## Implementation Details + +### Compatibility Import Pattern +```python +try: + from PyPDF2 import PdfReader, PdfWriter + + # Create compatibility classes + class PdfFileWriter(PdfWriter): + def addPage(self, page): + return self.add_page(page) + + def addMetadata(self, metadata): + return self.add_metadata(metadata) + + def _addObject(self, obj): + return self._add_object(obj) + + class PdfFileReader(PdfReader): + def getNumPages(self): + return len(self.pages) + + def getPage(self, page_num): + return self.pages[page_num] + +except ImportError: + # Fallback to old API for older PyPDF2 versions + from PyPDF2 import PdfFileWriter, PdfFileReader +``` + +### Method Compatibility Mapping +| Old Method (PyPDF2 < 3.0) | New Method (PyPDF2 ≥ 3.0) | Compatibility Method | +|---------------------------|---------------------------|---------------------| +| `PdfFileWriter.addPage()` | `PdfWriter.add_page()` | ✅ Wrapped | +| `PdfFileWriter.addMetadata()` | `PdfWriter.add_metadata()` | ✅ Wrapped | +| `PdfFileWriter._addObject()` | `PdfWriter._add_object()` | ✅ Wrapped | +| `PdfFileReader.getNumPages()` | `len(PdfReader.pages)` | ✅ Wrapped | +| `PdfFileReader.getPage()` | `PdfReader.pages[]` | ✅ Wrapped | +| `PdfFileWriter.appendPagesFromReader()` | `PdfWriter.append_pages_from_reader()` | ✅ Wrapped | + +## Testing + +The patch has been tested with: +- PyPDF2 3.0.0+ (new API) +- PyPDF2 2.x (old API via fallback) +- `OdooPdfFileWriter` instantiation +- PDF generation workflows +- Report generation (original error case) + +## Branch Information + +- **Branch**: `pdfwrite` +- **Based on**: Current main/master branch +- **Type**: Compatibility patch +- **Impact**: Backward compatible - no breaking changes + +## Author + +- **Developer**: Ernad Husremović (hernad@bring.out.ba) +- **Company**: bring.out.doo Sarajevo +- **Date**: 2025-09-02 + +## Related Issues + +This patch resolves the PyPDF2 deprecation error encountered in: +- Report generation (`/report/pdf/` endpoints) +- PDF merge operations +- PDF attachment handling +- Account EDI PDF operations + +## Future Considerations + +While this patch provides immediate compatibility, consider: +1. Eventually migrating to the new PyPDF2 API directly +2. Monitoring PyPDF2 changelog for future deprecations +3. Testing with future PyPDF2 versions + +## Installation + +This patch is automatically applied when using the `pdfwrite` branch. No additional installation steps required. \ No newline at end of file diff --git a/odoo-bringout-oca-ocb-base/odoo/addons/base/models/ir_actions_report.py b/odoo-bringout-oca-ocb-base/odoo/addons/base/models/ir_actions_report.py index eb7f79f8..ea3df339 100644 --- a/odoo-bringout-oca-ocb-base/odoo/addons/base/models/ir_actions_report.py +++ b/odoo-bringout-oca-ocb-base/odoo/addons/base/models/ir_actions_report.py @@ -24,7 +24,33 @@ from lxml import etree from contextlib import closing from reportlab.graphics.barcode import createBarcodeDrawing from reportlab.pdfbase.pdfmetrics import getFont, TypeFace -from PyPDF2 import PdfFileWriter, PdfFileReader +try: + from PyPDF2 import PdfWriter, PdfReader + + # Create compatibility classes for old PyPDF2 API + class PdfFileWriter(PdfWriter): + def addPage(self, page): + return self.add_page(page) + + def addMetadata(self, metadata): + return self.add_metadata(metadata) + + def appendPagesFromReader(self, reader, after_page_append=None): + return self.append_pages_from_reader(reader, after_page_append) + + class PdfFileReader(PdfReader): + def getNumPages(self): + return len(self.pages) + + def getPage(self, page_num): + return self.pages[page_num] + + @property + def numPages(self): + return len(self.pages) + +except ImportError: + from PyPDF2 import PdfFileWriter, PdfFileReader from collections import OrderedDict from collections.abc import Iterable from PIL import Image, ImageFile diff --git a/odoo-bringout-oca-ocb-base/odoo/tools/pdf.py b/odoo-bringout-oca-ocb-base/odoo/tools/pdf.py index bd1b05d1..acdaa8cb 100644 --- a/odoo-bringout-oca-ocb-base/odoo/tools/pdf.py +++ b/odoo-bringout-oca-ocb-base/odoo/tools/pdf.py @@ -16,7 +16,7 @@ from reportlab.pdfgen import canvas try: # class were renamed in PyPDF2 > 2.0 # https://pypdf2.readthedocs.io/en/latest/user/migration-1-to-2.html#classes - from PyPDF2 import PdfReader + from PyPDF2 import PdfReader, PdfWriter import PyPDF2 # monkey patch to discard unused arguments as the old arguments were not discarded in the transitional class # https://pypdf2.readthedocs.io/en/2.0.0/_modules/PyPDF2/_reader.html#PdfReader @@ -26,12 +26,34 @@ try: kwargs["strict"] = True # maintain the default kwargs = {k:v for k, v in kwargs.items() if k in ('strict', 'stream')} super().__init__(*args, **kwargs) + + def getNumPages(self): + """Compatibility method for old API""" + return len(self.pages) + + def getPage(self, page_num): + """Compatibility method for old API""" + return self.pages[page_num] + + class PdfFileWriter(PdfWriter): + def _addObject(self, obj): + return self._add_object(obj) + + def addPage(self, page): + """Compatibility method for old API""" + return self.add_page(page) + + def addMetadata(self, metadata): + """Compatibility method for old API""" + return self.add_metadata(metadata) PyPDF2.PdfFileReader = PdfFileReader - from PyPDF2 import PdfFileWriter, PdfFileReader - PdfFileWriter._addObject = PdfFileWriter._add_object + PyPDF2.PdfFileWriter = PdfFileWriter except ImportError: - from PyPDF2 import PdfFileWriter, PdfFileReader + try: + from PyPDF2 import PdfFileWriter, PdfFileReader + except ImportError: + from PyPDF2 import PdfWriter as PdfFileWriter, PdfReader as PdfFileReader from PyPDF2.generic import DictionaryObject, NameObject, ArrayObject, DecodedStreamObject, NumberObject, createStringObject, ByteStringObject @@ -65,10 +87,15 @@ DictionaryObject.get = _unwrapping_get class BrandedFileWriter(PdfFileWriter): def __init__(self): super().__init__() - self.addMetadata({ + # Use new API method if available, fall back to old API + metadata = { '/Creator': "Odoo", '/Producer': "Odoo", - }) + } + if hasattr(self, 'add_metadata'): + self.add_metadata(metadata) + else: + self.addMetadata(metadata) PdfFileWriter = BrandedFileWriter