DokuWiki Integration: Performance-Optimierung und Best Practices

- Connection Caching: Wiederverwendung von DokuWiki-Verbindungen (von 12min auf 20s für 50 Equipment)
- Template Caching: Template nur einmal laden statt pro Equipment
- Dokumentation: Best Practices für erweiterbare DokuWiki-Seitenstruktur (page.txt + page/ ohne start.txt)
- Catlist-Syntax dokumentiert für automatische Unterseiten-Auflistung
This commit is contained in:
Matthias Lotz 2025-12-24 14:13:59 +01:00
parent f79e126c8c
commit 0135035a54
3 changed files with 176 additions and 8 deletions

View File

@ -308,3 +308,108 @@ Daten: {name}|{status_smiley}|{ows_machine_id.category_icon}|{partner_id}|{mod
Spalten: Name|Status|Tags|Hersteller|Standort|Doku
Daten: {name}|{status_smiley}|{tags_list}|{partner_id}|{location}|{wiki_doku_link}
```
---
# Best Practices: DokuWiki Seitenstruktur
## Erweiterbare Dokumentation ohne Umbenennung
### Problem
Benutzer möchten zu Equipment-Seiten eigene Unterseiten hinzufügen, ohne die von Odoo generierten Seiten umbenennen zu müssen.
### Lösung: Parallele Seite + Namespace
DokuWiki erlaubt **gleichzeitig** eine Seite und einen gleichnamigen Namespace:
- Datei: `equipment.txt` (von Odoo generiert)
- Verzeichnis: `equipment/` (für Benutzer-Unterseiten)
**Die Seite `equipment.txt` hat Vorrang und bleibt die Haupt-Equipment-Seite!**
### Empfohlene Struktur
```
werkstatt/ausruestung/doku/
├── analog-oscilloscope-hm303-6.txt # Von Odoo generiert
├── analog-oscilloscope-hm303-6/ # Benutzer erstellt (optional)
│ ├── kalibrierung.txt # Benutzer-Unterseite
│ ├── messungen.txt # Benutzer-Unterseite
│ └── bilder/ # Weitere Unter-Namespaces möglich
│ └── oszillogramme.txt
├── cnc-fraese-xyz.txt # Von Odoo generiert
└── cnc-fraese-xyz/ # Benutzer-Namespace (optional)
├── programme.txt
└── werkzeuge.txt
```
### Wichtige Regeln
#### ✅ DO: Unterverzeichnis OHNE start.txt
```
equipment/
├── subpage1.txt
├── subpage2.txt
└── weitere/
└── details.txt
```
**Vorteile:**
- Breadcrumb zeigt: `Home » Werkstatt » Ausrüstung » Equipment » Subpage1`
- Keine Duplikation oder Verwirrung
- Equipment-Hauptseite bleibt `equipment.txt`
#### ❌ DON'T: Unterverzeichnis MIT start.txt
```
equipment/
├── start.txt # ❌ NICHT erstellen!
├── subpage1.txt
└── subpage2.txt
```
**Probleme:**
- Breadcrumb wird verwirrend: `Equipment` könnte auf zwei Seiten verweisen
- `equipment` und `equipment:start` zeigen unterschiedliche Inhalte
- Benutzer wissen nicht, welche Seite die "richtige" ist
### Catlist für Unterseiten-Auflistung
In der Odoo-generierten Hauptseite kann eine catlist-Anweisung eingefügt werden:
```dokuwiki
===== Dokumentation: Analog Oscilloscope HM303-6 =====
... Equipment-Details ...
==== Weitere Dokumentation ====
<catlist .:analog-oscilloscope-hm303-6 -noNSInBold -sortByTitle>
```
**Syntax-Erklärung:**
- `.:namespace` → Relativer Namespace (Punkt + Doppelpunkt!)
- `-noNSInBold` → Namespace-Präfix nicht fett darstellen
- `-sortByTitle` → Alphabetisch sortieren
- `-exclude:{ns1 ns2}` → Optional: Namespaces ausschließen
**Ergebnis:**
- Zeigt alle Unterseiten im `equipment/` Verzeichnis
- Automatisch aktualisiert wenn Benutzer neue Seiten erstellen
- Kein manuelles Pflegen von Links nötig
### Workflow
1. **Odoo synchronisiert** → Erstellt/aktualisiert `equipment.txt`
2. **Benutzer erstellt Unterseiten** → Erstellt Verzeichnis `equipment/` und fügt Seiten hinzu
3. **Catlist zeigt automatisch** → Alle Unterseiten werden auf der Hauptseite gelistet
4. **Keine Konflikte** → Odoo überschreibt nur `equipment.txt`, nie das `equipment/` Verzeichnis
### Vorteile dieser Struktur
- ✅ Odoo-Seiten bleiben unverändert und zentral verwaltbar
- ✅ Benutzer können frei Unterseiten erstellen ohne Odoo-Sync zu stören
- ✅ Keine Namenskollisionen oder Breadcrumb-Probleme
- ✅ Automatische Verlinkung via catlist
- ✅ Skalierbar: Beliebig viele Unterseiten und Unter-Namespaces möglich
- ✅ Klare Trennung: Was von Odoo kommt vs. was von Benutzern kommt

View File

@ -6,19 +6,42 @@ from odoo.exceptions import UserError
_logger = logging.getLogger(__name__)
# Connection Cache (pro Request wiederverwendet)
_connection_cache = {}
class DokuWikiClient(models.AbstractModel):
"""
Wrapper für DokuWiki XML-RPC API Client.
Managed die Verbindung zum DokuWiki und stellt Methoden für Seitenoperationen bereit.
Performance-Optimierung: Verbindungen werden gecached und wiederverwendet
um den Overhead von XML-RPC Verbindungsaufbauten zu minimieren.
"""
_name = 'dokuwiki.client'
_description = 'DokuWiki API Client'
@api.model
def _get_wiki_connection(self):
def _get_cache_key(self):
"""
Generiert einen Cache-Key basierend auf den Verbindungsparametern.
Returns:
str: Cache-Key (URL + User)
"""
IrConfigParameter = self.env['ir.config_parameter'].sudo()
wiki_url = IrConfigParameter.get_param('dokuwiki.url', '')
wiki_user = IrConfigParameter.get_param('dokuwiki.user', '')
return f"{wiki_url}:{wiki_user}"
@api.model
def _get_wiki_connection(self, use_cache=True):
"""
Erstellt eine DokuWiki-Verbindung mit Credentials aus System-Parametern.
Verbindungen werden gecached und wiederverwendet für bessere Performance.
Args:
use_cache (bool): Wenn True, wird eine gecachte Verbindung wiederverwendet
Returns:
DokuWiki: Verbundenes DokuWiki-Client-Objekt
@ -46,12 +69,34 @@ class DokuWikiClient(models.AbstractModel):
if not wiki_url.startswith(('http://', 'https://')):
raise UserError(f"DokuWiki URL muss mit http:// oder https:// beginnen: {wiki_url}")
# Cache-Key generieren
cache_key = self._get_cache_key()
# Gecachte Verbindung wiederverwenden wenn möglich
if use_cache and cache_key in _connection_cache:
try:
# Teste ob Verbindung noch lebt
cached_wiki = _connection_cache[cache_key]
_ = cached_wiki.version # Test-Call
_logger.debug(f"Wiederverwendung gecachter DokuWiki-Verbindung: {wiki_url}")
return cached_wiki
except Exception as e:
_logger.warning(f"Gecachte Verbindung ungültig, erstelle neue: {e}")
del _connection_cache[cache_key]
# Neue Verbindung erstellen
try:
_logger.info(f"Verbinde zu DokuWiki: {wiki_url} (User: {wiki_user})")
_logger.info(f"Neue DokuWiki-Verbindung: {wiki_url} (User: {wiki_user})")
wiki = DokuWiki(wiki_url, wiki_user, wiki_password)
# Test-Verbindung
version = wiki.version
_logger.info(f"DokuWiki-Verbindung erfolgreich: Version {version}")
# In Cache speichern
if use_cache:
_connection_cache[cache_key] = wiki
_logger.debug(f"Verbindung im Cache gespeichert: {cache_key}")
return wiki
except DokuWikiError as e:
error_details = f"URL: {wiki_url}, User: {wiki_user}, Fehler: {e}"
@ -70,6 +115,15 @@ class DokuWikiClient(models.AbstractModel):
except Exception as e:
_logger.error(f"Unerwarteter Fehler bei DokuWiki-Verbindung: {e}", exc_info=True)
raise UserError(f"Unerwarteter Fehler: {e}")
@api.model
def clear_connection_cache(self):
"""
Löscht den Verbindungs-Cache (z.B. nach Konfigurationsänderungen).
"""
global _connection_cache
_connection_cache.clear()
_logger.info("DokuWiki Verbindungs-Cache geleert")
@api.model
def create_page(self, page_id, content, summary="Erstellt von Odoo"):

View File

@ -7,6 +7,8 @@ from odoo.exceptions import UserError
_logger = logging.getLogger(__name__)
# Cache für Wiki-Templates (wird beim Server-Neustart geleert)
_template_cache = {}
class MaintenanceEquipment(models.Model):
"""
@ -242,17 +244,24 @@ class MaintenanceEquipment(models.Model):
str: Gerenderter Wiki-Markup-Inhalt
"""
self.ensure_one()
global _template_cache
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)
# Template aus Cache oder Wiki laden
if template_page_id not in _template_cache:
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)
_template_cache[template_page_id] = template_content
_logger.info(f"Template geladen und gecacht: {template_page_id} ({len(template_content)} Zeichen)")
else:
_logger.debug(f"Template aus Cache verwendet: {template_page_id}")
_logger.info(f"Template geladen: {template_page_id} ({len(template_content)} Zeichen)")
template_content = _template_cache[template_page_id]
# Werte-Dictionary vorbereiten
values = self._prepare_template_values(view_type)