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:
2026-09-23 12:43:19 +02:00
co-authored by Claude Opus 5.5
commit 8f45bffa99
21 changed files with 1444 additions and 0 deletions
+54
View File
@@ -0,0 +1,54 @@
"""Branding-Profile (Logo, Farben, Schrift, Word-Vorlage) aus PLUGINS_CONFIG."""
import re
from netbox.plugins import get_plugin_config
PLUGIN = "netbox_customer_export"
DEFAULT_BRANDING = {
"name": "Standard",
"company_name": "",
"logo_path": None,
"footer_text": "Vertraulich – nur für den internen Gebrauch des Kunden",
"primary_color": "1F4E79", # Überschriften, Tabellenköpfe
"accent_color": "2E75B6", # Unterüberschriften
"header_text_color": "FFFFFF", # Schrift im Tabellenkopf
"label_bg_color": "DEEAF6", # Beschriftungsspalte in Key/Value-Tabellen
"stripe_color": "F3F7FB", # jede zweite Tabellenzeile (PDF)
"font_family": "Calibri",
"font_size": 10,
# Word: .docx/.dotx, deren Formatvorlagen, Kopf-/Fußzeile und Seitenränder übernommen werden
"docx_template": None,
"docx_table_style": None, # z.B. "Gitternetztabelle 4 – Akzent 1" aus der Vorlage
"extra_css": "", # zusätzliches CSS für PDF/HTML
}
COLOR_KEYS = ("primary_color", "accent_color", "header_text_color", "label_bg_color", "stripe_color")
_HEX = re.compile(r"^[0-9A-Fa-f]{6}$")
def _normalize(profile):
for key in COLOR_KEYS:
value = str(profile[key]).lstrip("#")
if not _HEX.match(value):
raise ValueError(f"netbox_customer_export: {key}={profile[key]!r} ist keine Hex-Farbe (z.B. '1F4E79')")
profile[key] = value.upper()
return profile
def get_brandings():
"""Alle Profile als {key: dict}. Top-Level-Einstellungen gelten als Basis für jedes Profil."""
base = dict(DEFAULT_BRANDING)
for key in DEFAULT_BRANDING:
value = get_plugin_config(PLUGIN, key)
if value is not None:
base[key] = value
profiles = get_plugin_config(PLUGIN, "brandings") or {}
if not profiles:
return {"default": _normalize(base)}
return {key: _normalize({**base, "name": key, **profile}) for key, profile in profiles.items()}
def get_branding(key=None):
brandings = get_brandings()
return brandings.get(key) or next(iter(brandings.values()))