From f24e42a51657de5dd99fec8629b44323450dce01 Mon Sep 17 00:00:00 2001 From: Louis Date: Wed, 23 Sep 2026 13:21:11 +0200 Subject: [PATCH] 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 --- README.md | 33 +++++-- netbox_customer_export/__init__.py | 2 +- netbox_customer_export/branding.py | 29 +++++- netbox_customer_export/branding_views.py | 93 +++++++++++++++++++ netbox_customer_export/forms.py | 42 +++++++++ .../migrations/0001_brandingprofile.py | 43 +++++++++ netbox_customer_export/migrations/__init__.py | 0 netbox_customer_export/models.py | 76 +++++++++++++++ netbox_customer_export/navigation.py | 15 ++- .../netbox_customer_export/_field.html | 6 ++ .../branding_delete.html | 17 ++++ .../netbox_customer_export/branding_edit.html | 81 ++++++++++++++++ .../netbox_customer_export/branding_list.html | 57 ++++++++++++ netbox_customer_export/urls.py | 7 +- pyproject.toml | 2 +- 15 files changed, 491 insertions(+), 12 deletions(-) create mode 100644 netbox_customer_export/branding_views.py create mode 100644 netbox_customer_export/migrations/0001_brandingprofile.py create mode 100644 netbox_customer_export/migrations/__init__.py create mode 100644 netbox_customer_export/models.py create mode 100644 netbox_customer_export/templates/netbox_customer_export/_field.html create mode 100644 netbox_customer_export/templates/netbox_customer_export/branding_delete.html create mode 100644 netbox_customer_export/templates/netbox_customer_export/branding_edit.html create mode 100644 netbox_customer_export/templates/netbox_customer_export/branding_list.html diff --git a/README.md b/README.md index ac6f85c..4bedc4d 100644 --- a/README.md +++ b/README.md @@ -92,8 +92,8 @@ python3 manage.py clearsessions sudo systemctl restart netbox netbox-rq ``` -Die Befehle gehören zum üblichen NetBox-Ablauf nach jeder Plugin-Installation und laufen auch dann fehlerfrei, wenn es nichts zu tun gibt. -Die aktuelle Version hat noch keine eigenen Datenbanktabellen und keine statischen Dateien. Spätere Versionen können sie aber mitbringen. +`migrate` ist ab Version 0.2.0 **Pflicht**, denn es legt die Tabelle für die Branding-Profile an. Die übrigen Befehle gehören zum +üblichen NetBox-Ablauf nach jeder Plugin-Installation. ## Update @@ -142,11 +142,32 @@ sudo systemctl restart netbox netbox-rq ## Konfiguration -### Design / Branding +### Design / Branding in NetBox (empfohlen) -Farben, Schrift, Logo und eine Word-Vorlage lassen sich festlegen, entweder global oder als **mehrere Profile** -(z.B. pro eigener Firma oder Marke). Bei mehr als einem Profil erscheint im Exportformular das Auswahlfeld „Design / Firma“. -Die globalen Werte gelten als Basis, jedes Profil überschreibt nur die Werte, die es selbst setzt. +Unter **Plugins → Branding-Profile** legen Admins Design-Profile direkt in NetBox an: + +* Name, Firmenname, Fußzeile und **Logo-Upload** (PNG/JPG) +* Farben per Farbwähler: Primär, Akzent, Tabellenkopf-Schrift, Beschriftungen, Zebrastreifen +* Schriftart und -größe +* **Upload einer Word-Vorlage** (.docx/.dotx), optional mit dem Namen einer Tabellenformatvorlage +* zusätzliches CSS für PDF und HTML +* **Vorschau**-Button, der ein Beispieldokument im gewählten Design öffnet +* Ein Profil lässt sich als **Standard** markieren und ist dann im Exportformular vorausgewählt. + +Uploads landen unter `MEDIA_ROOT/customer-export/` (Standard: `/opt/netbox/netbox/media/customer-export/`) und sollten mitgesichert werden. +Beim Löschen eines Profils werden Logo und Vorlage mit entfernt. + +**Berechtigungen:** Superuser dürfen alles. Anderen Benutzern oder Gruppen weist man unter *Admin → Berechtigungen* eine +Berechtigung für den Objekttyp *Customer Export | Branding-Profil* zu. Mit *Ansehen* sieht man Liste und Vorschau, zum Pflegen +braucht es zusätzlich *Hinzufügen*, *Ändern* und *Löschen*. Techniker, die nur exportieren, benötigen keine davon. + +Sind in NetBox Profile angelegt, erscheinen sie zuerst in der Auswahl „Design / Firma“. Profile aus `configuration.py` +(siehe unten) werden zusätzlich angeboten, wenn dort `brandings` definiert ist. + +### Design / Branding per `configuration.py` + +Alternativ oder ergänzend lassen sich Profile in der Konfiguration festlegen, entweder global oder als **mehrere Profile** +(z.B. pro eigener Firma oder Marke). Die globalen Werte gelten als Basis, jedes Profil überschreibt nur die Werte, die es selbst setzt. ```python PLUGINS_CONFIG = { diff --git a/netbox_customer_export/__init__.py b/netbox_customer_export/__init__.py index b0910bf..56785bd 100644 --- a/netbox_customer_export/__init__.py +++ b/netbox_customer_export/__init__.py @@ -5,7 +5,7 @@ class CustomerExportConfig(PluginConfig): name = "netbox_customer_export" verbose_name = "Kunden-Export" description = "Kundendokumentation als PDF oder Word exportieren" - version = "0.1.1" + version = "0.2.0" author = "Claude" base_url = "customer-export" min_version = "4.6.8" diff --git a/netbox_customer_export/branding.py b/netbox_customer_export/branding.py index 11a5cbb..0fa3e03 100644 --- a/netbox_customer_export/branding.py +++ b/netbox_customer_export/branding.py @@ -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())) diff --git a/netbox_customer_export/branding_views.py b/netbox_customer_export/branding_views.py new file mode 100644 index 0000000..396b4b3 --- /dev/null +++ b/netbox_customer_export/branding_views.py @@ -0,0 +1,93 @@ +"""Verwaltung der Branding-Profile in der NetBox-Oberfläche.""" +from django.contrib import messages +from django.contrib.auth.mixins import PermissionRequiredMixin +from django.http import HttpResponse +from django.shortcuts import get_object_or_404, redirect, render +from django.utils import timezone +from django.views import View + +from .blocks import KV, P, T, Document, H +from .branding import _normalize +from .forms import BrandingProfileForm +from .models import BrandingProfile +from .renderers.html import render_html + +APP = "netbox_customer_export" + + +class BrandingListView(PermissionRequiredMixin, View): + permission_required = f"{APP}.view_brandingprofile" + + def get(self, request): + return render(request, f"{APP}/branding_list.html", {"profiles": BrandingProfile.objects.all()}) + + +class BrandingEditView(PermissionRequiredMixin, View): + template_name = f"{APP}/branding_edit.html" + + def get_permission_required(self): + return [f"{APP}.{'change' if self.kwargs.get('pk') else 'add'}_brandingprofile"] + + def _obj(self, pk): + return get_object_or_404(BrandingProfile, pk=pk) if pk else BrandingProfile() + + def get(self, request, pk=None): + obj = self._obj(pk) + return render(request, self.template_name, {"object": obj, "form": BrandingProfileForm(instance=obj)}) + + def post(self, request, pk=None): + obj = self._obj(pk) + form = BrandingProfileForm(request.POST, request.FILES, instance=obj) + if form.is_valid(): + obj = form.save() + messages.success(request, f"Branding-Profil „{obj}“ gespeichert.") + return redirect(f"plugins:{APP}:branding_list") + return render(request, self.template_name, {"object": obj, "form": form}) + + +class BrandingDeleteView(PermissionRequiredMixin, View): + permission_required = f"{APP}.delete_brandingprofile" + + def get(self, request, pk): + return render(request, f"{APP}/branding_delete.html", {"object": get_object_or_404(BrandingProfile, pk=pk)}) + + def post(self, request, pk): + obj = get_object_or_404(BrandingProfile, pk=pk) + name = str(obj) + for f in (obj.logo, obj.docx_template): + if f: + f.delete(save=False) + obj.delete() + messages.success(request, f"Branding-Profil „{name}“ gelöscht.") + return redirect(f"plugins:{APP}:branding_list") + + +class BrandingPreviewView(PermissionRequiredMixin, View): + """Beispieldokument (HTML) mit dem Profil – zum Prüfen von Farben und Logo.""" + + permission_required = f"{APP}.view_brandingprofile" + + def get(self, request, pk): + obj = get_object_or_404(BrandingProfile, pk=pk) + blocks = [ + H(1, "Übersicht"), + KV([("Standort", "Musterstandort"), ("Status", "Aktiv"), ("Anschrift", "Musterstraße 1\n12345 Musterstadt")]), + H(2, "Geräte"), + T( + ["Name", "Hersteller / Modell", "Rack / HE", "Management-IP"], + [ + ["SW-CORE-01", "HPE Aruba 6100", "Rack A / HE 20", "10.0.0.2/24"], + ["SW-ACC-01", "HPE Aruba 6000", "Rack A / HE 18", "10.0.0.3/24"], + ["FW-01", "Sophos XGS 2100", "Rack A / HE 16", "10.0.0.1/24"], + ], + ), + P("So sehen Überschriften, Tabellen und Fließtext mit diesem Profil aus."), + ] + doc = Document( + title="Netzwerkdokumentation Vorschau", + subtitle="Beispielkunde", + blocks=blocks, + meta={"author": request.user.username, "date": timezone.localtime().strftime("%d.%m.%Y")}, + branding=_normalize(obj.as_branding()), + ) + return HttpResponse(render_html(doc), content_type="text/html; charset=utf-8") diff --git a/netbox_customer_export/forms.py b/netbox_customer_export/forms.py index bdd82e0..d99887d 100644 --- a/netbox_customer_export/forms.py +++ b/netbox_customer_export/forms.py @@ -5,6 +5,7 @@ from tenancy.models import Tenant, TenantGroup from utilities.forms.fields import DynamicModelChoiceField from .branding import get_brandings +from .models import BrandingProfile from .sections import DEFAULT_DEVICE_COLUMNS, DEVICE_COLUMNS, SECTIONS FORMAT_CHOICES = (("pdf", "PDF"), ("docx", "Word (.docx)"), ("html", "HTML-Vorschau")) @@ -85,3 +86,44 @@ class ExportForm(forms.Form): self.fields["subtitle"].initial = f"{scope.label}: {scope.obj}" if not get_plugin_config("netbox_customer_export", "allow_secrets"): del self.fields["include_secrets"] + + +COLOR_FIELDS = ("primary_color", "accent_color", "header_text_color", "label_bg_color", "stripe_color") + + +class ColorInput(forms.TextInput): + """: Anzeige mit #, gespeichert wird ohne #.""" + + input_type = "color" + + def format_value(self, value): + return f"#{value.lstrip('#')}" if value else None + + def value_from_datadict(self, data, files, name): + value = super().value_from_datadict(data, files, name) + return value.lstrip("#").upper() if value else value + + +class BrandingProfileForm(forms.ModelForm): + class Meta: + model = BrandingProfile + exclude = ("last_updated",) + widgets = { + **{f: ColorInput(attrs={"class": "form-control form-control-color"}) for f in COLOR_FIELDS}, + "extra_css": forms.Textarea(attrs={"rows": 4, "class": "form-control font-monospace"}), + } + help_texts = { + "is_default": "Wird im Exportformular vorausgewählt.", + "logo": "PNG oder JPG, erscheint auf dem Deckblatt.", + "docx_template": ".docx oder .dotx: Formatvorlagen, Kopf-/Fußzeile und Ränder werden übernommen.", + "docx_table_style": "Name wie in Word angezeigt, z.B. Gitternetztabelle 4 – Akzent 1. Leer = eigene Einfärbung.", + } + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + for field in self.fields.values(): + w = field.widget + if isinstance(w, forms.CheckboxInput): + w.attrs.setdefault("class", "form-check-input") + else: + w.attrs.setdefault("class", "form-control") diff --git a/netbox_customer_export/migrations/0001_brandingprofile.py b/netbox_customer_export/migrations/0001_brandingprofile.py new file mode 100644 index 0000000..b4e3b86 --- /dev/null +++ b/netbox_customer_export/migrations/0001_brandingprofile.py @@ -0,0 +1,43 @@ +import django.core.validators +from django.db import migrations, models + +HEX = django.core.validators.RegexValidator("^[0-9A-Fa-f]{6}$", "Hex-Farbe ohne # erwartet, z.B. 1F4E79") + + +def color(default, verbose_name): + return models.CharField(default=default, max_length=6, validators=[HEX], verbose_name=verbose_name) + + +class Migration(migrations.Migration): + initial = True + dependencies = [] + + operations = [ + migrations.CreateModel( + name="BrandingProfile", + fields=[ + ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("name", models.CharField(max_length=100, unique=True, verbose_name="Name")), + ("is_default", models.BooleanField(default=False, verbose_name="Standardprofil")), + ("company_name", models.CharField(blank=True, max_length=200, verbose_name="Firmenname")), + ("footer_text", models.CharField(blank=True, default="Vertraulich – nur für den internen Gebrauch des Kunden", max_length=300, verbose_name="Fußzeile")), + ("logo", models.ImageField(blank=True, upload_to="customer-export/logos/", verbose_name="Logo")), + ("primary_color", color("1F4E79", "Primärfarbe")), + ("accent_color", color("2E75B6", "Akzentfarbe")), + ("header_text_color", color("FFFFFF", "Schrift Tabellenkopf")), + ("label_bg_color", color("DEEAF6", "Hintergrund Beschriftungen")), + ("stripe_color", color("F3F7FB", "Zebrastreifen")), + ("font_family", models.CharField(default="Calibri", max_length=100, verbose_name="Schriftart")), + ("font_size", models.PositiveSmallIntegerField(default=10, validators=[django.core.validators.MinValueValidator(7), django.core.validators.MaxValueValidator(16)], verbose_name="Schriftgröße (pt)")), + ("docx_template", models.FileField(blank=True, upload_to="customer-export/templates/", validators=[django.core.validators.FileExtensionValidator(["docx", "dotx"])], verbose_name="Word-Vorlage")), + ("docx_table_style", models.CharField(blank=True, max_length=100, verbose_name="Tabellenformatvorlage (Word)")), + ("extra_css", models.TextField(blank=True, verbose_name="Zusätzliches CSS (PDF/HTML)")), + ("last_updated", models.DateTimeField(auto_now=True)), + ], + options={ + "verbose_name": "Branding-Profil", + "verbose_name_plural": "Branding-Profile", + "ordering": ("-is_default", "name"), + }, + ), + ] diff --git a/netbox_customer_export/migrations/__init__.py b/netbox_customer_export/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/netbox_customer_export/models.py b/netbox_customer_export/models.py new file mode 100644 index 0000000..9c8a30f --- /dev/null +++ b/netbox_customer_export/models.py @@ -0,0 +1,76 @@ +from django.core.validators import FileExtensionValidator, MaxValueValidator, MinValueValidator, RegexValidator +from django.db import models +from django.urls import reverse + +hex_color = RegexValidator(r"^[0-9A-Fa-f]{6}$", "Hex-Farbe ohne # erwartet, z.B. 1F4E79") + + +def _color(default, verbose_name): + return models.CharField(max_length=6, default=default, validators=[hex_color], verbose_name=verbose_name) + + +class BrandingProfile(models.Model): + """In NetBox gepflegtes Design-Profil (Alternative/Ergänzung zu PLUGINS_CONFIG['brandings']).""" + + name = models.CharField(max_length=100, unique=True, verbose_name="Name") + is_default = models.BooleanField(default=False, verbose_name="Standardprofil") + company_name = models.CharField(max_length=200, blank=True, verbose_name="Firmenname") + footer_text = models.CharField( + max_length=300, blank=True, default="Vertraulich – nur für den internen Gebrauch des Kunden", verbose_name="Fußzeile" + ) + logo = models.ImageField(upload_to="customer-export/logos/", blank=True, verbose_name="Logo") + primary_color = _color("1F4E79", "Primärfarbe") + accent_color = _color("2E75B6", "Akzentfarbe") + header_text_color = _color("FFFFFF", "Schrift Tabellenkopf") + label_bg_color = _color("DEEAF6", "Hintergrund Beschriftungen") + stripe_color = _color("F3F7FB", "Zebrastreifen") + font_family = models.CharField(max_length=100, default="Calibri", verbose_name="Schriftart") + font_size = models.PositiveSmallIntegerField( + default=10, validators=[MinValueValidator(7), MaxValueValidator(16)], verbose_name="Schriftgröße (pt)" + ) + docx_template = models.FileField( + upload_to="customer-export/templates/", + blank=True, + validators=[FileExtensionValidator(["docx", "dotx"])], + verbose_name="Word-Vorlage", + ) + docx_table_style = models.CharField(max_length=100, blank=True, verbose_name="Tabellenformatvorlage (Word)") + extra_css = models.TextField(blank=True, verbose_name="Zusätzliches CSS (PDF/HTML)") + last_updated = models.DateTimeField(auto_now=True) + + class Meta: + ordering = ("-is_default", "name") + verbose_name = "Branding-Profil" + verbose_name_plural = "Branding-Profile" + + def __str__(self): + return self.name + + def get_absolute_url(self): + return reverse("plugins:netbox_customer_export:branding_edit", kwargs={"pk": self.pk}) + + def save(self, *args, **kwargs): + for field in ("primary_color", "accent_color", "header_text_color", "label_bg_color", "stripe_color"): + setattr(self, field, getattr(self, field).lstrip("#").upper()) + super().save(*args, **kwargs) + if self.is_default: + BrandingProfile.objects.exclude(pk=self.pk).filter(is_default=True).update(is_default=False) + + def as_branding(self): + """Dict im Format von branding.DEFAULT_BRANDING.""" + return { + "name": self.name, + "company_name": self.company_name, + "footer_text": self.footer_text, + "logo_path": self.logo.path if self.logo else None, + "primary_color": self.primary_color, + "accent_color": self.accent_color, + "header_text_color": self.header_text_color, + "label_bg_color": self.label_bg_color, + "stripe_color": self.stripe_color, + "font_family": self.font_family, + "font_size": self.font_size, + "docx_template": self.docx_template.path if self.docx_template else None, + "docx_table_style": self.docx_table_style, + "extra_css": self.extra_css, + } diff --git a/netbox_customer_export/navigation.py b/netbox_customer_export/navigation.py index e483294..b641c7b 100644 --- a/netbox_customer_export/navigation.py +++ b/netbox_customer_export/navigation.py @@ -1,4 +1,4 @@ -from netbox.plugins import PluginMenuItem +from netbox.plugins import PluginMenuButton, PluginMenuItem menu_items = ( PluginMenuItem( @@ -6,4 +6,17 @@ menu_items = ( link_text="Kundendokumentation", permissions=["dcim.view_site"], ), + PluginMenuItem( + link="plugins:netbox_customer_export:branding_list", + link_text="Branding-Profile", + permissions=["netbox_customer_export.view_brandingprofile"], + buttons=( + PluginMenuButton( + link="plugins:netbox_customer_export:branding_add", + title="Hinzufügen", + icon_class="mdi mdi-plus-thick", + permissions=["netbox_customer_export.add_brandingprofile"], + ), + ), + ), ) diff --git a/netbox_customer_export/templates/netbox_customer_export/_field.html b/netbox_customer_export/templates/netbox_customer_export/_field.html new file mode 100644 index 0000000..71c6222 --- /dev/null +++ b/netbox_customer_export/templates/netbox_customer_export/_field.html @@ -0,0 +1,6 @@ +
+ + {{ field }} + {% if field.help_text %}
{{ field.help_text }}
{% endif %} + {% for e in field.errors %}
{{ e }}
{% endfor %} +
diff --git a/netbox_customer_export/templates/netbox_customer_export/branding_delete.html b/netbox_customer_export/templates/netbox_customer_export/branding_delete.html new file mode 100644 index 0000000..a1c805f --- /dev/null +++ b/netbox_customer_export/templates/netbox_customer_export/branding_delete.html @@ -0,0 +1,17 @@ +{% extends 'generic/_base.html' %} +{% block title %}{{ object }} löschen{% endblock %} +{% block content %} +
+ {% csrf_token %} +
+
+

Soll das Branding-Profil {{ object }} wirklich gelöscht werden? + Logo und Word-Vorlage des Profils werden ebenfalls entfernt.

+
+ +
+
+{% endblock %} diff --git a/netbox_customer_export/templates/netbox_customer_export/branding_edit.html b/netbox_customer_export/templates/netbox_customer_export/branding_edit.html new file mode 100644 index 0000000..932c041 --- /dev/null +++ b/netbox_customer_export/templates/netbox_customer_export/branding_edit.html @@ -0,0 +1,81 @@ +{% extends 'generic/_base.html' %} +{% block title %}{% if object.pk %}{{ object }} bearbeiten{% else %}Neues Branding-Profil{% endif %}{% endblock %} +{% block content %} + + +
+ {% csrf_token %} + {% if form.non_field_errors %}
{{ form.non_field_errors }}
{% endif %} + +
+
+
+

Allgemein

+
+ {% include 'netbox_customer_export/_field.html' with field=form.name %} +
{{ form.is_default }} + {{ form.is_default.help_text }}
+ {% include 'netbox_customer_export/_field.html' with field=form.company_name %} + {% include 'netbox_customer_export/_field.html' with field=form.footer_text %} +
+ + {% if object.logo %}
Aktuelles Logo
{% endif %} + {{ form.logo }} +
{{ form.logo.help_text }}
+ {% for e in form.logo.errors %}
{{ e }}
{% endfor %} +
+
+
+
+ +
+
+

Farben & Schrift

+
+
+ {% for field in form %}{% if field.name in 'primary_color accent_color header_text_color label_bg_color stripe_color' %} +
+ + {{ field }} + {% for e in field.errors %}
{{ e }}
{% endfor %} +
+ {% endif %}{% endfor %} +
+
+
{% include 'netbox_customer_export/_field.html' with field=form.font_family %}
+
{% include 'netbox_customer_export/_field.html' with field=form.font_size %}
+
+
+
+
+ +
+
+

Word & PDF

+
+
+
+ + {{ form.docx_template }} +
{{ form.docx_template.help_text }}
+ {% for e in form.docx_template.errors %}
{{ e }}
{% endfor %} +
+ {% include 'netbox_customer_export/_field.html' with field=form.docx_table_style %} +
+
{% include 'netbox_customer_export/_field.html' with field=form.extra_css %}
+
+
+
+
+ +
+ Abbrechen + {% if object.pk %} Vorschau{% endif %} + +
+
+{% endblock %} diff --git a/netbox_customer_export/templates/netbox_customer_export/branding_list.html b/netbox_customer_export/templates/netbox_customer_export/branding_list.html new file mode 100644 index 0000000..3336b99 --- /dev/null +++ b/netbox_customer_export/templates/netbox_customer_export/branding_list.html @@ -0,0 +1,57 @@ +{% extends 'generic/_base.html' %} +{% block title %}Branding-Profile{% endblock %} +{% block controls %} + {% if perms.netbox_customer_export.add_brandingprofile %} + + {% endif %} +{% endblock %} +{% block content %} + +
+
+ + + + + + {% for p in profiles %} + + + + + + + + + + {% empty %} + + {% endfor %} + +
NameLogoFirmaFarbenSchriftWord-Vorlage
+ {% if perms.netbox_customer_export.change_brandingprofile %}{{ p.name }}{% else %}{{ p.name }}{% endif %} + {% if p.is_default %}Standard{% endif %} + {% if p.logo %}{% else %}—{% endif %}{{ p.company_name|default:"—" }} + + + + + {{ p.font_family }}, {{ p.font_size }} pt{% if p.docx_template %} {{ p.docx_template.name|cut:"customer-export/templates/" }}{% else %}—{% endif %} + + {% if perms.netbox_customer_export.change_brandingprofile %} + + {% endif %} + {% if perms.netbox_customer_export.delete_brandingprofile %} + + {% endif %} +
+ Noch keine Branding-Profile angelegt. Bis dahin gelten die Einstellungen aus configuration.py. +
+
+
+{% endblock %} diff --git a/netbox_customer_export/urls.py b/netbox_customer_export/urls.py index 9c0ff55..f5db161 100644 --- a/netbox_customer_export/urls.py +++ b/netbox_customer_export/urls.py @@ -1,8 +1,13 @@ from django.urls import path -from . import views +from . import branding_views, views urlpatterns = [ path("", views.ScopeSelectView.as_view(), name="select"), path("export///", views.ExportView.as_view(), name="export"), + path("branding/", branding_views.BrandingListView.as_view(), name="branding_list"), + path("branding/add/", branding_views.BrandingEditView.as_view(), name="branding_add"), + path("branding//", branding_views.BrandingEditView.as_view(), name="branding_edit"), + path("branding//delete/", branding_views.BrandingDeleteView.as_view(), name="branding_delete"), + path("branding//preview/", branding_views.BrandingPreviewView.as_view(), name="branding_preview"), ] diff --git a/pyproject.toml b/pyproject.toml index 7decbc2..7175012 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "netbox-customer-export" -version = "0.1.1" +version = "0.2.0" description = "NetBox-Plugin: Kundendokumentation (PDF/Word) für Standorte, Lokationen, Mandanten und Mandantengruppen" readme = "README.md" requires-python = ">=3.10"