mirror of
https://github.com/netbox-community/netbox.git
synced 2025-08-15 12:08:17 -06:00
Replace _connected_interface and _connected_circuittermination
with a generic foreign key for upcoming flexibility
This commit is contained in:
parent
f21a63382c
commit
c17a6f4184
@ -1,6 +1,7 @@
|
|||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db.models import Count, F
|
from django.db.models import Count, F
|
||||||
from django.http import HttpResponseForbidden
|
from django.http import HttpResponseForbidden
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
@ -462,7 +463,7 @@ class PowerOutletViewSet(CableTraceMixin, ModelViewSet):
|
|||||||
|
|
||||||
class InterfaceViewSet(CableTraceMixin, ModelViewSet):
|
class InterfaceViewSet(CableTraceMixin, ModelViewSet):
|
||||||
queryset = Interface.objects.prefetch_related(
|
queryset = Interface.objects.prefetch_related(
|
||||||
'device', '_connected_interface', '_connected_circuittermination', 'cable', 'ip_addresses', 'tags'
|
'device', 'connected_endpoint', 'cable', 'ip_addresses', 'tags'
|
||||||
).filter(
|
).filter(
|
||||||
device__isnull=False
|
device__isnull=False
|
||||||
)
|
)
|
||||||
@ -530,11 +531,13 @@ class PowerConnectionViewSet(ListModelMixin, GenericViewSet):
|
|||||||
|
|
||||||
class InterfaceConnectionViewSet(ListModelMixin, GenericViewSet):
|
class InterfaceConnectionViewSet(ListModelMixin, GenericViewSet):
|
||||||
queryset = Interface.objects.prefetch_related(
|
queryset = Interface.objects.prefetch_related(
|
||||||
'device', '_connected_interface__device'
|
'device', 'connected_endpoint__device'
|
||||||
).filter(
|
).filter(
|
||||||
# Avoid duplicate connections by only selecting the lower PK in a connected pair
|
# Avoid duplicate connections by only selecting the lower PK in a connected pair
|
||||||
_connected_interface__isnull=False,
|
connected_endpoint_type=ContentType.objects.get_for_model(Interface),
|
||||||
pk__lt=F('_connected_interface')
|
pk__lt=F('connected_endpoint_id')
|
||||||
|
).order_by(
|
||||||
|
'device'
|
||||||
)
|
)
|
||||||
serializer_class = serializers.InterfaceConnectionSerializer
|
serializer_class = serializers.InterfaceConnectionSerializer
|
||||||
filterset_class = filters.InterfaceConnectionFilter
|
filterset_class = filters.InterfaceConnectionFilter
|
||||||
@ -634,9 +637,9 @@ class ConnectedDeviceViewSet(ViewSet):
|
|||||||
|
|
||||||
# Determine local interface from peer interface's connection
|
# Determine local interface from peer interface's connection
|
||||||
peer_interface = get_object_or_404(Interface, device__name=peer_device_name, name=peer_interface_name)
|
peer_interface = get_object_or_404(Interface, device__name=peer_device_name, name=peer_interface_name)
|
||||||
local_interface = peer_interface._connected_interface
|
local_interface = peer_interface.connected_endpoint
|
||||||
|
|
||||||
if local_interface is None:
|
if not isinstance(local_interface, Interface):
|
||||||
return Response()
|
return Response()
|
||||||
|
|
||||||
return Response(serializers.DeviceSerializer(local_interface.device, context={'request': request}).data)
|
return Response(serializers.DeviceSerializer(local_interface.device, context={'request': request}).data)
|
||||||
|
@ -1046,7 +1046,7 @@ class InterfaceConnectionFilter(django_filters.FilterSet):
|
|||||||
return queryset
|
return queryset
|
||||||
return queryset.filter(
|
return queryset.filter(
|
||||||
Q(device__site__slug=value) |
|
Q(device__site__slug=value) |
|
||||||
Q(_connected_interface__device__site__slug=value)
|
Q(connected_interface__device__site__slug=value)
|
||||||
)
|
)
|
||||||
|
|
||||||
def filter_device(self, queryset, name, value):
|
def filter_device(self, queryset, name, value):
|
||||||
@ -1054,7 +1054,7 @@ class InterfaceConnectionFilter(django_filters.FilterSet):
|
|||||||
return queryset
|
return queryset
|
||||||
return queryset.filter(
|
return queryset.filter(
|
||||||
Q(device__name__icontains=value) |
|
Q(device__name__icontains=value) |
|
||||||
Q(_connected_interface__device__name__icontains=value)
|
Q(connected_interface__device__name__icontains=value)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
62
netbox/dcim/migrations/0075_generic_connected_endpoint.py
Normal file
62
netbox/dcim/migrations/0075_generic_connected_endpoint.py
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
# Generated by Django 2.2.5 on 2019-10-06 19:01
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
def connected_interface_to_endpoint(apps, schema_editor):
|
||||||
|
Interface = apps.get_model('dcim', 'Interface')
|
||||||
|
ContentType = apps.get_model('contenttypes', 'ContentType')
|
||||||
|
|
||||||
|
model_type = ContentType.objects.get_for_model(Interface)
|
||||||
|
for interface in Interface.objects.exclude(_connected_interface=None):
|
||||||
|
interface.connected_endpoint_type = model_type
|
||||||
|
interface.connected_endpoint_id = interface._connected_interface.pk
|
||||||
|
interface.save()
|
||||||
|
|
||||||
|
|
||||||
|
def connected_circuittermination_to_endpoint(apps, schema_editor):
|
||||||
|
Interface = apps.get_model('dcim', 'Interface')
|
||||||
|
ContentType = apps.get_model('contenttypes', 'ContentType')
|
||||||
|
CircuitTermination = apps.get_model('circuits', 'CircuitTermination')
|
||||||
|
|
||||||
|
model_type = ContentType.objects.get_for_model(CircuitTermination)
|
||||||
|
for interface in Interface.objects.exclude(_connected_circuittermination=None):
|
||||||
|
interface.connected_endpoint_type = model_type
|
||||||
|
interface.connected_endpoint_id = interface._connected_circuittermination.pk
|
||||||
|
interface.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('contenttypes', '0002_remove_content_type_name'),
|
||||||
|
('dcim', '0074_increase_field_length_platform_name_slug'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='interface',
|
||||||
|
name='connected_endpoint_id',
|
||||||
|
field=models.PositiveIntegerField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='interface',
|
||||||
|
name='connected_endpoint_type',
|
||||||
|
field=models.ForeignKey(blank=True, limit_choices_to={
|
||||||
|
'model__in': ['consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport', 'frontport',
|
||||||
|
'rearport', 'circuittermination']
|
||||||
|
}, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType'),
|
||||||
|
),
|
||||||
|
|
||||||
|
migrations.RunPython(connected_interface_to_endpoint),
|
||||||
|
migrations.RunPython(connected_circuittermination_to_endpoint),
|
||||||
|
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='interface',
|
||||||
|
name='_connected_circuittermination',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='interface',
|
||||||
|
name='_connected_interface',
|
||||||
|
),
|
||||||
|
]
|
@ -2169,20 +2169,30 @@ class Interface(CableTermination, ComponentModel):
|
|||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
max_length=64
|
max_length=64
|
||||||
)
|
)
|
||||||
_connected_interface = models.OneToOneField(
|
|
||||||
|
connected_endpoint_type = models.ForeignKey(
|
||||||
|
to=ContentType,
|
||||||
|
limit_choices_to={'model__in': CABLE_TERMINATION_TYPES},
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
related_name='+',
|
||||||
|
blank = True,
|
||||||
|
null = True
|
||||||
|
)
|
||||||
|
connected_endpoint_id = models.PositiveIntegerField(
|
||||||
|
blank=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
connected_endpoint = GenericForeignKey(
|
||||||
|
ct_field='connected_endpoint_type',
|
||||||
|
fk_field='connected_endpoint_id'
|
||||||
|
)
|
||||||
|
|
||||||
|
connected_interface = GenericRelation(
|
||||||
to='self',
|
to='self',
|
||||||
on_delete=models.SET_NULL,
|
object_id_field='connected_endpoint_id',
|
||||||
related_name='+',
|
content_type_field='connected_endpoint_type',
|
||||||
blank=True,
|
|
||||||
null=True
|
|
||||||
)
|
|
||||||
_connected_circuittermination = models.OneToOneField(
|
|
||||||
to='circuits.CircuitTermination',
|
|
||||||
on_delete=models.SET_NULL,
|
|
||||||
related_name='+',
|
|
||||||
blank=True,
|
|
||||||
null=True
|
|
||||||
)
|
)
|
||||||
|
|
||||||
connection_status = models.NullBooleanField(
|
connection_status = models.NullBooleanField(
|
||||||
choices=CONNECTION_STATUS_CHOICES,
|
choices=CONNECTION_STATUS_CHOICES,
|
||||||
blank=True
|
blank=True
|
||||||
@ -2366,30 +2376,6 @@ class Interface(CableTermination, ComponentModel):
|
|||||||
"""
|
"""
|
||||||
self.type = value
|
self.type = value
|
||||||
|
|
||||||
@property
|
|
||||||
def connected_endpoint(self):
|
|
||||||
if self._connected_interface:
|
|
||||||
return self._connected_interface
|
|
||||||
return self._connected_circuittermination
|
|
||||||
|
|
||||||
@connected_endpoint.setter
|
|
||||||
def connected_endpoint(self, value):
|
|
||||||
from circuits.models import CircuitTermination
|
|
||||||
|
|
||||||
if value is None:
|
|
||||||
self._connected_interface = None
|
|
||||||
self._connected_circuittermination = None
|
|
||||||
elif isinstance(value, Interface):
|
|
||||||
self._connected_interface = value
|
|
||||||
self._connected_circuittermination = None
|
|
||||||
elif isinstance(value, CircuitTermination):
|
|
||||||
self._connected_interface = None
|
|
||||||
self._connected_circuittermination = value
|
|
||||||
else:
|
|
||||||
raise ValueError(
|
|
||||||
"Connected endpoint must be an Interface or CircuitTermination, not {}.".format(type(value))
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def parent(self):
|
def parent(self):
|
||||||
return self.device or self.virtual_machine
|
return self.device or self.virtual_machine
|
||||||
|
@ -822,18 +822,18 @@ class InterfaceConnectionTable(BaseTable):
|
|||||||
)
|
)
|
||||||
device_b = tables.LinkColumn(
|
device_b = tables.LinkColumn(
|
||||||
viewname='dcim:device',
|
viewname='dcim:device',
|
||||||
accessor=Accessor('_connected_interface.device'),
|
accessor=Accessor('connected_endpoint.device'),
|
||||||
args=[Accessor('_connected_interface.device.pk')],
|
args=[Accessor('connected_endpoint.device.pk')],
|
||||||
verbose_name='Device B'
|
verbose_name='Device B'
|
||||||
)
|
)
|
||||||
interface_b = tables.LinkColumn(
|
interface_b = tables.LinkColumn(
|
||||||
viewname='dcim:interface',
|
viewname='dcim:interface',
|
||||||
accessor=Accessor('_connected_interface'),
|
accessor=Accessor('connected_endpoint'),
|
||||||
args=[Accessor('_connected_interface.pk')],
|
args=[Accessor('connected_endpoint.pk')],
|
||||||
verbose_name='Interface B'
|
verbose_name='Interface B'
|
||||||
)
|
)
|
||||||
description_b = tables.Column(
|
description_b = tables.Column(
|
||||||
accessor=Accessor('_connected_interface.description'),
|
accessor=Accessor('connected_endpoint.description'),
|
||||||
verbose_name='Description'
|
verbose_name='Description'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -993,7 +993,7 @@ class DeviceView(PermissionRequiredMixin, View):
|
|||||||
|
|
||||||
# Interfaces
|
# Interfaces
|
||||||
interfaces = device.vc_interfaces.prefetch_related(
|
interfaces = device.vc_interfaces.prefetch_related(
|
||||||
'lag', '_connected_interface__device', '_connected_circuittermination__circuit', 'cable',
|
'lag', 'connected_endpoint', 'cable',
|
||||||
'cable__termination_a', 'cable__termination_b', 'ip_addresses', 'tags'
|
'cable__termination_a', 'cable__termination_b', 'ip_addresses', 'tags'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1079,7 +1079,7 @@ class DeviceLLDPNeighborsView(PermissionRequiredMixin, View):
|
|||||||
|
|
||||||
device = get_object_or_404(Device, pk=pk)
|
device = get_object_or_404(Device, pk=pk)
|
||||||
interfaces = device.vc_interfaces.connectable().prefetch_related(
|
interfaces = device.vc_interfaces.connectable().prefetch_related(
|
||||||
'_connected_interface__device'
|
'connected_endpoint'
|
||||||
)
|
)
|
||||||
|
|
||||||
return render(request, 'dcim/device_lldp_neighbors.html', {
|
return render(request, 'dcim/device_lldp_neighbors.html', {
|
||||||
@ -1371,8 +1371,7 @@ class InterfaceView(PermissionRequiredMixin, View):
|
|||||||
|
|
||||||
return render(request, 'dcim/interface.html', {
|
return render(request, 'dcim/interface.html', {
|
||||||
'interface': interface,
|
'interface': interface,
|
||||||
'connected_interface': interface._connected_interface,
|
'connected_endpoint': interface.connected_endpoint,
|
||||||
'connected_circuittermination': interface._connected_circuittermination,
|
|
||||||
'ipaddress_table': ipaddress_table,
|
'ipaddress_table': ipaddress_table,
|
||||||
'vlan_table': vlan_table,
|
'vlan_table': vlan_table,
|
||||||
})
|
})
|
||||||
@ -1940,11 +1939,11 @@ class PowerConnectionsListView(PermissionRequiredMixin, ObjectListView):
|
|||||||
class InterfaceConnectionsListView(PermissionRequiredMixin, ObjectListView):
|
class InterfaceConnectionsListView(PermissionRequiredMixin, ObjectListView):
|
||||||
permission_required = 'dcim.view_interface'
|
permission_required = 'dcim.view_interface'
|
||||||
queryset = Interface.objects.prefetch_related(
|
queryset = Interface.objects.prefetch_related(
|
||||||
'device', 'cable', '_connected_interface__device'
|
'device', 'cable', 'connected_endpoint__device'
|
||||||
).filter(
|
).filter(
|
||||||
# Avoid duplicate connections by only selecting the lower PK in a connected pair
|
# Avoid duplicate connections by only selecting the lower PK in a connected pair
|
||||||
_connected_interface__isnull=False,
|
connected_endpoint_type=ContentType.objects.get_for_model(Interface),
|
||||||
pk__lt=F('_connected_interface')
|
pk__lt=F('connected_endpoint_id')
|
||||||
).order_by(
|
).order_by(
|
||||||
'device'
|
'device'
|
||||||
)
|
)
|
||||||
|
@ -638,23 +638,16 @@ class TopologyMap(models.Model):
|
|||||||
|
|
||||||
# Add all interface connections to the graph
|
# Add all interface connections to the graph
|
||||||
connected_interfaces = Interface.objects.prefetch_related(
|
connected_interfaces = Interface.objects.prefetch_related(
|
||||||
'_connected_interface__device'
|
'connected_endpoint__device'
|
||||||
).filter(
|
).filter(
|
||||||
Q(device__in=devices) | Q(_connected_interface__device__in=devices),
|
Q(device__in=devices) | Q(connected_interface__device__in=devices),
|
||||||
_connected_interface__isnull=False,
|
connected_endpoint_type=ContentType.objects.get_for_model(Interface),
|
||||||
pk__lt=F('_connected_interface')
|
pk__lt=F('connected_endpoint_id')
|
||||||
)
|
)
|
||||||
for interface in connected_interfaces:
|
for interface in connected_interfaces:
|
||||||
style = 'solid' if interface.connection_status == CONNECTION_STATUS_CONNECTED else 'dashed'
|
style = 'solid' if interface.connection_status == CONNECTION_STATUS_CONNECTED else 'dashed'
|
||||||
self.graph.edge(interface.device.name, interface.connected_endpoint.device.name, style=style)
|
self.graph.edge(interface.device.name, interface.connected_endpoint.device.name, style=style)
|
||||||
|
|
||||||
# Add all circuits to the graph
|
|
||||||
for termination in CircuitTermination.objects.filter(term_side='A', connected_endpoint__device__in=devices):
|
|
||||||
peer_termination = termination.get_peer_termination()
|
|
||||||
if (peer_termination is not None and peer_termination.interface is not None and
|
|
||||||
peer_termination.interface.device in devices):
|
|
||||||
self.graph.edge(termination.interface.device.name, peer_termination.interface.device.name, color='blue')
|
|
||||||
|
|
||||||
def add_console_connections(self, devices):
|
def add_console_connections(self, devices):
|
||||||
|
|
||||||
from dcim.models import ConsolePort
|
from dcim.models import ConsolePort
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db.models import Count, F
|
from django.db.models import Count, F
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.views.generic import View
|
from django.views.generic import View
|
||||||
@ -200,8 +201,8 @@ class HomeView(View):
|
|||||||
_connected_poweroutlet__isnull=False
|
_connected_poweroutlet__isnull=False
|
||||||
)
|
)
|
||||||
connected_interfaces = Interface.objects.filter(
|
connected_interfaces = Interface.objects.filter(
|
||||||
_connected_interface__isnull=False,
|
connected_endpoint_type=ContentType.objects.get_for_model(Interface),
|
||||||
pk__lt=F('_connected_interface')
|
pk__lt=F('connected_endpoint_id')
|
||||||
)
|
)
|
||||||
cables = Cable.objects.all()
|
cables = Cable.objects.all()
|
||||||
|
|
||||||
|
@ -110,27 +110,27 @@
|
|||||||
</div>
|
</div>
|
||||||
{% if interface.cable %}
|
{% if interface.cable %}
|
||||||
<table class="table table-hover panel-body attr-table">
|
<table class="table table-hover panel-body attr-table">
|
||||||
{% if connected_interface %}
|
{% if connected_endpoint.device %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>Device</td>
|
<td>Device</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ connected_interface.parent.get_absolute_url }}">{{ connected_interface.device }}</a>
|
<a href="{{ connected_endpoint.device.get_absolute_url }}">{{ connected_endpoint.device }}</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Name</td>
|
<td>Name</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ connected_interface.get_absolute_url }}">{{ connected_interface.name }}</a>
|
<a href="{{ connected_endpoint.get_absolute_url }}">{{ connected_endpoint.name }}</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Type</td>
|
<td>Type</td>
|
||||||
<td>{{ connected_interface.get_type_display }}</td>
|
<td>{{ connected_endpoint.get_type_display }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Enabled</td>
|
<td>Enabled</td>
|
||||||
<td>
|
<td>
|
||||||
{% if connected_interface.enabled %}
|
{% if connected_endpoint.enabled %}
|
||||||
<span class="text-success"><i class="fa fa-check"></i></span>
|
<span class="text-success"><i class="fa fa-check"></i></span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-danger"><i class="fa fa-close"></i></span>
|
<span class="text-danger"><i class="fa fa-close"></i></span>
|
||||||
@ -140,8 +140,8 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>LAG</td>
|
<td>LAG</td>
|
||||||
<td>
|
<td>
|
||||||
{% if connected_interface.lag%}
|
{% if connected_endpoint.lag%}
|
||||||
<a href="{{ connected_interface.lag.get_absolute_url }}">{{ connected_interface.lag }}</a>
|
<a href="{{ connected_endpoint.lag.get_absolute_url }}">{{ connected_endpoint.lag }}</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">None</span>
|
<span class="text-muted">None</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -149,22 +149,22 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Description</td>
|
<td>Description</td>
|
||||||
<td>{{ connected_interface.description|placeholder }}</td>
|
<td>{{ connected_endpoint.description|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>MTU</td>
|
<td>MTU</td>
|
||||||
<td>{{ connected_interface.mtu|placeholder }}</td>
|
<td>{{ connected_endpoint.mtu|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>MAC Address</td>
|
<td>MAC Address</td>
|
||||||
<td>{{ connected_interface.mac_address|placeholder }}</td>
|
<td>{{ connected_endpoint.mac_address|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>802.1Q Mode</td>
|
<td>802.1Q Mode</td>
|
||||||
<td>{{ connected_interface.get_mode_display }}</td>
|
<td>{{ connected_endpoint.get_mode_display }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% elif connected_circuittermination %}
|
{% elif connected_endpoint.circuit %}
|
||||||
{% with ct=connected_circuittermination %}
|
{% with ct=connected_endpoint %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>Provider</td>
|
<td>Provider</td>
|
||||||
<td><a href="{{ ct.circuit.provider.get_absolute_url }}">{{ ct.circuit.provider }}</a></td>
|
<td><a href="{{ ct.circuit.provider.get_absolute_url }}">{{ ct.circuit.provider }}</a></td>
|
||||||
|
Loading…
Reference in New Issue
Block a user