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,153 @@
{% extends 'base.html' %}
{% load static %}
{% block title %}DMS Dokumente Stiftungsverwaltung{% endblock %}
{% block content %}
<div class="row">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h3">
<i class="fas fa-folder-open text-primary me-2"></i>
Dokumentenverwaltung (DMS)
</h1>
<a href="{% url 'stiftung:dms_upload' %}" class="btn btn-primary">
<i class="fas fa-upload me-2"></i>Dokument hochladen
</a>
</div>
<!-- Suche & Filter -->
<div class="card shadow mb-4">
<div class="card-body py-2">
<form method="get" class="row g-2 align-items-center">
<div class="col-md-5">
<div class="input-group input-group-sm">
<span class="input-group-text"><i class="fas fa-search"></i></span>
<input type="text" name="q" value="{{ q }}" class="form-control"
placeholder="Volltextsuche (Titel, Beschreibung, Inhalt)"
hx-get="{% url 'stiftung:dms_search_api' %}"
hx-target="#search-results"
hx-trigger="keyup changed delay:400ms"
hx-include="[name='q']">
</div>
</div>
<div class="col-md-3">
<select name="kontext" class="form-select form-select-sm" onchange="this.form.submit()">
<option value="">Alle Typen</option>
{% for code, label in kontext_choices %}
<option value="{{ code }}" {% if code == kontext_filter %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-2">
<button type="submit" class="btn btn-sm btn-outline-primary w-100">Suchen</button>
</div>
{% if q or kontext_filter %}
<div class="col-md-2">
<a href="{% url 'stiftung:dms_list' %}" class="btn btn-sm btn-outline-secondary w-100">Reset</a>
</div>
{% endif %}
</form>
</div>
</div>
<!-- HTMX Live-Suchergebnisse -->
<div id="search-results"></div>
<!-- Dokument-Liste -->
<div class="card shadow">
<div class="card-header bg-dark text-white py-2 d-flex justify-content-between">
<span class="small fw-bold"><i class="fas fa-file me-2"></i>{{ gesamt }} Dokument(e)</span>
{% if q %}<span class="small text-warning">Suche: „{{ q }}"</span>{% endif %}
</div>
<div class="card-body p-0">
{% if page_obj.object_list %}
<div class="table-responsive">
<table class="table table-sm table-hover mb-0">
<thead class="table-light">
<tr>
<th>Titel</th>
<th>Typ</th>
<th>Zuordnung</th>
<th>Größe</th>
<th>Hochgeladen</th>
<th></th>
</tr>
</thead>
<tbody>
{% for dok in page_obj %}
<tr>
<td class="align-middle">
<a href="{% url 'stiftung:dms_detail' pk=dok.pk %}" class="text-decoration-none fw-semibold">
{% if dok.is_pdf %}<i class="fas fa-file-pdf text-danger me-1"></i>{% else %}<i class="fas fa-file text-muted me-1"></i>{% endif %}
{{ dok.titel|truncatechars:60 }}
</a>
{% if dok.beschreibung %}
<div class="small text-muted">{{ dok.beschreibung|truncatechars:80 }}</div>
{% endif %}
</td>
<td class="align-middle">
<span class="badge bg-secondary small">{{ dok.get_kontext_display }}</span>
</td>
<td class="align-middle small text-muted">
{% if dok.destinataer %}<div><i class="fas fa-user me-1"></i>{{ dok.destinataer.get_full_name }}</div>{% endif %}
{% if dok.land %}<div><i class="fas fa-map me-1"></i>{{ dok.land.lfd_nr }}</div>{% endif %}
{% if dok.paechter %}<div><i class="fas fa-user-tie me-1"></i>{{ dok.paechter.get_full_name }}</div>{% endif %}
</td>
<td class="align-middle small text-muted text-nowrap">{{ dok.get_human_size }}</td>
<td class="align-middle small text-muted text-nowrap">
{{ dok.erstellt_am|date:"d.m.Y" }}
{% if dok.erstellt_von %}<br>{{ dok.erstellt_von.get_full_name|default:dok.erstellt_von.username }}{% endif %}
</td>
<td class="align-middle text-end">
<a href="{% url 'stiftung:dms_download' pk=dok.pk %}" class="btn btn-xs btn-outline-success me-1" style="font-size:0.7rem;padding:2px 6px;" title="Herunterladen">
<i class="fas fa-download"></i>
</a>
<a href="{% url 'stiftung:dms_edit' pk=dok.pk %}" class="btn btn-xs btn-outline-secondary me-1" style="font-size:0.7rem;padding:2px 6px;" title="Bearbeiten">
<i class="fas fa-edit"></i>
</a>
<form method="post" action="{% url 'stiftung:dms_delete' pk=dok.pk %}" class="d-inline" onsubmit="return confirm('Dokument löschen?')">
{% csrf_token %}
<input type="hidden" name="next" value="{% url 'stiftung:dms_list' %}">
<button type="submit" class="btn btn-xs btn-outline-danger" style="font-size:0.7rem;padding:2px 6px;" title="Löschen">
<i class="fas fa-trash"></i>
</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Pagination -->
{% if page_obj.has_other_pages %}
<div class="d-flex justify-content-center py-3">
<nav>
<ul class="pagination pagination-sm mb-0">
{% if page_obj.has_previous %}
<li class="page-item"><a class="page-link" href="?page={{ page_obj.previous_page_number }}&q={{ q }}&kontext={{ kontext_filter }}"></a></li>
{% endif %}
<li class="page-item active"><a class="page-link">{{ page_obj.number }}/{{ page_obj.paginator.num_pages }}</a></li>
{% if page_obj.has_next %}
<li class="page-item"><a class="page-link" href="?page={{ page_obj.next_page_number }}&q={{ q }}&kontext={{ kontext_filter }}"></a></li>
{% endif %}
</ul>
</nav>
</div>
{% endif %}
{% else %}
<div class="text-muted text-center py-5">
<i class="fas fa-folder-open fa-3x mb-3 d-block opacity-25"></i>
{% if q %}Keine Dokumente für „{{ q }}" gefunden.{% else %}Noch keine Dokumente vorhanden.{% endif %}
<div class="mt-3">
<a href="{% url 'stiftung:dms_upload' %}" class="btn btn-primary btn-sm">
<i class="fas fa-upload me-1"></i>Erstes Dokument hochladen
</a>
</div>
</div>
{% endif %}
</div>
</div>
</div>
</div>
{% endblock %}