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

Add support for referencing peer field values in query_params

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

+ 10 - 14
netbox/circuits/forms.py

@@ -106,17 +106,15 @@ class ProviderFilterForm(BootstrapMixin, CustomFieldFilterForm):
     region = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         to_field_name='slug',
-        required=False,
-        widget=APISelectMultiple(
-            filter_for={
-                'site': 'region'
-            }
-        )
+        required=False
     )
     site = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),
         to_field_name='slug',
-        required=False
+        required=False,
+        query_params={
+            'region': '$region'
+        }
     )
     asn = forms.IntegerField(
         required=False,
@@ -282,17 +280,15 @@ class CircuitFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm
     region = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         to_field_name='slug',
-        required=False,
-        widget=APISelectMultiple(
-            filter_for={
-                'site': 'region'
-            }
-        )
+        required=False
     )
     site = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),
         to_field_name='slug',
-        required=False
+        required=False,
+        query_params={
+            'region': '$region'
+        }
     )
     commit_rate = forms.IntegerField(
         required=False,

+ 212 - 314
netbox/dcim/forms.py

@@ -68,27 +68,23 @@ class DeviceComponentFilterForm(BootstrapMixin, forms.Form):
     region = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         to_field_name='slug',
-        required=False,
-        widget=APISelectMultiple(
-            filter_for={
-                'site': 'region'
-            }
-        )
+        required=False
     )
     site = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),
         to_field_name='slug',
         required=False,
-        widget=APISelectMultiple(
-            filter_for={
-                'device_id': 'site',
-            }
-        )
+        query_params={
+            'region': '$region'
+        }
     )
     device_id = DynamicModelMultipleChoiceField(
         queryset=Device.objects.all(),
         required=False,
-        label='Device'
+        label='Device',
+        query_params={
+            'site': '$site'
+        }
     )
 
 
@@ -357,16 +353,14 @@ class SiteFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
 
 class RackGroupForm(BootstrapMixin, forms.ModelForm):
     site = DynamicModelChoiceField(
-        queryset=Site.objects.all(),
-        widget=APISelect(
-            filter_for={
-                'parent': 'site_id',
-            }
-        )
+        queryset=Site.objects.all()
     )
     parent = DynamicModelChoiceField(
         queryset=RackGroup.objects.all(),
-        required=False
+        required=False,
+        query_params={
+            'site_id': '$site'
+        }
     )
     slug = SlugField()
 
@@ -402,28 +396,24 @@ class RackGroupFilterForm(BootstrapMixin, forms.Form):
     region = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         to_field_name='slug',
-        required=False,
-        widget=APISelectMultiple(
-            filter_for={
-                'site': 'region',
-                'parent': 'region',
-            }
-        )
+        required=False
     )
     site = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),
         to_field_name='slug',
         required=False,
-        widget=APISelectMultiple(
-            filter_for={
-                'parent': 'site',
-            }
-        )
+        query_params={
+            'region': '$region'
+        }
     )
     parent = DynamicModelMultipleChoiceField(
         queryset=RackGroup.objects.all(),
         to_field_name='slug',
-        required=False
+        required=False,
+        query_params={
+            'region': '$region',
+            'site': '$site',
+        }
     )
 
 
@@ -458,16 +448,14 @@ class RackRoleCSVForm(CSVModelForm):
 
 class RackForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
     site = DynamicModelChoiceField(
-        queryset=Site.objects.all(),
-        widget=APISelect(
-            filter_for={
-                'group': 'site_id',
-            }
-        )
+        queryset=Site.objects.all()
     )
     group = DynamicModelChoiceField(
         queryset=RackGroup.objects.all(),
-        required=False
+        required=False,
+        query_params={
+            'site_id': '$site'
+        }
     )
     role = DynamicModelChoiceField(
         queryset=RackRole.objects.all(),
@@ -562,16 +550,14 @@ class RackBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
     )
     site = DynamicModelChoiceField(
         queryset=Site.objects.all(),
-        required=False,
-        widget=APISelect(
-            filter_for={
-                'group': 'site_id',
-            }
-        )
+        required=False
     )
     group = DynamicModelChoiceField(
         queryset=RackGroup.objects.all(),
-        required=False
+        required=False,
+        query_params={
+            'site_id': '$site'
+        }
     )
     tenant = DynamicModelChoiceField(
         queryset=Tenant.objects.all(),
@@ -649,30 +635,24 @@ class RackFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
     region = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         to_field_name='slug',
-        required=False,
-        widget=APISelectMultiple(
-            filter_for={
-                'site': 'region'
-            }
-        )
+        required=False
     )
     site = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),
         to_field_name='slug',
         required=False,
-        widget=APISelectMultiple(
-            filter_for={
-                'group_id': 'site'
-            }
-        )
+        query_params={
+            'region': '$region'
+        }
     )
     group_id = DynamicModelMultipleChoiceField(
-        queryset=RackGroup.objects.prefetch_related(
-            'site'
-        ),
+        queryset=RackGroup.objects.all(),
         required=False,
         label='Rack group',
-        null_option='None'
+        null_option='None',
+        query_params={
+            'site': '$site'
+        }
     )
     status = forms.MultipleChoiceField(
         choices=RackStatusChoices,
@@ -698,16 +678,13 @@ class RackElevationFilterForm(RackFilterForm):
         queryset=Rack.objects.all(),
         label='Rack',
         required=False,
-        display_field='display_name'
+        display_field='display_name',
+        query_params={
+            'site': '$site',
+            'group_id': '$group_id',
+        }
     )
 
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-
-        # Filter the rack field based on the site and group
-        self.fields['site'].widget.add_filter_for('id', 'site')
-        self.fields['group_id'].widget.add_filter_for('id', 'group_id')
-
 
 #
 # Rack reservations
@@ -716,25 +693,21 @@ class RackElevationFilterForm(RackFilterForm):
 class RackReservationForm(BootstrapMixin, TenancyForm, forms.ModelForm):
     site = DynamicModelChoiceField(
         queryset=Site.objects.all(),
-        required=False,
-        widget=APISelect(
-            filter_for={
-                'rack_group': 'site_id',
-                'rack': 'site_id',
-            }
-        )
+        required=False
     )
     rack_group = DynamicModelChoiceField(
         queryset=RackGroup.objects.all(),
         required=False,
-        widget=APISelect(
-            filter_for={
-                'rack': 'group_id'
-            }
-        )
+        query_params={
+            'site_id': '$site'
+        }
     )
     rack = DynamicModelChoiceField(
-        queryset=Rack.objects.all()
+        queryset=Rack.objects.all(),
+        query_params={
+            'site_id': '$site',
+            'group_id': 'rack',
+        }
     )
     units = NumericArrayField(
         base_field=forms.IntegerField(),
@@ -1011,16 +984,14 @@ class ComponentTemplateCreateForm(ComponentForm):
     """
     manufacturer = DynamicModelChoiceField(
         queryset=Manufacturer.objects.all(),
-        required=False,
-        widget=APISelect(
-            filter_for={
-                'device_type': 'manufacturer_id'
-            }
-        )
+        required=False
     )
     device_type = DynamicModelChoiceField(
         queryset=DeviceType.objects.all(),
-        display_field='model'
+        display_field='model',
+        query_params={
+            'manufacturer_id': '$manufacturer'
+        }
     )
     description = forms.CharField(
         required=False
@@ -1699,17 +1670,15 @@ class PlatformCSVForm(CSVModelForm):
 
 class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
     site = DynamicModelChoiceField(
-        queryset=Site.objects.all(),
-        widget=APISelect(
-            filter_for={
-                'rack': 'site_id'
-            }
-        )
+        queryset=Site.objects.all()
     )
     rack = DynamicModelChoiceField(
         queryset=Rack.objects.all(),
         required=False,
-        display_field='display_name'
+        display_field='display_name',
+        query_params={
+            'site_id': '$site'
+        }
     )
     position = forms.TypedChoiceField(
         required=False,
@@ -1717,22 +1686,22 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
         help_text="The lowest-numbered unit occupied by the device",
         widget=APISelect(
             api_url='/api/dcim/racks/{{rack}}/elevation/',
-            disabled_indicator='device'
+            disabled_indicator='device',
+            additional_query_params={
+                'face': '$face'
+            }
         )
     )
     manufacturer = DynamicModelChoiceField(
         queryset=Manufacturer.objects.all(),
-        required=False,
-        widget=APISelect(
-            filter_for={
-                'device_type': 'manufacturer_id',
-                'platform': 'manufacturer_id'
-            }
-        )
+        required=False
     )
     device_type = DynamicModelChoiceField(
         queryset=DeviceType.objects.all(),
-        display_field='model'
+        display_field='model',
+        query_params={
+            'manufacturer_id': '$manufacturer'
+        }
     )
     device_role = DynamicModelChoiceField(
         queryset=DeviceRole.objects.all()
@@ -1741,22 +1710,20 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
         queryset=Platform.objects.all(),
         required=False,
         query_params={
-            "manufacturer_id": "null"
+            'manufacturer_id': ['$manufacturer', 'null']
         }
     )
     cluster_group = DynamicModelChoiceField(
         queryset=ClusterGroup.objects.all(),
         required=False,
-        null_option='None',
-        widget=APISelect(
-            filter_for={
-                'cluster': 'group_id'
-            }
-        )
+        null_option='None'
     )
     cluster = DynamicModelChoiceField(
         queryset=Cluster.objects.all(),
-        required=False
+        required=False,
+        query_params={
+            'group_id': '$cluster_group'
+        }
     )
     comments = CommentField()
     local_context_data = JSONField(
@@ -1782,11 +1749,6 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
                                   "config context",
         }
         widgets = {
-            'face': StaticSelect2(
-                filter_for={
-                    'position': 'face'
-                }
-            ),
             'status': StaticSelect2(),
             'primary_ip4': StaticSelect2(),
             'primary_ip6': StaticSelect2(),
@@ -2084,39 +2046,33 @@ class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilt
     region = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         to_field_name='slug',
-        required=False,
-        widget=APISelectMultiple(
-            filter_for={
-                'site': 'region'
-            }
-        )
+        required=False
     )
     site = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),
         to_field_name='slug',
         required=False,
-        widget=APISelectMultiple(
-            filter_for={
-                'rack_group_id': 'site',
-                'rack_id': 'site',
-            }
-        )
+        query_params={
+            'region': '$region'
+        }
     )
     rack_group_id = DynamicModelMultipleChoiceField(
         queryset=RackGroup.objects.all(),
         required=False,
         label='Rack group',
-        widget=APISelectMultiple(
-            filter_for={
-                'rack_id': 'group_id',
-            }
-        )
+        query_params={
+            'site': '$site'
+        }
     )
     rack_id = DynamicModelMultipleChoiceField(
         queryset=Rack.objects.all(),
         required=False,
         label='Rack',
-        null_option='None'
+        null_option='None',
+        query_params={
+            'site': '$site',
+            'group_id': '$rack_group_id',
+        }
     )
     role = DynamicModelMultipleChoiceField(
         queryset=DeviceRole.objects.all(),
@@ -2125,21 +2081,20 @@ class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilt
         widget=APISelectMultiple(
         )
     )
-    manufacturer_id = DynamicModelMultipleChoiceField(
+    manufacturer = DynamicModelMultipleChoiceField(
         queryset=Manufacturer.objects.all(),
+        to_field_name='slug',
         required=False,
-        label='Manufacturer',
-        widget=APISelectMultiple(
-            filter_for={
-                'device_type_id': 'manufacturer_id',
-            }
-        )
+        label='Manufacturer'
     )
     device_type_id = DynamicModelMultipleChoiceField(
         queryset=DeviceType.objects.all(),
         required=False,
         label='Model',
-        display_field='model'
+        display_field='model',
+        query_params={
+            'manufacturer': '$manufacturer'
+        }
     )
     platform = DynamicModelMultipleChoiceField(
         queryset=Platform.objects.all(),
@@ -3424,30 +3379,26 @@ class ConnectCableToDeviceForm(BootstrapMixin, forms.ModelForm):
     termination_b_site = DynamicModelChoiceField(
         queryset=Site.objects.all(),
         label='Site',
-        required=False,
-        widget=APISelect(
-            filter_for={
-                'termination_b_rack': 'site_id',
-                'termination_b_device': 'site_id',
-            }
-        )
+        required=False
     )
     termination_b_rack = DynamicModelChoiceField(
         queryset=Rack.objects.all(),
         label='Rack',
         required=False,
         null_option='None',
-        widget=APISelect(
-            filter_for={
-                'termination_b_device': 'rack_id',
-            }
-        )
+        query_params={
+            'site_id': '$termination_b_site'
+        }
     )
     termination_b_device = DynamicModelChoiceField(
         queryset=Device.objects.all(),
         label='Device',
         required=False,
         display_field='display_name',
+        query_params={
+            'site_id': '$termination_b_site',
+            'rack_id': '$termination_b_rack',
+        },
         widget=APISelect(
             filter_for={
                 'termination_b_id': 'device_id',
@@ -3545,27 +3496,21 @@ class ConnectCableToCircuitTerminationForm(BootstrapMixin, forms.ModelForm):
     termination_b_provider = DynamicModelChoiceField(
         queryset=Provider.objects.all(),
         label='Provider',
-        required=False,
-        widget=APISelect(
-            filter_for={
-                'termination_b_circuit': 'provider_id',
-            }
-        )
+        required=False
     )
     termination_b_site = DynamicModelChoiceField(
         queryset=Site.objects.all(),
         label='Site',
-        required=False,
-        widget=APISelect(
-            filter_for={
-                'termination_b_circuit': 'site_id',
-            }
-        )
+        required=False
     )
     termination_b_circuit = DynamicModelChoiceField(
         queryset=Circuit.objects.all(),
         label='Circuit',
         display_field='cid',
+        query_params={
+            'provider_id': '$termination_b_provider',
+            'site_id': '$termination_b_site',
+        },
         widget=APISelect(
             filter_for={
                 'termination_b_id': 'circuit_id',
@@ -3595,29 +3540,25 @@ class ConnectCableToPowerFeedForm(BootstrapMixin, forms.ModelForm):
         queryset=Site.objects.all(),
         label='Site',
         required=False,
-        display_field='cid',
-        widget=APISelect(
-            filter_for={
-                'termination_b_rackgroup': 'site_id',
-                'termination_b_powerpanel': 'site_id',
-            }
-        )
+        display_field='cid'
     )
     termination_b_rackgroup = DynamicModelChoiceField(
         queryset=RackGroup.objects.all(),
         label='Rack Group',
         required=False,
         display_field='cid',
-        widget=APISelect(
-            filter_for={
-                'termination_b_powerpanel': 'rackgroup_id',
-            }
-        )
+        query_params={
+            'site_id': '$termination_b_site'
+        }
     )
     termination_b_powerpanel = DynamicModelChoiceField(
         queryset=PowerPanel.objects.all(),
         label='Power Panel',
         required=False,
+        query_params={
+            'site_id': '$termination_b_site',
+            'rack_group_id': '$termination_b_rackgroup',
+        },
         widget=APISelect(
             filter_for={
                 'termination_b_id': 'power_panel_id',
@@ -3843,34 +3784,21 @@ class CableFilterForm(BootstrapMixin, forms.Form):
     site = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),
         to_field_name='slug',
-        required=False,
-        widget=APISelectMultiple(
-            filter_for={
-                'rack_id': 'site',
-                'device_id': 'site',
-            }
-        )
+        required=False
     )
     tenant = DynamicModelMultipleChoiceField(
         queryset=Tenant.objects.all(),
         to_field_name='slug',
-        required=False,
-        widget=APISelectMultiple(
-            filter_for={
-                'device_id': 'tenant',
-            }
-        )
+        required=False
     )
     rack_id = DynamicModelMultipleChoiceField(
         queryset=Rack.objects.all(),
         required=False,
         label='Rack',
         null_option='None',
-        widget=APISelectMultiple(
-            filter_for={
-                'device_id': 'rack_id',
-            }
-        )
+        query_params={
+            'site': '$site'
+        }
     )
     type = forms.MultipleChoiceField(
         choices=add_blank_choice(CableTypeChoices),
@@ -3890,7 +3818,12 @@ class CableFilterForm(BootstrapMixin, forms.Form):
     device_id = DynamicModelMultipleChoiceField(
         queryset=Device.objects.all(),
         required=False,
-        label='Device'
+        label='Device',
+        query_params={
+            'site': '$site',
+            'tenant': '$tenant',
+            'rack_id': '$rack_id',
+        }
     )
     tag = TagFilterField(model)
 
@@ -3903,17 +3836,15 @@ class ConsoleConnectionFilterForm(BootstrapMixin, forms.Form):
     site = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),
         to_field_name='slug',
-        required=False,
-        widget=APISelectMultiple(
-            filter_for={
-                'device_id': 'site',
-            }
-        )
+        required=False
     )
     device_id = DynamicModelMultipleChoiceField(
         queryset=Device.objects.all(),
         required=False,
-        label='Device'
+        label='Device',
+        query_params={
+            'site': '$site'
+        }
     )
 
 
@@ -3921,17 +3852,15 @@ class PowerConnectionFilterForm(BootstrapMixin, forms.Form):
     site = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),
         to_field_name='slug',
-        required=False,
-        widget=APISelectMultiple(
-            filter_for={
-                'device_id': 'site',
-            }
-        )
+        required=False
     )
     device_id = DynamicModelMultipleChoiceField(
         queryset=Device.objects.all(),
         required=False,
-        label='Device'
+        label='Device',
+        query_params={
+            'site': '$site'
+        }
     )
 
 
@@ -3939,17 +3868,15 @@ class InterfaceConnectionFilterForm(BootstrapMixin, forms.Form):
     site = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),
         to_field_name='slug',
-        required=False,
-        widget=APISelectMultiple(
-            filter_for={
-                'device_id': 'site',
-            }
-        )
+        required=False
     )
     device_id = DynamicModelMultipleChoiceField(
         queryset=Device.objects.all(),
         required=False,
-        label='Device'
+        label='Device',
+        query_params={
+            'site': '$site'
+        }
     )
 
 
@@ -3967,27 +3894,23 @@ class DeviceSelectionForm(forms.Form):
 class VirtualChassisCreateForm(BootstrapMixin, forms.ModelForm):
     site = DynamicModelChoiceField(
         queryset=Site.objects.all(),
-        required=False,
-        widget=APISelect(
-            filter_for={
-                'rack': 'site_id',
-                'members': 'site_id',
-            }
-        )
+        required=False
     )
     rack = DynamicModelChoiceField(
         queryset=Rack.objects.all(),
         required=False,
         null_option='None',
-        widget=APISelect(
-            filter_for={
-                'members': 'rack_id'
-            }
-        )
+        query_params={
+            'site_id': '$site'
+        }
     )
     members = DynamicModelMultipleChoiceField(
         queryset=Device.objects.all(),
         required=False,
+        query_params={
+            'site_id': '$site',
+            'rack_id': '$rack',
+        }
     )
     initial_position = forms.IntegerField(
         initial=1,
@@ -4101,29 +4024,23 @@ class DeviceVCMembershipForm(forms.ModelForm):
 class VCMemberSelectForm(BootstrapMixin, forms.Form):
     site = DynamicModelChoiceField(
         queryset=Site.objects.all(),
-        required=False,
-        widget=APISelect(
-            filter_for={
-                'rack': 'site_id',
-                'device': 'site_id',
-            }
-        )
+        required=False
     )
     rack = DynamicModelChoiceField(
         queryset=Rack.objects.all(),
         required=False,
         null_option='None',
-        widget=APISelect(
-            filter_for={
-                'device': 'rack_id'
-            }
-        )
+        query_params={
+            'site_id': '$site'
+        }
     )
     device = DynamicModelChoiceField(
         queryset=Device.objects.all(),
         display_field='display_name',
         query_params={
-            'virtual_chassis_id': 'null'
+            'site_id': '$site',
+            'rack_id': '$rack',
+            'virtual_chassis_id': 'null',
         }
     )
 
@@ -4172,34 +4089,30 @@ class VirtualChassisFilterForm(BootstrapMixin, CustomFieldFilterForm):
     region = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         to_field_name='slug',
-        required=False,
-        widget=APISelectMultiple(
-            filter_for={
-                'site': 'region'
-            }
-        )
+        required=False
     )
     site = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),
         to_field_name='slug',
-        required=False
+        required=False,
+        query_params={
+            'region': '$region'
+        }
     )
     tenant_group = DynamicModelMultipleChoiceField(
         queryset=TenantGroup.objects.all(),
         to_field_name='slug',
         required=False,
-        null_option='None',
-        widget=APISelectMultiple(
-            filter_for={
-                'tenant': 'group'
-            }
-        )
+        null_option='None'
     )
     tenant = DynamicModelMultipleChoiceField(
         queryset=Tenant.objects.all(),
         to_field_name='slug',
         required=False,
-        null_option='None'
+        null_option='None',
+        query_params={
+            'group': '$tenant_group'
+        }
     )
     tag = TagFilterField(model)
 
@@ -4210,16 +4123,14 @@ class VirtualChassisFilterForm(BootstrapMixin, CustomFieldFilterForm):
 
 class PowerPanelForm(BootstrapMixin, forms.ModelForm):
     site = DynamicModelChoiceField(
-        queryset=Site.objects.all(),
-        widget=APISelect(
-            filter_for={
-                'rack_group': 'site_id',
-            }
-        )
+        queryset=Site.objects.all()
     )
     rack_group = DynamicModelChoiceField(
         queryset=RackGroup.objects.all(),
-        required=False
+        required=False,
+        query_params={
+            'site_id': '$site'
+        }
     )
     tags = DynamicModelMultipleChoiceField(
         queryset=Tag.objects.all(),
@@ -4266,16 +4177,14 @@ class PowerPanelBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm):
     )
     site = DynamicModelChoiceField(
         queryset=Site.objects.all(),
-        required=False,
-        widget=APISelect(
-            filter_for={
-                'rack_group': 'site_id',
-            }
-        )
+        required=False
     )
     rack_group = DynamicModelChoiceField(
         queryset=RackGroup.objects.all(),
-        required=False
+        required=False,
+        query_params={
+            'site_id': '$site'
+        }
     )
 
     class Meta:
@@ -4293,28 +4202,24 @@ class PowerPanelFilterForm(BootstrapMixin, CustomFieldFilterForm):
     region = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         to_field_name='slug',
-        required=False,
-        widget=APISelectMultiple(
-            filter_for={
-                'site': 'region'
-            }
-        )
+        required=False
     )
     site = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),
         to_field_name='slug',
         required=False,
-        widget=APISelectMultiple(
-            filter_for={
-                'rack_group_id': 'site',
-            }
-        )
+        query_params={
+            'region': '$region'
+        }
     )
     rack_group_id = DynamicModelMultipleChoiceField(
         queryset=RackGroup.objects.all(),
         required=False,
         label='Rack group (ID)',
-        null_option='None'
+        null_option='None',
+        query_params={
+            'site': '$site'
+        }
     )
     tag = TagFilterField(model)
 
@@ -4326,20 +4231,20 @@ class PowerPanelFilterForm(BootstrapMixin, CustomFieldFilterForm):
 class PowerFeedForm(BootstrapMixin, CustomFieldModelForm):
     site = DynamicModelChoiceField(
         queryset=Site.objects.all(),
-        required=False,
-        widget=APISelect(
-            filter_for={
-                'power_panel': 'site_id',
-                'rack': 'site_id',
-            }
-        )
+        required=False
     )
     power_panel = DynamicModelChoiceField(
-        queryset=PowerPanel.objects.all()
+        queryset=PowerPanel.objects.all(),
+        query_params={
+            'site_id': '$site'
+        }
     )
     rack = DynamicModelChoiceField(
         queryset=Rack.objects.all(),
-        required=False
+        required=False,
+        query_params={
+            'site_id': '$site'
+        }
     )
     comments = CommentField()
     tags = DynamicModelMultipleChoiceField(
@@ -4445,12 +4350,7 @@ class PowerFeedBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEd
     )
     power_panel = DynamicModelChoiceField(
         queryset=PowerPanel.objects.all(),
-        required=False,
-        widget=APISelect(
-            filter_for={
-                'rackgroup': 'site_id',
-            }
-        )
+        required=False
     )
     rack = DynamicModelChoiceField(
         queryset=Rack.objects.all(),
@@ -4509,35 +4409,33 @@ class PowerFeedFilterForm(BootstrapMixin, CustomFieldFilterForm):
     region = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         to_field_name='slug',
-        required=False,
-        widget=APISelectMultiple(
-            filter_for={
-                'site': 'region'
-            }
-        )
+        required=False
     )
     site = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),
         to_field_name='slug',
         required=False,
-        widget=APISelectMultiple(
-            filter_for={
-                'power_panel_id': 'site',
-                'rack_id': 'site',
-            }
-        )
+        query_params={
+            'region': '$region'
+        }
     )
     power_panel_id = DynamicModelMultipleChoiceField(
         queryset=PowerPanel.objects.all(),
         required=False,
         label='Power panel',
-        null_option='None'
+        null_option='None',
+        query_params={
+            'site': '$site'
+        }
     )
     rack_id = DynamicModelMultipleChoiceField(
         queryset=Rack.objects.all(),
         required=False,
         label='Rack',
-        null_option='None'
+        null_option='None',
+        query_params={
+            'site': '$site'
+        }
     )
     status = forms.MultipleChoiceField(
         choices=PowerFeedStatusChoices,

+ 62 - 88
netbox/ipam/forms.py

@@ -257,30 +257,26 @@ class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
     site = DynamicModelChoiceField(
         queryset=Site.objects.all(),
         required=False,
-        null_option='None',
-        widget=APISelect(
-            filter_for={
-                'vlan_group': 'site_id',
-                'vlan': 'site_id',
-            }
-        )
+        null_option='None'
     )
     vlan_group = DynamicModelChoiceField(
         queryset=VLANGroup.objects.all(),
         required=False,
         label='VLAN group',
         null_option='None',
-        widget=APISelect(
-            filter_for={
-                'vlan': 'group_id'
-            }
-        )
+        query_params={
+            'site_id': '$site'
+        }
     )
     vlan = DynamicModelChoiceField(
         queryset=VLAN.objects.all(),
         required=False,
         label='VLAN',
-        display_field='display_name'
+        display_field='display_name',
+        query_params={
+            'site_id': '$site',
+            'group_id': '$vlan_group',
+        }
     )
     role = DynamicModelChoiceField(
         queryset=Role.objects.all(),
@@ -470,18 +466,16 @@ class PrefixFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm)
     region = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         to_field_name='slug',
-        required=False,
-        widget=APISelectMultiple(
-            filter_for={
-                'site': 'region'
-            }
-        )
+        required=False
     )
     site = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),
         to_field_name='slug',
         required=False,
-        null_option='None'
+        null_option='None',
+        query_params={
+            'region': '$region'
+        }
     )
     role = DynamicModelMultipleChoiceField(
         queryset=Role.objects.all(),
@@ -506,30 +500,26 @@ class PrefixFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm)
 class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldModelForm):
     device = DynamicModelChoiceField(
         queryset=Device.objects.all(),
-        required=False,
-        widget=APISelect(
-            filter_for={
-                'interface': 'device_id'
-            }
-        )
+        required=False
     )
     interface = DynamicModelChoiceField(
         queryset=Interface.objects.all(),
-        required=False
+        required=False,
+        query_params={
+            'device_id': '$device'
+        }
     )
     virtual_machine = DynamicModelChoiceField(
         queryset=VirtualMachine.objects.all(),
-        required=False,
-        widget=APISelect(
-            filter_for={
-                'vminterface': 'virtual_machine_id'
-            }
-        )
+        required=False
     )
     vminterface = DynamicModelChoiceField(
         queryset=VMInterface.objects.all(),
         required=False,
-        label='Interface'
+        label='Interface',
+        query_params={
+            'virtual_machine_id': '$virtual_machine'
+        }
     )
     vrf = DynamicModelChoiceField(
         queryset=VRF.objects.all(),
@@ -539,13 +529,7 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldModel
     nat_site = DynamicModelChoiceField(
         queryset=Site.objects.all(),
         required=False,
-        label='Site',
-        widget=APISelect(
-            filter_for={
-                'nat_rack': 'site_id',
-                'nat_device': 'site_id'
-            }
-        )
+        label='Site'
     )
     nat_rack = DynamicModelChoiceField(
         queryset=Rack.objects.all(),
@@ -553,38 +537,34 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldModel
         label='Rack',
         display_field='display_name',
         null_option='None',
-        widget=APISelect(
-            filter_for={
-                'nat_device': 'rack_id'
-            }
-        )
+        query_params={
+            'site_id': '$site'
+        }
     )
     nat_device = DynamicModelChoiceField(
         queryset=Device.objects.all(),
         required=False,
         label='Device',
         display_field='display_name',
-        widget=APISelect(
-            filter_for={
-                'nat_inside': 'device_id'
-            }
-        )
+        query_params={
+            'site_id': '$site',
+            'rack_id': '$nat_rack',
+        }
     )
     nat_vrf = DynamicModelChoiceField(
         queryset=VRF.objects.all(),
         required=False,
-        label='VRF',
-        widget=APISelect(
-            filter_for={
-                'nat_inside': 'vrf_id'
-            }
-        )
+        label='VRF'
     )
     nat_inside = DynamicModelChoiceField(
         queryset=IPAddress.objects.all(),
         required=False,
         label='IP Address',
-        display_field='address'
+        display_field='address',
+        query_params={
+            'device_id': '$nat_device',
+            'vrf_if': '$nat_vrf',
+        }
     )
     primary_for_parent = forms.BooleanField(
         required=False,
@@ -956,18 +936,16 @@ class VLANGroupFilterForm(BootstrapMixin, forms.Form):
     region = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         to_field_name='slug',
-        required=False,
-        widget=APISelectMultiple(
-            filter_for={
-                'site': 'region',
-            }
-        )
+        required=False
     )
     site = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),
         to_field_name='slug',
         required=False,
-        null_option='None'
+        null_option='None',
+        query_params={
+            'region': '$region'
+        }
     )
 
 
@@ -979,16 +957,14 @@ class VLANForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
     site = DynamicModelChoiceField(
         queryset=Site.objects.all(),
         required=False,
-        null_option='None',
-        widget=APISelect(
-            filter_for={
-                'group': 'site_id'
-            }
-        )
+        null_option='None'
     )
     group = DynamicModelChoiceField(
         queryset=VLANGroup.objects.all(),
-        required=False
+        required=False,
+        query_params={
+            'site_id': '$site'
+        }
     )
     role = DynamicModelChoiceField(
         queryset=Role.objects.all(),
@@ -1072,16 +1048,14 @@ class VLANBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
     )
     site = DynamicModelChoiceField(
         queryset=Site.objects.all(),
-        required=False,
-        widget=APISelect(
-            filter_for={
-                'group': 'site_id'
-            }
-        )
+        required=False
     )
     group = DynamicModelChoiceField(
         queryset=VLANGroup.objects.all(),
-        required=False
+        required=False,
+        query_params={
+            'site_id': '$site'
+        }
     )
     tenant = DynamicModelChoiceField(
         queryset=Tenant.objects.all(),
@@ -1117,25 +1091,25 @@ class VLANFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
     region = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         to_field_name='slug',
-        required=False,
-        widget=APISelectMultiple(
-            filter_for={
-                'site': 'region',
-                'group_id': 'region'
-            }
-        )
+        required=False
     )
     site = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),
         to_field_name='slug',
         required=False,
-        null_option='None'
+        null_option='None',
+        query_params={
+            'region': '$region'
+        }
     )
     group_id = DynamicModelMultipleChoiceField(
         queryset=VLANGroup.objects.all(),
         required=False,
         label='VLAN group',
-        null_option='None'
+        null_option='None',
+        query_params={
+            'region': '$region'
+        }
     )
     status = forms.MultipleChoiceField(
         choices=VLANStatusChoices,

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

@@ -184,6 +184,17 @@ $(document).ready(function() {
                         var param_name = attr.name.split("data-additional-query-param-")[1];
 
                         $.each($.parseJSON(attr.value), function(index, value) {
+                            // Referencing the value of another form field
+                            if (value.startsWith('$')) {
+                                let ref_field = $('#id_' + value.slice(1));
+                                if (ref_field.val() && ref_field.is(":visible")) {
+                                    value = ref_field.val();
+                                } else if (ref_field.attr("required") && ref_field.attr("data-null-option")) {
+                                    value = "null";
+                                } else {
+                                    return true;  // Skip if ref_field has no value
+                                }
+                            }
                             if (param_name in parameters) {
                                 if (Array.isArray(parameters[param_name])) {
                                     parameters[param_name].push(value);

+ 10 - 14
netbox/tenancy/forms.py

@@ -119,16 +119,14 @@ class TenancyForm(forms.Form):
     tenant_group = DynamicModelChoiceField(
         queryset=TenantGroup.objects.all(),
         required=False,
-        null_option='None',
-        widget=APISelect(
-            filter_for={
-                'tenant': 'group_id',
-            }
-        )
+        null_option='None'
     )
     tenant = DynamicModelChoiceField(
         queryset=Tenant.objects.all(),
-        required=False
+        required=False,
+        query_params={
+            'group_id': '$tenant_group'
+        }
     )
 
     def __init__(self, *args, **kwargs):
@@ -148,16 +146,14 @@ class TenancyFilterForm(forms.Form):
         queryset=TenantGroup.objects.all(),
         to_field_name='slug',
         required=False,
-        null_option='None',
-        widget=APISelectMultiple(
-            filter_for={
-                'tenant': 'group'
-            }
-        )
+        null_option='None'
     )
     tenant = DynamicModelMultipleChoiceField(
         queryset=Tenant.objects.all(),
         to_field_name='slug',
         required=False,
-        null_option='None'
+        null_option='None',
+        query_params={
+            'group': '$tenant_group'
+        }
     )

+ 5 - 2
netbox/utilities/forms/widgets.py

@@ -193,10 +193,13 @@ class APISelect(SelectWithDisabled):
         :param name: The name of the query param
         :param value: The value of the query param
         """
-        key = 'data-additional-query-param-{}'.format(name)
+        key = f'data-additional-query-param-{name}'
 
         values = json.loads(self.attrs.get(key, '[]'))
-        values.append(value)
+        if type(value) is list:
+            values.extend(value)
+        else:
+            values.append(value)
 
         self.attrs[key] = json.dumps(values)
 

+ 30 - 46
netbox/virtualization/forms.py

@@ -171,18 +171,16 @@ class ClusterFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm
     region = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         to_field_name='slug',
-        required=False,
-        widget=APISelectMultiple(
-            filter_for={
-                'site': 'region'
-            }
-        )
+        required=False
     )
     site = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),
         to_field_name='slug',
         required=False,
-        null_option='None'
+        null_option='None',
+        query_params={
+            'region': '$region'
+        }
     )
     group = DynamicModelMultipleChoiceField(
         queryset=ClusterGroup.objects.all(),
@@ -197,38 +195,30 @@ class ClusterAddDevicesForm(BootstrapMixin, forms.Form):
     region = DynamicModelChoiceField(
         queryset=Region.objects.all(),
         required=False,
-        null_option='None',
-        widget=APISelect(
-            filter_for={
-                "site": "region_id",
-            }
-        )
+        null_option='None'
     )
     site = DynamicModelChoiceField(
         queryset=Site.objects.all(),
         required=False,
-        widget=APISelect(
-            filter_for={
-                "rack": "site_id",
-                "devices": "site_id",
-            }
-        )
+        query_params={
+            'region_id': '$region'
+        }
     )
     rack = DynamicModelChoiceField(
         queryset=Rack.objects.all(),
         required=False,
         null_option='None',
-        widget=APISelect(
-            filter_for={
-                "devices": "rack_id"
-            }
-        )
+        query_params={
+            'site_id': '$site'
+        }
     )
     devices = DynamicModelMultipleChoiceField(
         queryset=Device.objects.all(),
         display_field='display_name',
         query_params={
-            'cluster_id': 'null'
+            'site_id': '$site',
+            'rack_id': '$rack',
+            'cluster_id': 'null',
         }
     )
 
@@ -274,15 +264,13 @@ class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
     cluster_group = DynamicModelChoiceField(
         queryset=ClusterGroup.objects.all(),
         required=False,
-        null_option='None',
-        widget=APISelect(
-            filter_for={
-                "cluster": "group_id",
-            }
-        )
+        null_option='None'
     )
     cluster = DynamicModelChoiceField(
-        queryset=Cluster.objects.all()
+        queryset=Cluster.objects.all(),
+        query_params={
+            'group_id': '$cluster_group'
+        }
     )
     role = DynamicModelChoiceField(
         queryset=DeviceRole.objects.all(),
@@ -491,18 +479,16 @@ class VirtualMachineFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFil
     region = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         to_field_name='slug',
-        required=False,
-        widget=APISelectMultiple(
-            filter_for={
-                'site': 'region'
-            }
-        )
+        required=False
     )
     site = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),
         to_field_name='slug',
         required=False,
-        null_option='None'
+        null_option='None',
+        query_params={
+            'region': '$region'
+        }
     )
     role = DynamicModelMultipleChoiceField(
         queryset=DeviceRole.objects.filter(vm_role=True),
@@ -776,17 +762,15 @@ class VMInterfaceFilterForm(forms.Form):
     cluster_id = DynamicModelMultipleChoiceField(
         queryset=Cluster.objects.all(),
         required=False,
-        label='Cluster',
-        widget=APISelectMultiple(
-            filter_for={
-                'virtual_machine_id': 'cluster_id'
-            }
-        )
+        label='Cluster'
     )
     virtual_machine_id = DynamicModelMultipleChoiceField(
         queryset=VirtualMachine.objects.all(),
         required=False,
-        label='Virtual machine'
+        label='Virtual machine',
+        query_params={
+            'cluster_id': '$cluster_id'
+        }
     )
     enabled = forms.NullBooleanField(
         required=False,