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

Merge pull request #4375 from netbox-community/4374-dynamic-fields-api-url

Closes #4374: Automatically derive API endpoint for dynamic choice fields
Jeremy Stretch 6 лет назад
Родитель
Сommit
00afe7aa94

+ 8 - 29
netbox/circuits/forms.py

@@ -113,7 +113,6 @@ class ProviderFilterForm(BootstrapMixin, CustomFieldFilterForm):
         to_field_name='slug',
         required=False,
         widget=APISelectMultiple(
-            api_url="/api/dcim/regions/",
             value_field="slug",
             filter_for={
                 'site': 'region'
@@ -125,7 +124,6 @@ class ProviderFilterForm(BootstrapMixin, CustomFieldFilterForm):
         to_field_name='slug',
         required=False,
         widget=APISelectMultiple(
-            api_url="/api/dcim/sites/",
             value_field="slug",
         )
     )
@@ -167,16 +165,10 @@ class CircuitTypeCSVForm(forms.ModelForm):
 
 class CircuitForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
     provider = DynamicModelChoiceField(
-        queryset=Provider.objects.all(),
-        widget=APISelect(
-            api_url="/api/circuits/providers/"
-        )
+        queryset=Provider.objects.all()
     )
     type = DynamicModelChoiceField(
-        queryset=CircuitType.objects.all(),
-        widget=APISelect(
-            api_url="/api/circuits/circuit-types/"
-        )
+        queryset=CircuitType.objects.all()
     )
     comments = CommentField()
     tags = TagField(
@@ -245,17 +237,11 @@ class CircuitBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEdit
     )
     type = DynamicModelChoiceField(
         queryset=CircuitType.objects.all(),
-        required=False,
-        widget=APISelect(
-            api_url="/api/circuits/circuit-types/"
-        )
+        required=False
     )
     provider = DynamicModelChoiceField(
         queryset=Provider.objects.all(),
-        required=False,
-        widget=APISelect(
-            api_url="/api/circuits/providers/"
-        )
+        required=False
     )
     status = forms.ChoiceField(
         choices=add_blank_choice(CircuitStatusChoices),
@@ -265,10 +251,7 @@ class CircuitBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEdit
     )
     tenant = DynamicModelChoiceField(
         queryset=Tenant.objects.all(),
-        required=False,
-        widget=APISelect(
-            api_url="/api/tenancy/tenants/"
-        )
+        required=False
     )
     commit_rate = forms.IntegerField(
         required=False,
@@ -303,7 +286,6 @@ class CircuitFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm
         to_field_name='slug',
         required=False,
         widget=APISelectMultiple(
-            api_url="/api/circuits/circuit-types/",
             value_field="slug",
         )
     )
@@ -312,7 +294,6 @@ class CircuitFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm
         to_field_name='slug',
         required=False,
         widget=APISelectMultiple(
-            api_url="/api/circuits/providers/",
             value_field="slug",
         )
     )
@@ -326,7 +307,6 @@ class CircuitFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm
         to_field_name='slug',
         required=False,
         widget=APISelectMultiple(
-            api_url="/api/dcim/regions/",
             value_field="slug",
             filter_for={
                 'site': 'region'
@@ -338,7 +318,6 @@ class CircuitFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm
         to_field_name='slug',
         required=False,
         widget=APISelectMultiple(
-            api_url="/api/dcim/sites/",
             value_field="slug",
         )
     )
@@ -355,6 +334,9 @@ class CircuitFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm
 #
 
 class CircuitTerminationForm(BootstrapMixin, forms.ModelForm):
+    site = DynamicModelChoiceField(
+        queryset=Site.objects.all()
+    )
 
     class Meta:
         model = CircuitTermination
@@ -368,7 +350,4 @@ class CircuitTerminationForm(BootstrapMixin, forms.ModelForm):
         }
         widgets = {
             'term_side': forms.HiddenInput(),
-            'site': APISelect(
-                api_url="/api/dcim/sites/"
-            )
         }

Разница между файлами не показана из-за своего большого размера
+ 46 - 246
netbox/dcim/forms.py


+ 9 - 44
netbox/extras/forms.py

@@ -198,60 +198,36 @@ class ConfigContextForm(BootstrapMixin, forms.ModelForm):
     )
     sites = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),
-        required=False,
-        widget=APISelectMultiple(
-            api_url="/api/dcim/sites/"
-        )
+        required=False
     )
     roles = DynamicModelMultipleChoiceField(
         queryset=DeviceRole.objects.all(),
-        required=False,
-        widget=APISelectMultiple(
-            api_url="/api/dcim/device-roles/"
-        )
+        required=False
     )
     platforms = DynamicModelMultipleChoiceField(
         queryset=Platform.objects.all(),
-        required=False,
-        widget=APISelectMultiple(
-            api_url="/api/dcim/platforms/"
-        )
+        required=False
     )
     cluster_groups = DynamicModelMultipleChoiceField(
         queryset=ClusterGroup.objects.all(),
-        required=False,
-        widget=APISelectMultiple(
-            api_url="/api/virtualization/cluster-groups/"
-        )
+        required=False
     )
     clusters = DynamicModelMultipleChoiceField(
         queryset=Cluster.objects.all(),
-        required=False,
-        widget=APISelectMultiple(
-            api_url="/api/virtualization/clusters/"
-        )
+        required=False
     )
     tenant_groups = DynamicModelMultipleChoiceField(
         queryset=TenantGroup.objects.all(),
-        required=False,
-        widget=APISelectMultiple(
-            api_url="/api/tenancy/tenant-groups/"
-        )
+        required=False
     )
     tenants = DynamicModelMultipleChoiceField(
         queryset=Tenant.objects.all(),
-        required=False,
-        widget=APISelectMultiple(
-            api_url="/api/tenancy/tenants/"
-        )
+        required=False
     )
     tags = DynamicModelMultipleChoiceField(
         queryset=Tag.objects.all(),
         to_field_name='slug',
-        required=False,
-        widget=APISelectMultiple(
-            api_url="/api/extras/tags/"
-        )
+        required=False
     )
     data = JSONField(
         label=''
@@ -299,7 +275,6 @@ class ConfigContextFilterForm(BootstrapMixin, forms.Form):
         to_field_name='slug',
         required=False,
         widget=APISelectMultiple(
-            api_url="/api/dcim/regions/",
             value_field="slug",
         )
     )
@@ -308,7 +283,6 @@ class ConfigContextFilterForm(BootstrapMixin, forms.Form):
         to_field_name='slug',
         required=False,
         widget=APISelectMultiple(
-            api_url="/api/dcim/sites/",
             value_field="slug",
         )
     )
@@ -317,7 +291,6 @@ class ConfigContextFilterForm(BootstrapMixin, forms.Form):
         to_field_name='slug',
         required=False,
         widget=APISelectMultiple(
-            api_url="/api/dcim/device-roles/",
             value_field="slug",
         )
     )
@@ -326,7 +299,6 @@ class ConfigContextFilterForm(BootstrapMixin, forms.Form):
         to_field_name='slug',
         required=False,
         widget=APISelectMultiple(
-            api_url="/api/dcim/platforms/",
             value_field="slug",
         )
     )
@@ -335,24 +307,19 @@ class ConfigContextFilterForm(BootstrapMixin, forms.Form):
         to_field_name='slug',
         required=False,
         widget=APISelectMultiple(
-            api_url="/api/virtualization/cluster-groups/",
             value_field="slug",
         )
     )
     cluster_id = DynamicModelMultipleChoiceField(
         queryset=Cluster.objects.all(),
         required=False,
-        label='Cluster',
-        widget=APISelectMultiple(
-            api_url="/api/virtualization/clusters/",
-        )
+        label='Cluster'
     )
     tenant_group = DynamicModelMultipleChoiceField(
         queryset=TenantGroup.objects.all(),
         to_field_name='slug',
         required=False,
         widget=APISelectMultiple(
-            api_url="/api/tenancy/tenant-groups/",
             value_field="slug",
         )
     )
@@ -361,7 +328,6 @@ class ConfigContextFilterForm(BootstrapMixin, forms.Form):
         to_field_name='slug',
         required=False,
         widget=APISelectMultiple(
-            api_url="/api/tenancy/tenants/",
             value_field="slug",
         )
     )
@@ -370,7 +336,6 @@ class ConfigContextFilterForm(BootstrapMixin, forms.Form):
         to_field_name='slug',
         required=False,
         widget=APISelectMultiple(
-            api_url="/api/extras/tags/",
             value_field="slug",
         )
     )

+ 22 - 106
netbox/ipam/forms.py

@@ -78,10 +78,7 @@ class VRFBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm
     )
     tenant = DynamicModelChoiceField(
         queryset=Tenant.objects.all(),
-        required=False,
-        widget=APISelect(
-            api_url="/api/tenancy/tenants/"
-        )
+        required=False
     )
     enforce_unique = forms.NullBooleanField(
         required=False,
@@ -150,10 +147,7 @@ class RIRFilterForm(BootstrapMixin, forms.Form):
 
 class AggregateForm(BootstrapMixin, CustomFieldModelForm):
     rir = DynamicModelChoiceField(
-        queryset=RIR.objects.all(),
-        widget=APISelect(
-            api_url="/api/ipam/rirs/"
-        )
+        queryset=RIR.objects.all()
     )
     tags = TagField(
         required=False
@@ -196,10 +190,7 @@ class AggregateBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEd
     rir = DynamicModelChoiceField(
         queryset=RIR.objects.all(),
         required=False,
-        label='RIR',
-        widget=APISelect(
-            api_url="/api/ipam/rirs/"
-        )
+        label='RIR'
     )
     date_added = forms.DateField(
         required=False
@@ -236,7 +227,6 @@ class AggregateFilterForm(BootstrapMixin, CustomFieldFilterForm):
         required=False,
         label='RIR',
         widget=APISelectMultiple(
-            api_url="/api/ipam/rirs/",
             value_field="slug",
         )
     )
@@ -276,16 +266,12 @@ class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
     vrf = DynamicModelChoiceField(
         queryset=VRF.objects.all(),
         required=False,
-        label='VRF',
-        widget=APISelect(
-            api_url="/api/ipam/vrfs/",
-        )
+        label='VRF'
     )
     site = DynamicModelChoiceField(
         queryset=Site.objects.all(),
         required=False,
         widget=APISelect(
-            api_url="/api/dcim/sites/",
             filter_for={
                 'vlan_group': 'site_id',
                 'vlan': 'site_id',
@@ -300,7 +286,6 @@ class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
         required=False,
         label='VLAN group',
         widget=APISelect(
-            api_url='/api/ipam/vlan-groups/',
             filter_for={
                 'vlan': 'group_id'
             },
@@ -314,16 +299,12 @@ class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
         required=False,
         label='VLAN',
         widget=APISelect(
-            api_url='/api/ipam/vlans/',
             display_field='display_name'
         )
     )
     role = DynamicModelChoiceField(
         queryset=Role.objects.all(),
-        required=False,
-        widget=APISelect(
-            api_url="/api/ipam/roles/"
-        )
+        required=False
     )
     tags = TagField(required=False)
 
@@ -447,18 +428,12 @@ class PrefixBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF
     )
     site = DynamicModelChoiceField(
         queryset=Site.objects.all(),
-        required=False,
-        widget=APISelect(
-            api_url="/api/dcim/sites/"
-        )
+        required=False
     )
     vrf = DynamicModelChoiceField(
         queryset=VRF.objects.all(),
         required=False,
-        label='VRF',
-        widget=APISelect(
-            api_url="/api/ipam/vrfs/"
-        )
+        label='VRF'
     )
     prefix_length = forms.IntegerField(
         min_value=PREFIX_LENGTH_MIN,
@@ -467,10 +442,7 @@ class PrefixBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF
     )
     tenant = DynamicModelChoiceField(
         queryset=Tenant.objects.all(),
-        required=False,
-        widget=APISelect(
-            api_url="/api/tenancy/tenants/"
-        )
+        required=False
     )
     status = forms.ChoiceField(
         choices=add_blank_choice(PrefixStatusChoices),
@@ -479,10 +451,7 @@ class PrefixBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF
     )
     role = DynamicModelChoiceField(
         queryset=Role.objects.all(),
-        required=False,
-        widget=APISelect(
-            api_url="/api/ipam/roles/"
-        )
+        required=False
     )
     is_pool = forms.NullBooleanField(
         required=False,
@@ -536,7 +505,6 @@ class PrefixFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm)
         required=False,
         label='VRF',
         widget=APISelectMultiple(
-            api_url="/api/ipam/vrfs/",
             null_option=True,
         )
     )
@@ -550,7 +518,6 @@ class PrefixFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm)
         to_field_name='slug',
         required=False,
         widget=APISelectMultiple(
-            api_url="/api/dcim/regions/",
             value_field="slug",
             filter_for={
                 'site': 'region'
@@ -562,7 +529,6 @@ class PrefixFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm)
         to_field_name='slug',
         required=False,
         widget=APISelectMultiple(
-            api_url="/api/dcim/sites/",
             value_field="slug",
             null_option=True,
         )
@@ -572,7 +538,6 @@ class PrefixFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm)
         to_field_name='slug',
         required=False,
         widget=APISelectMultiple(
-            api_url="/api/ipam/roles/",
             value_field="slug",
             null_option=True,
         )
@@ -603,17 +568,13 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldModel
     vrf = DynamicModelChoiceField(
         queryset=VRF.objects.all(),
         required=False,
-        label='VRF',
-        widget=APISelect(
-            api_url="/api/ipam/vrfs/"
-        )
+        label='VRF'
     )
     nat_site = DynamicModelChoiceField(
         queryset=Site.objects.all(),
         required=False,
         label='Site',
         widget=APISelect(
-            api_url="/api/dcim/sites/",
             filter_for={
                 'nat_rack': 'site_id',
                 'nat_device': 'site_id'
@@ -625,7 +586,6 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldModel
         required=False,
         label='Rack',
         widget=APISelect(
-            api_url='/api/dcim/racks/',
             display_field='display_name',
             filter_for={
                 'nat_device': 'rack_id'
@@ -640,19 +600,17 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldModel
         required=False,
         label='Device',
         widget=APISelect(
-            api_url='/api/dcim/devices/',
             display_field='display_name',
             filter_for={
                 'nat_inside': 'device_id'
             }
         )
     )
-    nat_vrf = forms.ModelChoiceField(
+    nat_vrf = DynamicModelChoiceField(
         queryset=VRF.objects.all(),
         required=False,
         label='VRF',
         widget=APISelect(
-            api_url="/api/ipam/vrfs/",
             filter_for={
                 'nat_inside': 'vrf_id'
             }
@@ -663,7 +621,6 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldModel
         required=False,
         label='IP Address',
         widget=APISelect(
-            api_url='/api/ipam/ip-addresses/',
             display_field='address'
         )
     )
@@ -761,10 +718,7 @@ class IPAddressBulkAddForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
     vrf = DynamicModelChoiceField(
         queryset=VRF.objects.all(),
         required=False,
-        label='VRF',
-        widget=APISelect(
-            api_url="/api/ipam/vrfs/"
-        )
+        label='VRF'
     )
 
     class Meta:
@@ -913,10 +867,7 @@ class IPAddressBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEd
     vrf = DynamicModelChoiceField(
         queryset=VRF.objects.all(),
         required=False,
-        label='VRF',
-        widget=APISelect(
-            api_url="/api/ipam/vrfs/"
-        )
+        label='VRF'
     )
     mask_length = forms.IntegerField(
         min_value=IPADDRESS_MASK_LENGTH_MIN,
@@ -925,10 +876,7 @@ class IPAddressBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEd
     )
     tenant = DynamicModelChoiceField(
         queryset=Tenant.objects.all(),
-        required=False,
-        widget=APISelect(
-            api_url="/api/tenancy/tenants/"
-        )
+        required=False
     )
     status = forms.ChoiceField(
         choices=add_blank_choice(IPAddressStatusChoices),
@@ -960,10 +908,7 @@ class IPAddressAssignForm(BootstrapMixin, forms.Form):
         queryset=VRF.objects.all(),
         required=False,
         label='VRF',
-        empty_label='Global',
-        widget=APISelect(
-            api_url="/api/ipam/vrfs/"
-        )
+        empty_label='Global'
     )
     q = forms.CharField(
         required=False,
@@ -1007,7 +952,6 @@ class IPAddressFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterFo
         required=False,
         label='VRF',
         widget=APISelectMultiple(
-            api_url="/api/ipam/vrfs/",
             null_option=True,
         )
     )
@@ -1038,10 +982,7 @@ class IPAddressFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterFo
 class VLANGroupForm(BootstrapMixin, forms.ModelForm):
     site = DynamicModelChoiceField(
         queryset=Site.objects.all(),
-        required=False,
-        widget=APISelect(
-            api_url="/api/dcim/sites/"
-        )
+        required=False
     )
     slug = SlugField()
 
@@ -1078,7 +1019,6 @@ class VLANGroupFilterForm(BootstrapMixin, forms.Form):
         to_field_name='slug',
         required=False,
         widget=APISelectMultiple(
-            api_url="/api/dcim/regions/",
             value_field="slug",
             filter_for={
                 'site': 'region',
@@ -1090,7 +1030,6 @@ class VLANGroupFilterForm(BootstrapMixin, forms.Form):
         to_field_name='slug',
         required=False,
         widget=APISelectMultiple(
-            api_url="/api/dcim/sites/",
             value_field="slug",
             null_option=True,
         )
@@ -1106,7 +1045,6 @@ class VLANForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
         queryset=Site.objects.all(),
         required=False,
         widget=APISelect(
-            api_url="/api/dcim/sites/",
             filter_for={
                 'group': 'site_id'
             },
@@ -1117,17 +1055,11 @@ class VLANForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
     )
     group = DynamicModelChoiceField(
         queryset=VLANGroup.objects.all(),
-        required=False,
-        widget=APISelect(
-            api_url='/api/ipam/vlan-groups/',
-        )
+        required=False
     )
     role = DynamicModelChoiceField(
         queryset=Role.objects.all(),
-        required=False,
-        widget=APISelect(
-            api_url="/api/ipam/roles/"
-        )
+        required=False
     )
     tags = TagField(required=False)
 
@@ -1222,24 +1154,15 @@ class VLANBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
     )
     site = DynamicModelChoiceField(
         queryset=Site.objects.all(),
-        required=False,
-        widget=APISelect(
-            api_url="/api/dcim/sites/"
-        )
+        required=False
     )
     group = DynamicModelChoiceField(
         queryset=VLANGroup.objects.all(),
-        required=False,
-        widget=APISelect(
-            api_url="/api/ipam/vlan-groups/"
-        )
+        required=False
     )
     tenant = DynamicModelChoiceField(
         queryset=Tenant.objects.all(),
-        required=False,
-        widget=APISelect(
-            api_url="/api/tenancy/tenants/"
-        )
+        required=False
     )
     status = forms.ChoiceField(
         choices=add_blank_choice(VLANStatusChoices),
@@ -1248,10 +1171,7 @@ class VLANBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
     )
     role = DynamicModelChoiceField(
         queryset=Role.objects.all(),
-        required=False,
-        widget=APISelect(
-            api_url="/api/ipam/roles/"
-        )
+        required=False
     )
     description = forms.CharField(
         max_length=100,
@@ -1276,7 +1196,6 @@ class VLANFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
         to_field_name='slug',
         required=False,
         widget=APISelectMultiple(
-            api_url="/api/dcim/regions/",
             value_field="slug",
             filter_for={
                 'site': 'region',
@@ -1289,7 +1208,6 @@ class VLANFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
         to_field_name='slug',
         required=False,
         widget=APISelectMultiple(
-            api_url="/api/dcim/sites/",
             value_field="slug",
             null_option=True,
         )
@@ -1299,7 +1217,6 @@ class VLANFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
         required=False,
         label='VLAN group',
         widget=APISelectMultiple(
-            api_url="/api/ipam/vlan-groups/",
             null_option=True,
         )
     )
@@ -1313,7 +1230,6 @@ class VLANFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
         to_field_name='slug',
         required=False,
         widget=APISelectMultiple(
-            api_url="/api/ipam/roles/",
             value_field="slug",
             null_option=True,
         )

+ 3 - 13
netbox/secrets/forms.py

@@ -72,10 +72,7 @@ class SecretRoleCSVForm(forms.ModelForm):
 
 class SecretForm(BootstrapMixin, CustomFieldModelForm):
     device = DynamicModelChoiceField(
-        queryset=Device.objects.all(),
-        widget=APISelect(
-            api_url="/api/dcim/devices/"
-        )
+        queryset=Device.objects.all()
     )
     plaintext = forms.CharField(
         max_length=SECRET_PLAINTEXT_MAX_LENGTH,
@@ -94,10 +91,7 @@ class SecretForm(BootstrapMixin, CustomFieldModelForm):
         widget=forms.PasswordInput()
     )
     role = DynamicModelChoiceField(
-        queryset=SecretRole.objects.all(),
-        widget=APISelect(
-            api_url="/api/secrets/secret-roles/"
-        )
+        queryset=SecretRole.objects.all()
     )
     tags = TagField(
         required=False
@@ -166,10 +160,7 @@ class SecretBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF
     )
     role = DynamicModelChoiceField(
         queryset=SecretRole.objects.all(),
-        required=False,
-        widget=APISelect(
-            api_url="/api/secrets/secret-roles/"
-        )
+        required=False
     )
     name = forms.CharField(
         max_length=100,
@@ -193,7 +184,6 @@ class SecretFilterForm(BootstrapMixin, CustomFieldFilterForm):
         to_field_name='slug',
         required=False,
         widget=APISelectMultiple(
-            api_url="/api/secrets/secret-roles/",
             value_field="slug",
         )
     )

+ 3 - 16
netbox/tenancy/forms.py

@@ -44,10 +44,7 @@ class TenantForm(BootstrapMixin, CustomFieldModelForm):
     slug = SlugField()
     group = DynamicModelChoiceField(
         queryset=TenantGroup.objects.all(),
-        required=False,
-        widget=APISelect(
-            api_url="/api/tenancy/tenant-groups/"
-        )
+        required=False
     )
     comments = CommentField()
     tags = TagField(
@@ -89,10 +86,7 @@ class TenantBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF
     )
     group = DynamicModelChoiceField(
         queryset=TenantGroup.objects.all(),
-        required=False,
-        widget=APISelect(
-            api_url="/api/tenancy/tenant-groups/"
-        )
+        required=False
     )
 
     class Meta:
@@ -112,7 +106,6 @@ class TenantFilterForm(BootstrapMixin, CustomFieldFilterForm):
         to_field_name='slug',
         required=False,
         widget=APISelectMultiple(
-            api_url="/api/tenancy/tenant-groups/",
             value_field="slug",
             null_option=True,
         )
@@ -129,7 +122,6 @@ class TenancyForm(forms.Form):
         queryset=TenantGroup.objects.all(),
         required=False,
         widget=APISelect(
-            api_url="/api/tenancy/tenant-groups/",
             filter_for={
                 'tenant': 'group_id',
             },
@@ -140,10 +132,7 @@ class TenancyForm(forms.Form):
     )
     tenant = DynamicModelChoiceField(
         queryset=Tenant.objects.all(),
-        required=False,
-        widget=APISelect(
-            api_url='/api/tenancy/tenants/'
-        )
+        required=False
     )
 
     def __init__(self, *args, **kwargs):
@@ -164,7 +153,6 @@ class TenancyFilterForm(forms.Form):
         to_field_name='slug',
         required=False,
         widget=APISelectMultiple(
-            api_url="/api/tenancy/tenant-groups/",
             value_field="slug",
             null_option=True,
             filter_for={
@@ -177,7 +165,6 @@ class TenancyFilterForm(forms.Form):
         to_field_name='slug',
         required=False,
         widget=APISelectMultiple(
-            api_url="/api/tenancy/tenants/",
             value_field="slug",
             null_option=True,
         )

+ 18 - 3
netbox/utilities/forms.py

@@ -10,6 +10,7 @@ from django.conf import settings
 from django.contrib.postgres.forms.jsonb import JSONField as _JSONField, InvalidJSONInput
 from django.db.models import Count
 from django.forms import BoundField
+from django.urls import reverse
 
 from .choices import unpack_grouped_choices
 from .constants import *
@@ -252,7 +253,7 @@ class APISelect(SelectWithDisabled):
     """
     A select widget populated via an API call
 
-    :param api_url: API URL
+    :param api_url: API endpoint URL. Required if not set automatically by the parent field.
     :param display_field: (Optional) Field to display for child in selection list. Defaults to `name`.
     :param value_field: (Optional) Field to use for the option value in selection list. Defaults to `id`.
     :param disabled_indicator: (Optional) Mark option as disabled if this field equates true.
@@ -269,7 +270,7 @@ class APISelect(SelectWithDisabled):
     """
     def __init__(
         self,
-        api_url,
+        api_url=None,
         display_field=None,
         value_field=None,
         disabled_indicator=None,
@@ -285,7 +286,8 @@ class APISelect(SelectWithDisabled):
         super().__init__(*args, **kwargs)
 
         self.attrs['class'] = 'netbox-select2-api'
-        self.attrs['data-url'] = '/{}{}'.format(settings.BASE_PATH, api_url.lstrip('/'))  # Inject BASE_PATH
+        if api_url:
+            self.attrs['data-url'] = '/{}{}'.format(settings.BASE_PATH, api_url.lstrip('/'))  # Inject BASE_PATH
         if full:
             self.attrs['data-full'] = full
         if display_field:
@@ -566,6 +568,10 @@ class TagFilterField(forms.MultipleChoiceField):
 
 class DynamicModelChoiceMixin:
     filter = django_filters.ModelChoiceFilter
+    widget = APISelect
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
 
     def get_bound_field(self, form, field_name):
         bound_field = BoundField(form, self, field_name)
@@ -579,6 +585,14 @@ class DynamicModelChoiceMixin:
         else:
             self.queryset = self.queryset.none()
 
+        # Set the data URL on the APISelect widget (if not already set)
+        widget = bound_field.field.widget
+        if not widget.attrs.get('data-url'):
+            app_label = self.queryset.model._meta.app_label
+            model_name = self.queryset.model._meta.model_name
+            data_url = reverse('{}-api:{}-list'.format(app_label, model_name))
+            widget.attrs['data-url'] = data_url
+
         return bound_field
 
 
@@ -595,6 +609,7 @@ class DynamicModelMultipleChoiceField(DynamicModelChoiceMixin, forms.ModelMultip
     A multiple-choice version of DynamicModelChoiceField.
     """
     filter = django_filters.ModelMultipleChoiceFilter
+    widget = APISelectMultiple
 
 
 class LaxURLField(forms.URLField):

+ 14 - 76
netbox/virtualization/forms.py

@@ -9,7 +9,7 @@ from dcim.models import Device, DeviceRole, Interface, Platform, Rack, Region, S
 from extras.forms import (
     AddRemoveTagsForm, CustomFieldBulkEditForm, CustomFieldModelCSVForm, CustomFieldModelForm, CustomFieldFilterForm,
 )
-from ipam.models import IPAddress, VLANGroup, VLAN
+from ipam.models import IPAddress, VLAN
 from tenancy.forms import TenancyFilterForm, TenancyForm
 from tenancy.models import Tenant
 from utilities.forms import (
@@ -77,24 +77,15 @@ class ClusterGroupCSVForm(forms.ModelForm):
 
 class ClusterForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
     type = DynamicModelChoiceField(
-        queryset=ClusterType.objects.all(),
-        widget=APISelect(
-            api_url="/api/virtualization/cluster-types/"
-        )
+        queryset=ClusterType.objects.all()
     )
     group = DynamicModelChoiceField(
         queryset=ClusterGroup.objects.all(),
-        required=False,
-        widget=APISelect(
-            api_url="/api/virtualization/cluster-groups/"
-        )
+        required=False
     )
     site = DynamicModelChoiceField(
         queryset=Site.objects.all(),
-        required=False,
-        widget=APISelect(
-            api_url="/api/dcim/sites/"
-        )
+        required=False
     )
     comments = CommentField()
     tags = TagField(
@@ -157,31 +148,19 @@ class ClusterBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEdit
     )
     type = DynamicModelChoiceField(
         queryset=ClusterType.objects.all(),
-        required=False,
-        widget=APISelect(
-            api_url="/api/virtualization/cluster-types/"
-        )
+        required=False
     )
     group = DynamicModelChoiceField(
         queryset=ClusterGroup.objects.all(),
-        required=False,
-        widget=APISelect(
-            api_url="/api/virtualization/cluster-groups/"
-        )
+        required=False
     )
     tenant = DynamicModelChoiceField(
         queryset=Tenant.objects.all(),
-        required=False,
-        widget=APISelect(
-            api_url="/api/tenancy/tenants/"
-        )
+        required=False
     )
     site = DynamicModelChoiceField(
         queryset=Site.objects.all(),
-        required=False,
-        widget=APISelect(
-            api_url="/api/dcim/sites/"
-        )
+        required=False
     )
     comments = CommentField(
         widget=SmallTextarea,
@@ -205,7 +184,6 @@ class ClusterFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm
         to_field_name='slug',
         required=False,
         widget=APISelectMultiple(
-            api_url="/api/virtualization/cluster-types/",
             value_field='slug',
         )
     )
@@ -214,7 +192,6 @@ class ClusterFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm
         to_field_name='slug',
         required=False,
         widget=APISelectMultiple(
-            api_url="/api/dcim/regions/",
             value_field="slug",
             filter_for={
                 'site': 'region'
@@ -226,7 +203,6 @@ class ClusterFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm
         to_field_name='slug',
         required=False,
         widget=APISelectMultiple(
-            api_url="/api/dcim/sites/",
             value_field='slug',
             null_option=True,
         )
@@ -236,7 +212,6 @@ class ClusterFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm
         to_field_name='slug',
         required=False,
         widget=APISelectMultiple(
-            api_url="/api/virtualization/cluster-groups/",
             value_field='slug',
             null_option=True,
         )
@@ -249,7 +224,6 @@ class ClusterAddDevicesForm(BootstrapMixin, forms.Form):
         queryset=Region.objects.all(),
         required=False,
         widget=APISelect(
-            api_url="/api/dcim/regions/",
             filter_for={
                 "site": "region_id",
             },
@@ -262,7 +236,6 @@ class ClusterAddDevicesForm(BootstrapMixin, forms.Form):
         queryset=Site.objects.all(),
         required=False,
         widget=APISelect(
-            api_url='/api/dcim/sites/',
             filter_for={
                 "rack": "site_id",
                 "devices": "site_id",
@@ -273,7 +246,6 @@ class ClusterAddDevicesForm(BootstrapMixin, forms.Form):
         queryset=Rack.objects.all(),
         required=False,
         widget=APISelect(
-            api_url='/api/dcim/racks/',
             filter_for={
                 "devices": "rack_id"
             },
@@ -285,7 +257,6 @@ class ClusterAddDevicesForm(BootstrapMixin, forms.Form):
     devices = DynamicModelMultipleChoiceField(
         queryset=Device.objects.filter(cluster__isnull=True),
         widget=APISelectMultiple(
-            api_url='/api/dcim/devices/',
             display_field='display_name',
             disabled_indicator='cluster'
         )
@@ -334,7 +305,6 @@ class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
         queryset=ClusterGroup.objects.all(),
         required=False,
         widget=APISelect(
-            api_url='/api/virtualization/cluster-groups/',
             filter_for={
                 "cluster": "group_id",
             },
@@ -344,16 +314,12 @@ class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
         )
     )
     cluster = DynamicModelChoiceField(
-        queryset=Cluster.objects.all(),
-        widget=APISelect(
-            api_url='/api/virtualization/clusters/'
-        )
+        queryset=Cluster.objects.all()
     )
     role = DynamicModelChoiceField(
         queryset=DeviceRole.objects.all(),
         required=False,
         widget=APISelect(
-            api_url="/api/dcim/device-roles/",
             additional_query_params={
                 "vm_role": "True"
             }
@@ -361,10 +327,7 @@ class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
     )
     platform = DynamicModelChoiceField(
         queryset=Platform.objects.all(),
-        required=False,
-        widget=APISelect(
-            api_url='/api/dcim/platforms/'
-        )
+        required=False
     )
     tags = TagField(
         required=False
@@ -499,10 +462,7 @@ class VirtualMachineBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldB
     )
     cluster = DynamicModelChoiceField(
         queryset=Cluster.objects.all(),
-        required=False,
-        widget=APISelect(
-            api_url='/api/virtualization/clusters/'
-        )
+        required=False
     )
     role = DynamicModelChoiceField(
         queryset=DeviceRole.objects.filter(
@@ -510,7 +470,6 @@ class VirtualMachineBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldB
         ),
         required=False,
         widget=APISelect(
-            api_url="/api/dcim/device-roles/",
             additional_query_params={
                 "vm_role": "True"
             }
@@ -518,17 +477,11 @@ class VirtualMachineBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldB
     )
     tenant = DynamicModelChoiceField(
         queryset=Tenant.objects.all(),
-        required=False,
-        widget=APISelect(
-            api_url='/api/tenancy/tenants/'
-        )
+        required=False
     )
     platform = DynamicModelChoiceField(
         queryset=Platform.objects.all(),
-        required=False,
-        widget=APISelect(
-            api_url='/api/dcim/platforms/'
-        )
+        required=False
     )
     vcpus = forms.IntegerField(
         required=False,
@@ -568,7 +521,6 @@ class VirtualMachineFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFil
         to_field_name='slug',
         required=False,
         widget=APISelectMultiple(
-            api_url='/api/virtualization/cluster-groups/',
             value_field="slug",
             null_option=True,
         )
@@ -578,7 +530,6 @@ class VirtualMachineFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFil
         to_field_name='slug',
         required=False,
         widget=APISelectMultiple(
-            api_url='/api/virtualization/cluster-types/',
             value_field="slug",
             null_option=True,
         )
@@ -586,17 +537,13 @@ class VirtualMachineFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFil
     cluster_id = DynamicModelMultipleChoiceField(
         queryset=Cluster.objects.all(),
         required=False,
-        label='Cluster',
-        widget=APISelectMultiple(
-            api_url='/api/virtualization/clusters/',
-        )
+        label='Cluster'
     )
     region = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         to_field_name='slug',
         required=False,
         widget=APISelectMultiple(
-            api_url='/api/dcim/regions/',
             value_field="slug",
             filter_for={
                 'site': 'region'
@@ -608,7 +555,6 @@ class VirtualMachineFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFil
         to_field_name='slug',
         required=False,
         widget=APISelectMultiple(
-            api_url='/api/dcim/sites/',
             value_field="slug",
             null_option=True,
         )
@@ -618,7 +564,6 @@ class VirtualMachineFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFil
         to_field_name='slug',
         required=False,
         widget=APISelectMultiple(
-            api_url='/api/dcim/device-roles/',
             value_field="slug",
             null_option=True,
             additional_query_params={
@@ -636,7 +581,6 @@ class VirtualMachineFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFil
         to_field_name='slug',
         required=False,
         widget=APISelectMultiple(
-            api_url='/api/dcim/platforms/',
             value_field="slug",
             null_option=True,
         )
@@ -657,7 +601,6 @@ class InterfaceForm(BootstrapMixin, forms.ModelForm):
         queryset=VLAN.objects.all(),
         required=False,
         widget=APISelect(
-            api_url="/api/ipam/vlans/",
             display_field='display_name',
             full=True,
             additional_query_params={
@@ -669,7 +612,6 @@ class InterfaceForm(BootstrapMixin, forms.ModelForm):
         queryset=VLAN.objects.all(),
         required=False,
         widget=APISelectMultiple(
-            api_url="/api/ipam/vlans/",
             display_field='display_name',
             full=True,
             additional_query_params={
@@ -766,7 +708,6 @@ class InterfaceCreateForm(BootstrapMixin, forms.Form):
         queryset=VLAN.objects.all(),
         required=False,
         widget=APISelect(
-            api_url="/api/ipam/vlans/",
             display_field='display_name',
             full=True,
             additional_query_params={
@@ -778,7 +719,6 @@ class InterfaceCreateForm(BootstrapMixin, forms.Form):
         queryset=VLAN.objects.all(),
         required=False,
         widget=APISelectMultiple(
-            api_url="/api/ipam/vlans/",
             display_field='display_name',
             full=True,
             additional_query_params={
@@ -836,7 +776,6 @@ class InterfaceBulkEditForm(BootstrapMixin, BulkEditForm):
         queryset=VLAN.objects.all(),
         required=False,
         widget=APISelect(
-            api_url="/api/ipam/vlans/",
             display_field='display_name',
             full=True,
             additional_query_params={
@@ -848,7 +787,6 @@ class InterfaceBulkEditForm(BootstrapMixin, BulkEditForm):
         queryset=VLAN.objects.all(),
         required=False,
         widget=APISelectMultiple(
-            api_url="/api/ipam/vlans/",
             display_field='display_name',
             full=True,
             additional_query_params={

Некоторые файлы не были показаны из-за большого количества измененных файлов