From b26e70dbe99bbc8bc87ca89218b184276d8c8b77 Mon Sep 17 00:00:00 2001 From: "matthias.lotz" Date: Thu, 8 Jan 2026 17:13:34 +0100 Subject: [PATCH] fix: Support multiple file types (PNG, PDF, JPEG) for DokuWiki media uploads - Make upload_media accept ir.attachment records, IDs, or raw bytes - Detect file extension from binary content using imghdr - Build media_id with correct extension (.png, .jpg, .pdf) - Prevent DokuWiki 'file extension mismatch' error - Improve temp file cleanup with proper error handling --- .../models/dokuwiki_client.py | 98 ++++++++++++++++--- .../models/maintenance_equipment.py | 28 +++++- 2 files changed, 105 insertions(+), 21 deletions(-) diff --git a/open_workshop_dokuwiki/models/dokuwiki_client.py b/open_workshop_dokuwiki/models/dokuwiki_client.py index 76ea37d..ab36f80 100644 --- a/open_workshop_dokuwiki/models/dokuwiki_client.py +++ b/open_workshop_dokuwiki/models/dokuwiki_client.py @@ -278,27 +278,93 @@ class DokuWikiClient(models.AbstractModel): UserError: Bei Fehler beim Upload """ wiki = self._get_wiki_connection() - + import tempfile import os - - # Temporäre Datei erstellen (dokuwiki.py erwartet Datei-Pfad) - with tempfile.NamedTemporaryFile(delete=False, suffix='.jpg') as tmp_file: - tmp_file.write(file_content) - tmp_path = tmp_file.name - + import base64 + import imghdr + + tmp_path = None + + # Unterstütze mehrere Eingabetypen: + # - ir.attachment record (Recordset) + # - attachment id (int) + # - rohe Bytes + # - base64-codierter String try: - wiki.medias.add(media_id, tmp_path, overwrite=overwrite) - _logger.info(f"Mediendatei hochgeladen: {media_id}") - return True - except DokuWikiError as e: - error_msg = f"Fehler beim Hochladen der Mediendatei {media_id}: {e}" - _logger.error(error_msg) - raise UserError(error_msg) + content_bytes = None + ext = '' + + # ir.attachment Record + if hasattr(file_content, '_name') and getattr(file_content, '_name') == 'ir.attachment': + att = file_content.sudo() + if not att.datas: + raise UserError(f"Attachment {att.id} enthält keine Daten") + content_bytes = base64.b64decode(att.datas) + fname = att.datas_fname or '' + ext = os.path.splitext(fname)[1] + + # attachment id + elif isinstance(file_content, int): + att = self.env['ir.attachment'].sudo().browse(file_content) + if not att.exists() or not att.datas: + raise UserError(f"Attachment {file_content} nicht gefunden oder leer") + content_bytes = base64.b64decode(att.datas) + fname = att.datas_fname or '' + ext = os.path.splitext(fname)[1] + + # base64 string + elif isinstance(file_content, str): + try: + content_bytes = base64.b64decode(file_content) + except Exception: + # Fallback: treat as empty + raise UserError("Übergebener String konnte nicht als base64 decodiert werden") + ext = os.path.splitext(media_id)[1] + + # rohe bytes + else: + # assume bytes-like + content_bytes = file_content + # try extension from media_id + ext = os.path.splitext(media_id)[1] + + # Wenn noch keine Extension, versuche Bildtyp zu erkennen + if not ext: + try: + img_type = imghdr.what(None, content_bytes) + if img_type: + ext = f'.{img_type}' + except Exception: + ext = '' + + if not ext: + ext = '.bin' + + # Sicherstellen, dass ext mit Punkt beginnt + if not ext.startswith('.'): + ext = f'.{ext}' + + # Temporäre Datei erstellen (dokuwiki.py erwartet Datei-Pfad) + with tempfile.NamedTemporaryFile(delete=False, suffix=ext) as tmp_file: + tmp_file.write(content_bytes) + tmp_path = tmp_file.name + + try: + wiki.medias.add(media_id, tmp_path, overwrite=overwrite) + _logger.info(f"Mediendatei hochgeladen: {media_id} (tmp={tmp_path})") + return True + except DokuWikiError as e: + error_msg = f"Fehler beim Hochladen der Mediendatei {media_id}: {e}" + _logger.error(error_msg) + raise UserError(error_msg) finally: # Temporäre Datei löschen - if os.path.exists(tmp_path): - os.unlink(tmp_path) + if tmp_path and os.path.exists(tmp_path): + try: + os.unlink(tmp_path) + except Exception: + _logger.warning(f"Konnte temporäre Datei nicht löschen: {tmp_path}") @api.model def get_media_url(self, media_id): diff --git a/open_workshop_dokuwiki/models/maintenance_equipment.py b/open_workshop_dokuwiki/models/maintenance_equipment.py index f7e5dd1..e0fae4f 100644 --- a/open_workshop_dokuwiki/models/maintenance_equipment.py +++ b/open_workshop_dokuwiki/models/maintenance_equipment.py @@ -399,17 +399,35 @@ class MaintenanceEquipment(models.Model): ) # Area-Name normalisieren (wie bei Pages) area_name = self._normalize_wiki_name(self.ows_area_id.name) if self.ows_area_id else 'unbekannt' - media_id = f"{equipment_namespace}:{area_name}:{wiki_doku_id}.jpg" - - # Bild ins Wiki hochladen + # Bild ins Wiki hochladen (erkenne Extension aus Bytes) try: import base64 + import imghdr + # image_1920 ist base64-kodiert, in bytes umwandeln für XML-RPC image_bytes = base64.b64decode(self.image_1920) - + + # Typ/Extension erkennen (jpeg -> .jpg) + img_type = None + try: + img_type = imghdr.what(None, image_bytes) + except Exception: + img_type = None + + if img_type == 'jpeg': + ext = '.jpg' + elif img_type: + ext = f'.{img_type}' + elif image_bytes.startswith(b'%PDF'): + ext = '.pdf' + else: + ext = '.bin' + + media_id = f"{equipment_namespace}:{area_name}:{wiki_doku_id}{ext}" + dokuwiki_client = self.env['dokuwiki.client'] dokuwiki_client.upload_media(media_id, image_bytes, overwrite=True) - + # DokuWiki Image-Syntax: {{namespace:file.jpg?300}} values['image'] = f"{{{{:{media_id}?100}}}}" values['image_large'] = f"{{{{:{media_id}}}}}"