diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index 48fd996cb..9c32d4ccf 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -663,9 +663,7 @@ class DeviceSerializer(NetBoxModelSerializer): primary_ip = NestedIPAddressSerializer(read_only=True) primary_ip4 = NestedIPAddressSerializer(required=False, allow_null=True) primary_ip6 = NestedIPAddressSerializer(required=False, allow_null=True) - oob_ip = NestedIPAddressSerializer(read_only=True) - oob_ip4 = NestedIPAddressSerializer(required=False, allow_null=True) - oob_ip6 = NestedIPAddressSerializer(required=False, allow_null=True) + oob_ip = NestedIPAddressSerializer(required=False, allow_null=True) parent_device = serializers.SerializerMethodField() cluster = NestedClusterSerializer(required=False, allow_null=True) virtual_chassis = NestedVirtualChassisSerializer(required=False, allow_null=True, default=None) diff --git a/netbox/dcim/filtersets.py b/netbox/dcim/filtersets.py index 1da8818ad..b441d6673 100644 --- a/netbox/dcim/filtersets.py +++ b/netbox/dcim/filtersets.py @@ -1000,15 +1000,10 @@ class DeviceFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilter queryset=IPAddress.objects.all(), label=_('Primary IPv6 (ID)'), ) - oob_ip4_id = django_filters.ModelMultipleChoiceFilter( - field_name='oob_ip4', + oob_ip_id = django_filters.ModelMultipleChoiceFilter( + field_name='oob_ip', queryset=IPAddress.objects.all(), - label=_('OOB IPv4 (ID)'), - ) - oob_ip6_id = django_filters.ModelMultipleChoiceFilter( - field_name='oob_ip6', - queryset=IPAddress.objects.all(), - label=_('OOB IPv6 (ID)'), + label=_('OOB IP (ID)'), ) class Meta: @@ -1035,7 +1030,7 @@ class DeviceFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilter return queryset.exclude(params) def _has_oob_ip(self, queryset, name, value): - params = Q(oob_ip4__isnull=False) | Q(oob_ip6__isnull=False) + params = Q(oob_ip__isnull=False) if value: return queryset.filter(params) return queryset.exclude(params) diff --git a/netbox/dcim/forms/model_forms.py b/netbox/dcim/forms/model_forms.py index 693411f09..4a10fb222 100644 --- a/netbox/dcim/forms/model_forms.py +++ b/netbox/dcim/forms/model_forms.py @@ -451,7 +451,7 @@ class DeviceForm(TenancyForm, NetBoxModelForm): 'name', 'device_role', 'device_type', 'serial', 'asset_tag', 'site', 'rack', 'location', 'position', 'face', 'latitude', 'longitude', 'status', 'airflow', 'platform', 'primary_ip4', 'primary_ip6', 'cluster', 'tenant_group', 'tenant', 'virtual_chassis', 'vc_position', 'vc_priority', 'description', 'config_template', - 'comments', 'tags', 'local_context_data' 'oob_ip', + 'comments', 'tags', 'local_context_data', 'oob_ip', ] def __init__(self, *args, **kwargs): @@ -460,6 +460,7 @@ class DeviceForm(TenancyForm, NetBoxModelForm): if self.instance.pk: # Compile list of choices for primary IPv4 and IPv6 addresses + oob_ip_choices = [(None, '---------')] for family in [4, 6]: ip_choices = [(None, '---------')] @@ -475,6 +476,7 @@ class DeviceForm(TenancyForm, NetBoxModelForm): if interface_ips: ip_list = [(ip.id, f'{ip.address} ({ip.assigned_object})') for ip in interface_ips] ip_choices.append(('Interface IPs', ip_list)) + oob_ip_choices.append(('Interface IPv{}s'.format(family), ip_list)) # Collect NAT IPs nat_ips = IPAddress.objects.prefetch_related('nat_inside').filter( address__family=family, @@ -484,8 +486,9 @@ class DeviceForm(TenancyForm, NetBoxModelForm): if nat_ips: ip_list = [(ip.id, f'{ip.address} (NAT)') for ip in nat_ips] ip_choices.append(('NAT IPs', ip_list)) + oob_ip_choices.append(('Nat IPv{}s'.format(family), ip_list)) self.fields['primary_ip{}'.format(family)].choices = ip_choices - self.fields['oob_ip{}'.format(family)].choices = ip_choices + self.fields['oob_ip'].choices = oob_ip_choices # If editing an existing device, exclude it from the list of occupied rack units. This ensures that a device # can be flipped from one face to another. @@ -505,10 +508,8 @@ class DeviceForm(TenancyForm, NetBoxModelForm): self.fields['primary_ip4'].widget.attrs['readonly'] = True self.fields['primary_ip6'].choices = [] self.fields['primary_ip6'].widget.attrs['readonly'] = True - self.fields['oob_ip4'].choices = [] - self.fields['oob_ip4'].widget.attrs['readonly'] = True - self.fields['oob_ip6'].choices = [] - self.fields['oob_ip6'].widget.attrs['readonly'] = True + self.fields['oob_ip'].choices = [] + self.fields['oob_ip'].widget.attrs['readonly'] = True # Rack position position = self.data.get('position') or self.initial.get('position') diff --git a/netbox/dcim/migrations/0173_device_oob_ip4_device_oob_ip6.py b/netbox/dcim/migrations/0173_device_oob_ip4_device_oob_ip6.py deleted file mode 100644 index 15d1403ab..000000000 --- a/netbox/dcim/migrations/0173_device_oob_ip4_device_oob_ip6.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 4.1.9 on 2023-06-26 21:06 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('ipam', '0066_iprange_mark_utilized'), - ('dcim', '0172_larger_power_draw_values'), - ] - - operations = [ - migrations.AddField( - model_name='device', - name='oob_ip4', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='ipam.ipaddress'), - ), - migrations.AddField( - model_name='device', - name='oob_ip6', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='ipam.ipaddress'), - ), - ] diff --git a/netbox/dcim/migrations/0175_device_oob_ip.py b/netbox/dcim/migrations/0175_device_oob_ip.py new file mode 100644 index 000000000..bf6a88ba8 --- /dev/null +++ b/netbox/dcim/migrations/0175_device_oob_ip.py @@ -0,0 +1,25 @@ +# Generated by Django 4.1.9 on 2023-07-24 20:29 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + dependencies = [ + ('ipam', '0066_iprange_mark_utilized'), + ('dcim', '0174_rack_starting_unit'), + ] + + operations = [ + migrations.AddField( + model_name='device', + name='oob_ip', + field=models.OneToOneField( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name='+', + to='ipam.ipaddress', + ), + ), + ] diff --git a/netbox/dcim/models/devices.py b/netbox/dcim/models/devices.py index e2c98b986..73199638c 100644 --- a/netbox/dcim/models/devices.py +++ b/netbox/dcim/models/devices.py @@ -591,21 +591,13 @@ class Device(PrimaryModel, ConfigContextModel): null=True, verbose_name='Primary IPv6' ) - oob_ip4 = models.OneToOneField( + oob_ip = models.OneToOneField( to='ipam.IPAddress', on_delete=models.SET_NULL, related_name='+', blank=True, null=True, - verbose_name='OOB IPv4' - ) - oob_ip6 = models.OneToOneField( - to='ipam.IPAddress', - on_delete=models.SET_NULL, - related_name='+', - blank=True, - null=True, - verbose_name='OOB IPv6' + verbose_name='OOB IP' ) cluster = models.ForeignKey( to='virtualization.Cluster', @@ -820,31 +812,14 @@ class Device(PrimaryModel, ConfigContextModel): }) # OOB ip validation - if self.oob_ip4: - if self.oob_ip4.family != 4: - raise ValidationError({ - 'oob_ip4': f"{self.oob_ip4} is not an IPv4 address." - }) - if self.oob_ip4.assigned_object in vc_interfaces: + if self.oob_ip: + if self.oob_ip.assigned_object in vc_interfaces: pass - elif self.oob_ip4.nat_inside is not None and self.oob_ip4.nat_inside.assigned_object in vc_interfaces: + elif self.oob_ip.nat_inside is not None and self.oob_ip.nat_inside.assigned_object in vc_interfaces: pass else: raise ValidationError({ - 'oob_ip4': f"The specified IP address ({self.oob_ip4}) is not assigned to this device." - }) - if self.oob_ip6: - if self.oob_ip6.family != 6: - raise ValidationError({ - 'oob_ip6': f"{self.oob_ip6} is not an IPv6 address." - }) - if self.oob_ip6.assigned_object in vc_interfaces: - pass - elif self.oob_ip6.nat_inside is not None and self.oob_ip6.nat_inside.assigned_object in vc_interfaces: - pass - else: - raise ValidationError({ - 'oob_ip6': f"The specified IP address ({self.oob_ip6}) is not assigned to this device." + 'oob_ip': f"The specified IP address ({self.oob_ip}) is not assigned to this device." }) # Validate manufacturer/platform if hasattr(self, 'device_type') and self.platform: @@ -956,17 +931,6 @@ class Device(PrimaryModel, ConfigContextModel): else: return None - @property - def oob_ip(self): - if ConfigItem('PREFER_IPV4')() and self.oob_ip4: - return self.oob_ip4 - elif self.oob_ip6: - return self.oob_ip6 - elif self.oob_ip4: - return self.oob_ip4 - else: - return None - @property def interfaces_count(self): return self.vc_interfaces().count() diff --git a/netbox/dcim/tables/devices.py b/netbox/dcim/tables/devices.py index 883181ae8..7b38577ce 100644 --- a/netbox/dcim/tables/devices.py +++ b/netbox/dcim/tables/devices.py @@ -203,16 +203,7 @@ class DeviceTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable): ) oob_ip = tables.Column( linkify=True, - order_by=('oob_ip4', 'oob_ip6'), - verbose_name='OOB IP Address' - ) - oob_ip4 = tables.Column( - linkify=True, - verbose_name='OOB IPv4 Address' - ) - oob_ip6 = tables.Column( - linkify=True, - verbose_name='OOB IPv6 Address' + verbose_name='OOB IP' ) cluster = tables.Column( linkify=True diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index c751935d7..bec23d2f3 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -2456,7 +2456,7 @@ class InterfaceView(generic.ObjectView): vdc_table = tables.VirtualDeviceContextTable( data=instance.vdcs.restrict(request.user, 'view').prefetch_related('device'), exclude=('tenant', 'tenant_group', 'primary_ip', 'primary_ip4', 'primary_ip6', 'comments', 'tags', - 'created', 'last_updated', 'actions', 'oob_ip', 'oob_ip4', 'oob_ip6'), + 'created', 'last_updated', 'actions', 'oob_ip'), orderable=False ) diff --git a/netbox/ipam/models/ip.py b/netbox/ipam/models/ip.py index 8b3499c9c..ff5f796b4 100644 --- a/netbox/ipam/models/ip.py +++ b/netbox/ipam/models/ip.py @@ -853,14 +853,9 @@ class IPAddress(PrimaryModel): def is_oob_ip(self): if self.assigned_object: parent = getattr(self.assigned_object, 'parent_object', None) - if self.family == 4: - if parent.oob_ip4: - if parent.oob_ip4.pk == self.pk: - return True - if self.family == 6: - if parent.oob_ip6: - if parent.oob_ip6.pk == self.pk: - return True + if parent.oob_ip: + if parent.oob_ip.pk == self.pk: + return True return False @property diff --git a/netbox/templates/dcim/device.html b/netbox/templates/dcim/device.html index bdfa1e617..a43424fe6 100644 --- a/netbox/templates/dcim/device.html +++ b/netbox/templates/dcim/device.html @@ -240,30 +240,16 @@