diff --git a/netbox_documentation/__init__.py b/netbox_documentation/__init__.py index 2dbee2b..d69c23f 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.9.3" + version = "0.10.0" author = "LKE" base_url = "documentation" min_version = "4.0.0" diff --git a/netbox_documentation/models.py b/netbox_documentation/models.py index 4d8da1a..f5d9ac4 100644 --- a/netbox_documentation/models.py +++ b/netbox_documentation/models.py @@ -103,6 +103,18 @@ class DocumentCategory(NetBoxModel): def get_absolute_url(self): return reverse("plugins:netbox_documentation:documentcategory", args=[self.pk]) + @property + def ancestors(self): + """Ancestor folders from the root down to (excluding) this one, for breadcrumbs.""" + result = [] + parent = self.parent + seen = {self.pk} + while parent and parent.pk not in seen: + result.append(parent) + seen.add(parent.pk) + parent = parent.parent + return list(reversed(result)) + def clean(self): super().clean() from django.core.exceptions import ValidationError diff --git a/netbox_documentation/navigation.py b/netbox_documentation/navigation.py index 575d071..2d32e61 100644 --- a/netbox_documentation/navigation.py +++ b/netbox_documentation/navigation.py @@ -11,7 +11,7 @@ menu = PluginMenu( PluginMenuItem(link="plugins:netbox_documentation:documentassignment_list", link_text="Zuordnungen", buttons=( PluginMenuButton(link="plugins:netbox_documentation:documentassignment_add", title="Zuordnen", icon_class="mdi mdi-link-plus", color=ButtonColorChoices.GREEN), )), - PluginMenuItem(link="plugins:netbox_documentation:documentcategory_list", link_text="Ordner & Kategorien", buttons=( + PluginMenuItem(link="plugins:netbox_documentation:documentcategory_tree", link_text="Ordner & Kategorien", buttons=( PluginMenuButton(link="plugins:netbox_documentation:documentcategory_add", title="Ordner erstellen", icon_class="mdi mdi-folder-plus", color=ButtonColorChoices.GREEN), )), PluginMenuItem(link="plugins:netbox_documentation:document_archive", link_text="ZIP Export & Import"), diff --git a/netbox_documentation/templates/netbox_documentation/documentcategory.html b/netbox_documentation/templates/netbox_documentation/documentcategory.html index 3c10a8f..202600e 100644 --- a/netbox_documentation/templates/netbox_documentation/documentcategory.html +++ b/netbox_documentation/templates/netbox_documentation/documentcategory.html @@ -1,6 +1,15 @@ {% extends 'generic/object.html' %} {% block content %} +
{% if object.tenant_group or object.tenant %} diff --git a/netbox_documentation/templates/netbox_documentation/documentcategory_tree.html b/netbox_documentation/templates/netbox_documentation/documentcategory_tree.html new file mode 100644 index 0000000..7e34619 --- /dev/null +++ b/netbox_documentation/templates/netbox_documentation/documentcategory_tree.html @@ -0,0 +1,66 @@ +{% extends 'base/layout.html' %} + +{% block title %}Ordner & Kategorien{% endblock %} + +{% block content %} +
+
+ +
+ {% if perms.netbox_documentation.add_documentcategory %} + Ordner erstellen + {% endif %} + Listenansicht +
+
+
+
+
+ {% if tree %} +
    + {% for node in tree %} + {% include 'netbox_documentation/inc/category_tree_node.html' with node=node %} + {% endfor %} +
+ {% else %} +

Noch keine Ordner vorhanden.

+ {% endif %} +
+
+ + + + +{% endblock %} diff --git a/netbox_documentation/templates/netbox_documentation/inc/category_tree_node.html b/netbox_documentation/templates/netbox_documentation/inc/category_tree_node.html new file mode 100644 index 0000000..dc4fab5 --- /dev/null +++ b/netbox_documentation/templates/netbox_documentation/inc/category_tree_node.html @@ -0,0 +1,30 @@ +
  • + {% if node.children %} +
    + + + {{ node.object.name }} + {{ node.object.document_total }} + {% if node.object.tenant %} {{ node.object.tenant }}{% endif %} + {% if perms.netbox_documentation.change_documentcategory %} + + {% endif %} + +
      + {% for child in node.children %} + {% include 'netbox_documentation/inc/category_tree_node.html' with node=child %} + {% endfor %} +
    +
    + {% else %} +
    + + {{ node.object.name }} + {{ node.object.document_total }} + {% if node.object.tenant %} {{ node.object.tenant }}{% endif %} + {% if perms.netbox_documentation.change_documentcategory %} + + {% endif %} +
    + {% endif %} +
  • diff --git a/netbox_documentation/urls.py b/netbox_documentation/urls.py index 4bf154e..6033891 100644 --- a/netbox_documentation/urls.py +++ b/netbox_documentation/urls.py @@ -24,6 +24,7 @@ urlpatterns = ( path("assignments//edit/", views.AssignmentEditView.as_view(), name="documentassignment_edit"), path("assignments//delete/", views.AssignmentDeleteView.as_view(), name="documentassignment_delete"), path("folders/", views.DocumentCategoryListView.as_view(), name="documentcategory_list"), + path("folders/tree/", views.DocumentCategoryTreeView.as_view(), name="documentcategory_tree"), path("folders/add/", views.DocumentCategoryEditView.as_view(), name="documentcategory_add"), path("folders/import/", views.DocumentImportView.as_view(), name="documentcategory_bulk_import"), path("folders/delete/", views.DocumentCategoryBulkDeleteView.as_view(), name="documentcategory_bulk_delete"), diff --git a/netbox_documentation/views.py b/netbox_documentation/views.py index fa6b5cf..c7d0855 100644 --- a/netbox_documentation/views.py +++ b/netbox_documentation/views.py @@ -8,7 +8,7 @@ from django.contrib import messages from django.contrib.auth.mixins import PermissionRequiredMixin from django.core.exceptions import PermissionDenied from django.db import transaction -from django.db.models import Q +from django.db.models import Count, Q from django.http import FileResponse, Http404, JsonResponse from django.shortcuts import get_object_or_404, redirect, render from django.utils.text import slugify @@ -91,8 +91,32 @@ class DocumentCategoryListView(generic.ObjectListView): actions = (AddObject, BulkImport, BulkDelete) +class DocumentCategoryTreeView(PermissionRequiredMixin, View): + """Nextcloud/Bookstack-style nested folder browser for document categories.""" + + permission_required = "netbox_documentation.view_documentcategory" + template_name = "netbox_documentation/documentcategory_tree.html" + + def get(self, request): + categories = list( + DocumentCategory.objects.restrict(request.user, "view") + .select_related("tenant_group", "tenant") + .annotate(document_total=Count("documents", distinct=True)) + .order_by("name") + ) + nodes = {category.pk: {"object": category, "children": []} for category in categories} + roots = [] + for category in categories: + node = nodes[category.pk] + parent_node = nodes.get(category.parent_id) + (parent_node["children"] if parent_node else roots).append(node) + return render(request, self.template_name, {"tree": roots, "category_count": len(categories)}) + + class DocumentCategoryView(generic.ObjectView): - queryset = DocumentCategory.objects.select_related("tenant_group", "tenant").prefetch_related("children", "documents") + queryset = DocumentCategory.objects.select_related("parent", "tenant_group", "tenant").prefetch_related( + "parent__parent", "children", "documents", + ) class DocumentCategoryEditView(generic.ObjectEditView): diff --git a/pyproject.toml b/pyproject.toml index 477b922..a4b03e5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "netbox-documentation" -version = "0.9.3" +version = "0.10.0" description = "Integrated Markdown wiki and office document importer for NetBox" readme = "README.md" requires-python = ">=3.10"