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>
This commit is contained in:
2026-09-23 13:21:11 +02:00
co-authored by Claude Opus 5.5
parent 0c6bebe2e3
commit f24e42a516
15 changed files with 491 additions and 12 deletions
+27 -2
View File
@@ -36,8 +36,8 @@ def _normalize(profile):
return profile
def get_brandings():
"""Alle Profile als {key: dict}. Top-Level-Einstellungen gelten als Basis für jedes Profil."""
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)
@@ -49,6 +49,31 @@ def get_brandings():
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()))