diff --git a/netbox/dcim/forms/model_forms.py b/netbox/dcim/forms/model_forms.py index 92e0f30ae..76faa54ba 100644 --- a/netbox/dcim/forms/model_forms.py +++ b/netbox/dcim/forms/model_forms.py @@ -1705,6 +1705,22 @@ class VirtualDeviceContextForm(TenancyForm, NetBoxModelForm): 'rack_id': '$rack', } ) + primary_ip4 = DynamicModelChoiceField( + queryset=IPAddress.objects.all(), + required=False, + query_params={ + 'device_id': '$device', + 'family': 4, + } + ) + primary_ip6 = DynamicModelChoiceField( + queryset=IPAddress.objects.all(), + required=False, + query_params={ + 'device_id': '$device', + 'family': 6, + } + ) fieldsets = ( ('Assigned Device', ('region', 'site_group', 'site', 'location', 'rack', 'device')), @@ -1720,6 +1736,4 @@ class VirtualDeviceContextForm(TenancyForm, NetBoxModelForm): ] widgets = { 'status': StaticSelect(), - 'primary_ip4': StaticSelect(), - 'primary_ip6': StaticSelect(), } diff --git a/netbox/dcim/models/devices.py b/netbox/dcim/models/devices.py index 8b03015be..1e6581f8d 100644 --- a/netbox/dcim/models/devices.py +++ b/netbox/dcim/models/devices.py @@ -1175,3 +1175,25 @@ class VirtualDeviceContext(PrimaryModel): return self.primary_ip4 else: return None + + def clean(self): + super().clean() + vc_interfaces = self.device.vc_interfaces(if_master=False) + if self.primary_ip4: + if self.primary_ip4.family != 4: + raise ValidationError({ + 'primary_ip4': f"{self.primary_ip4} is not an IPv4 address." + }) + if self.primary_ip4.assigned_object not in vc_interfaces: + raise ValidationError({ + 'primary_ip4': f"The specified IP address ({self.primary_ip4}) is not assigned to this device." + }) + if self.primary_ip6: + if self.primary_ip6.family != 6: + raise ValidationError({ + 'primary_ip6': f"{self.primary_ip6} is not an IPv6 address." + }) + if self.primary_ip6.assigned_object not in vc_interfaces: + raise ValidationError({ + 'primary_ip6': f"The specified IP address ({self.primary_ip6}) is not assigned to this device." + }) diff --git a/netbox/ipam/models/ip.py b/netbox/ipam/models/ip.py index 20530d66c..d5efcdc7f 100644 --- a/netbox/ipam/models/ip.py +++ b/netbox/ipam/models/ip.py @@ -9,7 +9,7 @@ from django.utils.functional import cached_property from django.utils.translation import gettext as _ from dcim.fields import ASNField -from dcim.models import Device +from dcim.models import Device, VirtualDeviceContext from ipam.choices import * from ipam.constants import * from ipam.fields import IPNetworkField, IPAddressField @@ -866,7 +866,7 @@ class IPAddress(PrimaryModel): # Check for primary IP assignment that doesn't match the assigned device/VM if self.pk: - for cls, attr in ((Device, 'device'), (VirtualMachine, 'virtual_machine')): + for cls, attr in ((Device, 'device'), (VirtualMachine, 'virtual_machine'), (VirtualDeviceContext, 'device')): parent = cls.objects.filter(Q(primary_ip4=self) | Q(primary_ip6=self)).first() if parent and getattr(self.assigned_object, attr, None) != parent: # Check for a NAT relationship