From 359f752031a1e2d183bbb33ab03b8bc874381673 Mon Sep 17 00:00:00 2001 From: Louis Date: Thu, 17 Sep 2026 10:07:04 +0200 Subject: [PATCH] feat: Dokumentationen als Explorer-artigen Ordnerbaum anzeigen Neue Standardansicht fuer "Dokumentationen" im Windows-Explorer/ Nextcloud-Stil: Ordner und Unterordner als aufklappbarer Baum mit den zugehoerigen Dokumenten als Dateien darin, inkl. Live-Suche. Die bisherige Tabellenansicht bleibt als "Listenansicht" erreichbar. Co-Authored-By: Claude Sonnet 5 --- netbox_documentation/navigation.py | 2 +- .../netbox_documentation/document_tree.html | 70 +++++++++++++++++++ .../inc/document_tree_file.html | 8 +++ .../inc/document_tree_node.html | 20 ++++++ netbox_documentation/urls.py | 3 +- netbox_documentation/views.py | 37 ++++++++++ 6 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 netbox_documentation/templates/netbox_documentation/document_tree.html create mode 100644 netbox_documentation/templates/netbox_documentation/inc/document_tree_file.html create mode 100644 netbox_documentation/templates/netbox_documentation/inc/document_tree_node.html diff --git a/netbox_documentation/navigation.py b/netbox_documentation/navigation.py index 2d32e61..c2eb13b 100644 --- a/netbox_documentation/navigation.py +++ b/netbox_documentation/navigation.py @@ -4,7 +4,7 @@ from netbox.choices import ButtonColorChoices menu = PluginMenu( label="Dokumentation", groups=(("Wiki", ( - PluginMenuItem(link="plugins:netbox_documentation:document_list", link_text="Dokumentationen", buttons=( + PluginMenuItem(link="plugins:netbox_documentation:document_tree", link_text="Dokumentationen", buttons=( PluginMenuButton(link="plugins:netbox_documentation:document_add", title="Neu", icon_class="mdi mdi-plus-thick", color=ButtonColorChoices.GREEN), PluginMenuButton(link="plugins:netbox_documentation:document_import", title="Import", icon_class="mdi mdi-file-import", color=ButtonColorChoices.BLUE), )), diff --git a/netbox_documentation/templates/netbox_documentation/document_tree.html b/netbox_documentation/templates/netbox_documentation/document_tree.html new file mode 100644 index 0000000..fe27803 --- /dev/null +++ b/netbox_documentation/templates/netbox_documentation/document_tree.html @@ -0,0 +1,70 @@ +{% extends 'base/layout.html' %} + +{% block title %}Dokumentationen{% endblock %} + +{% block content %} +
+
+ +
+ {% if perms.netbox_documentation.add_document %} + HinzufĂĽgen + {% endif %} + Listenansicht +
+
+
+
+
+ {% if tree or uncategorized %} +
    + {% for node in tree %} + {% include 'netbox_documentation/inc/document_tree_node.html' with node=node %} + {% endfor %} + {% for document in uncategorized %} + {% include 'netbox_documentation/inc/document_tree_file.html' with document=document %} + {% endfor %} +
+ {% else %} +

Noch keine Dokumentationen vorhanden.

+ {% endif %} +
+
+ + + + +{% endblock %} diff --git a/netbox_documentation/templates/netbox_documentation/inc/document_tree_file.html b/netbox_documentation/templates/netbox_documentation/inc/document_tree_file.html new file mode 100644 index 0000000..04f9f6a --- /dev/null +++ b/netbox_documentation/templates/netbox_documentation/inc/document_tree_file.html @@ -0,0 +1,8 @@ +
  • +
    + + {{ document.title }} + {% if not document.is_published %}Entwurf{% endif %} + {% if document.tenant %} {{ document.tenant }}{% endif %} +
    +
  • diff --git a/netbox_documentation/templates/netbox_documentation/inc/document_tree_node.html b/netbox_documentation/templates/netbox_documentation/inc/document_tree_node.html new file mode 100644 index 0000000..776b16d --- /dev/null +++ b/netbox_documentation/templates/netbox_documentation/inc/document_tree_node.html @@ -0,0 +1,20 @@ +
  • +
    + + + {{ node.object.name }} + {% if node.object.tenant %} {{ node.object.tenant }}{% endif %} + +
      + {% for child in node.children %} + {% include 'netbox_documentation/inc/document_tree_node.html' with node=child %} + {% endfor %} + {% for document in node.documents %} + {% include 'netbox_documentation/inc/document_tree_file.html' with document=document %} + {% endfor %} + {% if not node.children and not node.documents %} +
    • Leer
    • + {% endif %} +
    +
    +
  • diff --git a/netbox_documentation/urls.py b/netbox_documentation/urls.py index 6033891..9866955 100644 --- a/netbox_documentation/urls.py +++ b/netbox_documentation/urls.py @@ -4,7 +4,8 @@ from . import models, views urlpatterns = ( path("editor-assets/", views.EditorAssetView.as_view(), name="editor_asset"), - path("", views.DocumentListView.as_view(), name="document_list"), + path("", views.DocumentTreeView.as_view(), name="document_tree"), + path("list/", views.DocumentListView.as_view(), name="document_list"), path("documents/add/", views.DocumentEditView.as_view(), name="document_add"), path("documents/import/", views.DocumentImportView.as_view(), name="document_import"), path("documents/import/", views.DocumentImportView.as_view(), name="document_bulk_import"), diff --git a/netbox_documentation/views.py b/netbox_documentation/views.py index c7d0855..78bbfde 100644 --- a/netbox_documentation/views.py +++ b/netbox_documentation/views.py @@ -52,6 +52,43 @@ class DocumentListView(generic.ObjectListView): actions = (AddObject, BulkImport, BulkDelete) +class DocumentTreeView(PermissionRequiredMixin, View): + """Windows-Explorer/Nextcloud-style folder browser: categories as folders, documents as files.""" + + permission_required = "netbox_documentation.view_document" + template_name = "netbox_documentation/document_tree.html" + + def get(self, request): + categories = list( + DocumentCategory.objects.restrict(request.user, "view") + .select_related("tenant_group", "tenant") + .order_by("name") + ) + documents = list( + Document.objects.restrict(request.user, "view") + .select_related("category", "tenant_group", "tenant") + .order_by("title") + ) + + nodes = {category.pk: {"object": category, "children": [], "documents": []} 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) + + uncategorized = [] + for document in documents: + node = nodes.get(document.category_id) + (node["documents"] if node else uncategorized).append(document) + + return render(request, self.template_name, { + "tree": roots, + "uncategorized": uncategorized, + "document_count": len(documents), + }) + + class DocumentView(generic.ObjectView): queryset = Document.objects.select_related("category", "tenant_group", "tenant").prefetch_related("assignments", "attachments")