diff --git a/README.md b/README.md index 20d8bd9..3fa26c5 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/netbox_documentation/__init__.py b/netbox_documentation/__init__.py index 5c72bc1..c0de995 100644 --- a/netbox_documentation/__init__.py +++ b/netbox_documentation/__init__.py @@ -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" diff --git a/netbox_documentation/archive.py b/netbox_documentation/archive.py index 37fbd12..abd102c 100644 --- a/netbox_documentation/archive.py +++ b/netbox_documentation/archive.py @@ -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: diff --git a/netbox_documentation/forms.py b/netbox_documentation/forms.py index 2a1819b..78f3793 100644 --- a/netbox_documentation/forms.py +++ b/netbox_documentation/forms.py @@ -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): diff --git a/netbox_documentation/templates/netbox_documentation/document_archive.html b/netbox_documentation/templates/netbox_documentation/document_archive.html index 5ce3393..237ca81 100644 --- a/netbox_documentation/templates/netbox_documentation/document_archive.html +++ b/netbox_documentation/templates/netbox_documentation/document_archive.html @@ -9,7 +9,7 @@
Dokumentationen exportieren
-

Ausgewählte Dokumentationen gemeinsam mit Ordnern, Objektzuordnungen und Anhängen herunterladen.

+

Mehrere Ordner suchen und hinzufügen. Alle enthaltenen Dokumentationen werden gemeinsam mit Objektzuordnungen und Anhängen heruntergeladen.

{% csrf_token %} diff --git a/netbox_documentation/views.py b/netbox_documentation/views.py index c3e00a3..5a4e9b3 100644 --- a/netbox_documentation/views.py +++ b/netbox_documentation/views.py @@ -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 diff --git a/pyproject.toml b/pyproject.toml index d1be459..b866259 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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"