mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-14 09:51:22 -06:00
Oob ip (devices) (#13013)
* initial oob_ip support for devices * add primary ip and oob ip checkmark to ip address view * add oob ip to device view and device edit view * pep8 * make is_oob_ip and is_primary_ip generic for other models * refactor oob_ip * fix oob ip signal * string capitalisation * Misc cleanup --------- Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
This commit is contained in:
parent
7600d7b344
commit
154b8236a2
@ -87,6 +87,10 @@ Each device may designate one primary IPv4 address and/or one primary IPv6 addre
|
|||||||
!!! tip
|
!!! tip
|
||||||
NetBox will prefer IPv6 addresses over IPv4 addresses by default. This can be changed by setting the `PREFER_IPV4` configuration parameter.
|
NetBox will prefer IPv6 addresses over IPv4 addresses by default. This can be changed by setting the `PREFER_IPV4` configuration parameter.
|
||||||
|
|
||||||
|
### Out-of-band (OOB) IP Address
|
||||||
|
|
||||||
|
Each device may designate its out-of-band IP address. Out-of-band IPs are typically used to access network infrastructure via a physically separate management network.
|
||||||
|
|
||||||
### Cluster
|
### Cluster
|
||||||
|
|
||||||
If this device will serve as a host for a virtualization [cluster](../virtualization/cluster.md), it can be assigned here. (Host devices can also be assigned by editing the cluster.)
|
If this device will serve as a host for a virtualization [cluster](../virtualization/cluster.md), it can be assigned here. (Host devices can also be assigned by editing the cluster.)
|
||||||
|
@ -663,6 +663,7 @@ class DeviceSerializer(NetBoxModelSerializer):
|
|||||||
primary_ip = NestedIPAddressSerializer(read_only=True)
|
primary_ip = NestedIPAddressSerializer(read_only=True)
|
||||||
primary_ip4 = NestedIPAddressSerializer(required=False, allow_null=True)
|
primary_ip4 = NestedIPAddressSerializer(required=False, allow_null=True)
|
||||||
primary_ip6 = NestedIPAddressSerializer(required=False, allow_null=True)
|
primary_ip6 = NestedIPAddressSerializer(required=False, allow_null=True)
|
||||||
|
oob_ip = NestedIPAddressSerializer(required=False, allow_null=True)
|
||||||
parent_device = serializers.SerializerMethodField()
|
parent_device = serializers.SerializerMethodField()
|
||||||
cluster = NestedClusterSerializer(required=False, allow_null=True)
|
cluster = NestedClusterSerializer(required=False, allow_null=True)
|
||||||
virtual_chassis = NestedVirtualChassisSerializer(required=False, allow_null=True, default=None)
|
virtual_chassis = NestedVirtualChassisSerializer(required=False, allow_null=True, default=None)
|
||||||
@ -686,11 +687,11 @@ class DeviceSerializer(NetBoxModelSerializer):
|
|||||||
fields = [
|
fields = [
|
||||||
'id', 'url', 'display', 'name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag',
|
'id', 'url', 'display', 'name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag',
|
||||||
'site', 'location', 'rack', 'position', 'face', 'latitude', 'longitude', 'parent_device', 'status',
|
'site', 'location', 'rack', 'position', 'face', 'latitude', 'longitude', 'parent_device', 'status',
|
||||||
'airflow', 'primary_ip', 'primary_ip4', 'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position',
|
'airflow', 'primary_ip', 'primary_ip4', 'primary_ip6', 'oob_ip', 'cluster', 'virtual_chassis',
|
||||||
'vc_priority', 'description', 'comments', 'config_template', 'local_context_data', 'tags', 'custom_fields',
|
'vc_position', 'vc_priority', 'description', 'comments', 'config_template', 'local_context_data', 'tags',
|
||||||
'created', 'last_updated', 'console_port_count', 'console_server_port_count', 'power_port_count',
|
'custom_fields', 'created', 'last_updated', 'console_port_count', 'console_server_port_count',
|
||||||
'power_outlet_count', 'interface_count', 'front_port_count', 'rear_port_count', 'device_bay_count',
|
'power_port_count', 'power_outlet_count', 'interface_count', 'front_port_count', 'rear_port_count',
|
||||||
'module_bay_count', 'inventory_item_count',
|
'device_bay_count', 'module_bay_count', 'inventory_item_count',
|
||||||
]
|
]
|
||||||
|
|
||||||
@extend_schema_field(NestedDeviceSerializer)
|
@extend_schema_field(NestedDeviceSerializer)
|
||||||
@ -712,11 +713,11 @@ class DeviceWithConfigContextSerializer(DeviceSerializer):
|
|||||||
fields = [
|
fields = [
|
||||||
'id', 'url', 'display', 'name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag',
|
'id', 'url', 'display', 'name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag',
|
||||||
'site', 'location', 'rack', 'position', 'face', 'parent_device', 'status', 'airflow', 'primary_ip',
|
'site', 'location', 'rack', 'position', 'face', 'parent_device', 'status', 'airflow', 'primary_ip',
|
||||||
'primary_ip4', 'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'description',
|
'primary_ip4', 'primary_ip6', 'oob_ip', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority',
|
||||||
'comments', 'local_context_data', 'tags', 'custom_fields', 'config_context', 'config_template',
|
'description', 'comments', 'local_context_data', 'tags', 'custom_fields', 'config_context',
|
||||||
'created', 'last_updated', 'console_port_count', 'console_server_port_count', 'power_port_count',
|
'config_template', 'created', 'last_updated', 'console_port_count', 'console_server_port_count',
|
||||||
'power_outlet_count', 'interface_count', 'front_port_count', 'rear_port_count', 'device_bay_count',
|
'power_port_count', 'power_outlet_count', 'interface_count', 'front_port_count', 'rear_port_count',
|
||||||
'module_bay_count', 'inventory_item_count',
|
'device_bay_count', 'module_bay_count', 'inventory_item_count',
|
||||||
]
|
]
|
||||||
|
|
||||||
@extend_schema_field(serializers.JSONField(allow_null=True))
|
@extend_schema_field(serializers.JSONField(allow_null=True))
|
||||||
|
@ -941,6 +941,10 @@ class DeviceFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilter
|
|||||||
method='_has_primary_ip',
|
method='_has_primary_ip',
|
||||||
label=_('Has a primary IP'),
|
label=_('Has a primary IP'),
|
||||||
)
|
)
|
||||||
|
has_oob_ip = django_filters.BooleanFilter(
|
||||||
|
method='_has_oob_ip',
|
||||||
|
label=_('Has an out-of-band IP'),
|
||||||
|
)
|
||||||
virtual_chassis_id = django_filters.ModelMultipleChoiceFilter(
|
virtual_chassis_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
field_name='virtual_chassis',
|
field_name='virtual_chassis',
|
||||||
queryset=VirtualChassis.objects.all(),
|
queryset=VirtualChassis.objects.all(),
|
||||||
@ -996,6 +1000,11 @@ class DeviceFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilter
|
|||||||
queryset=IPAddress.objects.all(),
|
queryset=IPAddress.objects.all(),
|
||||||
label=_('Primary IPv6 (ID)'),
|
label=_('Primary IPv6 (ID)'),
|
||||||
)
|
)
|
||||||
|
oob_ip_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='oob_ip',
|
||||||
|
queryset=IPAddress.objects.all(),
|
||||||
|
label=_('OOB IP (ID)'),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Device
|
model = Device
|
||||||
@ -1020,6 +1029,12 @@ class DeviceFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilter
|
|||||||
return queryset.filter(params)
|
return queryset.filter(params)
|
||||||
return queryset.exclude(params)
|
return queryset.exclude(params)
|
||||||
|
|
||||||
|
def _has_oob_ip(self, queryset, name, value):
|
||||||
|
params = Q(oob_ip__isnull=False)
|
||||||
|
if value:
|
||||||
|
return queryset.filter(params)
|
||||||
|
return queryset.exclude(params)
|
||||||
|
|
||||||
def _virtual_chassis_member(self, queryset, name, value):
|
def _virtual_chassis_member(self, queryset, name, value):
|
||||||
return queryset.exclude(virtual_chassis__isnull=value)
|
return queryset.exclude(virtual_chassis__isnull=value)
|
||||||
|
|
||||||
|
@ -629,7 +629,7 @@ class DeviceFilterForm(
|
|||||||
('Components', (
|
('Components', (
|
||||||
'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces', 'pass_through_ports',
|
'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces', 'pass_through_ports',
|
||||||
)),
|
)),
|
||||||
('Miscellaneous', ('has_primary_ip', 'virtual_chassis_member', 'config_template_id', 'local_context_data'))
|
('Miscellaneous', ('has_primary_ip', 'has_oob_ip', 'virtual_chassis_member', 'config_template_id', 'local_context_data'))
|
||||||
)
|
)
|
||||||
region_id = DynamicModelMultipleChoiceField(
|
region_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=Region.objects.all(),
|
queryset=Region.objects.all(),
|
||||||
@ -723,6 +723,13 @@ class DeviceFilterForm(
|
|||||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
has_oob_ip = forms.NullBooleanField(
|
||||||
|
required=False,
|
||||||
|
label='Has an OOB IP',
|
||||||
|
widget=forms.Select(
|
||||||
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
|
)
|
||||||
|
)
|
||||||
virtual_chassis_member = forms.NullBooleanField(
|
virtual_chassis_member = forms.NullBooleanField(
|
||||||
required=False,
|
required=False,
|
||||||
label='Virtual chassis member',
|
label='Virtual chassis member',
|
||||||
|
@ -449,9 +449,9 @@ class DeviceForm(TenancyForm, NetBoxModelForm):
|
|||||||
model = Device
|
model = Device
|
||||||
fields = [
|
fields = [
|
||||||
'name', 'device_role', 'device_type', 'serial', 'asset_tag', 'site', 'rack', 'location', 'position', 'face',
|
'name', 'device_role', 'device_type', 'serial', 'asset_tag', 'site', 'rack', 'location', 'position', 'face',
|
||||||
'latitude', 'longitude', 'status', 'airflow', 'platform', 'primary_ip4', 'primary_ip6', 'cluster',
|
'latitude', 'longitude', 'status', 'airflow', 'platform', 'primary_ip4', 'primary_ip6', 'oob_ip', 'cluster',
|
||||||
'tenant_group', 'tenant', 'virtual_chassis', 'vc_position', 'vc_priority', 'description', 'config_template',
|
'tenant_group', 'tenant', 'virtual_chassis', 'vc_position', 'vc_priority', 'description', 'config_template',
|
||||||
'comments', 'tags', 'local_context_data'
|
'comments', 'tags', 'local_context_data',
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
@ -460,6 +460,7 @@ class DeviceForm(TenancyForm, NetBoxModelForm):
|
|||||||
if self.instance.pk:
|
if self.instance.pk:
|
||||||
|
|
||||||
# Compile list of choices for primary IPv4 and IPv6 addresses
|
# Compile list of choices for primary IPv4 and IPv6 addresses
|
||||||
|
oob_ip_choices = [(None, '---------')]
|
||||||
for family in [4, 6]:
|
for family in [4, 6]:
|
||||||
ip_choices = [(None, '---------')]
|
ip_choices = [(None, '---------')]
|
||||||
|
|
||||||
@ -475,6 +476,7 @@ class DeviceForm(TenancyForm, NetBoxModelForm):
|
|||||||
if interface_ips:
|
if interface_ips:
|
||||||
ip_list = [(ip.id, f'{ip.address} ({ip.assigned_object})') for ip in interface_ips]
|
ip_list = [(ip.id, f'{ip.address} ({ip.assigned_object})') for ip in interface_ips]
|
||||||
ip_choices.append(('Interface IPs', ip_list))
|
ip_choices.append(('Interface IPs', ip_list))
|
||||||
|
oob_ip_choices.extend(ip_list)
|
||||||
# Collect NAT IPs
|
# Collect NAT IPs
|
||||||
nat_ips = IPAddress.objects.prefetch_related('nat_inside').filter(
|
nat_ips = IPAddress.objects.prefetch_related('nat_inside').filter(
|
||||||
address__family=family,
|
address__family=family,
|
||||||
@ -485,6 +487,7 @@ class DeviceForm(TenancyForm, NetBoxModelForm):
|
|||||||
ip_list = [(ip.id, f'{ip.address} (NAT)') for ip in nat_ips]
|
ip_list = [(ip.id, f'{ip.address} (NAT)') for ip in nat_ips]
|
||||||
ip_choices.append(('NAT IPs', ip_list))
|
ip_choices.append(('NAT IPs', ip_list))
|
||||||
self.fields['primary_ip{}'.format(family)].choices = ip_choices
|
self.fields['primary_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
|
# 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.
|
# can be flipped from one face to another.
|
||||||
@ -504,6 +507,8 @@ class DeviceForm(TenancyForm, NetBoxModelForm):
|
|||||||
self.fields['primary_ip4'].widget.attrs['readonly'] = True
|
self.fields['primary_ip4'].widget.attrs['readonly'] = True
|
||||||
self.fields['primary_ip6'].choices = []
|
self.fields['primary_ip6'].choices = []
|
||||||
self.fields['primary_ip6'].widget.attrs['readonly'] = True
|
self.fields['primary_ip6'].widget.attrs['readonly'] = True
|
||||||
|
self.fields['oob_ip'].choices = []
|
||||||
|
self.fields['oob_ip'].widget.attrs['readonly'] = True
|
||||||
|
|
||||||
# Rack position
|
# Rack position
|
||||||
position = self.data.get('position') or self.initial.get('position')
|
position = self.data.get('position') or self.initial.get('position')
|
||||||
|
25
netbox/dcim/migrations/0175_device_oob_ip.py
Normal file
25
netbox/dcim/migrations/0175_device_oob_ip.py
Normal file
@ -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',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -39,7 +39,7 @@ def recalculate_device_counts(apps, schema_editor):
|
|||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('dcim', '0174_rack_starting_unit'),
|
('dcim', '0175_device_oob_ip'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
@ -591,6 +591,14 @@ class Device(PrimaryModel, ConfigContextModel):
|
|||||||
null=True,
|
null=True,
|
||||||
verbose_name='Primary IPv6'
|
verbose_name='Primary IPv6'
|
||||||
)
|
)
|
||||||
|
oob_ip = models.OneToOneField(
|
||||||
|
to='ipam.IPAddress',
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
related_name='+',
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
verbose_name='Out-of-band IP'
|
||||||
|
)
|
||||||
cluster = models.ForeignKey(
|
cluster = models.ForeignKey(
|
||||||
to='virtualization.Cluster',
|
to='virtualization.Cluster',
|
||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
@ -816,7 +824,7 @@ class Device(PrimaryModel, ConfigContextModel):
|
|||||||
except DeviceType.DoesNotExist:
|
except DeviceType.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Validate primary IP addresses
|
# Validate primary & OOB IP addresses
|
||||||
vc_interfaces = self.vc_interfaces(if_master=False)
|
vc_interfaces = self.vc_interfaces(if_master=False)
|
||||||
if self.primary_ip4:
|
if self.primary_ip4:
|
||||||
if self.primary_ip4.family != 4:
|
if self.primary_ip4.family != 4:
|
||||||
@ -844,6 +852,15 @@ class Device(PrimaryModel, ConfigContextModel):
|
|||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'primary_ip6': f"The specified IP address ({self.primary_ip6}) is not assigned to this device."
|
'primary_ip6': f"The specified IP address ({self.primary_ip6}) is not assigned to this device."
|
||||||
})
|
})
|
||||||
|
if self.oob_ip:
|
||||||
|
if self.oob_ip.assigned_object in vc_interfaces:
|
||||||
|
pass
|
||||||
|
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_ip': f"The specified IP address ({self.oob_ip}) is not assigned to this device."
|
||||||
|
})
|
||||||
|
|
||||||
# Validate manufacturer/platform
|
# Validate manufacturer/platform
|
||||||
if hasattr(self, 'device_type') and self.platform:
|
if hasattr(self, 'device_type') and self.platform:
|
||||||
|
@ -201,6 +201,10 @@ class DeviceTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
|
|||||||
linkify=True,
|
linkify=True,
|
||||||
verbose_name='IPv6 Address'
|
verbose_name='IPv6 Address'
|
||||||
)
|
)
|
||||||
|
oob_ip = tables.Column(
|
||||||
|
linkify=True,
|
||||||
|
verbose_name='OOB IP'
|
||||||
|
)
|
||||||
cluster = tables.Column(
|
cluster = tables.Column(
|
||||||
linkify=True
|
linkify=True
|
||||||
)
|
)
|
||||||
@ -267,8 +271,8 @@ class DeviceTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
|
|||||||
'pk', 'id', 'name', 'status', 'tenant', 'tenant_group', 'device_role', 'manufacturer', 'device_type',
|
'pk', 'id', 'name', 'status', 'tenant', 'tenant_group', 'device_role', 'manufacturer', 'device_type',
|
||||||
'platform', 'serial', 'asset_tag', 'region', 'site_group', 'site', 'location', 'rack', 'parent_device',
|
'platform', 'serial', 'asset_tag', 'region', 'site_group', 'site', 'location', 'rack', 'parent_device',
|
||||||
'device_bay_position', 'position', 'face', 'latitude', 'longitude', 'airflow', 'primary_ip', 'primary_ip4',
|
'device_bay_position', 'position', 'face', 'latitude', 'longitude', 'airflow', 'primary_ip', 'primary_ip4',
|
||||||
'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'description', 'config_template',
|
'primary_ip6', 'oob_ip', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'description',
|
||||||
'comments', 'contacts', 'tags', 'created', 'last_updated',
|
'config_template', 'comments', 'contacts', 'tags', 'created', 'last_updated',
|
||||||
)
|
)
|
||||||
default_columns = (
|
default_columns = (
|
||||||
'pk', 'name', 'status', 'tenant', 'site', 'location', 'rack', 'device_role', 'manufacturer', 'device_type',
|
'pk', 'name', 'status', 'tenant', 'site', 'location', 'rack', 'device_role', 'manufacturer', 'device_type',
|
||||||
|
@ -2452,11 +2452,13 @@ class InterfaceView(generic.ObjectView):
|
|||||||
queryset = Interface.objects.all()
|
queryset = Interface.objects.all()
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
# Get assigned VDC's
|
# Get assigned VDCs
|
||||||
vdc_table = tables.VirtualDeviceContextTable(
|
vdc_table = tables.VirtualDeviceContextTable(
|
||||||
data=instance.vdcs.restrict(request.user, 'view').prefetch_related('device'),
|
data=instance.vdcs.restrict(request.user, 'view').prefetch_related('device'),
|
||||||
exclude=('tenant', 'tenant_group', 'primary_ip', 'primary_ip4', 'primary_ip6', 'comments', 'tags',
|
exclude=(
|
||||||
'created', 'last_updated', 'actions', ),
|
'tenant', 'tenant_group', 'primary_ip', 'primary_ip4', 'primary_ip6', 'oob_ip', 'comments', 'tags',
|
||||||
|
'created', 'last_updated', 'actions',
|
||||||
|
),
|
||||||
orderable=False
|
orderable=False
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -849,6 +849,24 @@ class IPAddress(PrimaryModel):
|
|||||||
return self.address.version
|
return self.address.version
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_oob_ip(self):
|
||||||
|
if self.assigned_object:
|
||||||
|
parent = getattr(self.assigned_object, 'parent_object', None)
|
||||||
|
if parent.oob_ip_id == self.pk:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_primary_ip(self):
|
||||||
|
if self.assigned_object:
|
||||||
|
parent = getattr(self.assigned_object, 'parent_object', None)
|
||||||
|
if self.family == 4 and parent.primary_ip4_id == self.pk:
|
||||||
|
return True
|
||||||
|
if self.family == 6 and parent.primary_ip6_id == self.pk:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def _set_mask_length(self, value):
|
def _set_mask_length(self, value):
|
||||||
"""
|
"""
|
||||||
Expose the IPNetwork object's prefixlen attribute on the parent model so that it can be manipulated directly,
|
Expose the IPNetwork object's prefixlen attribute on the parent model so that it can be manipulated directly,
|
||||||
|
@ -52,13 +52,19 @@ def handle_prefix_deleted(instance, **kwargs):
|
|||||||
@receiver(pre_delete, sender=IPAddress)
|
@receiver(pre_delete, sender=IPAddress)
|
||||||
def clear_primary_ip(instance, **kwargs):
|
def clear_primary_ip(instance, **kwargs):
|
||||||
"""
|
"""
|
||||||
When an IPAddress is deleted, trigger save() on any Devices/VirtualMachines for which it
|
When an IPAddress is deleted, trigger save() on any Devices/VirtualMachines for which it was a primary IP.
|
||||||
was a primary IP.
|
|
||||||
"""
|
"""
|
||||||
field_name = f'primary_ip{instance.family}'
|
field_name = f'primary_ip{instance.family}'
|
||||||
device = Device.objects.filter(**{field_name: instance}).first()
|
if device := Device.objects.filter(**{field_name: instance}).first():
|
||||||
if device:
|
|
||||||
device.save()
|
device.save()
|
||||||
virtualmachine = VirtualMachine.objects.filter(**{field_name: instance}).first()
|
if virtualmachine := VirtualMachine.objects.filter(**{field_name: instance}).first():
|
||||||
if virtualmachine:
|
|
||||||
virtualmachine.save()
|
virtualmachine.save()
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(pre_delete, sender=IPAddress)
|
||||||
|
def clear_oob_ip(instance, **kwargs):
|
||||||
|
"""
|
||||||
|
When an IPAddress is deleted, trigger save() on any Devices for which it was a OOB IP.
|
||||||
|
"""
|
||||||
|
if device := Device.objects.filter(oob_ip=instance).first():
|
||||||
|
device.save()
|
||||||
|
@ -239,6 +239,17 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Out-of-band IP</th>
|
||||||
|
<td>
|
||||||
|
{% if object.oob_ip %}
|
||||||
|
<a href="{{ object.oob_ip.get_absolute_url }}" id="oob_ip">{{ object.oob_ip.address.ip }}</a>
|
||||||
|
{% copy_content "oob_ip" %}
|
||||||
|
{% else %}
|
||||||
|
{{ ''|placeholder }}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
{% if object.cluster %}
|
{% if object.cluster %}
|
||||||
<tr>
|
<tr>
|
||||||
<th>Cluster</th>
|
<th>Cluster</th>
|
||||||
|
@ -68,6 +68,7 @@
|
|||||||
{% if object.pk %}
|
{% if object.pk %}
|
||||||
{% render_field form.primary_ip4 %}
|
{% render_field form.primary_ip4 %}
|
||||||
{% render_field form.primary_ip6 %}
|
{% render_field form.primary_ip6 %}
|
||||||
|
{% render_field form.oob_ip %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -96,6 +96,14 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Primary IP</td>
|
||||||
|
<td>{% checkmark object.is_primary_ip %}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>OOB IP</td>
|
||||||
|
<td>{% checkmark object.is_oob_ip %}</td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user