From 8f03a19b5fdcffc534b6be38a734e1736ad7717c Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 13 Mar 2024 10:15:34 -0400 Subject: [PATCH] Introduce ObjectAttribute for displaying read-only instance attributes on forms --- netbox/extras/forms/model_forms.py | 4 +++ netbox/extras/views.py | 1 - netbox/ipam/forms/model_forms.py | 5 +++ netbox/ipam/views.py | 1 - .../extras/imageattachment_edit.html | 19 ---------- .../ipam/fhrpgroupassignment_edit.html | 19 ---------- .../tenancy/contactassignment_edit.html | 35 ------------------- netbox/tenancy/forms/model_forms.py | 5 +++ netbox/tenancy/views.py | 1 - netbox/utilities/forms/rendering.py | 8 ++++- .../form_helpers/render_fieldset.html | 11 ++++++ netbox/utilities/templatetags/form_helpers.py | 9 ++++- 12 files changed, 40 insertions(+), 78 deletions(-) delete mode 100644 netbox/templates/extras/imageattachment_edit.html delete mode 100644 netbox/templates/ipam/fhrpgroupassignment_edit.html delete mode 100644 netbox/templates/tenancy/contactassignment_edit.html diff --git a/netbox/extras/forms/model_forms.py b/netbox/extras/forms/model_forms.py index 09d2d9535..4e62b3ab7 100644 --- a/netbox/extras/forms/model_forms.py +++ b/netbox/extras/forms/model_forms.py @@ -17,6 +17,7 @@ from utilities.forms.fields import ( CommentField, ContentTypeChoiceField, ContentTypeMultipleChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, JSONField, SlugField, ) +from utilities.forms.rendering import ObjectAttribute from utilities.forms.widgets import ChoicesWidget, HTMXSelect from virtualization.models import Cluster, ClusterGroup, ClusterType @@ -526,6 +527,9 @@ class ConfigTemplateForm(SyncedDataMixin, forms.ModelForm): class ImageAttachmentForm(forms.ModelForm): + fieldsets = ( + (None, (ObjectAttribute('parent'), 'name', 'image')), + ) class Meta: model = ImageAttachment diff --git a/netbox/extras/views.py b/netbox/extras/views.py index 1fa2a30aa..cb3fdd39c 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -759,7 +759,6 @@ class ImageAttachmentListView(generic.ObjectListView): class ImageAttachmentEditView(generic.ObjectEditView): queryset = ImageAttachment.objects.all() form = forms.ImageAttachmentForm - template_name = 'extras/imageattachment_edit.html' def alter_object(self, instance, request, args, kwargs): if not instance.pk: diff --git a/netbox/ipam/forms/model_forms.py b/netbox/ipam/forms/model_forms.py index 47087139a..07f782f7f 100644 --- a/netbox/ipam/forms/model_forms.py +++ b/netbox/ipam/forms/model_forms.py @@ -16,6 +16,7 @@ from utilities.forms.fields import ( CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, NumericArrayField, SlugField, ) +from utilities.forms.rendering import ObjectAttribute from utilities.forms.widgets import DatePicker from virtualization.models import Cluster, ClusterGroup, VirtualMachine, VMInterface @@ -502,6 +503,10 @@ class FHRPGroupAssignmentForm(forms.ModelForm): queryset=FHRPGroup.objects.all() ) + fieldsets = ( + (None, (ObjectAttribute('interface'), 'group', 'priority')), + ) + class Meta: model = FHRPGroupAssignment fields = ('group', 'priority') diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 9c4a9a102..79716f082 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -1059,7 +1059,6 @@ class FHRPGroupBulkDeleteView(generic.BulkDeleteView): class FHRPGroupAssignmentEditView(generic.ObjectEditView): queryset = FHRPGroupAssignment.objects.all() form = forms.FHRPGroupAssignmentForm - template_name = 'ipam/fhrpgroupassignment_edit.html' def alter_object(self, instance, request, args, kwargs): if not instance.pk: diff --git a/netbox/templates/extras/imageattachment_edit.html b/netbox/templates/extras/imageattachment_edit.html deleted file mode 100644 index 75b2ce48b..000000000 --- a/netbox/templates/extras/imageattachment_edit.html +++ /dev/null @@ -1,19 +0,0 @@ -{% extends 'generic/object_edit.html' %} -{% load helpers %} -{% load form_helpers %} - -{% block form %} -
-
- -
-
- {{ object.parent|linkify }} -
-
-
- {% render_form form %} -
-{% endblock form %} diff --git a/netbox/templates/ipam/fhrpgroupassignment_edit.html b/netbox/templates/ipam/fhrpgroupassignment_edit.html deleted file mode 100644 index bbc1505f2..000000000 --- a/netbox/templates/ipam/fhrpgroupassignment_edit.html +++ /dev/null @@ -1,19 +0,0 @@ -{% extends 'generic/object_edit.html' %} -{% load form_helpers %} -{% load i18n %} - -{% block form %} -
-
-
{% trans "FHRP Group Assignment" %}
-
-
- -
- -
-
- {% render_field form.group %} - {% render_field form.priority %} -
-{% endblock %} diff --git a/netbox/templates/tenancy/contactassignment_edit.html b/netbox/templates/tenancy/contactassignment_edit.html deleted file mode 100644 index 342debcbb..000000000 --- a/netbox/templates/tenancy/contactassignment_edit.html +++ /dev/null @@ -1,35 +0,0 @@ -{% extends 'generic/object_edit.html' %} -{% load helpers %} -{% load form_helpers %} -{% load i18n %} - -{% block form %} - {% for field in form.hidden_fields %} - {{ field }} - {% endfor %} -
-
-
{% trans "Contact Assignment" %}
-
-
- -
- -
-
- {% render_field form.group %} - {% render_field form.contact %} - {% render_field form.role %} - {% render_field form.priority %} - {% render_field form.tags %} -
- -
-
-
{% trans "Custom Fields" %}
-
- {% render_custom_fields form %} -
-{% endblock %} diff --git a/netbox/tenancy/forms/model_forms.py b/netbox/tenancy/forms/model_forms.py index 140d9cf9a..7dcb4e433 100644 --- a/netbox/tenancy/forms/model_forms.py +++ b/netbox/tenancy/forms/model_forms.py @@ -4,6 +4,7 @@ from django.utils.translation import gettext_lazy as _ from netbox.forms import NetBoxModelForm from tenancy.models import * from utilities.forms.fields import CommentField, DynamicModelChoiceField, SlugField +from utilities.forms.rendering import ObjectAttribute __all__ = ( 'ContactAssignmentForm', @@ -140,6 +141,10 @@ class ContactAssignmentForm(NetBoxModelForm): queryset=ContactRole.objects.all() ) + fieldsets = ( + (None, (ObjectAttribute('object'), 'group', 'contact', 'role', 'priority', 'tags')), + ) + class Meta: model = ContactAssignment fields = ( diff --git a/netbox/tenancy/views.py b/netbox/tenancy/views.py index 4c4d263df..d30793a16 100644 --- a/netbox/tenancy/views.py +++ b/netbox/tenancy/views.py @@ -369,7 +369,6 @@ class ContactAssignmentListView(generic.ObjectListView): class ContactAssignmentEditView(generic.ObjectEditView): queryset = ContactAssignment.objects.all() form = forms.ContactAssignmentForm - template_name = 'tenancy/contactassignment_edit.html' def alter_object(self, instance, request, args, kwargs): if not instance.pk: diff --git a/netbox/utilities/forms/rendering.py b/netbox/utilities/forms/rendering.py index ad87930a9..d60f3f061 100644 --- a/netbox/utilities/forms/rendering.py +++ b/netbox/utilities/forms/rendering.py @@ -3,8 +3,8 @@ import string from functools import cached_property __all__ = ( - 'FieldGroup', 'InlineFields', + 'ObjectAttribute', 'TabbedFieldGroups', ) @@ -41,3 +41,9 @@ class TabbedFieldGroups: 'fields': group.field_names, } for i, group in enumerate(self.groups, start=1) ] + + +class ObjectAttribute: + + def __init__(self, name): + self.name = name diff --git a/netbox/utilities/templates/form_helpers/render_fieldset.html b/netbox/utilities/templates/form_helpers/render_fieldset.html index ee1f50293..d4c7981f7 100644 --- a/netbox/utilities/templates/form_helpers/render_fieldset.html +++ b/netbox/utilities/templates/form_helpers/render_fieldset.html @@ -12,6 +12,17 @@ {# Single form field #} {% render_field items.0 %} + {% elif layout == 'attribute' %} + {# A static attribute of the form's instance #} +
+ +
+
+ {{ items.0|linkify }} +
+
+
+ {% elif layout == 'inline' %} {# Multiple form fields on the same line #}
diff --git a/netbox/utilities/templatetags/form_helpers.py b/netbox/utilities/templatetags/form_helpers.py index c55a6b98b..e336ac21b 100644 --- a/netbox/utilities/templatetags/form_helpers.py +++ b/netbox/utilities/templatetags/form_helpers.py @@ -1,6 +1,6 @@ from django import template -from utilities.forms.rendering import InlineFields, TabbedFieldGroups +from utilities.forms.rendering import InlineFields, ObjectAttribute, TabbedFieldGroups __all__ = ( 'getfield', @@ -81,6 +81,13 @@ def render_fieldset(form, fieldset, heading=None): ('tabs', None, tabs) ) + elif type(item) is ObjectAttribute: + value = getattr(form.instance, item.name) + label = value._meta.verbose_name if hasattr(value, '_meta') else item.name + rows.append( + ('attribute', label.title(), [value]) + ) + # A single form field elif item in form.fields: rows.append(