Files
Netbox-Documentation/netbox_documentation/forms.py
T
MrBlake 59598fbd74 feat: Ordnerstruktur, Medienupload und erweiterten Editor ergänzen
- NetBox-4.6-Verknüpfungsformular korrigieren
- hierarchische Dokumentationsordner hinzufügen
- breite Editoransicht und Fokusmodus implementieren
- sicheren direkten Bild-Upload integrieren
- Ordner über Suche und REST-API bereitstellen
2026-07-22 11:04:10 +02:00

119 lines
5.5 KiB
Python

from django import forms
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.db.models import Q
from utilities.forms import get_field_value
from utilities.forms.fields import ContentTypeChoiceField, DynamicModelChoiceField, SlugField
from utilities.forms.rendering import FieldSet
from utilities.forms.widgets import HTMXSelect
from netbox.forms import NetBoxModelForm
from dcim.models import Device
from .models import Document, DocumentAssignment, DocumentCategory
def allowed_content_types():
labels = settings.PLUGINS_CONFIG.get("netbox_documentation", {}).get(
"allowed_object_types", []
)
pairs = [value.split(".", 1) for value in labels if "." in value]
query = Q()
for app_label, model in pairs:
query |= Q(app_label=app_label, model=model)
return ContentType.objects.filter(query).order_by("app_label", "model")
class DocumentForm(NetBoxModelForm):
slug = SlugField(slug_source="title")
body = forms.CharField(required=False, label="Inhalt", widget=forms.Textarea(attrs={
"rows": 32, "class": "rich-text-editor", "data-rich-text-editor": "true"
}), help_text="Formatierter Text mit Tabellen, Farben, Schriftarten und Größen.")
body_format = forms.CharField(widget=forms.HiddenInput(), initial="html")
category = DynamicModelChoiceField(queryset=DocumentCategory.objects.all(), required=False, label="Ordner")
fieldsets = (
FieldSet("title", "slug", "category", "summary", "is_published", "tags", name="Dokumentation"),
FieldSet("body", "body_format", name="Inhalt"),
)
class Meta:
model = Document
fields = ("title", "slug", "category", "summary", "body", "body_format", "is_published", "tags")
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Legacy Markdown remains readable. Once edited in WYSIWYG mode it is
# converted to HTML so the editor never exposes Markdown as plain text.
if self.instance.pk and self.instance.body_format == "markdown" and not self.is_bound:
from markdown import markdown
self.initial["body"] = markdown(self.instance.body, extensions=("extra", "sane_lists"))
self.initial["body_format"] = "html"
class AssignmentForm(NetBoxModelForm):
assigned_object_type = ContentTypeChoiceField(queryset=ContentType.objects.none(), label="Objekttyp", widget=HTMXSelect())
assigned_object = DynamicModelChoiceField(
queryset=Device.objects.none(), label="NetBox-Objekt", required=True, disabled=True, selector=True
)
fieldsets = (FieldSet("document", "assigned_object_type", "assigned_object", "note", "tags", name="Dokumentation zuordnen"),)
class Meta:
model = DocumentAssignment
fields = ("document", "assigned_object_type", "note", "tags")
def __init__(self, *args, **kwargs):
instance = kwargs.get("instance")
initial = kwargs.setdefault("initial", {})
if instance and instance.pk and instance.assigned_object:
initial["assigned_object"] = instance.assigned_object
super().__init__(*args, **kwargs)
self.fields["assigned_object_type"].queryset = allowed_content_types()
ct_id = get_field_value(self, "assigned_object_type")
if ct_id:
content_type = self.fields["assigned_object_type"].queryset.filter(pk=ct_id).first()
else:
content_type = None
if content_type and content_type.model_class():
model = content_type.model_class()
self.fields["assigned_object"].queryset = model.objects.all()
self.fields["assigned_object"].widget.attrs["selector"] = model._meta.label_lower
self.fields["assigned_object"].disabled = False
self.fields["assigned_object"].label = model._meta.verbose_name.title()
def clean(self):
super().clean()
self.instance.assigned_object = self.cleaned_data.get("assigned_object")
class DocumentCategoryForm(NetBoxModelForm):
slug = SlugField(slug_source="name")
parent = DynamicModelChoiceField(queryset=DocumentCategory.objects.all(), required=False, label="Übergeordneter Ordner")
fieldsets = (FieldSet("name", "slug", "parent", "description", "tags", name="Ordner"),)
class Meta:
model = DocumentCategory
fields = ("name", "slug", "parent", "description", "tags")
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance.pk:
self.fields["parent"].queryset = DocumentCategory.objects.exclude(pk=self.instance.pk)
class ImportForm(forms.Form):
file = forms.FileField(label="Word-, Excel- oder PDF-Datei")
title = forms.CharField(max_length=200, required=False, help_text="Leer lassen, um den Dateinamen zu verwenden")
append = forms.BooleanField(required=False, initial=False, label="An bestehende Dokumentation anhängen")
document = forms.ModelChoiceField(queryset=Document.objects.all(), required=False, label="Bestehende Dokumentation")
def clean(self):
data = super().clean()
if data.get("append") and not data.get("document"):
self.add_error("document", "Zum Anhängen muss eine Dokumentation gewählt werden.")
upload = data.get("file")
limit = settings.PLUGINS_CONFIG.get("netbox_documentation", {}).get("max_import_size_mb", 25)
if upload and upload.size > limit * 1024 * 1024:
self.add_error("file", f"Die Datei ist größer als {limit} MB.")
return data