Repository: netbox-community/netbox

Issue:
IP Address Interface Assignment silently discards selected Device if no Interface is selected
### NetBox version

v3.4.2

### Python version

3.8

### Steps to Reproduce

1. Create a Device
2. Fill out required fields
3. Save Device
4. Create IP Address
5. Fill out required fields
6. Choose the created Device in the Interface Assignment
7. Do not choose any Interface in the Interface Assignment
8. Save IP Address

### Expected Behavior

When saving the IP Address object the form gets validated and at this point I expect to get a warning which tells me to choose an interface in case I want to assign this IP Address to the Device, else to clear the device field.

### Observed Behavior

The device selection gets silently discarded. Users can't find the IP Address they have "assigned" to a Device anywhere. Only on editing the IP Address again shows that the value never got stored.


Discussion:
> I expect to get a warning which tells me to choose an interface in case I want to assign this IP Address to the Device, else to clear the device field.

We're working an improvement for this in #10054, which will replace the current set of form fields and hopefully make the process of selecting a related object much more natural. I'm not sure there's anything we can do before that to improve this that wouldn't require an unjustifiable amount of development effort, but I'm open to suggestions.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. NetBox is governed by a small group of core maintainers which means not all opened issues may receive direct feedback. **Do not** attempt to circumvent this process by "bumping" the issue; doing so will result in its immediate closure and you may be barred from participating in any future discussions. Please see our [contributing guide](https://github.com/netbox-community/netbox/blob/develop/CONTRIBUTING.md).
Closing this out as no specific improvements have been proposed.

Resolving patch:
diff --git a/netbox/ipam/forms/model_forms.py b/netbox/ipam/forms/model_forms.py
--- a/netbox/ipam/forms/model_forms.py
+++ b/netbox/ipam/forms/model_forms.py
@@ -262,38 +262,21 @@ class Meta:
 
 
 class IPAddressForm(TenancyForm, NetBoxModelForm):
-    device = DynamicModelChoiceField(
-        queryset=Device.objects.all(),
-        required=False,
-        initial_params={
-            'interfaces': '$interface'
-        }
-    )
     interface = DynamicModelChoiceField(
         queryset=Interface.objects.all(),
         required=False,
-        query_params={
-            'device_id': '$device'
-        }
-    )
-    virtual_machine = DynamicModelChoiceField(
-        queryset=VirtualMachine.objects.all(),
-        required=False,
-        initial_params={
-            'interfaces': '$vminterface'
-        }
+        selector=True,
     )
     vminterface = DynamicModelChoiceField(
         queryset=VMInterface.objects.all(),
         required=False,
+        selector=True,
         label=_('Interface'),
-        query_params={
-            'virtual_machine_id': '$virtual_machine'
-        }
     )
     fhrpgroup = DynamicModelChoiceField(
         queryset=FHRPGroup.objects.all(),
         required=False,
+        selector=True,
         label=_('FHRP Group')
     )
     vrf = DynamicModelChoiceField(
@@ -301,33 +284,11 @@ class IPAddressForm(TenancyForm, NetBoxModelForm):
         required=False,
         label=_('VRF')
     )
-    nat_device = DynamicModelChoiceField(
-        queryset=Device.objects.all(),
-        required=False,
-        selector=True,
-        label=_('Device')
-    )
-    nat_virtual_machine = DynamicModelChoiceField(
-        queryset=VirtualMachine.objects.all(),
-        required=False,
-        selector=True,
-        label=_('Virtual Machine')
-    )
-    nat_vrf = DynamicModelChoiceField(
-        queryset=VRF.objects.all(),
-        required=False,
-        selector=True,
-        label=_('VRF')
-    )
     nat_inside = DynamicModelChoiceField(
         queryset=IPAddress.objects.all(),
         required=False,
+        selector=True,
         label=_('IP Address'),
-        query_params={
-            'device_id': '$nat_device',
-            'virtual_machine_id': '$nat_virtual_machine',
-            'vrf_id': '$nat_vrf',
-        }
     )
     primary_for_parent = forms.BooleanField(
         required=False,
@@ -338,8 +299,8 @@ class IPAddressForm(TenancyForm, NetBoxModelForm):
     class Meta:
         model = IPAddress
         fields = [
-            'address', 'vrf', 'status', 'role', 'dns_name', 'primary_for_parent', 'nat_device', 'nat_virtual_machine',
-            'nat_vrf', 'nat_inside', 'tenant_group', 'tenant', 'description', 'comments', 'tags',
+            'address', 'vrf', 'status', 'role', 'dns_name', 'primary_for_parent', 'nat_inside', 'tenant_group',
+            'tenant', 'description', 'comments', 'tags',
         ]
 
     def __init__(self, *args, **kwargs):
@@ -354,17 +315,6 @@ def __init__(self, *args, **kwargs):
                 initial['vminterface'] = instance.assigned_object
             elif type(instance.assigned_object) is FHRPGroup:
                 initial['fhrpgroup'] = instance.assigned_object
-            if instance.nat_inside:
-                nat_inside_parent = instance.nat_inside.assigned_object
-                if type(nat_inside_parent) is Interface:
-                    initial['nat_site'] = nat_inside_parent.device.site.pk
-                    if nat_inside_parent.device.rack:
-                        initial['nat_rack'] = nat_inside_parent.device.rack.pk
-                    initial['nat_device'] = nat_inside_parent.device.pk
-                elif type(nat_inside_parent) is VMInterface:
-                    if cluster := nat_inside_parent.virtual_machine.cluster:
-                        initial['nat_cluster'] = cluster.pk
-                    initial['nat_virtual_machine'] = nat_inside_parent.virtual_machine.pk
         kwargs['initial'] = initial
 
         super().__init__(*args, **kwargs)


Test patch: