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 ## 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 ## Installation
@@ -131,7 +131,7 @@ Nach Aktivierung stehen die üblichen NetBox-Plugin-Endpunkte bereit:
## ZIP-Archiv ## 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 - Dokumentinhalt und Metadaten
- vollständige Ordnerpfade - vollständige Ordnerpfade
+1 -1
View File
@@ -5,7 +5,7 @@ class DocumentationConfig(PluginConfig):
name = "netbox_documentation" name = "netbox_documentation"
verbose_name = "NetBox Dokumentation" verbose_name = "NetBox Dokumentation"
description = "Wiki und Office-Dokumentation direkt in NetBox" description = "Wiki und Office-Dokumentation direkt in NetBox"
version = "0.4.0" version = "0.4.1"
author = "LKE" author = "LKE"
base_url = "documentation" base_url = "documentation"
min_version = "4.0.0" min_version = "4.0.0"
+14
View File
@@ -20,6 +20,20 @@ class ArchiveFailure(ValueError):
pass 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): def _category_path(category):
path, seen = [], set() path, seen = [], set()
while category and category.pk not in seen: 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.contrib.contenttypes.models import ContentType
from django.db.models import Q from django.db.models import Q
from utilities.forms import get_field_value 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.rendering import FieldSet
from utilities.forms.widgets import HTMXSelect from utilities.forms.widgets import HTMXSelect
from netbox.forms import NetBoxModelForm from netbox.forms import NetBoxModelForm
@@ -119,17 +121,22 @@ class ImportForm(forms.Form):
class ArchiveExportForm(forms.Form): class ArchiveExportForm(forms.Form):
documents = forms.ModelMultipleChoiceField( categories = DynamicModelMultipleChoiceField(
queryset=Document.objects.none(), widget=forms.CheckboxSelectMultiple, queryset=DocumentCategory.objects.all(), selector=True,
label="Dokumentationen", 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): def __init__(self, *args, user=None, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
queryset = Document.objects.all().order_by("title") queryset = DocumentCategory.objects.all().order_by("name")
if user is not None: if user is not None:
queryset = queryset.restrict(user, "view") queryset = queryset.restrict(user, "view")
self.fields["documents"].queryset = queryset self.fields["categories"].queryset = queryset
class ArchiveImportForm(forms.Form): class ArchiveImportForm(forms.Form):
@@ -9,7 +9,7 @@
<div class="card h-100"> <div class="card h-100">
<h5 class="card-header"><i class="mdi mdi-folder-zip"></i> Dokumentationen exportieren</h5> <h5 class="card-header"><i class="mdi mdi-folder-zip"></i> Dokumentationen exportieren</h5>
<div class="card-body"> <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"> <form method="post">
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="action" value="export"> <input type="hidden" name="action" value="export">
+8 -2
View File
@@ -16,7 +16,7 @@ from .forms import (
ArchiveExportForm, ArchiveImportForm, AssignmentForm, DocumentCategoryForm, ArchiveExportForm, ArchiveImportForm, AssignmentForm, DocumentCategoryForm,
DocumentForm, ImportForm, 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 .importers import ImportFailure, import_document
from .models import Document, DocumentAssignment, DocumentAttachment, DocumentCategory from .models import Document, DocumentAssignment, DocumentAttachment, DocumentCategory
from .tables import DocumentTable, AssignmentTable, DocumentCategoryTable from .tables import DocumentTable, AssignmentTable, DocumentCategoryTable
@@ -189,7 +189,13 @@ class DocumentArchiveView(View):
form = ArchiveExportForm(request.POST, user=request.user) form = ArchiveExportForm(request.POST, user=request.user)
if not form.is_valid(): if not form.is_valid():
return self._render(request, export_form=form) 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 = FileResponse(stream, content_type="application/zip")
response["Content-Disposition"] = 'attachment; filename="netbox-documentation-export.zip"' response["Content-Disposition"] = 'attachment; filename="netbox-documentation-export.zip"'
return response return response
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "netbox-documentation" name = "netbox-documentation"
version = "0.4.0" version = "0.4.1"
description = "Integrated Markdown wiki and office document importer for NetBox" description = "Integrated Markdown wiki and office document importer for NetBox"
readme = "README.md" readme = "README.md"
requires-python = ">=3.10" requires-python = ">=3.10"