feat: druckoptimierte PDF-Ansicht für Dokumentationen ergänzen

This commit is contained in:
2026-07-22 12:48:01 +02:00
parent 0492c67b9f
commit 882a8d528f
7 changed files with 74 additions and 3 deletions
+2 -1
View File
@@ -11,6 +11,7 @@ Ein in NetBox integriertes Markdown-Wiki für Betriebsdokumentationen und Anleit
- Bilder direkt vom eigenen Gerät in die Dokumentation hochladen
- Formatierte Word-Inhalte inklusive Tabellen und unterstützten Zwischenablage-Bildern einfügen
- Mehrere Dokumentationen mit Ordnern, Zuordnungen und Anhängen als ZIP exportieren und wieder importieren
- Druckoptimierte A4-Ansicht zum Drucken oder Speichern als PDF
- Eine Dokumentation mehreren Objekten zuordnen und umgekehrt
- Unbegrenzt viele Objektzuordnungen pro Dokumentation; nur identische Doppelzuordnungen werden verhindert
- Unterstützte Standardobjekte: Region, Standort, Location, Rack, Gerät, VM, VM-Cluster und Mandant/Kunde
@@ -21,7 +22,7 @@ Ein in NetBox integriertes Markdown-Wiki für Betriebsdokumentationen und Anleit
## Kompatibilität
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.
Die Version `0.4.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.
## Installation
+1 -1
View File
@@ -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.1"
version = "0.4.2"
author = "LKE"
base_url = "documentation"
min_version = "4.0.0"
@@ -1,5 +1,10 @@
{% extends 'generic/object.html' %}
{% load helpers %}
{% block extra_controls %}
<a href="{% url 'plugins:netbox_documentation:document_print' pk=object.pk %}" target="_blank" class="btn btn-outline-secondary">
<i class="mdi mdi-printer"></i> Drucken / PDF
</a>
{% endblock extra_controls %}
{% block content %}
<div class="row">
<div class="col col-md-9">
@@ -0,0 +1,52 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ object.title }} Druckansicht</title>
<style>
:root { color-scheme: light; }
* { box-sizing: border-box; }
body { margin: 0; background: #eef1f4; color: #1f2937; font: 11pt Arial, Helvetica, sans-serif; line-height: 1.5; }
.toolbar { position: sticky; top: 0; z-index: 2; display: flex; justify-content: center; gap: .75rem; padding: .8rem; background: #fff; border-bottom: 1px solid #ccd2d8; }
.toolbar button, .toolbar a { border: 1px solid #6c757d; border-radius: .3rem; padding: .55rem .9rem; background: #fff; color: #212529; text-decoration: none; cursor: pointer; font: inherit; }
.toolbar button { color: #fff; background: #4263eb; border-color: #4263eb; }
article { width: min(210mm, calc(100% - 2rem)); min-height: 297mm; margin: 1.5rem auto; padding: 18mm 17mm; background: #fff; box-shadow: 0 .2rem 1rem rgba(0,0,0,.12); }
h1 { margin: 0 0 .25rem; font-size: 24pt; line-height: 1.2; }
.summary { color: #4b5563; font-size: 13pt; margin: 0 0 1rem; }
.meta { display: flex; flex-wrap: wrap; gap: .4rem 1.25rem; padding: .65rem 0; margin-bottom: 1.4rem; color: #6b7280; border-top: 1px solid #ddd; border-bottom: 1px solid #ddd; font-size: 9pt; }
.content img { max-width: 100%; height: auto; }
.content table { width: 100%; border-collapse: collapse; margin: 1em 0; }
.content th, .content td { border: 1px solid #777; padding: .35rem .5rem; vertical-align: top; }
.content pre, .content code { white-space: pre-wrap; overflow-wrap: anywhere; font-family: Consolas, monospace; }
.content a { color: #174ea6; overflow-wrap: anywhere; }
@page { size: A4; margin: 15mm; }
@media print {
body { background: #fff; }
.toolbar { display: none !important; }
article { width: auto; min-height: 0; margin: 0; padding: 0; box-shadow: none; }
a { color: inherit !important; text-decoration: none; }
table, figure, img, pre, blockquote { break-inside: avoid; }
h1, h2, h3, h4 { break-after: avoid; }
}
</style>
</head>
<body>
<div class="toolbar">
<button type="button" onclick="window.print()">Drucken / Als PDF speichern</button>
<a href="{{ object.get_absolute_url }}">Zurück zur Dokumentation</a>
</div>
<article>
<header>
<h1>{{ object.title }}</h1>
{% if object.summary %}<p class="summary">{{ object.summary }}</p>{% endif %}
<div class="meta">
{% if object.category %}<span>Ordner: {{ object.category }}</span>{% endif %}
<span>Stand: {{ object.last_updated|date:"d.m.Y H:i" }}</span>
{% if object.assignments.all %}<span>Zugeordnet: {% for assignment in object.assignments.all %}{{ assignment.assigned_object }}{% if not forloop.last %}, {% endif %}{% endfor %}</span>{% endif %}
</div>
</header>
<main class="content">{{ object.rendered_body|safe }}</main>
</article>
</body>
</html>
+1
View File
@@ -9,6 +9,7 @@ urlpatterns = (
path("documents/import/", views.DocumentImportView.as_view(), name="document_import"),
path("archive/", views.DocumentArchiveView.as_view(), name="document_archive"),
path("documents/<int:pk>/", views.DocumentView.as_view(), name="document"),
path("documents/<int:pk>/print/", views.DocumentPrintView.as_view(), name="document_print"),
path("documents/<int:pk>/edit/", views.DocumentEditView.as_view(), name="document_edit"),
path("documents/<int:pk>/media-upload/", views.DocumentMediaUploadView.as_view(), name="document_media_upload"),
path("documents/<int:pk>/delete/", views.DocumentDeleteView.as_view(), name="document_delete"),
+12
View File
@@ -47,6 +47,18 @@ class DocumentView(generic.ObjectView):
queryset = Document.objects.prefetch_related("assignments", "attachments")
class DocumentPrintView(PermissionRequiredMixin, View):
permission_required = "netbox_documentation.view_document"
template_name = "netbox_documentation/document_print.html"
def get(self, request, pk):
document = get_object_or_404(
Document.objects.restrict(request.user, "view").select_related("category").prefetch_related("assignments"),
pk=pk,
)
return render(request, self.template_name, {"object": document})
class DocumentEditView(generic.ObjectEditView):
queryset = Document.objects.all()
form = DocumentForm
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "netbox-documentation"
version = "0.4.1"
version = "0.4.2"
description = "Integrated Markdown wiki and office document importer for NetBox"
readme = "README.md"
requires-python = ">=3.10"