NetBox Customer Export plugin with configurable branding
Plugin for NetBox 4.6.8–4.6.99 that exports customer documentation (PDF, Word, HTML preview) for a site, location, tenant or tenant group. Technicians pick sections, device roles and columns before exporting. Branding profiles (colors, font, logo, footer, Word template incl. .dotx, table style, extra CSS) are configurable via PLUGINS_CONFIG and selectable per export. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.http import Http404, HttpResponse
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.utils import timezone
|
||||
from django.utils.text import slugify
|
||||
from django.views import View
|
||||
|
||||
from .blocks import Document
|
||||
from .branding import get_branding
|
||||
from .forms import ExportForm, ScopeSelectForm
|
||||
from .renderers import MissingDependency, render as render_document
|
||||
from .scope import SCOPE_MODELS, Scope
|
||||
from .sections import SECTIONS
|
||||
|
||||
|
||||
class ScopeSelectView(LoginRequiredMixin, View):
|
||||
template_name = "netbox_customer_export/select.html"
|
||||
|
||||
def get(self, request):
|
||||
return render(request, self.template_name, {"form": ScopeSelectForm()})
|
||||
|
||||
def post(self, request):
|
||||
form = ScopeSelectForm(request.POST)
|
||||
if form.is_valid():
|
||||
return redirect("plugins:netbox_customer_export:export", kind=form.cleaned_data["kind"], pk=form.cleaned_data["obj"].pk)
|
||||
return render(request, self.template_name, {"form": form})
|
||||
|
||||
|
||||
class ExportView(LoginRequiredMixin, View):
|
||||
template_name = "netbox_customer_export/export.html"
|
||||
|
||||
def _scope(self, request, kind, pk):
|
||||
model = SCOPE_MODELS.get(kind)
|
||||
if model is None:
|
||||
raise Http404
|
||||
obj = get_object_or_404(model.objects.restrict(request.user, "view"), pk=pk)
|
||||
return Scope(kind, obj, request.user)
|
||||
|
||||
def _render_form(self, request, scope, form):
|
||||
grouped = {}
|
||||
for s in SECTIONS.values():
|
||||
grouped.setdefault(s.group, []).append(s)
|
||||
return render(request, self.template_name, {"object": scope.obj, "scope": scope, "form": form, "section_groups": grouped})
|
||||
|
||||
def get(self, request, kind, pk):
|
||||
scope = self._scope(request, kind, pk)
|
||||
return self._render_form(request, scope, ExportForm(scope=scope))
|
||||
|
||||
def post(self, request, kind, pk):
|
||||
scope = self._scope(request, kind, pk)
|
||||
form = ExportForm(request.POST, scope=scope)
|
||||
if not form.is_valid():
|
||||
return self._render_form(request, scope, form)
|
||||
|
||||
opts = form.cleaned_data
|
||||
blocks = []
|
||||
for key in opts["sections"]:
|
||||
blocks += SECTIONS[key].func(scope, opts)
|
||||
|
||||
doc = Document(
|
||||
title=opts["title"],
|
||||
subtitle=opts.get("subtitle", ""),
|
||||
blocks=blocks,
|
||||
meta={
|
||||
"author": opts.get("author") or request.user.get_full_name() or request.user.username,
|
||||
"date": timezone.localtime().strftime("%d.%m.%Y"),
|
||||
},
|
||||
branding=get_branding(opts["branding"]),
|
||||
)
|
||||
try:
|
||||
content, content_type, ext = render_document(doc, opts["format"], request)
|
||||
except MissingDependency as e:
|
||||
messages.error(request, str(e))
|
||||
return self._render_form(request, scope, form)
|
||||
|
||||
response = HttpResponse(content, content_type=content_type)
|
||||
if ext != "html":
|
||||
filename = f"{slugify(opts['title']) or 'dokumentation'}_{timezone.localdate():%Y%m%d}.{ext}"
|
||||
response["Content-Disposition"] = f'attachment; filename="{filename}"'
|
||||
return response
|
||||
Reference in New Issue
Block a user