Files
NetBox-Customer-Export/netbox_customer_export/views.py
T
MrBlakeandClaude Opus 5.5 062413a2cd feat: tidy topology layout, symbols for all device roles, drop draw.io
Topology:
- Orthogonal routing: each parent drops to its own bus lane in the gap
  below it (interval coloring prevents overlaps), color-coded per
  parent; parallel links drawn thicker. Adjacent same-row links are
  straight, other same-row links use a lane above the row.
- Port labels sit at the target device, stacked per uplink, instead of
  piling up at line midpoints.
- Crossing reduction via repeated down/up barycenter sweeps; rows wrap
  at 12 devices.
- Wide diagrams scale to page width in HTML/PDF instead of being cut off.

Symbols:
- New built-in symbols: modem/NTU (RAD), radio link, PoE injector, NAS,
  tape library, UPS/ATS and PBX.
- Detection checks the device role first, then model/manufacturer, and
  covers the common typos "Acces Point", "Controler" and "Libary".
  Rooms and cable management are hidden; UPS devices are now shown.

Removed the draw.io export format.

Bump version to 0.5.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 14:55:06 +02:00

82 lines
3.1 KiB
Python

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