Просмотр исходного кода

Closes #4837: Use dynamic form widget for relationships to MPTT objects

Jeremy Stretch 5 лет назад
Родитель
Сommit
15525392a2

+ 2 - 0
docs/release-notes/version-2.9.md

@@ -25,6 +25,7 @@ When running a report or custom script, the task is now queued for background pr
 * [#4806](https://github.com/netbox-community/netbox/issues/4806) - Add a `url` field to all API serializers
 * [#4807](https://github.com/netbox-community/netbox/issues/4807) - Add bulk edit ability for device bay templates
 * [#4817](https://github.com/netbox-community/netbox/issues/4817) - Standardize device/VM component `name` field to 64 characters
+* [#4837](https://github.com/netbox-community/netbox/issues/4837) - Use dynamic form widget for relationships to MPTT objects (e.g. regions)
 
 ### Configuration Changes
 
@@ -52,6 +53,7 @@ When running a report or custom script, the task is now queued for background pr
 * extras.Report: The `failed` field has been removed. The `completed` (boolean) and `status` (string) fields have been introduced to convey the status of a report's most recent execution. Additionally, the `result` field now conveys the nested representation of a JobResult.
 * extras.Script: Added `module` and `result` fields. The `result` field now conveys the nested representation of a JobResult.
 * A `url` field is now included on all object representations, identifying the unique REST API URL for each object.
+* A `_depth` field has been added to all objects which feature a self-recursive hierarchy (namely regions, rack groups, and tenant groups).
 
 ### Other Changes
 

+ 4 - 2
netbox/dcim/api/nested_serializers.py

@@ -47,10 +47,11 @@ __all__ = [
 class NestedRegionSerializer(WritableNestedSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:region-detail')
     site_count = serializers.IntegerField(read_only=True)
+    _depth = serializers.IntegerField(source='level', read_only=True)
 
     class Meta:
         model = models.Region
-        fields = ['id', 'url', 'name', 'slug', 'site_count']
+        fields = ['id', 'url', 'name', 'slug', 'site_count', '_depth']
 
 
 class NestedSiteSerializer(WritableNestedSerializer):
@@ -68,10 +69,11 @@ class NestedSiteSerializer(WritableNestedSerializer):
 class NestedRackGroupSerializer(WritableNestedSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rackgroup-detail')
     rack_count = serializers.IntegerField(read_only=True)
+    _depth = serializers.IntegerField(source='level', read_only=True)
 
     class Meta:
         model = models.RackGroup
-        fields = ['id', 'url', 'name', 'slug', 'rack_count']
+        fields = ['id', 'url', 'name', 'slug', 'rack_count', '_depth']
 
 
 class NestedRackRoleSerializer(WritableNestedSerializer):

+ 4 - 2
netbox/dcim/api/serializers.py

@@ -63,10 +63,11 @@ class RegionSerializer(serializers.ModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:region-detail')
     parent = NestedRegionSerializer(required=False, allow_null=True)
     site_count = serializers.IntegerField(read_only=True)
+    _depth = serializers.IntegerField(source='level', read_only=True)
 
     class Meta:
         model = Region
-        fields = ['id', 'url', 'name', 'slug', 'parent', 'description', 'site_count']
+        fields = ['id', 'url', 'name', 'slug', 'parent', 'description', 'site_count', '_depth']
 
 
 class SiteSerializer(TaggedObjectSerializer, CustomFieldModelSerializer):
@@ -101,10 +102,11 @@ class RackGroupSerializer(ValidatedModelSerializer):
     site = NestedSiteSerializer()
     parent = NestedRackGroupSerializer(required=False, allow_null=True)
     rack_count = serializers.IntegerField(read_only=True)
+    _depth = serializers.IntegerField(source='level', read_only=True)
 
     class Meta:
         model = RackGroup
-        fields = ['id', 'url', 'name', 'slug', 'site', 'parent', 'description', 'rack_count']
+        fields = ['id', 'url', 'name', 'slug', 'site', 'parent', 'description', 'rack_count', '_depth']
 
 
 class RackRoleSerializer(ValidatedModelSerializer):

+ 6 - 10
netbox/dcim/forms.py

@@ -6,7 +6,6 @@ from django.contrib.contenttypes.models import ContentType
 from django.contrib.postgres.forms.array import SimpleArrayField
 from django.core.exceptions import ObjectDoesNotExist
 from django.utils.safestring import mark_safe
-from mptt.forms import TreeNodeChoiceField
 from netaddr import EUI
 from netaddr.core import AddrFormatError
 from timezone_field import TimeZoneFormField
@@ -179,10 +178,9 @@ class MACAddressField(forms.Field):
 #
 
 class RegionForm(BootstrapMixin, forms.ModelForm):
-    parent = TreeNodeChoiceField(
+    parent = DynamicModelChoiceField(
         queryset=Region.objects.all(),
-        required=False,
-        widget=StaticSelect2()
+        required=False
     )
     slug = SlugField()
 
@@ -219,10 +217,9 @@ class RegionFilterForm(BootstrapMixin, forms.Form):
 #
 
 class SiteForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
-    region = TreeNodeChoiceField(
+    region = DynamicModelChoiceField(
         queryset=Region.objects.all(),
-        required=False,
-        widget=StaticSelect2()
+        required=False
     )
     slug = SlugField()
     comments = CommentField()
@@ -305,10 +302,9 @@ class SiteBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
         initial='',
         widget=StaticSelect2()
     )
-    region = TreeNodeChoiceField(
+    region = DynamicModelChoiceField(
         queryset=Region.objects.all(),
-        required=False,
-        widget=StaticSelect2()
+        required=False
     )
     tenant = DynamicModelChoiceField(
         queryset=Tenant.objects.all(),

+ 3 - 5
netbox/extras/forms.py

@@ -2,14 +2,13 @@ from django import forms
 from django.contrib.auth.models import User
 from django.contrib.contenttypes.models import ContentType
 from django.utils.safestring import mark_safe
-from mptt.forms import TreeNodeMultipleChoiceField
 
 from dcim.models import DeviceRole, Platform, Region, Site
 from tenancy.models import Tenant, TenantGroup
 from utilities.forms import (
     add_blank_choice, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, ColorSelect,
     ContentTypeSelect, CSVModelForm, DateTimePicker, DynamicModelMultipleChoiceField, JSONField, SlugField,
-    StaticSelect2, StaticSelect2Multiple, BOOLEAN_WITH_BLANK_CHOICES,
+    StaticSelect2, BOOLEAN_WITH_BLANK_CHOICES,
 )
 from virtualization.models import Cluster, ClusterGroup
 from .choices import *
@@ -211,10 +210,9 @@ class TagBulkEditForm(BootstrapMixin, BulkEditForm):
 #
 
 class ConfigContextForm(BootstrapMixin, forms.ModelForm):
-    regions = TreeNodeMultipleChoiceField(
+    regions = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
-        required=False,
-        widget=StaticSelect2Multiple()
+        required=False
     )
     sites = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),

+ 0 - 12
netbox/extras/scripts.py

@@ -3,7 +3,6 @@ import json
 import logging
 import os
 import pkgutil
-import time
 import traceback
 from collections import OrderedDict
 
@@ -12,11 +11,8 @@ from django import forms
 from django.conf import settings
 from django.core.validators import RegexValidator
 from django.db import transaction
-from django.utils import timezone
 from django.utils.decorators import classproperty
 from django_rq import job
-from mptt.forms import TreeNodeChoiceField, TreeNodeMultipleChoiceField
-from mptt.models import MPTTModel
 
 from extras.api.serializers import ScriptOutputSerializer
 from extras.choices import JobResultStatusChoices, LogLevelChoices
@@ -182,10 +178,6 @@ class ObjectVar(ScriptVariable):
         # Queryset for field choices
         self.field_attrs['queryset'] = queryset
 
-        # Update form field for MPTT (nested) objects
-        if issubclass(queryset.model, MPTTModel):
-            self.form_field = TreeNodeChoiceField
-
 
 class MultiObjectVar(ScriptVariable):
     """
@@ -199,10 +191,6 @@ class MultiObjectVar(ScriptVariable):
         # Queryset for field choices
         self.field_attrs['queryset'] = queryset
 
-        # Update form field for MPTT (nested) objects
-        if issubclass(queryset.model, MPTTModel):
-            self.form_field = TreeNodeMultipleChoiceField
-
 
 class FileVar(ScriptVariable):
     """

+ 4 - 0
netbox/project-static/js/forms.js

@@ -222,6 +222,10 @@ $(document).ready(function() {
 
                 results = results.reduce((results,record,idx) => {
                     record.text = record[element.getAttribute('display-field')] || record.name;
+                    if (record._depth) {
+                        // Annotate hierarchical depth for MPTT objects
+                        record.text = '--'.repeat(record._depth) + ' ' + record.text;
+                    }
                     record.id = record[element.getAttribute('value-field')] || record.id;
                     if(element.getAttribute('disabled-indicator') && record[element.getAttribute('disabled-indicator')]) {
                         // The disabled-indicator equated to true, so we disable this option

+ 2 - 1
netbox/tenancy/api/nested_serializers.py

@@ -16,10 +16,11 @@ __all__ = [
 class NestedTenantGroupSerializer(WritableNestedSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='tenancy-api:tenantgroup-detail')
     tenant_count = serializers.IntegerField(read_only=True)
+    _depth = serializers.IntegerField(source='level', read_only=True)
 
     class Meta:
         model = TenantGroup
-        fields = ['id', 'url', 'name', 'slug', 'tenant_count']
+        fields = ['id', 'url', 'name', 'slug', 'tenant_count', '_depth']
 
 
 class NestedTenantSerializer(WritableNestedSerializer):

+ 2 - 1
netbox/tenancy/api/serializers.py

@@ -15,10 +15,11 @@ class TenantGroupSerializer(ValidatedModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='tenancy-api:tenantgroup-detail')
     parent = NestedTenantGroupSerializer(required=False, allow_null=True)
     tenant_count = serializers.IntegerField(read_only=True)
+    _depth = serializers.IntegerField(source='level', read_only=True)
 
     class Meta:
         model = TenantGroup
-        fields = ['id', 'url', 'name', 'slug', 'parent', 'description', 'tenant_count']
+        fields = ['id', 'url', 'name', 'slug', 'parent', 'description', 'tenant_count', '_depth']
 
 
 class TenantSerializer(TaggedObjectSerializer, CustomFieldModelSerializer):

+ 1 - 4
netbox/tenancy/forms.py

@@ -18,10 +18,7 @@ from .models import Tenant, TenantGroup
 class TenantGroupForm(BootstrapMixin, forms.ModelForm):
     parent = DynamicModelChoiceField(
         queryset=TenantGroup.objects.all(),
-        required=False,
-        widget=APISelect(
-            api_url="/api/tenancy/tenant-groups/"
-        )
+        required=False
     )
     slug = SlugField()