mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Closes #4837: Use dynamic form widget for relationships to MPTT objects
This commit is contained in:
@ -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
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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,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(),
|
||||
|
@ -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(),
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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()
|
||||
|
||||
|
Reference in New Issue
Block a user