Files
stiftung-management-system/app/stiftung/views/dms.py
SysAdmin Agent 781d410f88 Fix DMS edit FieldError: use pachtbeginn instead of vertragsbeginn
LandVerpachtung model uses pachtbeginn, not vertragsbeginn.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 11:36:34 +00:00

281 lines
9.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# views/dms.py
# Phase 3: Django-natives DMS Dokumentenverwaltung ohne Paperless-NGX
import os
from datetime import date
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.contrib.postgres.search import SearchQuery, SearchRank
from django.core.paginator import Paginator
from django.http import FileResponse, Http404, JsonResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.utils import timezone
from django.views.decorators.http import require_POST
from stiftung.models import (
Destinataer, DokumentDatei, Foerderung, Land, LandVerpachtung, Paechter, Rentmeister
)
# ---------------------------------------------------------------------------
# Hilfsfunktionen
# ---------------------------------------------------------------------------
def _save_upload(request, instance: DokumentDatei):
"""Speichert Upload-Metadaten (Dateityp, Größe, FTS)."""
if instance.datei:
f = instance.datei
instance.dateiname_original = os.path.basename(f.name)
instance.dateityp = getattr(f, "content_type", "") or ""
instance.dateigroesse = f.size if hasattr(f, "size") else 0
instance.erstellt_von = request.user
instance.save()
instance.update_suchvektor()
# ---------------------------------------------------------------------------
# DMS Hauptseiten
# ---------------------------------------------------------------------------
@login_required
def dms_list(request):
"""Dokumenten-Übersicht mit Filter und Suche."""
q = request.GET.get("q", "").strip()
kontext_filter = request.GET.get("kontext", "")
entity_filter = request.GET.get("entity", "") # z.B. "destinataer"
entity_id = request.GET.get("entity_id", "")
qs = DokumentDatei.objects.select_related(
"destinataer", "land", "paechter", "verpachtung", "erstellt_von"
)
# Volltextsuche
if q:
search_query = SearchQuery(q, config="german")
qs = qs.annotate(rank=SearchRank("suchvektor", search_query)).filter(
rank__gt=0.01
).order_by("-rank")
else:
qs = qs.order_by("-erstellt_am")
if kontext_filter:
qs = qs.filter(kontext=kontext_filter)
if entity_filter == "destinataer" and entity_id:
qs = qs.filter(destinataer_id=entity_id)
elif entity_filter == "land" and entity_id:
qs = qs.filter(land_id=entity_id)
elif entity_filter == "paechter" and entity_id:
qs = qs.filter(paechter_id=entity_id)
elif entity_filter == "verpachtung" and entity_id:
qs = qs.filter(verpachtung_id=entity_id)
paginator = Paginator(qs, 25)
page_obj = paginator.get_page(request.GET.get("page"))
context = {
"page_obj": page_obj,
"q": q,
"kontext_filter": kontext_filter,
"kontext_choices": DokumentDatei.KONTEXT_CHOICES,
"gesamt": qs.count() if not q else paginator.count,
}
return render(request, "stiftung/dms/list.html", context)
@login_required
def dms_detail(request, pk):
"""Dokument-Detailseite mit Download-Link."""
dok = get_object_or_404(DokumentDatei, pk=pk)
context = {"dok": dok}
return render(request, "stiftung/dms/detail.html", context)
@login_required
def dms_download(request, pk):
"""Direkter Datei-Download."""
dok = get_object_or_404(DokumentDatei, pk=pk)
if not dok.datei or not dok.datei.storage.exists(dok.datei.name):
raise Http404("Datei nicht gefunden.")
response = FileResponse(
dok.datei.open("rb"),
as_attachment=True,
filename=dok.dateiname_original or os.path.basename(dok.datei.name),
)
return response
@login_required
def dms_upload(request):
"""HTMX-Drag&Drop-Upload unterstützt normale POST-Anfragen und HTMX-Requests."""
# Pre-fill entity links from GET params
initial = {
"destinataer_id": request.GET.get("destinataer", ""),
"land_id": request.GET.get("land", ""),
"paechter_id": request.GET.get("paechter", ""),
"verpachtung_id": request.GET.get("verpachtung", ""),
"foerderung_id": request.GET.get("foerderung", ""),
"kontext": request.GET.get("kontext", "anderes"),
}
if request.method == "POST":
datei = request.FILES.get("datei")
titel = request.POST.get("titel", "").strip()
beschreibung = request.POST.get("beschreibung", "").strip()
kontext = request.POST.get("kontext", "anderes")
if not datei:
if request.htmx:
return JsonResponse({"error": "Keine Datei übermittelt."}, status=400)
messages.error(request, "Bitte eine Datei auswählen.")
else:
if not titel:
titel = os.path.splitext(datei.name)[0][:255]
dok = DokumentDatei(
titel=titel,
beschreibung=beschreibung,
kontext=kontext,
datei=datei,
)
# Entity links
dest_id = request.POST.get("destinataer_id", "").strip()
land_id = request.POST.get("land_id", "").strip()
paechter_id = request.POST.get("paechter_id", "").strip()
verp_id = request.POST.get("verpachtung_id", "").strip()
foerd_id = request.POST.get("foerderung_id", "").strip()
if dest_id:
try:
dok.destinataer_id = dest_id
except Exception:
pass
if land_id:
try:
dok.land_id = land_id
except Exception:
pass
if paechter_id:
try:
dok.paechter_id = paechter_id
except Exception:
pass
if verp_id:
try:
dok.verpachtung_id = verp_id
except Exception:
pass
if foerd_id:
try:
dok.foerderung_id = foerd_id
except Exception:
pass
_save_upload(request, dok)
if request.htmx:
return render(request, "stiftung/dms/partials/upload_success.html", {"dok": dok})
messages.success(request, f'Dokument \u201e{dok.titel}\u201c erfolgreich hochgeladen.')
return redirect("stiftung:dms_detail", pk=dok.pk)
# GET: zeige Upload-Formular
destinataere = Destinataer.objects.filter(aktiv=True).order_by("nachname", "vorname")
laendereien = Land.objects.filter(aktiv=True).order_by("lfd_nr")
paechter_qs = Paechter.objects.filter(aktiv=True).order_by("nachname")
context = {
"initial": initial,
"kontext_choices": DokumentDatei.KONTEXT_CHOICES,
"destinataere": destinataere,
"laendereien": laendereien,
"paechter_qs": paechter_qs,
}
return render(request, "stiftung/dms/upload.html", context)
@login_required
@require_POST
def dms_delete(request, pk):
"""Löscht ein Dokument inkl. Datei."""
dok = get_object_or_404(DokumentDatei, pk=pk)
titel = dok.titel
# Datei physisch löschen
if dok.datei:
try:
dok.datei.delete(save=False)
except Exception:
pass
dok.delete()
messages.success(request, f'Dokument \u201e{titel}\u201c gel\u00f6scht.')
next_url = request.POST.get("next") or "stiftung:dms_list"
if next_url.startswith("/"):
return redirect(next_url)
return redirect(next_url)
@login_required
def dms_search_api(request):
"""HTMX-Suche: gibt gerendertes Partial mit Suchergebnissen zurück."""
q = request.GET.get("q", "").strip()
if not q:
return render(request, "stiftung/dms/partials/search_results.html", {"results": []})
search_query = SearchQuery(q, config="german")
results = (
DokumentDatei.objects.annotate(rank=SearchRank("suchvektor", search_query))
.filter(rank__gt=0.01)
.select_related("destinataer", "land")
.order_by("-rank")[:20]
)
return render(
request,
"stiftung/dms/partials/search_results.html",
{"results": results, "q": q},
)
@login_required
def dms_edit(request, pk):
"""Bearbeite Metadaten eines Dokuments (kein Datei-Austausch)."""
dok = get_object_or_404(DokumentDatei, pk=pk)
if request.method == "POST":
dok.titel = request.POST.get("titel", dok.titel).strip()[:255]
dok.beschreibung = request.POST.get("beschreibung", "").strip()
dok.kontext = request.POST.get("kontext", dok.kontext)
# Entity assignments
dest_id = request.POST.get("destinataer_id", "").strip()
land_id = request.POST.get("land_id", "").strip()
paechter_id = request.POST.get("paechter_id", "").strip()
verp_id = request.POST.get("verpachtung_id", "").strip()
dok.destinataer_id = int(dest_id) if dest_id else None
dok.land_id = int(land_id) if land_id else None
dok.paechter_id = int(paechter_id) if paechter_id else None
dok.verpachtung_id = verp_id if verp_id else None
dok.save()
dok.update_suchvektor()
messages.success(request, "Metadaten gespeichert.")
return redirect("stiftung:dms_detail", pk=dok.pk)
destinataere = Destinataer.objects.filter(aktiv=True).order_by("nachname", "vorname")
laendereien = Land.objects.filter(aktiv=True).order_by("lfd_nr")
paechter_qs = Paechter.objects.filter(aktiv=True).order_by("nachname")
verpachtungen = LandVerpachtung.objects.select_related("land", "paechter").order_by("-pachtbeginn")[:50]
context = {
"dok": dok,
"kontext_choices": DokumentDatei.KONTEXT_CHOICES,
"destinataere": destinataere,
"laendereien": laendereien,
"paechter_qs": paechter_qs,
"verpachtungen": verpachtungen,
}
return render(request, "stiftung/dms/edit.html", context)