feat: ZIP-Export auf durchsuchbare Ordnerauswahl umstellen

This commit is contained in:
2026-07-22 12:41:44 +02:00
parent c586e9fdaa
commit 0492c67b9f
7 changed files with 40 additions and 13 deletions
+2 -2
View File
@@ -21,7 +21,7 @@ Ein in NetBox integriertes Markdown-Wiki für Betriebsdokumentationen und Anleit
## Kompatibilität
Die Version `0.4.0` zielt auf NetBox 4.x (mindestens 4.0). Vor einem produktiven Rollout sollte das Plugin gegen die konkret eingesetzte NetBox-Minor-Version in einer Testinstanz geprüft werden.
Die Version `0.4.1` zielt auf NetBox 4.x (mindestens 4.0). Vor einem produktiven Rollout sollte das Plugin gegen die konkret eingesetzte NetBox-Minor-Version in einer Testinstanz geprüft werden.
## Installation
@@ -131,7 +131,7 @@ Nach Aktivierung stehen die üblichen NetBox-Plugin-Endpunkte bereit:
## ZIP-Archiv
Unter **Dokumentation → ZIP Export & Import** können mehrere Dokumentationen ausgewählt und gemeinsam heruntergeladen werden. Das Archiv enthält:
Unter **Dokumentation → ZIP Export & Import** können mehrere Ordner über ein Suchfeld hinzugefügt und gemeinsam heruntergeladen werden. Optional werden alle Unterordner rekursiv einbezogen. Dokumentationen, die durch überlappende Ordnerauswahlen mehrfach gefunden werden, landen nur einmal im Archiv. Das Archiv enthält:
- Dokumentinhalt und Metadaten
- vollständige Ordnerpfade
+1 -1
View File
@@ -5,7 +5,7 @@ class DocumentationConfig(PluginConfig):
name = "netbox_documentation"
verbose_name = "NetBox Dokumentation"
description = "Wiki und Office-Dokumentation direkt in NetBox"
version = "0.4.0"
version = "0.4.1"
author = "LKE"
base_url = "documentation"
min_version = "4.0.0"
+14
View File
@@ -20,6 +20,20 @@ class ArchiveFailure(ValueError):
pass
def documents_for_categories(categories, include_subfolders=True, user=None):
"""Return distinct permitted documents contained in the selected category trees."""
category_ids = set(categories.values_list("pk", flat=True))
frontier = set(category_ids)
while include_subfolders and frontier:
children = set(DocumentCategory.objects.filter(parent_id__in=frontier).values_list("pk", flat=True))
frontier = children - category_ids
category_ids.update(children)
documents = Document.objects.filter(category_id__in=category_ids).distinct().order_by("title")
if user is not None:
documents = documents.restrict(user, "view")
return documents
def _category_path(category):
path, seen = [], set()
while category and category.pk not in seen:
+13 -6
View File
@@ -3,7 +3,9 @@ from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.db.models import Q
from utilities.forms import get_field_value
from utilities.forms.fields import ContentTypeChoiceField, DynamicModelChoiceField, SlugField
from utilities.forms.fields import (
ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, SlugField,
)
from utilities.forms.rendering import FieldSet
from utilities.forms.widgets import HTMXSelect
from netbox.forms import NetBoxModelForm
@@ -119,17 +121,22 @@ class ImportForm(forms.Form):
class ArchiveExportForm(forms.Form):
documents = forms.ModelMultipleChoiceField(
queryset=Document.objects.none(), widget=forms.CheckboxSelectMultiple,
label="Dokumentationen",
categories = DynamicModelMultipleChoiceField(
queryset=DocumentCategory.objects.all(), selector=True,
label="Ordner und Kategorien",
help_text="Einen oder mehrere Ordner suchen und hinzufügen.",
)
include_subfolders = forms.BooleanField(
required=False, initial=True, label="Unterordner einbeziehen",
help_text="Exportiert Dokumentationen aus allen untergeordneten Ordnern.",
)
def __init__(self, *args, user=None, **kwargs):
super().__init__(*args, **kwargs)
queryset = Document.objects.all().order_by("title")
queryset = DocumentCategory.objects.all().order_by("name")
if user is not None:
queryset = queryset.restrict(user, "view")
self.fields["documents"].queryset = queryset
self.fields["categories"].queryset = queryset
class ArchiveImportForm(forms.Form):
@@ -9,7 +9,7 @@
<div class="card h-100">
<h5 class="card-header"><i class="mdi mdi-folder-zip"></i> Dokumentationen exportieren</h5>
<div class="card-body">
<p>Ausgewählte Dokumentationen gemeinsam mit Ordnern, Objektzuordnungen und Anhängen herunterladen.</p>
<p>Mehrere Ordner suchen und hinzufügen. Alle enthaltenen Dokumentationen werden gemeinsam mit Objektzuordnungen und Anhängen heruntergeladen.</p>
<form method="post">
{% csrf_token %}
<input type="hidden" name="action" value="export">
+8 -2
View File
@@ -16,7 +16,7 @@ from .forms import (
ArchiveExportForm, ArchiveImportForm, AssignmentForm, DocumentCategoryForm,
DocumentForm, ImportForm,
)
from .archive import ArchiveFailure, export_documents, import_archive
from .archive import ArchiveFailure, documents_for_categories, export_documents, import_archive
from .importers import ImportFailure, import_document
from .models import Document, DocumentAssignment, DocumentAttachment, DocumentCategory
from .tables import DocumentTable, AssignmentTable, DocumentCategoryTable
@@ -189,7 +189,13 @@ class DocumentArchiveView(View):
form = ArchiveExportForm(request.POST, user=request.user)
if not form.is_valid():
return self._render(request, export_form=form)
stream = export_documents(form.cleaned_data["documents"])
documents = documents_for_categories(
form.cleaned_data["categories"], form.cleaned_data["include_subfolders"], request.user
)
if not documents.exists():
form.add_error("categories", "Die ausgewählten Ordner enthalten keine exportierbaren Dokumentationen.")
return self._render(request, export_form=form)
stream = export_documents(documents)
response = FileResponse(stream, content_type="application/zip")
response["Content-Disposition"] = 'attachment; filename="netbox-documentation-export.zip"'
return response
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "netbox-documentation"
version = "0.4.0"
version = "0.4.1"
description = "Integrated Markdown wiki and office document importer for NetBox"
readme = "README.md"
requires-python = ">=3.10"