From 4690e67b32e3f4b7b9cb64eee3b57182edbf7aa0 Mon Sep 17 00:00:00 2001 From: Louis Date: Thu, 23 Jul 2026 16:03:56 +0200 Subject: [PATCH] =?UTF-8?q?perf:=20nur=20ausgew=C3=A4hlte=20Excel-Bl=C3=A4?= =?UTF-8?q?tter=20vollst=C3=A4ndig=20importieren?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- netbox_documentation/__init__.py | 2 +- netbox_documentation/importers.py | 10 ++++-- netbox_documentation/views.py | 51 +++++++++++++++++++------------ pyproject.toml | 2 +- tests/test_importers.py | 23 ++++++++++++++ 5 files changed, 63 insertions(+), 25 deletions(-) diff --git a/netbox_documentation/__init__.py b/netbox_documentation/__init__.py index d30b857..b9138b5 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.7.8" + version = "0.7.9" author = "LKE" base_url = "documentation" min_version = "4.0.0" diff --git a/netbox_documentation/importers.py b/netbox_documentation/importers.py index a57e0e4..c6c593c 100644 --- a/netbox_documentation/importers.py +++ b/netbox_documentation/importers.py @@ -30,6 +30,7 @@ class ExcelSheetResult: markdown: str images: list[ImportedImage] = field(default_factory=list) body_format: str = "markdown" + source_index: int = 0 def import_document(upload, flatten_excel=False) -> ImportResult: @@ -100,7 +101,7 @@ def _xlsx(content, flatten=False): def import_excel_sheets( upload, flatten=False, *, metadata_only=False, preview_max_rows=None, - include_image_data=True, + include_image_data=True, selected_indexes=None, ) -> list[ExcelSheetResult]: """Convert each non-empty worksheet into an individual document payload.""" from openpyxl import load_workbook @@ -112,7 +113,10 @@ def import_excel_sheets( upload.seek(0) workbook = load_workbook(upload, read_only=False, data_only=True) results = [] - for sheet in workbook.worksheets: + selected_indexes = set(selected_indexes) if selected_indexes is not None else None + for source_index, sheet in enumerate(workbook.worksheets): + if selected_indexes is not None and source_index not in selected_indexes: + continue images = [] for number, image in enumerate(getattr(sheet, "_images", ()), 1): image_format = (getattr(image, "format", None) or "png").lower() @@ -147,7 +151,7 @@ def import_excel_sheets( markdown = "" results.append(ExcelSheetResult( title=sheet.title, markdown=markdown, images=images, - body_format="html", + body_format="html", source_index=source_index, )) workbook.close() upload.seek(0) diff --git a/netbox_documentation/views.py b/netbox_documentation/views.py index 68bc34e..121d5d2 100644 --- a/netbox_documentation/views.py +++ b/netbox_documentation/views.py @@ -201,7 +201,10 @@ class DocumentImportView(PermissionRequiredMixin, View): user=request.user, category=form.cleaned_data["category"], file=upload, original_name=upload.name, content_type=upload.content_type or "", flatten=form.cleaned_data.get("flatten_excel_tables", False), - sheet_metadata=[{"title": sheet.title, "image_count": len(sheet.images)} for sheet in sheets], + sheet_metadata=[{ + "title": sheet.title, "image_count": len(sheet.images), + "source_index": sheet.source_index, + } for sheet in sheets], ) return redirect("plugins:netbox_documentation:excel_import_preview", token=preview.pk) @@ -213,13 +216,14 @@ class ExcelImportPreviewView(PermissionRequiredMixin, View): def get_preview(self, request, token): return get_object_or_404(ExcelImportPreview, pk=token, user=request.user) - def read_sheets(self, preview, *, for_preview=False): + def read_sheets(self, preview, *, for_preview=False, selected_indexes=None): preview.file.open("rb") try: return import_excel_sheets( preview.file, flatten=preview.flatten, preview_max_rows=100 if for_preview else None, include_image_data=not for_preview, + selected_indexes=selected_indexes, ) finally: preview.file.close() @@ -270,15 +274,15 @@ class ExcelImportPreviewView(PermissionRequiredMixin, View): self.delete_preview(preview) messages.info(request, "Excel-Import wurde abgebrochen.") return redirect("plugins:netbox_documentation:document_import") - try: - sheets = self.read_sheets(preview) - except (ImportFailure, Exception) as exc: - self.delete_preview(preview) - messages.error(request, f"Die Exceldatei konnte nicht erneut gelesen werden: {exc}") - return redirect("plugins:netbox_documentation:document_import") formset = ExcelSheetSelectionFormSet(request.POST) if not formset.is_valid(): - return self.render_preview(request, preview, sheets, formset) + try: + preview_sheets = self.read_sheets(preview, for_preview=True) + except (ImportFailure, Exception) as exc: + self.delete_preview(preview) + messages.error(request, f"Die Exceldatei konnte nicht erneut gelesen werden: {exc}") + return redirect("plugins:netbox_documentation:document_import") + return self.render_preview(request, preview, preview_sheets, formset) if not preview.category: self.delete_preview(preview) messages.error(request, "Der gewählte Zielordner existiert nicht mehr. Bitte den Import erneut starten.") @@ -287,10 +291,18 @@ class ExcelImportPreviewView(PermissionRequiredMixin, View): for form in formset.forms: data = form.cleaned_data if data.get("include"): - if data["index"] < 0 or data["index"] >= len(sheets): + if data["index"] < 0 or data["index"] >= len(preview.sheet_metadata): raise PermissionDenied - selections[data["index"]] = data["title"].strip() - result = self.create_documents(preview, sheets, selections) + metadata = preview.sheet_metadata[data["index"]] + source_index = metadata.get("source_index", data["index"]) + selections[source_index] = data["title"].strip() + try: + selected_sheets = self.read_sheets(preview, selected_indexes=selections) + except (ImportFailure, Exception) as exc: + messages.error(request, f"Die ausgewählten Excel-Blätter konnten nicht importiert werden: {exc}") + return redirect("plugins:netbox_documentation:excel_import_preview", token=preview.pk) + sheets_by_index = {sheet.source_index: sheet for sheet in selected_sheets} + result = self.create_documents(preview, sheets_by_index, selections) self.delete_preview(preview) messages.success(request, ( f"{result['documents']} Arbeitsblätter als Dokumentationen in „{result['category']}“ importiert; " @@ -302,11 +314,6 @@ class ExcelImportPreviewView(PermissionRequiredMixin, View): @transaction.atomic def create_documents(preview, sheets, selections): from django.core.files.base import ContentFile - preview.file.open("rb") - try: - original_content = preview.file.read() - finally: - preview.file.close() created_documents, image_count = [], 0 for index, title in selections.items(): sheet = sheets[index] @@ -345,12 +352,16 @@ class ExcelImportPreviewView(PermissionRequiredMixin, View): created_documents.append(document) if settings.PLUGINS_CONFIG.get("netbox_documentation", {}).get("keep_imported_file", True): for document in created_documents: + preview.file.open("rb") attachment = DocumentAttachment( document=document, original_name=preview.original_name, - content_type=preview.content_type, size=len(original_content), + content_type=preview.content_type, size=preview.file.size, ) - attachment.file.save(preview.original_name, ContentFile(original_content), save=False) - attachment.save() + try: + attachment.file.save(preview.original_name, preview.file, save=False) + attachment.save() + finally: + preview.file.close() return {"documents": len(created_documents), "images": image_count, "category": preview.category} @staticmethod diff --git a/pyproject.toml b/pyproject.toml index 4320c85..0a8c06c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "netbox-documentation" -version = "0.7.8" +version = "0.7.9" description = "Integrated Markdown wiki and office document importer for NetBox" readme = "README.md" requires-python = ">=3.10" diff --git a/tests/test_importers.py b/tests/test_importers.py index 0aeadd1..fc6681a 100644 --- a/tests/test_importers.py +++ b/tests/test_importers.py @@ -113,6 +113,29 @@ def test_excel_multi_sheet_import_creates_one_result_per_sheet(): assert "Leitstelle" in results[1].markdown +def test_excel_import_renders_only_selected_source_sheets(): + workbook = Workbook() + first = workbook.active + first.title = "Nicht gewählt" + first.append(["Sehr viele Daten"]) + second = workbook.create_sheet("Gewählt") + second.append(["Nur dieses Blatt"]) + third = workbook.create_sheet("Auch nicht gewählt") + third.append(["Weitere Daten"]) + stream = BytesIO() + workbook.save(stream) + + results = import_excel_sheets( + Upload(stream.getvalue(), "auswahl.xlsx"), selected_indexes={1}, + ) + + assert len(results) == 1 + assert results[0].title == "Gewählt" + assert results[0].source_index == 1 + assert "Nur dieses Blatt" in results[0].markdown + assert "Sehr viele Daten" not in results[0].markdown + + def test_excel_metadata_phase_skips_document_rendering(): workbook = Workbook() sheet = workbook.active