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:
124
app/stiftung/management/commands/migrate_paperless_dokumente.py
Normal file
124
app/stiftung/management/commands/migrate_paperless_dokumente.py
Normal 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"
|
||||
Reference in New Issue
Block a user