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>
80 lines
2.8 KiB
Python
80 lines
2.8 KiB
Python
"""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()))
|