mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-22 03:56:53 -06:00
Deprecated the InterfaceConnection model
This commit is contained in:
parent
2ec8dc8319
commit
f30367e094
@ -237,9 +237,7 @@ class CircuitTerminationForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
interface = ChainedModelChoiceField(
|
interface = ChainedModelChoiceField(
|
||||||
queryset=Interface.objects.connectable().select_related(
|
queryset=Interface.objects.connectable().select_related('circuit_termination'),
|
||||||
'circuit_termination', 'connected_as_a', 'connected_as_b'
|
|
||||||
),
|
|
||||||
chains=(
|
chains=(
|
||||||
('device', 'device'),
|
('device', 'device'),
|
||||||
),
|
),
|
||||||
|
@ -6,10 +6,9 @@ from circuits.models import Circuit, CircuitTermination
|
|||||||
from dcim.constants import *
|
from dcim.constants import *
|
||||||
from dcim.models import (
|
from dcim.models import (
|
||||||
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
||||||
DeviceBayTemplate, DeviceType, DeviceRole, FrontPanelPort, FrontPanelPortTemplate, Interface, InterfaceConnection,
|
DeviceBayTemplate, DeviceType, DeviceRole, FrontPanelPort, FrontPanelPortTemplate, Interface, InterfaceTemplate,
|
||||||
InterfaceTemplate, Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort,
|
Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack,
|
||||||
PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPanelPort, RearPanelPortTemplate, Region, Site,
|
RackGroup, RackReservation, RackRole, RearPanelPort, RearPanelPortTemplate, Region, Site, VirtualChassis,
|
||||||
VirtualChassis,
|
|
||||||
)
|
)
|
||||||
from extras.api.customfields import CustomFieldModelSerializer
|
from extras.api.customfields import CustomFieldModelSerializer
|
||||||
from ipam.models import IPAddress, VLAN
|
from ipam.models import IPAddress, VLAN
|
||||||
@ -614,7 +613,7 @@ class IsConnectedMixin(object):
|
|||||||
"""
|
"""
|
||||||
Return True if the interface has a connected interface or circuit.
|
Return True if the interface has a connected interface or circuit.
|
||||||
"""
|
"""
|
||||||
if obj.connection:
|
if obj.connected_endpoint:
|
||||||
return True
|
return True
|
||||||
if hasattr(obj, 'circuit_termination') and obj.circuit_termination is not None:
|
if hasattr(obj, 'circuit_termination') and obj.circuit_termination is not None:
|
||||||
return True
|
return True
|
||||||
@ -662,8 +661,8 @@ class InterfaceSerializer(TaggitSerializer, IsConnectedMixin, ValidatedModelSeri
|
|||||||
device = NestedDeviceSerializer()
|
device = NestedDeviceSerializer()
|
||||||
form_factor = ChoiceField(choices=IFACE_FF_CHOICES, required=False)
|
form_factor = ChoiceField(choices=IFACE_FF_CHOICES, required=False)
|
||||||
lag = NestedInterfaceSerializer(required=False, allow_null=True)
|
lag = NestedInterfaceSerializer(required=False, allow_null=True)
|
||||||
|
connected_endpoint = NestedInterfaceSerializer(required=False, allow_null=True)
|
||||||
is_connected = serializers.SerializerMethodField(read_only=True)
|
is_connected = serializers.SerializerMethodField(read_only=True)
|
||||||
interface_connection = serializers.SerializerMethodField(read_only=True)
|
|
||||||
circuit_termination = InterfaceCircuitTerminationSerializer(read_only=True)
|
circuit_termination = InterfaceCircuitTerminationSerializer(read_only=True)
|
||||||
mode = ChoiceField(choices=IFACE_MODE_CHOICES, required=False, allow_null=True)
|
mode = ChoiceField(choices=IFACE_MODE_CHOICES, required=False, allow_null=True)
|
||||||
untagged_vlan = InterfaceVLANSerializer(required=False, allow_null=True)
|
untagged_vlan = InterfaceVLANSerializer(required=False, allow_null=True)
|
||||||
@ -679,7 +678,7 @@ class InterfaceSerializer(TaggitSerializer, IsConnectedMixin, ValidatedModelSeri
|
|||||||
model = Interface
|
model = Interface
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'device', 'name', 'form_factor', 'enabled', 'lag', 'mtu', 'mac_address', 'mgmt_only', 'description',
|
'id', 'device', 'name', 'form_factor', 'enabled', 'lag', 'mtu', 'mac_address', 'mgmt_only', 'description',
|
||||||
'is_connected', 'interface_connection', 'circuit_termination', 'mode', 'untagged_vlan', 'tagged_vlans',
|
'is_connected', 'connected_endpoint', 'circuit_termination', 'mode', 'untagged_vlan', 'tagged_vlans',
|
||||||
'tags',
|
'tags',
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -702,15 +701,6 @@ class InterfaceSerializer(TaggitSerializer, IsConnectedMixin, ValidatedModelSeri
|
|||||||
|
|
||||||
return super(InterfaceSerializer, self).validate(data)
|
return super(InterfaceSerializer, self).validate(data)
|
||||||
|
|
||||||
def get_interface_connection(self, obj):
|
|
||||||
if obj.connection:
|
|
||||||
context = {
|
|
||||||
'request': self.context['request'],
|
|
||||||
'interface': obj.connected_interface,
|
|
||||||
}
|
|
||||||
return ContextualInterfaceConnectionSerializer(obj.connection, context=context).data
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Rear panel ports
|
# Rear panel ports
|
||||||
@ -804,36 +794,17 @@ class InventoryItemSerializer(TaggitSerializer, ValidatedModelSerializer):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class InterfaceConnectionSerializer(ValidatedModelSerializer):
|
class InterfaceConnectionSerializer(ValidatedModelSerializer):
|
||||||
interface_a = NestedInterfaceSerializer()
|
interface_a = serializers.SerializerMethodField()
|
||||||
interface_b = NestedInterfaceSerializer()
|
interface_b = NestedInterfaceSerializer(source='connected_endpoint')
|
||||||
connection_status = ChoiceField(choices=CONNECTION_STATUS_CHOICES, required=False)
|
connection_status = ChoiceField(choices=CONNECTION_STATUS_CHOICES, required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = InterfaceConnection
|
model = Interface
|
||||||
fields = ['id', 'interface_a', 'interface_b', 'connection_status']
|
fields = ['interface_a', 'interface_b', 'connection_status']
|
||||||
|
|
||||||
|
def get_interface_a(self, obj):
|
||||||
class NestedInterfaceConnectionSerializer(WritableNestedSerializer):
|
context = {'request': self.context['request']}
|
||||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:interfaceconnection-detail')
|
return NestedInterfaceSerializer(instance=obj, context=context).data
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = InterfaceConnection
|
|
||||||
fields = ['id', 'url', 'connection_status']
|
|
||||||
|
|
||||||
|
|
||||||
class ContextualInterfaceConnectionSerializer(serializers.ModelSerializer):
|
|
||||||
"""
|
|
||||||
A read-only representation of an InterfaceConnection from the perspective of either of its two connected Interfaces.
|
|
||||||
"""
|
|
||||||
interface = serializers.SerializerMethodField(read_only=True)
|
|
||||||
connection_status = ChoiceField(choices=CONNECTION_STATUS_CHOICES, read_only=True)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = InterfaceConnection
|
|
||||||
fields = ['id', 'interface', 'connection_status']
|
|
||||||
|
|
||||||
def get_interface(self, obj):
|
|
||||||
return NestedInterfaceSerializer(self.context['interface'], context=self.context).data
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -60,7 +60,7 @@ router.register(r'inventory-items', views.InventoryItemViewSet)
|
|||||||
# Connections
|
# Connections
|
||||||
router.register(r'console-connections', views.ConsoleConnectionViewSet, base_name='consoleconnections')
|
router.register(r'console-connections', views.ConsoleConnectionViewSet, base_name='consoleconnections')
|
||||||
router.register(r'power-connections', views.PowerConnectionViewSet, base_name='powerconnections')
|
router.register(r'power-connections', views.PowerConnectionViewSet, base_name='powerconnections')
|
||||||
router.register(r'interface-connections', views.InterfaceConnectionViewSet)
|
router.register(r'interface-connections', views.InterfaceConnectionViewSet, base_name='interfaceconnections')
|
||||||
|
|
||||||
# Virtual chassis
|
# Virtual chassis
|
||||||
router.register(r'virtual-chassis', views.VirtualChassisViewSet)
|
router.register(r'virtual-chassis', views.VirtualChassisViewSet)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.db.models import 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
|
||||||
from drf_yasg import openapi
|
from drf_yasg import openapi
|
||||||
@ -14,10 +15,9 @@ from rest_framework.viewsets import GenericViewSet, ViewSet
|
|||||||
from dcim import filters
|
from dcim import filters
|
||||||
from dcim.models import (
|
from dcim.models import (
|
||||||
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
||||||
DeviceBayTemplate, DeviceRole, DeviceType, FrontPanelPort, FrontPanelPortTemplate, Interface, InterfaceConnection,
|
DeviceBayTemplate, DeviceRole, DeviceType, FrontPanelPort, FrontPanelPortTemplate, Interface, InterfaceTemplate,
|
||||||
InterfaceTemplate, Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort,
|
Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack,
|
||||||
PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPanelPort, RearPanelPortTemplate, Region, Site,
|
RackGroup, RackReservation, RackRole, RearPanelPort, RearPanelPortTemplate, Region, Site, VirtualChassis,
|
||||||
VirtualChassis,
|
|
||||||
)
|
)
|
||||||
from extras.api.serializers import RenderedGraphSerializer
|
from extras.api.serializers import RenderedGraphSerializer
|
||||||
from extras.api.views import CustomFieldModelViewSet
|
from extras.api.views import CustomFieldModelViewSet
|
||||||
@ -35,8 +35,7 @@ class DCIMFieldChoicesViewSet(FieldChoicesViewSet):
|
|||||||
fields = (
|
fields = (
|
||||||
(Device, ['face', 'status']),
|
(Device, ['face', 'status']),
|
||||||
(ConsolePort, ['connection_status']),
|
(ConsolePort, ['connection_status']),
|
||||||
(Interface, ['form_factor', 'mode']),
|
(Interface, ['connection_status', 'form_factor', 'mode']),
|
||||||
(InterfaceConnection, ['connection_status']),
|
|
||||||
(InterfaceTemplate, ['form_factor']),
|
(InterfaceTemplate, ['form_factor']),
|
||||||
(PowerPort, ['connection_status']),
|
(PowerPort, ['connection_status']),
|
||||||
(Rack, ['type', 'width']),
|
(Rack, ['type', 'width']),
|
||||||
@ -419,7 +418,12 @@ class PowerConnectionViewSet(ListModelMixin, GenericViewSet):
|
|||||||
|
|
||||||
|
|
||||||
class InterfaceConnectionViewSet(ModelViewSet):
|
class InterfaceConnectionViewSet(ModelViewSet):
|
||||||
queryset = InterfaceConnection.objects.select_related('interface_a__device', 'interface_b__device')
|
queryset = Interface.objects.select_related(
|
||||||
|
'device', 'connected_endpoint__device'
|
||||||
|
).filter(
|
||||||
|
connected_endpoint__isnull=False,
|
||||||
|
pk__lt=F('connected_endpoint')
|
||||||
|
)
|
||||||
serializer_class = serializers.InterfaceConnectionSerializer
|
serializer_class = serializers.InterfaceConnectionSerializer
|
||||||
filter_class = filters.InterfaceConnectionFilter
|
filter_class = filters.InterfaceConnectionFilter
|
||||||
|
|
||||||
|
@ -14,10 +14,9 @@ from .constants import (
|
|||||||
)
|
)
|
||||||
from .models import (
|
from .models import (
|
||||||
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
||||||
DeviceBayTemplate, DeviceRole, DeviceType, FrontPanelPort, FrontPanelPortTemplate, Interface, InterfaceConnection,
|
DeviceBayTemplate, DeviceRole, DeviceType, FrontPanelPort, FrontPanelPortTemplate, Interface, InterfaceTemplate,
|
||||||
InterfaceTemplate, InventoryItem, Manufacturer, Platform, PowerOutlet, PowerOutletTemplate, PowerPort,
|
InventoryItem, Manufacturer, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack,
|
||||||
PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPanelPort, RearPanelPortTemplate, Region, Site,
|
RackGroup, RackReservation, RackRole, RearPanelPort, RearPanelPortTemplate, Region, Site, VirtualChassis,
|
||||||
VirtualChassis,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -853,21 +852,21 @@ class InterfaceConnectionFilter(django_filters.FilterSet):
|
|||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = InterfaceConnection
|
model = Interface
|
||||||
fields = ['connection_status']
|
fields = ['connection_status']
|
||||||
|
|
||||||
def filter_site(self, queryset, name, value):
|
def filter_site(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
return queryset
|
return queryset
|
||||||
return queryset.filter(
|
return queryset.filter(
|
||||||
Q(interface_a__device__site__slug=value) |
|
Q(device__site__slug=value) |
|
||||||
Q(interface_b__device__site__slug=value)
|
Q(connected_endpoint__device__site__slug=value)
|
||||||
)
|
)
|
||||||
|
|
||||||
def filter_device(self, queryset, name, value):
|
def filter_device(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
return queryset
|
return queryset
|
||||||
return queryset.filter(
|
return queryset.filter(
|
||||||
Q(interface_a__device__name__icontains=value) |
|
Q(device__name__icontains=value) |
|
||||||
Q(interface_b__device__name__icontains=value)
|
Q(connected_endpoint__device__name__icontains=value)
|
||||||
)
|
)
|
||||||
|
@ -5746,158 +5746,5 @@
|
|||||||
"mgmt_only": true,
|
"mgmt_only": true,
|
||||||
"description": ""
|
"description": ""
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "dcim.interfaceconnection",
|
|
||||||
"pk": 3,
|
|
||||||
"fields": {
|
|
||||||
"interface_a": 99,
|
|
||||||
"interface_b": 15,
|
|
||||||
"connection_status": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "dcim.interfaceconnection",
|
|
||||||
"pk": 4,
|
|
||||||
"fields": {
|
|
||||||
"interface_a": 100,
|
|
||||||
"interface_b": 153,
|
|
||||||
"connection_status": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "dcim.interfaceconnection",
|
|
||||||
"pk": 5,
|
|
||||||
"fields": {
|
|
||||||
"interface_a": 46,
|
|
||||||
"interface_b": 14,
|
|
||||||
"connection_status": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "dcim.interfaceconnection",
|
|
||||||
"pk": 6,
|
|
||||||
"fields": {
|
|
||||||
"interface_a": 47,
|
|
||||||
"interface_b": 152,
|
|
||||||
"connection_status": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "dcim.interfaceconnection",
|
|
||||||
"pk": 7,
|
|
||||||
"fields": {
|
|
||||||
"interface_a": 91,
|
|
||||||
"interface_b": 144,
|
|
||||||
"connection_status": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "dcim.interfaceconnection",
|
|
||||||
"pk": 8,
|
|
||||||
"fields": {
|
|
||||||
"interface_a": 92,
|
|
||||||
"interface_b": 145,
|
|
||||||
"connection_status": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "dcim.interfaceconnection",
|
|
||||||
"pk": 16,
|
|
||||||
"fields": {
|
|
||||||
"interface_a": 189,
|
|
||||||
"interface_b": 37,
|
|
||||||
"connection_status": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "dcim.interfaceconnection",
|
|
||||||
"pk": 17,
|
|
||||||
"fields": {
|
|
||||||
"interface_a": 192,
|
|
||||||
"interface_b": 175,
|
|
||||||
"connection_status": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "dcim.interfaceconnection",
|
|
||||||
"pk": 18,
|
|
||||||
"fields": {
|
|
||||||
"interface_a": 195,
|
|
||||||
"interface_b": 41,
|
|
||||||
"connection_status": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "dcim.interfaceconnection",
|
|
||||||
"pk": 19,
|
|
||||||
"fields": {
|
|
||||||
"interface_a": 198,
|
|
||||||
"interface_b": 179,
|
|
||||||
"connection_status": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "dcim.interfaceconnection",
|
|
||||||
"pk": 20,
|
|
||||||
"fields": {
|
|
||||||
"interface_a": 191,
|
|
||||||
"interface_b": 197,
|
|
||||||
"connection_status": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "dcim.interfaceconnection",
|
|
||||||
"pk": 21,
|
|
||||||
"fields": {
|
|
||||||
"interface_a": 194,
|
|
||||||
"interface_b": 200,
|
|
||||||
"connection_status": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "dcim.interfaceconnection",
|
|
||||||
"pk": 22,
|
|
||||||
"fields": {
|
|
||||||
"interface_a": 9,
|
|
||||||
"interface_b": 218,
|
|
||||||
"connection_status": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "dcim.interfaceconnection",
|
|
||||||
"pk": 23,
|
|
||||||
"fields": {
|
|
||||||
"interface_a": 8,
|
|
||||||
"interface_b": 206,
|
|
||||||
"connection_status": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "dcim.interfaceconnection",
|
|
||||||
"pk": 24,
|
|
||||||
"fields": {
|
|
||||||
"interface_a": 7,
|
|
||||||
"interface_b": 212,
|
|
||||||
"connection_status": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "dcim.interfaceconnection",
|
|
||||||
"pk": 25,
|
|
||||||
"fields": {
|
|
||||||
"interface_a": 217,
|
|
||||||
"interface_b": 205,
|
|
||||||
"connection_status": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "dcim.interfaceconnection",
|
|
||||||
"pk": 26,
|
|
||||||
"fields": {
|
|
||||||
"interface_a": 216,
|
|
||||||
"interface_b": 211,
|
|
||||||
"connection_status": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -26,10 +26,9 @@ from virtualization.models import Cluster
|
|||||||
from .constants import *
|
from .constants import *
|
||||||
from .models import (
|
from .models import (
|
||||||
Cable, DeviceBay, DeviceBayTemplate, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate,
|
Cable, DeviceBay, DeviceBayTemplate, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate,
|
||||||
Device, DeviceRole, DeviceType, FrontPanelPort, FrontPanelPortTemplate, Interface, InterfaceConnection,
|
Device, DeviceRole, DeviceType, FrontPanelPort, FrontPanelPortTemplate, Interface, InterfaceTemplate, Manufacturer,
|
||||||
InterfaceTemplate, Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort,
|
InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup,
|
||||||
PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPanelPort, RearPanelPortTemplate, Region, Site,
|
RackReservation, RackRole, RearPanelPort, RearPanelPortTemplate, Region, Site, VirtualChassis,
|
||||||
VirtualChassis,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
DEVICE_BY_PK_RE = r'{\d+\}'
|
DEVICE_BY_PK_RE = r'{\d+\}'
|
||||||
@ -2017,173 +2016,6 @@ class InterfaceBulkDisconnectForm(ConfirmationForm):
|
|||||||
pk = forms.ModelMultipleChoiceField(queryset=Interface.objects.all(), widget=forms.MultipleHiddenInput)
|
pk = forms.ModelMultipleChoiceField(queryset=Interface.objects.all(), widget=forms.MultipleHiddenInput)
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Interface connections
|
|
||||||
#
|
|
||||||
|
|
||||||
class InterfaceConnectionForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm):
|
|
||||||
interface_a = forms.ChoiceField(
|
|
||||||
choices=[],
|
|
||||||
widget=SelectWithDisabled,
|
|
||||||
label='Interface'
|
|
||||||
)
|
|
||||||
site_b = forms.ModelChoiceField(
|
|
||||||
queryset=Site.objects.all(),
|
|
||||||
label='Site',
|
|
||||||
required=False,
|
|
||||||
widget=forms.Select(
|
|
||||||
attrs={'filter-for': 'rack_b'}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
rack_b = ChainedModelChoiceField(
|
|
||||||
queryset=Rack.objects.all(),
|
|
||||||
chains=(
|
|
||||||
('site', 'site_b'),
|
|
||||||
),
|
|
||||||
label='Rack',
|
|
||||||
required=False,
|
|
||||||
widget=APISelect(
|
|
||||||
api_url='/api/dcim/racks/?site_id={{site_b}}',
|
|
||||||
attrs={'filter-for': 'device_b', 'nullable': 'true'}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
device_b = ChainedModelChoiceField(
|
|
||||||
queryset=Device.objects.all(),
|
|
||||||
chains=(
|
|
||||||
('site', 'site_b'),
|
|
||||||
('rack', 'rack_b'),
|
|
||||||
),
|
|
||||||
label='Device',
|
|
||||||
required=False,
|
|
||||||
widget=APISelect(
|
|
||||||
api_url='/api/dcim/devices/?site_id={{site_b}}&rack_id={{rack_b}}',
|
|
||||||
display_field='display_name',
|
|
||||||
attrs={'filter-for': 'interface_b'}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
livesearch = forms.CharField(
|
|
||||||
required=False,
|
|
||||||
label='Device',
|
|
||||||
widget=Livesearch(
|
|
||||||
query_key='q',
|
|
||||||
query_url='dcim-api:device-list',
|
|
||||||
field_to_update='device_b'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
interface_b = ChainedModelChoiceField(
|
|
||||||
queryset=Interface.objects.connectable().select_related(
|
|
||||||
'circuit_termination', 'connected_as_a', 'connected_as_b'
|
|
||||||
),
|
|
||||||
chains=(
|
|
||||||
('device', 'device_b'),
|
|
||||||
),
|
|
||||||
label='Interface',
|
|
||||||
widget=APISelect(
|
|
||||||
api_url='/api/dcim/interfaces/?device_id={{device_b}}&type=physical',
|
|
||||||
disabled_indicator='is_connected'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = InterfaceConnection
|
|
||||||
fields = ['interface_a', 'site_b', 'rack_b', 'device_b', 'interface_b', 'livesearch', 'connection_status']
|
|
||||||
|
|
||||||
def __init__(self, device_a, *args, **kwargs):
|
|
||||||
|
|
||||||
super(InterfaceConnectionForm, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
# Initialize interface A choices
|
|
||||||
device_a_interfaces = device_a.vc_interfaces.connectable().order_naturally().select_related(
|
|
||||||
'circuit_termination', 'connected_as_a', 'connected_as_b'
|
|
||||||
)
|
|
||||||
self.fields['interface_a'].choices = [
|
|
||||||
(iface.id, {'label': iface.name, 'disabled': iface.is_connected}) for iface in device_a_interfaces
|
|
||||||
]
|
|
||||||
|
|
||||||
# Mark connected interfaces as disabled
|
|
||||||
if self.data.get('device_b'):
|
|
||||||
self.fields['interface_b'].choices = []
|
|
||||||
for iface in self.fields['interface_b'].queryset:
|
|
||||||
self.fields['interface_b'].choices.append(
|
|
||||||
(iface.id, {'label': iface.name, 'disabled': iface.is_connected})
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class InterfaceConnectionCSVForm(forms.ModelForm):
|
|
||||||
device_a = FlexibleModelChoiceField(
|
|
||||||
queryset=Device.objects.all(),
|
|
||||||
to_field_name='name',
|
|
||||||
help_text='Name or ID of device A',
|
|
||||||
error_messages={'invalid_choice': 'Device A not found.'}
|
|
||||||
)
|
|
||||||
interface_a = forms.CharField(
|
|
||||||
help_text='Name of interface A'
|
|
||||||
)
|
|
||||||
device_b = FlexibleModelChoiceField(
|
|
||||||
queryset=Device.objects.all(),
|
|
||||||
to_field_name='name',
|
|
||||||
help_text='Name or ID of device B',
|
|
||||||
error_messages={'invalid_choice': 'Device B not found.'}
|
|
||||||
)
|
|
||||||
interface_b = forms.CharField(
|
|
||||||
help_text='Name of interface B'
|
|
||||||
)
|
|
||||||
connection_status = CSVChoiceField(
|
|
||||||
choices=CONNECTION_STATUS_CHOICES,
|
|
||||||
help_text='Connection status'
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = InterfaceConnection
|
|
||||||
fields = InterfaceConnection.csv_headers
|
|
||||||
|
|
||||||
def clean_interface_a(self):
|
|
||||||
|
|
||||||
interface_name = self.cleaned_data.get('interface_a')
|
|
||||||
if not interface_name:
|
|
||||||
return None
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Retrieve interface by name
|
|
||||||
interface = Interface.objects.get(
|
|
||||||
device=self.cleaned_data['device_a'], name=interface_name
|
|
||||||
)
|
|
||||||
# Check for an existing connection to this interface
|
|
||||||
if InterfaceConnection.objects.filter(Q(interface_a=interface) | Q(interface_b=interface)).count():
|
|
||||||
raise forms.ValidationError("{} {} is already connected".format(
|
|
||||||
self.cleaned_data['device_a'], interface_name
|
|
||||||
))
|
|
||||||
except Interface.DoesNotExist:
|
|
||||||
raise forms.ValidationError("Invalid interface ({} {})".format(
|
|
||||||
self.cleaned_data['device_a'], interface_name
|
|
||||||
))
|
|
||||||
|
|
||||||
return interface
|
|
||||||
|
|
||||||
def clean_interface_b(self):
|
|
||||||
|
|
||||||
interface_name = self.cleaned_data.get('interface_b')
|
|
||||||
if not interface_name:
|
|
||||||
return None
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Retrieve interface by name
|
|
||||||
interface = Interface.objects.get(
|
|
||||||
device=self.cleaned_data['device_b'], name=interface_name
|
|
||||||
)
|
|
||||||
# Check for an existing connection to this interface
|
|
||||||
if InterfaceConnection.objects.filter(Q(interface_a=interface) | Q(interface_b=interface)).count():
|
|
||||||
raise forms.ValidationError("{} {} is already connected".format(
|
|
||||||
self.cleaned_data['device_b'], interface_name
|
|
||||||
))
|
|
||||||
except Interface.DoesNotExist:
|
|
||||||
raise forms.ValidationError("Invalid interface ({} {})".format(
|
|
||||||
self.cleaned_data['device_b'], interface_name
|
|
||||||
))
|
|
||||||
|
|
||||||
return interface
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Front panel ports
|
# Front panel ports
|
||||||
#
|
#
|
||||||
|
@ -17,6 +17,7 @@ def console_connections_to_cables(apps, schema_editor):
|
|||||||
consoleserverport_type = ContentType.objects.get_for_model(ConsoleServerPort)
|
consoleserverport_type = ContentType.objects.get_for_model(ConsoleServerPort)
|
||||||
|
|
||||||
# Create a new Cable instance from each console connection
|
# Create a new Cable instance from each console connection
|
||||||
|
print("\n Adding console connections... ", end='', flush=True)
|
||||||
for consoleport in ConsolePort.objects.filter(connected_endpoint__isnull=False):
|
for consoleport in ConsolePort.objects.filter(connected_endpoint__isnull=False):
|
||||||
c = Cable()
|
c = Cable()
|
||||||
# We have to assign GFK fields manually because we're inside a migration.
|
# We have to assign GFK fields manually because we're inside a migration.
|
||||||
@ -27,6 +28,9 @@ def console_connections_to_cables(apps, schema_editor):
|
|||||||
c.connection_status = consoleport.connection_status
|
c.connection_status = consoleport.connection_status
|
||||||
c.save()
|
c.save()
|
||||||
|
|
||||||
|
cable_count = Cable.objects.filter(endpoint_a_type=consoleport_type).count()
|
||||||
|
print("{} cables created".format(cable_count))
|
||||||
|
|
||||||
|
|
||||||
def power_connections_to_cables(apps, schema_editor):
|
def power_connections_to_cables(apps, schema_editor):
|
||||||
"""
|
"""
|
||||||
@ -42,6 +46,7 @@ def power_connections_to_cables(apps, schema_editor):
|
|||||||
poweroutlet_type = ContentType.objects.get_for_model(PowerOutlet)
|
poweroutlet_type = ContentType.objects.get_for_model(PowerOutlet)
|
||||||
|
|
||||||
# Create a new Cable instance from each power connection
|
# Create a new Cable instance from each power connection
|
||||||
|
print(" Adding power connections... ", end='', flush=True)
|
||||||
for powerport in PowerPort.objects.filter(connected_endpoint__isnull=False):
|
for powerport in PowerPort.objects.filter(connected_endpoint__isnull=False):
|
||||||
c = Cable()
|
c = Cable()
|
||||||
# We have to assign GFK fields manually because we're inside a migration.
|
# We have to assign GFK fields manually because we're inside a migration.
|
||||||
@ -52,6 +57,9 @@ def power_connections_to_cables(apps, schema_editor):
|
|||||||
c.connection_status = powerport.connection_status
|
c.connection_status = powerport.connection_status
|
||||||
c.save()
|
c.save()
|
||||||
|
|
||||||
|
cable_count = Cable.objects.filter(endpoint_a_type=powerport_type).count()
|
||||||
|
print("{} cables created".format(cable_count))
|
||||||
|
|
||||||
|
|
||||||
def interface_connections_to_cables(apps, schema_editor):
|
def interface_connections_to_cables(apps, schema_editor):
|
||||||
"""
|
"""
|
||||||
@ -66,6 +74,7 @@ def interface_connections_to_cables(apps, schema_editor):
|
|||||||
interface_type = ContentType.objects.get_for_model(Interface)
|
interface_type = ContentType.objects.get_for_model(Interface)
|
||||||
|
|
||||||
# Create a new Cable instance from each InterfaceConnection
|
# Create a new Cable instance from each InterfaceConnection
|
||||||
|
print(" Adding interface connections... ", end='', flush=True)
|
||||||
for conn in InterfaceConnection.objects.all():
|
for conn in InterfaceConnection.objects.all():
|
||||||
c = Cable()
|
c = Cable()
|
||||||
# We have to assign GFK fields manually because we're inside a migration.
|
# We have to assign GFK fields manually because we're inside a migration.
|
||||||
@ -76,8 +85,23 @@ def interface_connections_to_cables(apps, schema_editor):
|
|||||||
c.connection_status = conn.connection_status
|
c.connection_status = conn.connection_status
|
||||||
c.save()
|
c.save()
|
||||||
|
|
||||||
|
# connected_endpoint and connection_status must be manually assigned
|
||||||
|
# since these are new fields on Interface
|
||||||
|
Interface.objects.filter(pk=conn.interface_a_id).update(
|
||||||
|
connected_endpoint=conn.interface_b_id,
|
||||||
|
connection_status=conn.connection_status
|
||||||
|
)
|
||||||
|
Interface.objects.filter(pk=conn.interface_b_id).update(
|
||||||
|
connected_endpoint=conn.interface_a_id,
|
||||||
|
connection_status=conn.connection_status
|
||||||
|
)
|
||||||
|
|
||||||
|
cable_count = Cable.objects.filter(endpoint_a_type=interface_type).count()
|
||||||
|
print("{} cables created".format(cable_count))
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
atomic = False
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('contenttypes', '0002_remove_content_type_name'),
|
('contenttypes', '0002_remove_content_type_name'),
|
||||||
@ -142,9 +166,34 @@ class Migration(migrations.Migration):
|
|||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='poweroutlets', to='dcim.Device'),
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='poweroutlets', to='dcim.Device'),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
# Alter the Interface model
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='interface',
|
||||||
|
name='connected_endpoint',
|
||||||
|
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.Interface'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='interface',
|
||||||
|
name='connection_status',
|
||||||
|
field=models.NullBooleanField(default=True),
|
||||||
|
),
|
||||||
|
|
||||||
# Copy console/power/interface connections as Cables
|
# Copy console/power/interface connections as Cables
|
||||||
migrations.RunPython(console_connections_to_cables),
|
migrations.RunPython(console_connections_to_cables),
|
||||||
migrations.RunPython(power_connections_to_cables),
|
migrations.RunPython(power_connections_to_cables),
|
||||||
migrations.RunPython(interface_connections_to_cables),
|
migrations.RunPython(interface_connections_to_cables),
|
||||||
|
|
||||||
|
# Delete the InterfaceConnection model
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='interfaceconnection',
|
||||||
|
name='interface_a',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='interfaceconnection',
|
||||||
|
name='interface_b',
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='InterfaceConnection',
|
||||||
|
),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
@ -1826,7 +1826,7 @@ class PowerOutlet(ComponentModel):
|
|||||||
class Interface(ComponentModel):
|
class Interface(ComponentModel):
|
||||||
"""
|
"""
|
||||||
A network interface within a Device or VirtualMachine. A physical Interface can connect to exactly one other
|
A network interface within a Device or VirtualMachine. A physical Interface can connect to exactly one other
|
||||||
Interface via the creation of an InterfaceConnection.
|
Interface.
|
||||||
"""
|
"""
|
||||||
device = models.ForeignKey(
|
device = models.ForeignKey(
|
||||||
to='Device',
|
to='Device',
|
||||||
@ -1842,6 +1842,20 @@ class Interface(ComponentModel):
|
|||||||
null=True,
|
null=True,
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
|
name = models.CharField(
|
||||||
|
max_length=64
|
||||||
|
)
|
||||||
|
connected_endpoint = models.OneToOneField(
|
||||||
|
to='self',
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
related_name='+',
|
||||||
|
blank=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
connection_status = models.NullBooleanField(
|
||||||
|
choices=CONNECTION_STATUS_CHOICES,
|
||||||
|
default=CONNECTION_STATUS_CONNECTED
|
||||||
|
)
|
||||||
lag = models.ForeignKey(
|
lag = models.ForeignKey(
|
||||||
to='self',
|
to='self',
|
||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
@ -1850,9 +1864,6 @@ class Interface(ComponentModel):
|
|||||||
blank=True,
|
blank=True,
|
||||||
verbose_name='Parent LAG'
|
verbose_name='Parent LAG'
|
||||||
)
|
)
|
||||||
name = models.CharField(
|
|
||||||
max_length=64
|
|
||||||
)
|
|
||||||
form_factor = models.PositiveSmallIntegerField(
|
form_factor = models.PositiveSmallIntegerField(
|
||||||
choices=IFACE_FF_CHOICES,
|
choices=IFACE_FF_CHOICES,
|
||||||
default=IFACE_FF_10GE_SFP_PLUS
|
default=IFACE_FF_10GE_SFP_PLUS
|
||||||
@ -2002,10 +2013,7 @@ class Interface(ComponentModel):
|
|||||||
changed_object=self,
|
changed_object=self,
|
||||||
related_object=parent_obj,
|
related_object=parent_obj,
|
||||||
action=action,
|
action=action,
|
||||||
object_data=serialize_object(self, extra={
|
object_data=serialize_object(self)
|
||||||
'connected_interface': self.connected_interface.pk if self.connection else None,
|
|
||||||
'connection_status': self.connection.connection_status if self.connection else None,
|
|
||||||
})
|
|
||||||
).save()
|
).save()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -2034,140 +2042,7 @@ class Interface(ComponentModel):
|
|||||||
return bool(self.circuit_termination)
|
return bool(self.circuit_termination)
|
||||||
except ObjectDoesNotExist:
|
except ObjectDoesNotExist:
|
||||||
pass
|
pass
|
||||||
return bool(self.connection)
|
return bool(self.connected_endpoint)
|
||||||
|
|
||||||
@property
|
|
||||||
def connection(self):
|
|
||||||
try:
|
|
||||||
return self.connected_as_a
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
return self.connected_as_b
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
pass
|
|
||||||
return None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def connected_interface(self):
|
|
||||||
try:
|
|
||||||
if self.connected_as_a:
|
|
||||||
return self.connected_as_a.interface_b
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
if self.connected_as_b:
|
|
||||||
return self.connected_as_b.interface_a
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
pass
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class InterfaceConnection(models.Model):
|
|
||||||
"""
|
|
||||||
An InterfaceConnection represents a symmetrical, one-to-one connection between two Interfaces. There is no
|
|
||||||
significant difference between the interface_a and interface_b fields.
|
|
||||||
"""
|
|
||||||
interface_a = models.OneToOneField(
|
|
||||||
to='dcim.Interface',
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
related_name='connected_as_a'
|
|
||||||
)
|
|
||||||
interface_b = models.OneToOneField(
|
|
||||||
to='dcim.Interface',
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
related_name='connected_as_b'
|
|
||||||
)
|
|
||||||
connection_status = models.BooleanField(
|
|
||||||
choices=CONNECTION_STATUS_CHOICES,
|
|
||||||
default=CONNECTION_STATUS_CONNECTED,
|
|
||||||
verbose_name='Status'
|
|
||||||
)
|
|
||||||
|
|
||||||
csv_headers = ['device_a', 'interface_a', 'device_b', 'interface_b', 'connection_status']
|
|
||||||
|
|
||||||
def clean(self):
|
|
||||||
|
|
||||||
# An interface cannot be connected to itself
|
|
||||||
if self.interface_a == self.interface_b:
|
|
||||||
raise ValidationError({
|
|
||||||
'interface_b': "Cannot connect an interface to itself."
|
|
||||||
})
|
|
||||||
|
|
||||||
# Only connectable interface types are permitted
|
|
||||||
if self.interface_a.form_factor in NONCONNECTABLE_IFACE_TYPES:
|
|
||||||
raise ValidationError({
|
|
||||||
'interface_a': '{} is not a connectable interface type.'.format(
|
|
||||||
self.interface_a.get_form_factor_display()
|
|
||||||
)
|
|
||||||
})
|
|
||||||
if self.interface_b.form_factor in NONCONNECTABLE_IFACE_TYPES:
|
|
||||||
raise ValidationError({
|
|
||||||
'interface_b': '{} is not a connectable interface type.'.format(
|
|
||||||
self.interface_b.get_form_factor_display()
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
# Prevent the A side of one connection from being the B side of another
|
|
||||||
interface_a_connections = InterfaceConnection.objects.filter(
|
|
||||||
Q(interface_a=self.interface_a) |
|
|
||||||
Q(interface_b=self.interface_a)
|
|
||||||
).exclude(pk=self.pk)
|
|
||||||
if interface_a_connections.exists():
|
|
||||||
raise ValidationError({
|
|
||||||
'interface_a': "This interface is already connected."
|
|
||||||
})
|
|
||||||
interface_b_connections = InterfaceConnection.objects.filter(
|
|
||||||
Q(interface_a=self.interface_b) |
|
|
||||||
Q(interface_b=self.interface_b)
|
|
||||||
).exclude(pk=self.pk)
|
|
||||||
if interface_b_connections.exists():
|
|
||||||
raise ValidationError({
|
|
||||||
'interface_b': "This interface is already connected."
|
|
||||||
})
|
|
||||||
|
|
||||||
def to_csv(self):
|
|
||||||
return (
|
|
||||||
self.interface_a.device.identifier,
|
|
||||||
self.interface_a.name,
|
|
||||||
self.interface_b.device.identifier,
|
|
||||||
self.interface_b.name,
|
|
||||||
self.get_connection_status_display(),
|
|
||||||
)
|
|
||||||
|
|
||||||
def log_change(self, user, request_id, action):
|
|
||||||
"""
|
|
||||||
Create a new ObjectChange for each of the two affected Interfaces.
|
|
||||||
"""
|
|
||||||
interfaces = (
|
|
||||||
(self.interface_a, self.interface_b),
|
|
||||||
(self.interface_b, self.interface_a),
|
|
||||||
)
|
|
||||||
|
|
||||||
for interface, peer_interface in interfaces:
|
|
||||||
if action == OBJECTCHANGE_ACTION_DELETE:
|
|
||||||
connection_data = {
|
|
||||||
'connected_interface': None,
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
connection_data = {
|
|
||||||
'connected_interface': peer_interface.pk,
|
|
||||||
'connection_status': self.connection_status
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
|
||||||
parent_obj = interface.parent
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
parent_obj = None
|
|
||||||
|
|
||||||
ObjectChange(
|
|
||||||
user=user,
|
|
||||||
request_id=request_id,
|
|
||||||
changed_object=interface,
|
|
||||||
related_object=parent_obj,
|
|
||||||
action=OBJECTCHANGE_ACTION_UPDATE,
|
|
||||||
object_data=serialize_object(interface, extra=connection_data)
|
|
||||||
).save()
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -5,10 +5,9 @@ from tenancy.tables import COL_TENANT
|
|||||||
from utilities.tables import BaseTable, BooleanColumn, ToggleColumn
|
from utilities.tables import BaseTable, BooleanColumn, ToggleColumn
|
||||||
from .models import (
|
from .models import (
|
||||||
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
||||||
DeviceBayTemplate, DeviceRole, DeviceType, FrontPanelPort, FrontPanelPortTemplate, Interface, InterfaceConnection,
|
DeviceBayTemplate, DeviceRole, DeviceType, FrontPanelPort, FrontPanelPortTemplate, Interface, InterfaceTemplate,
|
||||||
InterfaceTemplate, InventoryItem, Manufacturer, Platform, PowerOutlet, PowerOutletTemplate, PowerPort,
|
InventoryItem, Manufacturer, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack,
|
||||||
PowerPortTemplate, Rack, RackGroup, RackReservation, RearPanelPort, RearPanelPortTemplate, Region, Site,
|
RackGroup, RackReservation, RearPanelPort, RearPanelPortTemplate, Region, Site, VirtualChassis,
|
||||||
VirtualChassis,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
REGION_LINK = """
|
REGION_LINK = """
|
||||||
@ -654,17 +653,33 @@ class PowerConnectionTable(BaseTable):
|
|||||||
|
|
||||||
|
|
||||||
class InterfaceConnectionTable(BaseTable):
|
class InterfaceConnectionTable(BaseTable):
|
||||||
device_a = tables.LinkColumn('dcim:device', accessor=Accessor('interface_a.device'),
|
device_a = tables.LinkColumn(
|
||||||
args=[Accessor('interface_a.device.pk')], verbose_name='Device A')
|
viewname='dcim:device',
|
||||||
interface_a = tables.LinkColumn('dcim:interface', accessor=Accessor('interface_a'),
|
accessor=Accessor('device'),
|
||||||
args=[Accessor('interface_a.pk')], verbose_name='Interface A')
|
args=[Accessor('device.pk')],
|
||||||
device_b = tables.LinkColumn('dcim:device', accessor=Accessor('interface_b.device'),
|
verbose_name='Device A'
|
||||||
args=[Accessor('interface_b.device.pk')], verbose_name='Device B')
|
)
|
||||||
interface_b = tables.LinkColumn('dcim:interface', accessor=Accessor('interface_b'),
|
interface_a = tables.LinkColumn(
|
||||||
args=[Accessor('interface_b.pk')], verbose_name='Interface B')
|
viewname='dcim:interface',
|
||||||
|
accessor=Accessor('name'),
|
||||||
|
args=[Accessor('pk')],
|
||||||
|
verbose_name='Interface A'
|
||||||
|
)
|
||||||
|
device_b = tables.LinkColumn(
|
||||||
|
viewname='dcim:device',
|
||||||
|
accessor=Accessor('connected_endpoint.device'),
|
||||||
|
args=[Accessor('connected_endpoint.device.pk')],
|
||||||
|
verbose_name='Device B'
|
||||||
|
)
|
||||||
|
interface_b = tables.LinkColumn(
|
||||||
|
viewname='dcim:interface',
|
||||||
|
accessor=Accessor('connected_endpoint.name'),
|
||||||
|
args=[Accessor('connected_endpoint.pk')],
|
||||||
|
verbose_name='Interface B'
|
||||||
|
)
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = InterfaceConnection
|
model = Interface
|
||||||
fields = ('device_a', 'interface_a', 'device_b', 'interface_b')
|
fields = ('device_a', 'interface_a', 'device_b', 'interface_b')
|
||||||
|
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ from dcim.constants import (
|
|||||||
)
|
)
|
||||||
from dcim.models import (
|
from dcim.models import (
|
||||||
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
||||||
DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceConnection, InterfaceTemplate, Manufacturer,
|
DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceTemplate, Manufacturer,
|
||||||
InventoryItem, Platform, PowerPort, PowerPortTemplate, PowerOutlet, PowerOutletTemplate, Rack, RackGroup,
|
InventoryItem, Platform, PowerPort, PowerPortTemplate, PowerOutlet, PowerOutletTemplate, Rack, RackGroup,
|
||||||
RackReservation, RackRole, Region, Site, VirtualChassis,
|
RackReservation, RackRole, Region, Site, VirtualChassis,
|
||||||
)
|
)
|
||||||
@ -2393,6 +2393,7 @@ class InterfaceTest(APITestCase):
|
|||||||
url = reverse('dcim-api:interface-detail', kwargs={'pk': self.interface1.pk})
|
url = reverse('dcim-api:interface-detail', kwargs={'pk': self.interface1.pk})
|
||||||
response = self.client.get(url, **self.header)
|
response = self.client.get(url, **self.header)
|
||||||
|
|
||||||
|
self.assertHttpStatus(response, status.HTTP_200_OK)
|
||||||
self.assertEqual(response.data['name'], self.interface1.name)
|
self.assertEqual(response.data['name'], self.interface1.name)
|
||||||
|
|
||||||
def test_get_interface_graphs(self):
|
def test_get_interface_graphs(self):
|
||||||
@ -2882,179 +2883,44 @@ class PowerConnectionTest(APITestCase):
|
|||||||
self.assertEqual(response.data['count'], 3)
|
self.assertEqual(response.data['count'], 3)
|
||||||
|
|
||||||
|
|
||||||
class InterfaceConnectionTest(APITestCase):
|
# class ConnectedDeviceTest(APITestCase):
|
||||||
|
#
|
||||||
def setUp(self):
|
# def setUp(self):
|
||||||
|
#
|
||||||
super(InterfaceConnectionTest, self).setUp()
|
# super(ConnectedDeviceTest, self).setUp()
|
||||||
|
#
|
||||||
site = Site.objects.create(name='Test Site 1', slug='test-site-1')
|
# self.site1 = Site.objects.create(name='Test Site 1', slug='test-site-1')
|
||||||
manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
|
# self.site2 = Site.objects.create(name='Test Site 2', slug='test-site-2')
|
||||||
devicetype = DeviceType.objects.create(
|
# manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
|
||||||
manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1'
|
# self.devicetype1 = DeviceType.objects.create(
|
||||||
)
|
# manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1'
|
||||||
devicerole = DeviceRole.objects.create(
|
# )
|
||||||
name='Test Device Role 1', slug='test-device-role-1', color='ff0000'
|
# self.devicetype2 = DeviceType.objects.create(
|
||||||
)
|
# manufacturer=manufacturer, model='Test Device Type 2', slug='test-device-type-2'
|
||||||
self.device = Device.objects.create(
|
# )
|
||||||
device_type=devicetype, device_role=devicerole, name='Test Device 1', site=site
|
# self.devicerole1 = DeviceRole.objects.create(
|
||||||
)
|
# name='Test Device Role 1', slug='test-device-role-1', color='ff0000'
|
||||||
self.interface1 = Interface.objects.create(device=self.device, name='Test Interface 1')
|
# )
|
||||||
self.interface2 = Interface.objects.create(device=self.device, name='Test Interface 2')
|
# self.devicerole2 = DeviceRole.objects.create(
|
||||||
self.interface3 = Interface.objects.create(device=self.device, name='Test Interface 3')
|
# name='Test Device Role 2', slug='test-device-role-2', color='00ff00'
|
||||||
self.interface4 = Interface.objects.create(device=self.device, name='Test Interface 4')
|
# )
|
||||||
self.interface5 = Interface.objects.create(device=self.device, name='Test Interface 5')
|
# self.device1 = Device.objects.create(
|
||||||
self.interface6 = Interface.objects.create(device=self.device, name='Test Interface 6')
|
# device_type=self.devicetype1, device_role=self.devicerole1, name='TestDevice1', site=self.site1
|
||||||
self.interface7 = Interface.objects.create(device=self.device, name='Test Interface 7')
|
# )
|
||||||
self.interface8 = Interface.objects.create(device=self.device, name='Test Interface 8')
|
# self.device2 = Device.objects.create(
|
||||||
self.interface9 = Interface.objects.create(device=self.device, name='Test Interface 9')
|
# device_type=self.devicetype1, device_role=self.devicerole1, name='TestDevice2', site=self.site1
|
||||||
self.interface10 = Interface.objects.create(device=self.device, name='Test Interface 10')
|
# )
|
||||||
self.interface11 = Interface.objects.create(device=self.device, name='Test Interface 11')
|
# self.interface1 = Interface.objects.create(device=self.device1, name='eth0')
|
||||||
self.interface12 = Interface.objects.create(device=self.device, name='Test Interface 12')
|
# self.interface2 = Interface.objects.create(device=self.device2, name='eth0')
|
||||||
self.interfaceconnection1 = InterfaceConnection.objects.create(
|
# InterfaceConnection.objects.create(interface_a=self.interface1, interface_b=self.interface2)
|
||||||
interface_a=self.interface1, interface_b=self.interface2
|
#
|
||||||
)
|
# def test_get_connected_device(self):
|
||||||
self.interfaceconnection2 = InterfaceConnection.objects.create(
|
#
|
||||||
interface_a=self.interface3, interface_b=self.interface4
|
# url = reverse('dcim-api:connected-device-list')
|
||||||
)
|
# response = self.client.get(url + '?peer-device=TestDevice2&peer-interface=eth0', **self.header)
|
||||||
self.interfaceconnection3 = InterfaceConnection.objects.create(
|
#
|
||||||
interface_a=self.interface5, interface_b=self.interface6
|
# self.assertHttpStatus(response, status.HTTP_200_OK)
|
||||||
)
|
# self.assertEqual(response.data['name'], self.device1.name)
|
||||||
|
|
||||||
def test_get_interfaceconnection(self):
|
|
||||||
|
|
||||||
url = reverse('dcim-api:interfaceconnection-detail', kwargs={'pk': self.interfaceconnection1.pk})
|
|
||||||
response = self.client.get(url, **self.header)
|
|
||||||
|
|
||||||
self.assertEqual(response.data['interface_a']['id'], self.interfaceconnection1.interface_a_id)
|
|
||||||
self.assertEqual(response.data['interface_b']['id'], self.interfaceconnection1.interface_b_id)
|
|
||||||
|
|
||||||
def test_list_interfaceconnections(self):
|
|
||||||
|
|
||||||
url = reverse('dcim-api:interfaceconnection-list')
|
|
||||||
response = self.client.get(url, **self.header)
|
|
||||||
|
|
||||||
self.assertEqual(response.data['count'], 3)
|
|
||||||
|
|
||||||
def test_list_interfaceconnections_brief(self):
|
|
||||||
|
|
||||||
url = reverse('dcim-api:interfaceconnection-list')
|
|
||||||
response = self.client.get('{}?brief=1'.format(url), **self.header)
|
|
||||||
|
|
||||||
self.assertEqual(
|
|
||||||
sorted(response.data['results'][0]),
|
|
||||||
['connection_status', 'id', 'url']
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_create_interfaceconnection(self):
|
|
||||||
|
|
||||||
data = {
|
|
||||||
'interface_a': self.interface7.pk,
|
|
||||||
'interface_b': self.interface8.pk,
|
|
||||||
}
|
|
||||||
|
|
||||||
url = reverse('dcim-api:interfaceconnection-list')
|
|
||||||
response = self.client.post(url, data, format='json', **self.header)
|
|
||||||
|
|
||||||
self.assertHttpStatus(response, status.HTTP_201_CREATED)
|
|
||||||
self.assertEqual(InterfaceConnection.objects.count(), 4)
|
|
||||||
interfaceconnection4 = InterfaceConnection.objects.get(pk=response.data['id'])
|
|
||||||
self.assertEqual(interfaceconnection4.interface_a_id, data['interface_a'])
|
|
||||||
self.assertEqual(interfaceconnection4.interface_b_id, data['interface_b'])
|
|
||||||
|
|
||||||
def test_create_interfaceconnection_bulk(self):
|
|
||||||
|
|
||||||
data = [
|
|
||||||
{
|
|
||||||
'interface_a': self.interface7.pk,
|
|
||||||
'interface_b': self.interface8.pk,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'interface_a': self.interface9.pk,
|
|
||||||
'interface_b': self.interface10.pk,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'interface_a': self.interface11.pk,
|
|
||||||
'interface_b': self.interface12.pk,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
url = reverse('dcim-api:interfaceconnection-list')
|
|
||||||
response = self.client.post(url, data, format='json', **self.header)
|
|
||||||
|
|
||||||
self.assertHttpStatus(response, status.HTTP_201_CREATED)
|
|
||||||
self.assertEqual(InterfaceConnection.objects.count(), 6)
|
|
||||||
for i in range(0, 3):
|
|
||||||
self.assertEqual(response.data[i]['interface_a']['id'], data[i]['interface_a'])
|
|
||||||
self.assertEqual(response.data[i]['interface_b']['id'], data[i]['interface_b'])
|
|
||||||
|
|
||||||
def test_update_interfaceconnection(self):
|
|
||||||
|
|
||||||
new_connection_status = not self.interfaceconnection1.connection_status
|
|
||||||
|
|
||||||
data = {
|
|
||||||
'interface_a': self.interface7.pk,
|
|
||||||
'interface_b': self.interface8.pk,
|
|
||||||
'connection_status': new_connection_status,
|
|
||||||
}
|
|
||||||
|
|
||||||
url = reverse('dcim-api:interfaceconnection-detail', kwargs={'pk': self.interfaceconnection1.pk})
|
|
||||||
response = self.client.put(url, data, format='json', **self.header)
|
|
||||||
|
|
||||||
self.assertHttpStatus(response, status.HTTP_200_OK)
|
|
||||||
self.assertEqual(InterfaceConnection.objects.count(), 3)
|
|
||||||
interfaceconnection1 = InterfaceConnection.objects.get(pk=response.data['id'])
|
|
||||||
self.assertEqual(interfaceconnection1.interface_a_id, data['interface_a'])
|
|
||||||
self.assertEqual(interfaceconnection1.interface_b_id, data['interface_b'])
|
|
||||||
self.assertEqual(interfaceconnection1.connection_status, data['connection_status'])
|
|
||||||
|
|
||||||
def test_delete_interfaceconnection(self):
|
|
||||||
|
|
||||||
url = reverse('dcim-api:interfaceconnection-detail', kwargs={'pk': self.interfaceconnection1.pk})
|
|
||||||
response = self.client.delete(url, **self.header)
|
|
||||||
|
|
||||||
self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
|
|
||||||
self.assertEqual(InterfaceConnection.objects.count(), 2)
|
|
||||||
|
|
||||||
|
|
||||||
class ConnectedDeviceTest(APITestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
|
|
||||||
super(ConnectedDeviceTest, self).setUp()
|
|
||||||
|
|
||||||
self.site1 = Site.objects.create(name='Test Site 1', slug='test-site-1')
|
|
||||||
self.site2 = Site.objects.create(name='Test Site 2', slug='test-site-2')
|
|
||||||
manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
|
|
||||||
self.devicetype1 = DeviceType.objects.create(
|
|
||||||
manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1'
|
|
||||||
)
|
|
||||||
self.devicetype2 = DeviceType.objects.create(
|
|
||||||
manufacturer=manufacturer, model='Test Device Type 2', slug='test-device-type-2'
|
|
||||||
)
|
|
||||||
self.devicerole1 = DeviceRole.objects.create(
|
|
||||||
name='Test Device Role 1', slug='test-device-role-1', color='ff0000'
|
|
||||||
)
|
|
||||||
self.devicerole2 = DeviceRole.objects.create(
|
|
||||||
name='Test Device Role 2', slug='test-device-role-2', color='00ff00'
|
|
||||||
)
|
|
||||||
self.device1 = Device.objects.create(
|
|
||||||
device_type=self.devicetype1, device_role=self.devicerole1, name='TestDevice1', site=self.site1
|
|
||||||
)
|
|
||||||
self.device2 = Device.objects.create(
|
|
||||||
device_type=self.devicetype1, device_role=self.devicerole1, name='TestDevice2', site=self.site1
|
|
||||||
)
|
|
||||||
self.interface1 = Interface.objects.create(device=self.device1, name='eth0')
|
|
||||||
self.interface2 = Interface.objects.create(device=self.device2, name='eth0')
|
|
||||||
InterfaceConnection.objects.create(interface_a=self.interface1, interface_b=self.interface2)
|
|
||||||
|
|
||||||
def test_get_connected_device(self):
|
|
||||||
|
|
||||||
url = reverse('dcim-api:connected-device-list')
|
|
||||||
response = self.client.get(url + '?peer-device=TestDevice2&peer-interface=eth0', **self.header)
|
|
||||||
|
|
||||||
self.assertHttpStatus(response, status.HTTP_200_OK)
|
|
||||||
self.assertEqual(response.data['name'], self.device1.name)
|
|
||||||
|
|
||||||
|
|
||||||
class VirtualChassisTest(APITestCase):
|
class VirtualChassisTest(APITestCase):
|
||||||
|
@ -207,8 +207,9 @@ urlpatterns = [
|
|||||||
url(r'^devices/(?P<pk>\d+)/interfaces/edit/$', views.InterfaceBulkEditView.as_view(), name='interface_bulk_edit'),
|
url(r'^devices/(?P<pk>\d+)/interfaces/edit/$', views.InterfaceBulkEditView.as_view(), name='interface_bulk_edit'),
|
||||||
url(r'^devices/(?P<pk>\d+)/interfaces/disconnect/$', views.InterfaceBulkDisconnectView.as_view(), name='interface_bulk_disconnect'),
|
url(r'^devices/(?P<pk>\d+)/interfaces/disconnect/$', views.InterfaceBulkDisconnectView.as_view(), name='interface_bulk_disconnect'),
|
||||||
url(r'^devices/(?P<pk>\d+)/interfaces/delete/$', views.InterfaceBulkDeleteView.as_view(), name='interface_bulk_delete'),
|
url(r'^devices/(?P<pk>\d+)/interfaces/delete/$', views.InterfaceBulkDeleteView.as_view(), name='interface_bulk_delete'),
|
||||||
url(r'^devices/(?P<pk>\d+)/interface-connections/add/$', views.InterfaceConnectionAddView.as_view(), name='interfaceconnection_add'),
|
# url(r'^devices/(?P<pk>\d+)/interface-connections/add/$', views.InterfaceConnectionAddView.as_view(), name='interfaceconnection_add'),
|
||||||
url(r'^interface-connections/(?P<pk>\d+)/delete/$', views.InterfaceConnectionDeleteView.as_view(), name='interfaceconnection_delete'),
|
url(r'^interfaces/(?P<endpoint_a_id>\d+)/connect/$', views.CableConnectView.as_view(), name='interface_connect', kwargs={'endpoint_a_type': Interface}),
|
||||||
|
# url(r'^interface-connections/(?P<pk>\d+)/delete/$', views.InterfaceConnectionDeleteView.as_view(), name='interfaceconnection_delete'),
|
||||||
url(r'^interfaces/(?P<pk>\d+)/$', views.InterfaceView.as_view(), name='interface'),
|
url(r'^interfaces/(?P<pk>\d+)/$', views.InterfaceView.as_view(), name='interface'),
|
||||||
url(r'^interfaces/(?P<pk>\d+)/edit/$', views.InterfaceEditView.as_view(), name='interface_edit'),
|
url(r'^interfaces/(?P<pk>\d+)/edit/$', views.InterfaceEditView.as_view(), name='interface_edit'),
|
||||||
url(r'^interfaces/(?P<pk>\d+)/assign-vlans/$', views.InterfaceAssignVLANsView.as_view(), name='interface_assign_vlans'),
|
url(r'^interfaces/(?P<pk>\d+)/assign-vlans/$', views.InterfaceAssignVLANsView.as_view(), name='interface_assign_vlans'),
|
||||||
@ -253,11 +254,11 @@ urlpatterns = [
|
|||||||
|
|
||||||
# Console/power/interface connections
|
# Console/power/interface connections
|
||||||
url(r'^console-connections/$', views.ConsoleConnectionsListView.as_view(), name='console_connections_list'),
|
url(r'^console-connections/$', views.ConsoleConnectionsListView.as_view(), name='console_connections_list'),
|
||||||
url(r'^console-connections/import/$', views.ConsoleConnectionsBulkImportView.as_view(), name='console_connections_import'),
|
# url(r'^console-connections/import/$', views.ConsoleConnectionsBulkImportView.as_view(), name='console_connections_import'),
|
||||||
url(r'^power-connections/$', views.PowerConnectionsListView.as_view(), name='power_connections_list'),
|
url(r'^power-connections/$', views.PowerConnectionsListView.as_view(), name='power_connections_list'),
|
||||||
url(r'^power-connections/import/$', views.PowerConnectionsBulkImportView.as_view(), name='power_connections_import'),
|
# url(r'^power-connections/import/$', views.PowerConnectionsBulkImportView.as_view(), name='power_connections_import'),
|
||||||
url(r'^interface-connections/$', views.InterfaceConnectionsListView.as_view(), name='interface_connections_list'),
|
url(r'^interface-connections/$', views.InterfaceConnectionsListView.as_view(), name='interface_connections_list'),
|
||||||
url(r'^interface-connections/import/$', views.InterfaceConnectionsBulkImportView.as_view(), name='interface_connections_import'),
|
# url(r'^interface-connections/import/$', views.InterfaceConnectionsBulkImportView.as_view(), name='interface_connections_import'),
|
||||||
|
|
||||||
# Virtual chassis
|
# Virtual chassis
|
||||||
url(r'^virtual-chassis/$', views.VirtualChassisListView.as_view(), name='virtualchassis_list'),
|
url(r'^virtual-chassis/$', views.VirtualChassisListView.as_view(), name='virtualchassis_list'),
|
||||||
|
@ -6,7 +6,7 @@ from django.contrib.auth.mixins import PermissionRequiredMixin
|
|||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.core.paginator import EmptyPage, PageNotAnInteger
|
from django.core.paginator import EmptyPage, PageNotAnInteger
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import Count, Q
|
from django.db.models import Count, F, Q
|
||||||
from django.forms import modelformset_factory
|
from django.forms import modelformset_factory
|
||||||
from django.http import Http404, HttpResponseRedirect
|
from django.http import Http404, HttpResponseRedirect
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
@ -33,10 +33,9 @@ from . import filters, forms, tables
|
|||||||
from .constants import CONNECTION_STATUS_CONNECTED
|
from .constants import CONNECTION_STATUS_CONNECTED
|
||||||
from .models import (
|
from .models import (
|
||||||
Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
||||||
DeviceBayTemplate, DeviceRole, DeviceType, FrontPanelPort, FrontPanelPortTemplate, Interface, InterfaceConnection,
|
DeviceBayTemplate, DeviceRole, DeviceType, FrontPanelPort, FrontPanelPortTemplate, Interface, InterfaceTemplate,
|
||||||
InterfaceTemplate, Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort,
|
Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack,
|
||||||
PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPanelPort, RearPanelPortTemplate, Region, Site,
|
RackGroup, RackReservation, RackRole, RearPanelPort, RearPanelPortTemplate, Region, Site, VirtualChassis,
|
||||||
VirtualChassis,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -905,8 +904,7 @@ class DeviceView(View):
|
|||||||
interfaces = device.vc_interfaces.order_naturally(
|
interfaces = device.vc_interfaces.order_naturally(
|
||||||
device.device_type.interface_ordering
|
device.device_type.interface_ordering
|
||||||
).select_related(
|
).select_related(
|
||||||
'connected_as_a__interface_b__device', 'connected_as_b__interface_a__device',
|
'connected_endpoint__device', 'circuit_termination__circuit'
|
||||||
'circuit_termination__circuit'
|
|
||||||
).prefetch_related('ip_addresses')
|
).prefetch_related('ip_addresses')
|
||||||
|
|
||||||
# Front panel ports
|
# Front panel ports
|
||||||
@ -999,7 +997,7 @@ class DeviceLLDPNeighborsView(PermissionRequiredMixin, View):
|
|||||||
interfaces = device.vc_interfaces.order_naturally(
|
interfaces = device.vc_interfaces.order_naturally(
|
||||||
device.device_type.interface_ordering
|
device.device_type.interface_ordering
|
||||||
).connectable().select_related(
|
).connectable().select_related(
|
||||||
'connected_as_a', 'connected_as_b'
|
'connected_endpoint__device'
|
||||||
)
|
)
|
||||||
|
|
||||||
return render(request, 'dcim/device_lldp_neighbors.html', {
|
return render(request, 'dcim/device_lldp_neighbors.html', {
|
||||||
@ -1736,10 +1734,9 @@ class InterfaceBulkDisconnectView(PermissionRequiredMixin, BulkDisconnectView):
|
|||||||
form = forms.InterfaceBulkDisconnectForm
|
form = forms.InterfaceBulkDisconnectForm
|
||||||
|
|
||||||
def disconnect_objects(self, interfaces):
|
def disconnect_objects(self, interfaces):
|
||||||
count, _ = InterfaceConnection.objects.filter(
|
return Interface.objects.filter(connected_endpoint__in=interfaces).update(
|
||||||
Q(interface_a__in=interfaces) | Q(interface_b__in=interfaces)
|
connected_endpoint=None, connection_status=None
|
||||||
).delete()
|
)
|
||||||
return count
|
|
||||||
|
|
||||||
|
|
||||||
class InterfaceBulkEditView(PermissionRequiredMixin, BulkEditView):
|
class InterfaceBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||||
@ -2016,115 +2013,6 @@ class DeviceBulkAddDeviceBayView(PermissionRequiredMixin, BulkComponentCreateVie
|
|||||||
default_return_url = 'dcim:device_list'
|
default_return_url = 'dcim:device_list'
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Interface connections
|
|
||||||
#
|
|
||||||
|
|
||||||
class InterfaceConnectionAddView(PermissionRequiredMixin, GetReturnURLMixin, View):
|
|
||||||
permission_required = 'dcim.add_interfaceconnection'
|
|
||||||
default_return_url = 'dcim:device_list'
|
|
||||||
|
|
||||||
def get(self, request, pk):
|
|
||||||
|
|
||||||
device = get_object_or_404(Device, pk=pk)
|
|
||||||
form = forms.InterfaceConnectionForm(device, initial={
|
|
||||||
'interface_a': request.GET.get('interface_a'),
|
|
||||||
'site_b': request.GET.get('site_b'),
|
|
||||||
'rack_b': request.GET.get('rack_b'),
|
|
||||||
'device_b': request.GET.get('device_b'),
|
|
||||||
'interface_b': request.GET.get('interface_b'),
|
|
||||||
})
|
|
||||||
|
|
||||||
return render(request, 'dcim/interfaceconnection_edit.html', {
|
|
||||||
'device': device,
|
|
||||||
'form': form,
|
|
||||||
'return_url': device.get_absolute_url(),
|
|
||||||
})
|
|
||||||
|
|
||||||
def post(self, request, pk):
|
|
||||||
|
|
||||||
device = get_object_or_404(Device, pk=pk)
|
|
||||||
form = forms.InterfaceConnectionForm(device, request.POST)
|
|
||||||
|
|
||||||
if form.is_valid():
|
|
||||||
|
|
||||||
interfaceconnection = form.save()
|
|
||||||
msg = 'Connected <a href="{}">{}</a> {} to <a href="{}">{}</a> {}'.format(
|
|
||||||
interfaceconnection.interface_a.device.get_absolute_url(),
|
|
||||||
escape(interfaceconnection.interface_a.device),
|
|
||||||
escape(interfaceconnection.interface_a.name),
|
|
||||||
interfaceconnection.interface_b.device.get_absolute_url(),
|
|
||||||
escape(interfaceconnection.interface_b.device),
|
|
||||||
escape(interfaceconnection.interface_b.name),
|
|
||||||
)
|
|
||||||
messages.success(request, mark_safe(msg))
|
|
||||||
|
|
||||||
if '_addanother' in request.POST:
|
|
||||||
base_url = reverse('dcim:interfaceconnection_add', kwargs={'pk': device.pk})
|
|
||||||
device_b = interfaceconnection.interface_b.device
|
|
||||||
params = urlencode({
|
|
||||||
'rack_b': device_b.rack.pk if device_b.rack else '',
|
|
||||||
'device_b': device_b.pk,
|
|
||||||
})
|
|
||||||
return HttpResponseRedirect('{}?{}'.format(base_url, params))
|
|
||||||
else:
|
|
||||||
return redirect('dcim:device', pk=device.pk)
|
|
||||||
|
|
||||||
return render(request, 'dcim/interfaceconnection_edit.html', {
|
|
||||||
'device': device,
|
|
||||||
'form': form,
|
|
||||||
'return_url': device.get_absolute_url(),
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
class InterfaceConnectionDeleteView(PermissionRequiredMixin, GetReturnURLMixin, View):
|
|
||||||
permission_required = 'dcim.delete_interfaceconnection'
|
|
||||||
default_return_url = 'dcim:device_list'
|
|
||||||
|
|
||||||
def get(self, request, pk):
|
|
||||||
|
|
||||||
interfaceconnection = get_object_or_404(InterfaceConnection, pk=pk)
|
|
||||||
form = forms.ConfirmationForm()
|
|
||||||
|
|
||||||
return render(request, 'dcim/interfaceconnection_delete.html', {
|
|
||||||
'interfaceconnection': interfaceconnection,
|
|
||||||
'form': form,
|
|
||||||
'return_url': self.get_return_url(request, interfaceconnection),
|
|
||||||
})
|
|
||||||
|
|
||||||
def post(self, request, pk):
|
|
||||||
|
|
||||||
interfaceconnection = get_object_or_404(InterfaceConnection, pk=pk)
|
|
||||||
form = forms.ConfirmationForm(request.POST)
|
|
||||||
|
|
||||||
if form.is_valid():
|
|
||||||
interfaceconnection.delete()
|
|
||||||
msg = 'Disconnected <a href="{}">{}</a> {} from <a href="{}">{}</a> {}'.format(
|
|
||||||
interfaceconnection.interface_a.device.get_absolute_url(),
|
|
||||||
escape(interfaceconnection.interface_a.device),
|
|
||||||
escape(interfaceconnection.interface_a.name),
|
|
||||||
interfaceconnection.interface_b.device.get_absolute_url(),
|
|
||||||
escape(interfaceconnection.interface_b.device),
|
|
||||||
escape(interfaceconnection.interface_b.name),
|
|
||||||
)
|
|
||||||
messages.success(request, mark_safe(msg))
|
|
||||||
|
|
||||||
return redirect(self.get_return_url(request, interfaceconnection))
|
|
||||||
|
|
||||||
return render(request, 'dcim/interfaceconnection_delete.html', {
|
|
||||||
'interfaceconnection': interfaceconnection,
|
|
||||||
'form': form,
|
|
||||||
'return_url': self.get_return_url(request, interfaceconnection),
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
class InterfaceConnectionsBulkImportView(PermissionRequiredMixin, BulkImportView):
|
|
||||||
permission_required = 'dcim.change_interface'
|
|
||||||
model_form = forms.InterfaceConnectionCSVForm
|
|
||||||
table = tables.InterfaceConnectionTable
|
|
||||||
default_return_url = 'dcim:interface_connections_list'
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Connections
|
# Connections
|
||||||
#
|
#
|
||||||
@ -2158,10 +2046,11 @@ class PowerConnectionsListView(ObjectListView):
|
|||||||
|
|
||||||
|
|
||||||
class InterfaceConnectionsListView(ObjectListView):
|
class InterfaceConnectionsListView(ObjectListView):
|
||||||
queryset = InterfaceConnection.objects.select_related(
|
queryset = Interface.objects.select_related(
|
||||||
'interface_a__device', 'interface_b__device'
|
'connected_endpoint__device',
|
||||||
).order_by(
|
).filter(
|
||||||
'interface_a__device__name', 'interface_a__name'
|
connected_endpoint__isnull=False,
|
||||||
|
pk__lt=F('connected_endpoint'),
|
||||||
)
|
)
|
||||||
filter = filters.InterfaceConnectionFilter
|
filter = filters.InterfaceConnectionFilter
|
||||||
filter_form = forms.InterfaceConnectionFilterForm
|
filter_form = forms.InterfaceConnectionFilterForm
|
||||||
|
@ -49,7 +49,7 @@ GRAPH_TYPE_CHOICES = (
|
|||||||
EXPORTTEMPLATE_MODELS = [
|
EXPORTTEMPLATE_MODELS = [
|
||||||
'provider', 'circuit', # Circuits
|
'provider', 'circuit', # Circuits
|
||||||
'site', 'region', 'rack', 'rackgroup', 'manufacturer', 'devicetype', 'device', # DCIM
|
'site', 'region', 'rack', 'rackgroup', 'manufacturer', 'devicetype', 'device', # DCIM
|
||||||
'consoleport', 'powerport', 'interfaceconnection', 'virtualchassis', # DCIM
|
'consoleport', 'powerport', 'interface', 'virtualchassis', # DCIM
|
||||||
'aggregate', 'prefix', 'ipaddress', 'vlan', 'vrf', 'service', # IPAM
|
'aggregate', 'prefix', 'ipaddress', 'vlan', 'vrf', 'service', # IPAM
|
||||||
'secret', # Secrets
|
'secret', # Secrets
|
||||||
'tenant', # Tenancy
|
'tenant', # Tenancy
|
||||||
|
@ -504,15 +504,18 @@ class TopologyMap(models.Model):
|
|||||||
def add_network_connections(self, devices):
|
def add_network_connections(self, devices):
|
||||||
|
|
||||||
from circuits.models import CircuitTermination
|
from circuits.models import CircuitTermination
|
||||||
from dcim.models import InterfaceConnection
|
from dcim.models import Interface
|
||||||
|
|
||||||
# Add all interface connections to the graph
|
# Add all interface connections to the graph
|
||||||
connections = InterfaceConnection.objects.filter(
|
connected_interfaces = Interface.objects.select_related(
|
||||||
interface_a__device__in=devices, interface_b__device__in=devices
|
'connected_endpoint__device'
|
||||||
|
).filter(
|
||||||
|
Q(device__in=devices) | Q(connected_endpoint__device__in=devices),
|
||||||
|
connected_endpoint__isnull=False,
|
||||||
)
|
)
|
||||||
for c in connections:
|
for interface in connected_interfaces:
|
||||||
style = 'solid' if c.connection_status == CONNECTION_STATUS_CONNECTED else 'dashed'
|
style = 'solid' if interface.connection_status == CONNECTION_STATUS_CONNECTED else 'dashed'
|
||||||
self.graph.edge(c.interface_a.device.name, c.interface_b.device.name, style=style)
|
self.graph.edge(interface.device.name, interface.connected_endpoint.device.name, style=style)
|
||||||
|
|
||||||
# Add all circuits to the graph
|
# Add all circuits to the graph
|
||||||
for termination in CircuitTermination.objects.filter(term_side='A', interface__device__in=devices):
|
for termination in CircuitTermination.objects.filter(term_side='A', interface__device__in=devices):
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from django.db.models import Count
|
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
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
@ -14,8 +14,7 @@ from dcim.filters import (
|
|||||||
DeviceFilter, DeviceTypeFilter, RackFilter, RackGroupFilter, SiteFilter, VirtualChassisFilter
|
DeviceFilter, DeviceTypeFilter, RackFilter, RackGroupFilter, SiteFilter, VirtualChassisFilter
|
||||||
)
|
)
|
||||||
from dcim.models import (
|
from dcim.models import (
|
||||||
ConsolePort, Device, DeviceType, InterfaceConnection, PowerPort, Rack, RackGroup, Site,
|
ConsolePort, Device, DeviceType, Interface, PowerPort, Rack, RackGroup, Site, VirtualChassis
|
||||||
VirtualChassis
|
|
||||||
)
|
)
|
||||||
from dcim.tables import (
|
from dcim.tables import (
|
||||||
DeviceDetailTable, DeviceTypeTable, RackTable, RackGroupTable, SiteTable, VirtualChassisTable
|
DeviceDetailTable, DeviceTypeTable, RackTable, RackGroupTable, SiteTable, VirtualChassisTable
|
||||||
@ -157,6 +156,17 @@ class HomeView(View):
|
|||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
|
|
||||||
|
connected_consoleports = ConsolePort.objects.filter(
|
||||||
|
connected_endpoint__isnull=False
|
||||||
|
)
|
||||||
|
connected_powerports = PowerPort.objects.filter(
|
||||||
|
connected_endpoint__isnull=False
|
||||||
|
)
|
||||||
|
connected_interfaces = Interface.objects.filter(
|
||||||
|
connected_endpoint__isnull=False,
|
||||||
|
pk__lt=F('connected_endpoint')
|
||||||
|
)
|
||||||
|
|
||||||
stats = {
|
stats = {
|
||||||
|
|
||||||
# Organization
|
# Organization
|
||||||
@ -166,9 +176,9 @@ class HomeView(View):
|
|||||||
# DCIM
|
# DCIM
|
||||||
'rack_count': Rack.objects.count(),
|
'rack_count': Rack.objects.count(),
|
||||||
'device_count': Device.objects.count(),
|
'device_count': Device.objects.count(),
|
||||||
'interface_connections_count': InterfaceConnection.objects.count(),
|
'interface_connections_count': connected_interfaces.count(),
|
||||||
'console_connections_count': ConsolePort.objects.filter(connected_endpoint__isnull=False).count(),
|
'console_connections_count': connected_consoleports.count(),
|
||||||
'power_connections_count': PowerPort.objects.filter(connected_endpoint__isnull=False).count(),
|
'power_connections_count': connected_powerports.count(),
|
||||||
|
|
||||||
# IPAM
|
# IPAM
|
||||||
'vrf_count': VRF.objects.count(),
|
'vrf_count': VRF.objects.count(),
|
||||||
|
@ -3,9 +3,6 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
{% if perms.dcim.change_consoleport %}
|
|
||||||
{% import_button 'dcim:console_connections_import' %}
|
|
||||||
{% endif %}
|
|
||||||
{% export_button content_type %}
|
{% export_button content_type %}
|
||||||
</div>
|
</div>
|
||||||
<h1>{% block title %}Console Connections{% endblock %}</h1>
|
<h1>{% block title %}Console Connections{% endblock %}</h1>
|
||||||
|
@ -549,7 +549,7 @@
|
|||||||
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit
|
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if interfaces and perms.dcim.delete_interfaceconnection %}
|
{% if interfaces and perms.dcim.change_interface %}
|
||||||
<button type="submit" name="_disconnect" formaction="{% url 'dcim:interface_bulk_disconnect' pk=device.pk %}" class="btn btn-danger btn-xs">
|
<button type="submit" name="_disconnect" formaction="{% url 'dcim:interface_bulk_disconnect' pk=device.pk %}" class="btn btn-danger btn-xs">
|
||||||
<span class="glyphicon glyphicon-resize-full" aria-hidden="true"></span> Disconnect
|
<span class="glyphicon glyphicon-resize-full" aria-hidden="true"></span> Disconnect
|
||||||
</button>
|
</button>
|
||||||
|
@ -106,7 +106,7 @@
|
|||||||
<i class="glyphicon glyphicon-resize-full" aria-hidden="true"></i>
|
<i class="glyphicon glyphicon-resize-full" aria-hidden="true"></i>
|
||||||
</a>
|
</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="{% url 'dcim:interfaceconnection_add' pk=device.pk %}?interface_a={{ iface.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-success btn-xs" title="Connect">
|
<a href="{% url 'dcim:interface_connect' endpoint_a_id=device.pk %}?return_url={{ device.get_absolute_url }}" class="btn btn-success btn-xs" title="Connect">
|
||||||
<i class="glyphicon glyphicon-resize-small" aria-hidden="true"></i>
|
<i class="glyphicon glyphicon-resize-small" aria-hidden="true"></i>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -3,9 +3,6 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
{% if perms.dcim.add_interfaceconnection %}
|
|
||||||
{% import_button 'dcim:interface_connections_import' %}
|
|
||||||
{% endif %}
|
|
||||||
{% export_button content_type %}
|
{% export_button content_type %}
|
||||||
</div>
|
</div>
|
||||||
<h1>{% block title %}Interface Connections{% endblock %}</h1>
|
<h1>{% block title %}Interface Connections{% endblock %}</h1>
|
||||||
|
@ -3,9 +3,6 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
{% if perms.dcim.change_powerport %}
|
|
||||||
{% import_button 'dcim:power_connections_import' %}
|
|
||||||
{% endif %}
|
|
||||||
{% export_button content_type %}
|
{% export_button content_type %}
|
||||||
</div>
|
</div>
|
||||||
<h1>{% block title %}Power Connections{% endblock %}</h1>
|
<h1>{% block title %}Power Connections{% endblock %}</h1>
|
||||||
|
@ -180,27 +180,12 @@
|
|||||||
<li class="divider"></li>
|
<li class="divider"></li>
|
||||||
<li class="dropdown-header">Connections</li>
|
<li class="dropdown-header">Connections</li>
|
||||||
<li>
|
<li>
|
||||||
{% if perms.dcim.change_consoleport %}
|
|
||||||
<div class="buttons pull-right">
|
|
||||||
<a href="{% url 'dcim:console_connections_import' %}" class="btn btn-xs btn-info" title="Import"><i class="fa fa-download"></i></a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<a href="{% url 'dcim:console_connections_list' %}">Console Connections</a>
|
<a href="{% url 'dcim:console_connections_list' %}">Console Connections</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
{% if perms.dcim.change_powerport %}
|
|
||||||
<div class="buttons pull-right">
|
|
||||||
<a href="{% url 'dcim:power_connections_import' %}" class="btn btn-xs btn-info" title="Import"><i class="fa fa-download"></i></a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<a href="{% url 'dcim:power_connections_list' %}">Power Connections</a>
|
<a href="{% url 'dcim:power_connections_list' %}">Power Connections</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
{% if perms.dcim.add_interfaceconnection %}
|
|
||||||
<div class="buttons pull-right">
|
|
||||||
<a href="{% url 'dcim:interface_connections_import' %}" class="btn btn-xs btn-info" title="Import"><i class="fa fa-download"></i></a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<a href="{% url 'dcim:interface_connections_list' %}">Interface Connections</a>
|
<a href="{% url 'dcim:interface_connections_list' %}">Interface Connections</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
Loading…
Reference in New Issue
Block a user