Verbesserung der Suche vorhandener Dokumente
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
|
||||
<identifier value="de.mmorath.paperless"/>
|
||||
<version value="1.2.0"/>
|
||||
<version value="1.3.0"/>
|
||||
|
||||
<display-name>
|
||||
<name lang="de">Paperless-Connector</name>
|
||||
|
||||
@@ -7,6 +7,7 @@ von Hand kodiert.
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
import mimetypes
|
||||
import os
|
||||
import ssl
|
||||
@@ -242,14 +243,119 @@ class PaperlessClient:
|
||||
self._json("documents/", {"page_size": 1})
|
||||
return True
|
||||
|
||||
# Felder, die die Trefferliste braucht. Ohne diese Einschränkung
|
||||
# liefert Paperless je Treffer auch den vollständigen Inhalt - bei
|
||||
# fünfzig Treffern sind das schnell mehrere Megabyte.
|
||||
SUCHFELDER = ("id,title,created,added,archive_serial_number,"
|
||||
"correspondent,document_type,tags,original_file_name")
|
||||
|
||||
# Zeichen mit besonderer Bedeutung in der Abfragesprache des
|
||||
# Suchindex. Unbehandelt führen sie zu Syntaxfehlern oder zu
|
||||
# Treffern, die niemand erwartet - ein Bindestrich im Namen wäre
|
||||
# sonst ein Ausschluss.
|
||||
_SONDERZEICHEN = re.compile(r'[+\-&|!(){}\[\]^"~*?:\\/]')
|
||||
|
||||
def search(self, query, limit=50):
|
||||
params = {"page_size": limit,
|
||||
"ordering": "-added",
|
||||
"fields": "id,title,created,added,archive_serial_number,"
|
||||
"correspondent,document_type,tags,original_file_name"}
|
||||
if query:
|
||||
params["query"] = query
|
||||
return (self._json("documents/", params) or {}).get("results", [])
|
||||
"""
|
||||
Dokumente suchen. Vier Wege, in dieser Reihenfolge:
|
||||
|
||||
1. Archiv-Seriennummer, wenn die Eingabe nur aus Ziffern
|
||||
besteht. Sie ist ein eigenes Feld - die Volltextsuche
|
||||
findet sie NICHT.
|
||||
|
||||
2. Die GLOBALE SUCHE von Paperless (/api/search/). Das ist
|
||||
dieselbe Schnittstelle, die die Suchleiste oben in der
|
||||
Weboberfläche bedient. Sie verbindet Titelsuche und
|
||||
Volltext und liefert damit die Ergebnisse, die man aus der
|
||||
Oberfläche kennt.
|
||||
|
||||
3. Teilzeichenkette in Titel und Inhalt. Greift, wenn die
|
||||
globale Suche nichts liefert oder in der eingesetzten
|
||||
Version fehlt.
|
||||
|
||||
4. Volltextsuche über den Index, mit UND-Verknüpfung. Sie
|
||||
beherrscht Wortstämme und findet auch Beugungsformen.
|
||||
|
||||
Zwei Fallstricke, die hier berücksichtigt sind:
|
||||
|
||||
* Bei der Volltextsuche darf KEIN ordering mitgegeben werden.
|
||||
Paperless sortiert dort nach Relevanz; eine eigene
|
||||
Sortierangabe führte dazu, dass die Suchanfrage verworfen
|
||||
wurde und schlicht die zuletzt hinzugefügten Dokumente kamen.
|
||||
|
||||
* Ohne UND-Verknüpfung sucht der Index mit ODER. "Anschreiben
|
||||
Markus Straub" lieferte dann alles, was irgendeines der drei
|
||||
Wörter enthält - der gesuchte Treffer ging darin unter.
|
||||
"""
|
||||
q = (query or "").strip()
|
||||
|
||||
if not q:
|
||||
return self._suchlauf({"ordering": "-added"}, limit)
|
||||
|
||||
# 1. Seriennummer
|
||||
if q.isdigit():
|
||||
treffer = self._suchlauf({"archive_serial_number": q}, limit)
|
||||
if treffer:
|
||||
return treffer
|
||||
|
||||
# 2. Globale Suche wie in der Weboberfläche
|
||||
treffer = self._globale_suche(q, limit)
|
||||
if treffer:
|
||||
return treffer
|
||||
|
||||
# 3. Titel und Inhalt
|
||||
treffer = self._suchlauf({"title_content": q,
|
||||
"ordering": "-created"}, limit)
|
||||
if treffer:
|
||||
return treffer
|
||||
|
||||
# 4. Volltext mit UND, ohne ordering.
|
||||
#
|
||||
# Nach dem Ersetzen der Sonderzeichen ERNEUT zerlegen: Aus
|
||||
# "Müller-Lüdenscheidt" würde sonst ein Wort mit Leerzeichen,
|
||||
# und die UND-Verknüpfung wäre wieder aufgehoben.
|
||||
worte = self._SONDERZEICHEN.sub(" ", q).split()
|
||||
if not worte:
|
||||
return []
|
||||
return self._suchlauf({"query": " AND ".join(worte)}, limit)
|
||||
|
||||
def _globale_suche(self, q, limit):
|
||||
"""
|
||||
Die Schnittstelle hinter der Suchleiste der Weboberfläche.
|
||||
|
||||
Sie gibt es erst ab neueren Paperless-Ständen, und die Form der
|
||||
Antwort ist nicht überall gleich - mal ein Feld "documents", mal
|
||||
"results". Deshalb wird beides angenommen und bei jedem Fehler
|
||||
stillschweigend auf den nächsten Weg ausgewichen.
|
||||
"""
|
||||
try:
|
||||
antwort = self._json("search/", {"query": q,
|
||||
"page_size": limit})
|
||||
except PaperlessError:
|
||||
return []
|
||||
if not antwort:
|
||||
return []
|
||||
|
||||
for schluessel in ("documents", "results"):
|
||||
rows = antwort.get(schluessel)
|
||||
if isinstance(rows, list) and rows:
|
||||
# Die globale Suche liefert je nach Version verschiedene
|
||||
# Objektarten. Nur echte Dokumente durchlassen.
|
||||
return [r for r in rows
|
||||
if isinstance(r, dict) and "id" in r
|
||||
and "title" in r][:limit]
|
||||
return []
|
||||
|
||||
def _suchlauf(self, zusatz, limit):
|
||||
params = {"page_size": limit, "fields": self.SUCHFELDER}
|
||||
params.update(zusatz)
|
||||
try:
|
||||
return (self._json("documents/", params) or {}).get("results", [])
|
||||
except PaperlessError:
|
||||
# Ein nicht unterstützter Parameter soll nicht den ganzen
|
||||
# Dialog zum Scheitern bringen - der nächste Weg wird
|
||||
# ohnehin probiert.
|
||||
return []
|
||||
|
||||
def document(self, doc_id, full_perms=False):
|
||||
params = {"full_perms": "true"} if full_perms else None
|
||||
|
||||
Reference in New Issue
Block a user