From 90787fd360b0907d93c7c4c765922f65030c4c14 Mon Sep 17 00:00:00 2001 From: Louis Date: Wed, 2 Sep 2026 10:49:03 +0200 Subject: [PATCH] fix: Mandant vor erstem Speichern importierter Ordner setzen --- README.md | 2 +- netbox_documentation/__init__.py | 2 +- netbox_documentation/archive.py | 49 ++++++++++++++++++++++++++------ pyproject.toml | 2 +- 4 files changed, 43 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index c545c2b..db5ac9a 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Ein in NetBox integriertes Markdown-Wiki für Betriebsdokumentationen und Anleit ## Kompatibilität -Die Version `0.9.2` 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.9.3` 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 diff --git a/netbox_documentation/__init__.py b/netbox_documentation/__init__.py index 215cb95..2dbee2b 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.2" + version = "0.9.3" author = "LKE" base_url = "documentation" min_version = "4.0.0" diff --git a/netbox_documentation/archive.py b/netbox_documentation/archive.py index 4f18eb5..7841dbd 100644 --- a/netbox_documentation/archive.py +++ b/netbox_documentation/archive.py @@ -5,6 +5,7 @@ from zipfile import ZIP_DEFLATED, BadZipFile, ZipFile from django.conf import settings from django.contrib.contenttypes.models import ContentType +from django.core.exceptions import ValidationError from django.db import transaction from django.utils.text import slugify from tenancy.models import Tenant, TenantGroup @@ -21,6 +22,14 @@ class ArchiveFailure(ValueError): pass +def _save_imported_object(instance, context, **save_kwargs): + try: + instance.save(**save_kwargs) + except ValidationError as exc: + messages = getattr(exc, "messages", None) or [str(exc)] + raise ArchiveFailure(f"{context}: {'; '.join(messages)}") from exc + + 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)) @@ -173,15 +182,32 @@ def _category_from_path(names, tenancy_path=None): if not name: continue slug = slugify(name)[:100] or "ordner" + tenancy = tenancy_path[index] if index < len(tenancy_path) and isinstance(tenancy_path[index], dict) else {} + context = f"Ordner „{name}“" + tenant_group = _resolve_tenant_group(tenancy.get("tenant_group"), context) + tenant = _resolve_tenant(tenancy.get("tenant"), context) + if tenant and tenant_group and tenant.group_id != tenant_group.pk: + raise ArchiveFailure( + f"{context}: Der Mandant „{tenant.slug}“ gehört nicht zur " + f"Mandantengruppe „{tenant_group.slug}“ auf dieser Instanz." + ) category = DocumentCategory.objects.filter(parent=parent, slug=slug).first() if not category: - category = DocumentCategory.objects.create(name=name, slug=slug, parent=parent) - tenancy = tenancy_path[index] if index < len(tenancy_path) and isinstance(tenancy_path[index], dict) else {} - if tenancy: - context = f"Ordner „{name}“" - category.tenant_group = _resolve_tenant_group(tenancy.get("tenant_group"), context) - category.tenant = _resolve_tenant(tenancy.get("tenant"), context) - category.save(update_fields=("tenant_group", "tenant", "last_updated")) + # Set tenancy before the first save. NetBox installations may + # enforce a tenant via custom validation rules, so creating the + # category first and assigning its tenant in a second save fails. + category = DocumentCategory( + name=name, slug=slug, parent=parent, + tenant_group=tenant_group, tenant=tenant, + ) + _save_imported_object(category, context) + elif tenancy: + category.tenant_group = tenant_group + category.tenant = tenant + _save_imported_object( + category, context, + update_fields=("tenant_group", "tenant", "last_updated"), + ) parent = category return parent @@ -237,7 +263,12 @@ def import_archive(upload, update_existing=False): context = f"Dokument „{document.title}“" document.tenant_group = _resolve_tenant_group(record.get("tenant_group"), context) document.tenant = _resolve_tenant(record.get("tenant"), context) - document.save() + if document.tenant and document.tenant_group and document.tenant.group_id != document.tenant_group_id: + raise ArchiveFailure( + f"{context}: Der Mandant „{document.tenant.slug}“ gehört nicht zur " + f"Mandantengruppe „{document.tenant_group.slug}“ auf dieser Instanz." + ) + _save_imported_object(document, context) for assignment in record.get("assignments") or []: label = f"{assignment.get('app_label')}.{assignment.get('model')}" if label not in allowed: @@ -280,4 +311,4 @@ def import_archive(upload, update_existing=False): if item.get("source_url"): document.body = document.body.replace(str(item["source_url"]), attachment.file.url) document.save(update_fields=("body", "last_updated")) - return {"created": created, "updated": updated, "skipped_assignments": skipped_assignments} \ No newline at end of file + return {"created": created, "updated": updated, "skipped_assignments": skipped_assignments} diff --git a/pyproject.toml b/pyproject.toml index 59fdcc0..477b922 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "netbox-documentation" -version = "0.9.2" +version = "0.9.3" description = "Integrated Markdown wiki and office document importer for NetBox" readme = "README.md" requires-python = ">=3.10"