diff --git a/open_workshop_dokuwiki/README.md b/open_workshop_dokuwiki/README.md new file mode 100644 index 0000000..ac351cf --- /dev/null +++ b/open_workshop_dokuwiki/README.md @@ -0,0 +1,35 @@ +Verfügbare Platzhalter: + +Basis-Felder (maintenance.equipment): + +{name} - Equipment-Name +{serial_no} - Seriennummer +{model} - Modell +{category} - Kategoriename +{status} - Status (aus status_id) +{location} - Standort +{ows_area} - Bereichsname +{assign_date} - Zuweisungsdatum (formatiert) +{cost} - Kosten +{warranty_date} - Garantiedatum (formatiert) +{note} - Notizen +Spezial-Felder: + +{view_type} - "Bereich" oder "Einsatzzweck" +{view_name} - Name des Bereichs/Einsatzzwecks +{wiki_doku_page} - ID der zentralen Doku-Seite +{wiki_doku_link} - Fertiger Link zur zentralen Doku-Seite +{odoo_link} - Fertiger Link zur Odoo Equipment-Seite +{odoo_url} - URL zur Odoo Equipment-Seite (ohne Link-Markup) +{sync_datetime} - Aktuelles Datum/Zeit +{image} - Equipment-Bild (300px Breite) - Format: {{:media_id?300}} +{image_large} - Equipment-Bild (Originalgröße) - Format: {{:media_id}} +{image_id} - Media-ID des Bildes (z.B. werkstatt:ausruestung:media:sabako-laser.jpg) +ows.machine Felder (falls verknüpft): + +{ows_machine_id.name} - Name +{ows_machine_id.model} - Modell +{ows_machine_id.serial_no} - Seriennummer +{ows_machine_id.location} - Standort +{ows_machine_id.note} - Notizen +{ows_machine_id.category} - Kategorie \ No newline at end of file diff --git a/open_workshop_dokuwiki/models/dokuwiki_client.py b/open_workshop_dokuwiki/models/dokuwiki_client.py index b5b4954..097f717 100644 --- a/open_workshop_dokuwiki/models/dokuwiki_client.py +++ b/open_workshop_dokuwiki/models/dokuwiki_client.py @@ -175,3 +175,67 @@ class DokuWikiClient(models.AbstractModel): wiki_url = wiki_url.rstrip('/') return f"{wiki_url}/doku.php?id={page_id}" + + @api.model + def upload_media(self, media_id, file_content, overwrite=True): + """ + Lädt eine Mediendatei (Bild) ins DokuWiki hoch. + + Args: + media_id (str): DokuWiki Media-ID (z.B. "werkstatt:ausruestung:equipment_3.jpg") + file_content (bytes): Binärer Dateiinhalt + overwrite (bool): Existierende Datei überschreiben + + Returns: + bool: True bei Erfolg + + Raises: + 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 + + 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) + finally: + # Temporäre Datei löschen + if os.path.exists(tmp_path): + os.unlink(tmp_path) + + @api.model + def get_media_url(self, media_id): + """ + Generiert die vollständige URL zu einer DokuWiki-Mediendatei. + + Args: + media_id (str): DokuWiki Media-ID + + Returns: + str: Vollständige URL zur Mediendatei + """ + IrConfigParameter = self.env['ir.config_parameter'].sudo() + wiki_url = IrConfigParameter.get_param('dokuwiki.url', '') + + if not wiki_url: + return "" + + # Entferne trailing slash falls vorhanden + wiki_url = wiki_url.rstrip('/') + + # Ersetze : durch / für Media-Pfad + media_path = media_id.replace(':', '/') + + return f"{wiki_url}/lib/exe/fetch.php?media={media_id}" diff --git a/open_workshop_dokuwiki/models/maintenance_equipment.py b/open_workshop_dokuwiki/models/maintenance_equipment.py index 370a334..4154a92 100644 --- a/open_workshop_dokuwiki/models/maintenance_equipment.py +++ b/open_workshop_dokuwiki/models/maintenance_equipment.py @@ -11,6 +11,14 @@ _logger = logging.getLogger(__name__) class MaintenanceEquipment(models.Model): _inherit = 'maintenance.equipment' + # Bild-Feld + image_1920 = fields.Image( + string='Bild', + max_width=1920, + max_height=1920, + help='Equipment-Bild (wird beim Wiki-Sync hochgeladen)' + ) + # Wiki-Felder wiki_doku_id = fields.Char( string='Wiki Dokumentations-ID', @@ -143,9 +151,163 @@ class MaintenanceEquipment(models.Model): return name or "unnamed" - def _generate_wiki_main_page_content(self, view_type='area'): + def _render_template_from_wiki(self, view_type='area'): """ - Generiert den Wiki-Markup-Inhalt für eine Haupt-Ansichtsseite. + Lädt c_template aus DokuWiki und ersetzt Platzhalter mit Odoo-Feldwerten. + Template-Pfad: werkstatt:ausruestung:c_template + + Platzhalter-Format: + - {feldname} für maintenance.equipment Felder, z.B. {name}, {serial_no} + - {ows_machine_id.feldname} für ows.machine Felder, z.B. {ows_machine_id.power} + - {wiki_doku_page} für die zentrale Doku-Seite ID + - {wiki_doku_link} für Link zur zentralen Doku-Seite + - {ows_area} für Bereichsname + - {category} für Kategoriename + - {sync_datetime} für aktuelles Datum/Zeit + + Args: + view_type (str): 'area' oder 'purpose' + + Returns: + str: Gerenderter Wiki-Markup-Inhalt + """ + self.ensure_one() + dokuwiki_client = self.env['dokuwiki.client'] + template_page_id = 'werkstatt:ausruestung:c_template' + + try: + # Template aus Wiki laden + template_content = dokuwiki_client.get_page(template_page_id) + if not template_content: + _logger.warning(f"Template {template_page_id} nicht gefunden, verwende Fallback") + return self._generate_wiki_main_page_content_fallback(view_type) + + _logger.info(f"Template geladen: {template_page_id} ({len(template_content)} Zeichen)") + + # Werte-Dictionary vorbereiten + values = self._prepare_template_values(view_type) + + # Platzhalter ersetzen + rendered_content = template_content + for key, value in values.items(): + placeholder = '{' + key + '}' + rendered_content = rendered_content.replace(placeholder, str(value or '')) + + return rendered_content + + except Exception as e: + _logger.warning(f"Fehler beim Laden des Templates {template_page_id}: {e}") + return self._generate_wiki_main_page_content_fallback(view_type) + + def _prepare_template_values(self, view_type='area'): + """ + Bereitet Dictionary mit allen verfügbaren Feldwerten für Template-Rendering vor. + + Args: + view_type (str): 'area' oder 'purpose' + + Returns: + dict: Dictionary mit Feldnamen als Keys und Werten + """ + self.ensure_one() + + # Zentrale Doku-Seite + doku_page_id = self._get_wiki_doku_page_id() + + # Odoo Base URL + base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url', 'http://localhost:8069') + odoo_equipment_url = f"{base_url}/web#id={self.id}&model=maintenance.equipment&view_type=form" + + # Basis-Werte für maintenance.equipment + values = { + # Spezielle Werte + 'wiki_doku_page': doku_page_id, + 'wiki_doku_link': f"[[{doku_page_id}|✏️ Zentrale Dokumentation bearbeiten]]", + 'odoo_link': f"[[{odoo_equipment_url}|🔗 In Odoo öffnen]]", + 'odoo_url': odoo_equipment_url, + 'sync_datetime': datetime.now().strftime('%d.%m.%Y %H:%M'), + + # Standard Equipment-Felder + 'name': self.name or '', + 'serial_no': self.serial_no or '', + 'model': self.model or '', + 'ows_area': self.ows_area_id.name if self.ows_area_id else '', + 'category': self.category_id.name if self.category_id else '', + 'status': self.status_id.name if self.status_id else '', + 'location': self.location or '', + 'assign_date': self.assign_date.strftime('%d.%m.%Y') if self.assign_date else '', + 'cost': str(self.cost) if self.cost else '', + 'warranty_date': self.warranty_date.strftime('%d.%m.%Y') if self.warranty_date else '', + 'color': str(self.color) if self.color else '', + 'note': self.note or '', + } + + # ows.machine Felder hinzufügen (falls verknüpft) + if self.ows_machine_id: + machine = self.ows_machine_id + # Nur existierende Felder hinzufügen + ows_machine_fields = {} + + # Standard-Felder von ows.machine + if hasattr(machine, 'name') and machine.name: + ows_machine_fields['ows_machine_id.name'] = machine.name + if hasattr(machine, 'model') and machine.model: + ows_machine_fields['ows_machine_id.model'] = machine.model + if hasattr(machine, 'serial_no') and machine.serial_no: + ows_machine_fields['ows_machine_id.serial_no'] = machine.serial_no + if hasattr(machine, 'location') and machine.location: + ows_machine_fields['ows_machine_id.location'] = machine.location + if hasattr(machine, 'note'): + ows_machine_fields['ows_machine_id.note'] = machine.note or '' + if hasattr(machine, 'category_id') and machine.category_id: + ows_machine_fields['ows_machine_id.category'] = machine.category_id.name + + values.update(ows_machine_fields) + + # Bild-Upload und Referenz (falls vorhanden) + if self.image_1920: + wiki_doku_id = self._get_wiki_doku_id() + # Media-ID: werkstatt:ausruestung:media:equipment_name.jpg + media_id = f"werkstatt:ausruestung:media:{wiki_doku_id}.jpg" + + # Bild ins Wiki hochladen + try: + import base64 + # image_1920 ist base64-kodiert, in bytes umwandeln für XML-RPC + image_bytes = base64.b64decode(self.image_1920) + + 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}?300}}}}" + values['image_large'] = f"{{{{:{media_id}}}}}" + values['image_id'] = media_id + _logger.info(f"Bild hochgeladen: {media_id}") + except Exception as e: + _logger.error(f"Fehler beim Bild-Upload: {e}", exc_info=True) + values['image'] = '' + values['image_large'] = '' + values['image_id'] = '' + else: + values['image'] = '' + values['image_large'] = '' + values['image_id'] = '' + + # View-Typ spezifische Werte + if view_type == 'area': + values['view_type'] = 'Bereich' + values['view_name'] = self.ows_area_id.name if self.ows_area_id else 'Unbekannt' + else: + values['view_type'] = 'Einsatzzweck' + values['view_name'] = 'TODO: Einsatzzweck' + + return values + + def _generate_wiki_main_page_content_fallback(self, view_type='area'): + """ + Fallback-Methode: Generiert hart-codierten Wiki-Markup-Inhalt, + wenn c_template.txt nicht verfügbar ist. Args: view_type (str): 'area' oder 'purpose' @@ -183,6 +345,19 @@ class MaintenanceEquipment(models.Model): """ return content + def _generate_wiki_main_page_content(self, view_type='area'): + """ + Generiert den Wiki-Markup-Inhalt für die Haupt-Ansichtsseite. + Verwendet c_template.txt aus DokuWiki falls verfügbar, sonst Fallback. + + Args: + view_type (str): 'area' oder 'purpose' + + Returns: + str: Wiki-Markup-Inhalt + """ + return self._render_template_from_wiki(view_type) + def _generate_wiki_doku_page_content(self): """ Generiert den initialen Wiki-Markup-Inhalt für die zentrale Dokumentationsseite. @@ -270,7 +445,7 @@ Hier kann die Dokumentation für {self.name} geschrieben werden. else: _logger.info(f"Zentrale Doku-Seite für {self.name} existiert bereits: {doku_page_id}") - # 2. Haupt-Ansichtsseite nach Bereich erstellen/aktualisieren + # 2. Haupt-Ansichtsseite nach Bereich erstellen/aktualisieren (verwendet c_template.txt) area_page_id = self._get_wiki_page_id_by_area() area_content = self._generate_wiki_main_page_content(view_type='area') dokuwiki_client.create_page( diff --git a/open_workshop_dokuwiki/views/maintenance_equipment_views.xml b/open_workshop_dokuwiki/views/maintenance_equipment_views.xml index 49f4e5c..59826ff 100644 --- a/open_workshop_dokuwiki/views/maintenance_equipment_views.xml +++ b/open_workshop_dokuwiki/views/maintenance_equipment_views.xml @@ -8,6 +8,11 @@ + + + + +