Phase 3: Django-natives DMS – Paperless-NGX durch DokumentDatei ersetzt

- Neues Modell DokumentDatei mit PostgreSQL FTS (SearchVectorField, GinIndex)
- Upload-Pfad: dokumente/YYYY/MM/<uuid>/dateiname
- 7 DMS-Views: list, detail, download, upload (HTMX Drag&Drop), delete, edit, search_api
- Templates: list, detail, edit, upload mit Drag&Drop-Zone, Partials
- URLs: /dms/ komplett verdrahtet
- Sidebar: DMS als Primäreintrag, Paperless als Legacy
- Migrationsskript: manage.py migrate_paperless_dokumente (DokumentLink → DokumentDatei)
- compose.yml: paperless-Dienst deaktiviert (Legacy-Kommentarblock)
- Migration 0048 angewendet

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
SysAdmin Agent
2026-03-11 11:10:08 +00:00
parent ee2c827d85
commit a79a0989d6
16 changed files with 1219 additions and 35 deletions

View File

@@ -0,0 +1,124 @@
# management/commands/migrate_paperless_dokumente.py
# Phase 3: Migriert DokumentLink-Einträge zu DokumentDatei (falls Paperless-Dateien lokal verfügbar)
#
# Verwendung:
# python manage.py migrate_paperless_dokumente [--dry-run] [--limit N]
#
# Was dieser Befehl tut:
# 1. Alle DokumentLink-Objekte abrufen (Paperless-Verweise)
# 2. Für jeden Link: DokumentDatei erstellen, falls noch keine existiert (paperless_dokument_id)
# 3. Suchvektor aktualisieren
# 4. paperless_dokument_id setzen, damit künftige Läufe Duplikate überspringen
import os
from django.core.management.base import BaseCommand, CommandError
from django.db import transaction
from stiftung.models import DokumentDatei, DokumentLink
class Command(BaseCommand):
help = "Migriert Paperless-DokumentLink-Einträge zu DokumentDatei (Metadaten only)"
def add_arguments(self, parser):
parser.add_argument(
"--dry-run",
action="store_true",
help="Zeigt an, was migriert würde, ohne Änderungen vorzunehmen.",
)
parser.add_argument(
"--limit",
type=int,
default=0,
help="Maximale Anzahl Einträge (0 = alle).",
)
def handle(self, *args, **options):
dry_run = options["dry_run"]
limit = options["limit"]
links = DokumentLink.objects.select_related(
"destinataer", "land", "paechter", "verpachtung"
).order_by("pk")
if limit > 0:
links = links[:limit]
total = links.count()
self.stdout.write(f"Gefundene DokumentLinks: {total}")
if dry_run:
self.stdout.write(self.style.WARNING("DRY-RUN keine Datenbankänderungen."))
created = 0
skipped = 0
for link in links:
# Bereits migriert?
if DokumentDatei.objects.filter(
paperless_dokument_id=link.paperless_document_id
).exists():
skipped += 1
continue
titel = link.titel or f"Paperless #{link.paperless_document_id}"
kontext = link.kontext or _guess_kontext(titel)
if dry_run:
self.stdout.write(
f" [DRY] Würde anlegen: {titel!r} (kontext={kontext}, "
f"paperless_id={link.paperless_document_id})"
)
created += 1
continue
with transaction.atomic():
dok = DokumentDatei(
titel=titel,
beschreibung=link.beschreibung or "",
kontext=kontext,
paperless_dokument_id=link.paperless_document_id,
)
# Assign FKs by ID (DokumentLink stores raw UUIDs, not FK relations)
if link.destinataer_id:
dok.destinataer_id = link.destinataer_id
if link.land_id:
dok.land_id = link.land_id
if link.paechter_id:
dok.paechter_id = link.paechter_id
if link.land_verpachtung_id:
dok.verpachtung_id = link.land_verpachtung_id
dok.save()
dok.update_suchvektor()
created += 1
self.stdout.write(
self.style.SUCCESS(
f"Fertig: {created} angelegt, {skipped} übersprungen (bereits migriert)."
)
)
def _guess_kontext(title_lower: str) -> str:
"""Leitet den Kontext-Code aus dem Titel ab."""
t = title_lower.lower()
if any(kw in t for kw in ["pachtvertrag", "pachtvertr"]):
return "pachtvertrag"
if any(kw in t for kw in ["antrag", "förderantrag"]):
return "antrag"
if any(kw in t for kw in ["nachweis", "verwendungsnachweis"]):
return "verwendungsnachweis"
if any(kw in t for kw in ["rechnung"]):
return "rechnung"
if any(kw in t for kw in ["bericht", "jahresbericht"]):
return "bericht"
if any(kw in t for kw in ["karte", "landkarte", "flurkarte"]):
return "landkarte"
if any(kw in t for kw in ["bescheid"]):
return "bescheid"
if any(kw in t for kw in ["korrespondenz", "brief"]):
return "korrespondenz"
if any(kw in t for kw in ["studium", "immatrikulation", "zeugnis"]):
return "studiennachweis"
return "anderes"