Some iPhone users upload HEIC files with a ``.jpg`` extension. Odoo stores the file as-is, but the mimetype is detected as ``image/...``, so the code enters the image processing branch. ``reportlab``'s ``ImageReader`` relies on ``PIL.Image.open()`` which cannot identify HEIC files without an explicit plugin, causing a ``PIL.UnidentifiedImageError`` and a 500 error on report download. Fix by: - Registering the ``pillow-heif`` opener at module load time (optional dependency, silently ignored if not installed) so that Pillow can decode HEIC/HEIF images. - Converting any image to JPEG in memory via Pillow before passing it to ``ImageReader``, bypassing ``reportlab``'s own format detection entirely. - Wrapping the image block in a ``try/except`` to gracefully skip attachments that cannot be decoded, consistent with the existing PDF error handling.
86 lines
3.7 KiB
Python
86 lines
3.7 KiB
Python
import odoo.addons.web.controllers.main as main
|
|
import ast
|
|
import base64
|
|
import io
|
|
from odoo.tools.pdf import OdooPdfFileReader, OdooPdfFileWriter
|
|
import PyPDF2
|
|
import logging
|
|
from odoo import http
|
|
from reportlab.pdfgen import canvas
|
|
from reportlab.lib.utils import ImageReader
|
|
from reportlab.lib.pagesizes import letter, A4
|
|
try:
|
|
from pillow_heif import register_heif_opener
|
|
register_heif_opener()
|
|
except ImportError:
|
|
pass
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class Extension(main.ReportController):
|
|
|
|
@http.route(['/report/download'], type='http', auth="user")
|
|
def report_download(self, data, token, context=None):
|
|
"""
|
|
In case of hr expense sheet report : merge PDF with other attachments
|
|
"""
|
|
|
|
res = super(Extension,self).report_download(data, token, context)
|
|
|
|
if "hr_expense.report_expense_sheet" in ast.literal_eval(data)[0]: #check if we are generating expense sheet report pdf
|
|
writer = OdooPdfFileWriter() #Open a file writer to create new PDF
|
|
|
|
# Open main pdf and read it and write it page by page in new pdf
|
|
main_pdf_reader = OdooPdfFileReader(io.BytesIO(res.data), strict=False)
|
|
for n in range(main_pdf_reader.getNumPages()):
|
|
writer.addPage(main_pdf_reader.getPage(n))
|
|
|
|
# Parse data (last char after "/") to get id of current sheet
|
|
expense_sheet_id = int(ast.literal_eval(data)[0].split('/')[-1])
|
|
|
|
# Get ids of related expenses
|
|
expense_ids = [e.id for e in http.request.env['hr.expense.sheet'].browse(expense_sheet_id).expense_line_ids]
|
|
|
|
# Get related attachments
|
|
attachments = http.request.env['ir.attachment'].search([('res_id','in',expense_ids),('res_model','=','hr.expense')])
|
|
|
|
# Open each attachments, and write it page by page in new pdf
|
|
for att in attachments:
|
|
if att.mimetype == "application/pdf":
|
|
try:
|
|
attachment_reader = OdooPdfFileReader(io.BytesIO(base64.b64decode(att.datas)), strict=False)
|
|
for n in range(attachment_reader.getNumPages()):
|
|
writer.addPage(attachment_reader.getPage(n))
|
|
except PyPDF2.utils.PdfReadError: # Case of non-pdf attachments
|
|
logger.info('Attachment %s cannot be merged in expense report'%(att.name,))
|
|
elif 'image/' in att.mimetype:
|
|
try:
|
|
from PIL import Image as PilImage
|
|
raw = base64.b64decode(att.datas)
|
|
pil_img = PilImage.open(io.BytesIO(raw))
|
|
if pil_img.mode not in ('RGB', 'RGBA'):
|
|
pil_img = pil_img.convert('RGB')
|
|
jpeg_buffer = io.BytesIO()
|
|
pil_img.save(jpeg_buffer, format='JPEG')
|
|
jpeg_buffer.seek(0)
|
|
packet = io.BytesIO()
|
|
can = canvas.Canvas(packet)
|
|
img = ImageReader(jpeg_buffer)
|
|
can.drawImage(img, 0, 0, A4[0]*0.9, A4[1]*0.9, preserveAspectRatio=True)
|
|
can.save()
|
|
packet.seek(0)
|
|
attachment_reader = OdooPdfFileReader(packet)
|
|
writer.addPage(attachment_reader.getPage(0))
|
|
except Exception:
|
|
logger.info('Attachment %s cannot be merged in expense report', att.name)
|
|
|
|
# Write new pdf to res.data
|
|
buffer = io.BytesIO()
|
|
writer.write(buffer)
|
|
pdf_content = buffer.getvalue()
|
|
|
|
res.data = pdf_content
|
|
|
|
return res
|