Files
MrBlakeandClaude Opus 5.5 8f45bffa99 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>
2026-09-23 12:43:19 +02:00

130 lines
4.5 KiB
Python

"""Ermittelt, welche Objekte zu einem Export-Scope (Standort, Lokation, Mandant, Mandantengruppe) gehören."""
from django.db.models import Q
from circuits.models import Circuit
from dcim.models import Device, Location, PowerFeed, Rack, Site
from ipam.models import VLAN, VRF, IPAddress, Prefix
from tenancy.models import Tenant, TenantGroup
from virtualization.models import VirtualMachine
from wireless.models import WirelessLAN
SCOPE_MODELS = {
"site": Site,
"location": Location,
"tenant": Tenant,
"tenantgroup": TenantGroup,
}
SCOPE_LABELS = {
"site": "Standort",
"location": "Lokation",
"tenant": "Mandant",
"tenantgroup": "Mandantengruppe",
}
NONE = Q(pk__in=[])
class Scope:
def __init__(self, kind, obj, user):
self.kind = kind
self.obj = obj
self.user = user
self.label = SCOPE_LABELS[kind]
self.locations = None
self.tenants = None
if kind == "location":
self.locations = obj.get_descendants(include_self=True)
elif kind == "tenant":
self.tenants = Tenant.objects.filter(pk=obj.pk)
elif kind == "tenantgroup":
self.tenants = Tenant.objects.filter(group__in=obj.get_descendants(include_self=True))
# -- Hilfsfunktionen --------------------------------------------------
def restrict(self, qs):
if hasattr(qs, "restrict"):
return qs.restrict(self.user, "view")
return qs
def _q(self, site=(), location=(), tenant=("tenant",)):
"""Baut einen Q-Filter; jedes Argument ist eine Liste von Feldpfaden, die ODER-verknüpft werden."""
if self.kind == "site":
paths, value = site, self.obj
elif self.kind == "location":
paths, value = [f"{p}__in" for p in location], self.locations
else:
paths, value = [f"{p}__in" for p in tenant], self.tenants
q = NONE
for p in paths:
q |= Q(**{p: value})
return q
def _qs(self, model, **paths):
return self.restrict(model.objects.filter(self._q(**paths)).distinct())
# -- Objekte ----------------------------------------------------------
def sites(self):
if self.kind == "site":
return self.restrict(Site.objects.filter(pk=self.obj.pk))
if self.kind == "location":
return self.restrict(Site.objects.filter(pk=self.obj.site_id))
return self._qs(Site, tenant=("tenant", "devices__tenant", "racks__tenant"))
def location_objects(self):
return self._qs(Location, site=("site",), location=("pk",), tenant=("tenant", "devices__tenant"))
def racks(self):
return self._qs(Rack, site=("site",), location=("location",), tenant=("tenant", "devices__tenant"))
def devices(self):
return self._qs(Device, site=("site",), location=("location",))
def virtual_machines(self):
return self._qs(
VirtualMachine,
site=("site", "cluster___site"),
location=("cluster___location", "device__location"),
)
def prefixes(self):
return self._qs(Prefix, site=("_site",), location=("_location",))
def vlans(self):
return self._qs(
VLAN,
site=("site", "prefixes___site"),
location=(
"prefixes___location",
"interfaces_as_untagged__device__location",
"interfaces_as_tagged__device__location",
),
)
def vrfs(self):
q = Q(prefixes__in=self.prefixes())
if self.tenants is not None:
q |= Q(tenant__in=self.tenants)
return self.restrict(VRF.objects.filter(q).distinct())
def ip_addresses(self):
q = Q(interface__device__in=self.devices()) | Q(vminterface__virtual_machine__in=self.virtual_machines())
if self.tenants is not None:
q |= Q(tenant__in=self.tenants)
return self.restrict(IPAddress.objects.filter(q).distinct())
def circuits(self):
return self._qs(Circuit, site=("terminations___site",), location=("terminations___location",))
def wireless_lans(self):
return self._qs(WirelessLAN, site=("_site",), location=("_location",))
def power_feeds(self):
return self._qs(PowerFeed, site=("power_panel__site",), location=("power_panel__location",))
def tenant_objects(self):
if self.tenants is not None:
return self.restrict(self.tenants)
return self.restrict(Tenant.objects.filter(devices__in=self.devices()).distinct())