Files
MrBlakeandClaude Opus 5.5 f24e42a516 feat: manage branding profiles in the NetBox UI
New BrandingProfile model (migration 0001) with logo upload, color
pickers, font, Word template upload, table style and extra CSS, plus
list/edit/delete views, an HTML preview and a menu entry with add
button. Access is controlled via the model's Django permissions.

Profiles from the database are offered first in the export form (the
default profile preselected); profiles from PLUGINS_CONFIG still work.

Bump version to 0.2.0 - requires "manage.py migrate".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 13:21:11 +02:00

80 lines
2.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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 _config_brandings():
"""Profile aus PLUGINS_CONFIG. 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 _db_brandings():
"""In NetBox gepflegte Profile; das Standardprofil zuerst."""
from django.db import DatabaseError
from .models import BrandingProfile
try:
return {f"db-{p.pk}": _normalize(p.as_branding()) for p in BrandingProfile.objects.all()}
except DatabaseError: # Migration noch nicht ausgeführt
return {}
def get_brandings():
"""Alle Profile als {key: dict}: zuerst die aus NetBox, dann die aus der Konfiguration.
Sobald es Profile in NetBox gibt, entfällt das implizite Konfigurations-Profil "default"
(nur Top-Level-Werte, kein "brandings"), damit die Auswahl nicht doppelt ist.
"""
db = _db_brandings()
config = _config_brandings()
if db and not get_plugin_config(PLUGIN, "brandings"):
config = {}
return {**db, **config}
def get_branding(key=None):
brandings = get_brandings()
return brandings.get(key) or next(iter(brandings.values()))