mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-19 09:53:34 -06:00
Cleanup for #9102
This commit is contained in:
parent
1beb8522b9
commit
9a7f3f8c1a
@ -1,4 +1,5 @@
|
|||||||
from circuits import filtersets, models
|
from circuits import filtersets, models
|
||||||
|
from dcim.graphql.mixins import CabledObjectMixin
|
||||||
from extras.graphql.mixins import CustomFieldsMixin, TagsMixin
|
from extras.graphql.mixins import CustomFieldsMixin, TagsMixin
|
||||||
from netbox.graphql.types import ObjectType, OrganizationalObjectType, NetBoxObjectType
|
from netbox.graphql.types import ObjectType, OrganizationalObjectType, NetBoxObjectType
|
||||||
|
|
||||||
@ -11,7 +12,7 @@ __all__ = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class CircuitTerminationType(CustomFieldsMixin, TagsMixin, ObjectType):
|
class CircuitTerminationType(CustomFieldsMixin, TagsMixin, CabledObjectMixin, ObjectType):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.CircuitTermination
|
model = models.CircuitTermination
|
||||||
|
@ -52,16 +52,13 @@ class CabledObjectSerializer(serializers.ModelSerializer):
|
|||||||
"""
|
"""
|
||||||
Return the appropriate serializer for the link termination model.
|
Return the appropriate serializer for the link termination model.
|
||||||
"""
|
"""
|
||||||
if not obj.cable:
|
if not obj.link_peers:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# Return serialized peer termination objects
|
# Return serialized peer termination objects
|
||||||
if obj.link_peers:
|
serializer = get_serializer_for_model(obj.link_peers[0], prefix='Nested')
|
||||||
serializer = get_serializer_for_model(obj.link_peers[0], prefix='Nested')
|
context = {'request': self.context['request']}
|
||||||
context = {'request': self.context['request']}
|
return serializer(obj.link_peers, context=context, many=True).data
|
||||||
return serializer(obj.link_peers, context=context, many=True).data
|
|
||||||
|
|
||||||
return []
|
|
||||||
|
|
||||||
@swagger_serializer_method(serializer_or_field=serializers.BooleanField)
|
@swagger_serializer_method(serializer_or_field=serializers.BooleanField)
|
||||||
def get__occupied(self, obj):
|
def get__occupied(self, obj):
|
||||||
@ -77,8 +74,7 @@ class ConnectedEndpointsSerializer(serializers.ModelSerializer):
|
|||||||
connected_endpoints_reachable = serializers.SerializerMethodField(read_only=True)
|
connected_endpoints_reachable = serializers.SerializerMethodField(read_only=True)
|
||||||
|
|
||||||
def get_connected_endpoints_type(self, obj):
|
def get_connected_endpoints_type(self, obj):
|
||||||
endpoints = obj.connected_endpoints
|
if endpoints := obj.connected_endpoints:
|
||||||
if endpoints:
|
|
||||||
return f'{endpoints[0]._meta.app_label}.{endpoints[0]._meta.model_name}'
|
return f'{endpoints[0]._meta.app_label}.{endpoints[0]._meta.model_name}'
|
||||||
|
|
||||||
@swagger_serializer_method(serializer_or_field=serializers.ListField)
|
@swagger_serializer_method(serializer_or_field=serializers.ListField)
|
||||||
@ -86,8 +82,7 @@ class ConnectedEndpointsSerializer(serializers.ModelSerializer):
|
|||||||
"""
|
"""
|
||||||
Return the appropriate serializer for the type of connected object.
|
Return the appropriate serializer for the type of connected object.
|
||||||
"""
|
"""
|
||||||
endpoints = obj.connected_endpoints
|
if endpoints := obj.connected_endpoints:
|
||||||
if endpoints:
|
|
||||||
serializer = get_serializer_for_model(endpoints[0], prefix='Nested')
|
serializer = get_serializer_for_model(endpoints[0], prefix='Nested')
|
||||||
context = {'request': self.context['request']}
|
context = {'request': self.context['request']}
|
||||||
return serializer(endpoints, many=True, context=context).data
|
return serializer(endpoints, many=True, context=context).data
|
||||||
@ -1016,15 +1011,15 @@ class CableSerializer(NetBoxModelSerializer):
|
|||||||
]
|
]
|
||||||
|
|
||||||
def _get_terminations_type(self, obj, side):
|
def _get_terminations_type(self, obj, side):
|
||||||
assert side.lower() in ('a', 'b')
|
assert side in CableEndChoices.values()
|
||||||
terms = [t.termination for t in obj.terminations.all() if t.cable_end == side.upper()]
|
terms = getattr(obj, f'get_{side.lower()}_terminations')()
|
||||||
if terms:
|
if terms:
|
||||||
ct = ContentType.objects.get_for_model(terms[0])
|
ct = ContentType.objects.get_for_model(terms[0])
|
||||||
return f"{ct.app_label}.{ct.model}"
|
return f"{ct.app_label}.{ct.model}"
|
||||||
|
|
||||||
def _get_terminations(self, obj, side):
|
def _get_terminations(self, obj, side):
|
||||||
assert side.lower() in ('a', 'b')
|
assert side in CableEndChoices.values()
|
||||||
terms = [t.termination for t in obj.terminations.all() if t.cable_end == side.upper()]
|
terms = getattr(obj, f'get_{side.lower()}_terminations')()
|
||||||
if not terms:
|
if not terms:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@ -1037,19 +1032,19 @@ class CableSerializer(NetBoxModelSerializer):
|
|||||||
|
|
||||||
@swagger_serializer_method(serializer_or_field=serializers.CharField)
|
@swagger_serializer_method(serializer_or_field=serializers.CharField)
|
||||||
def get_a_terminations_type(self, obj):
|
def get_a_terminations_type(self, obj):
|
||||||
return self._get_terminations_type(obj, 'a')
|
return self._get_terminations_type(obj, CableEndChoices.SIDE_A)
|
||||||
|
|
||||||
@swagger_serializer_method(serializer_or_field=serializers.CharField)
|
@swagger_serializer_method(serializer_or_field=serializers.CharField)
|
||||||
def get_b_terminations_type(self, obj):
|
def get_b_terminations_type(self, obj):
|
||||||
return self._get_terminations_type(obj, 'b')
|
return self._get_terminations_type(obj, CableEndChoices.SIDE_B)
|
||||||
|
|
||||||
@swagger_serializer_method(serializer_or_field=serializers.DictField)
|
@swagger_serializer_method(serializer_or_field=serializers.DictField)
|
||||||
def get_a_terminations(self, obj):
|
def get_a_terminations(self, obj):
|
||||||
return self._get_terminations(obj, 'a')
|
return self._get_terminations(obj, CableEndChoices.SIDE_A)
|
||||||
|
|
||||||
@swagger_serializer_method(serializer_or_field=serializers.DictField)
|
@swagger_serializer_method(serializer_or_field=serializers.DictField)
|
||||||
def get_b_terminations(self, obj):
|
def get_b_terminations(self, obj):
|
||||||
return self._get_terminations(obj, 'b')
|
return self._get_terminations(obj, CableEndChoices.SIDE_B)
|
||||||
|
|
||||||
|
|
||||||
class TracedCableSerializer(serializers.ModelSerializer):
|
class TracedCableSerializer(serializers.ModelSerializer):
|
||||||
@ -1066,7 +1061,7 @@ class TracedCableSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
|
|
||||||
class CableTerminationSerializer(NetBoxModelSerializer):
|
class CableTerminationSerializer(NetBoxModelSerializer):
|
||||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:cable-detail')
|
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:cabletermination-detail')
|
||||||
termination_type = ContentTypeField(
|
termination_type = ContentTypeField(
|
||||||
queryset=ContentType.objects.filter(CABLE_TERMINATION_MODELS)
|
queryset=ContentType.objects.filter(CABLE_TERMINATION_MODELS)
|
||||||
)
|
)
|
||||||
|
@ -15,6 +15,7 @@ from circuits.models import Circuit
|
|||||||
from dcim import filtersets
|
from dcim import filtersets
|
||||||
from dcim.constants import CABLE_TRACE_SVG_DEFAULT_WIDTH
|
from dcim.constants import CABLE_TRACE_SVG_DEFAULT_WIDTH
|
||||||
from dcim.models import *
|
from dcim.models import *
|
||||||
|
from dcim.svg import CableTraceSVG
|
||||||
from extras.api.views import ConfigContextQuerySetMixin
|
from extras.api.views import ConfigContextQuerySetMixin
|
||||||
from ipam.models import Prefix, VLAN
|
from ipam.models import Prefix, VLAN
|
||||||
from netbox.api.authentication import IsAuthenticatedOrLoginNotRequired
|
from netbox.api.authentication import IsAuthenticatedOrLoginNotRequired
|
||||||
@ -52,37 +53,30 @@ class PathEndpointMixin(object):
|
|||||||
# Initialize the path array
|
# Initialize the path array
|
||||||
path = []
|
path = []
|
||||||
|
|
||||||
|
# Render SVG image if requested
|
||||||
if request.GET.get('render', None) == 'svg':
|
if request.GET.get('render', None) == 'svg':
|
||||||
# Render SVG
|
|
||||||
try:
|
try:
|
||||||
width = int(request.GET.get('width', CABLE_TRACE_SVG_DEFAULT_WIDTH))
|
width = int(request.GET.get('width', CABLE_TRACE_SVG_DEFAULT_WIDTH))
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
width = CABLE_TRACE_SVG_DEFAULT_WIDTH
|
width = CABLE_TRACE_SVG_DEFAULT_WIDTH
|
||||||
drawing = obj.get_trace_svg(
|
drawing = CableTraceSVG(self, base_url=request.build_absolute_uri('/'), width=width)
|
||||||
base_url=request.build_absolute_uri('/'),
|
return HttpResponse(drawing.render().tostring(), content_type='image/svg+xml')
|
||||||
width=width
|
|
||||||
)
|
|
||||||
return HttpResponse(drawing.tostring(), content_type='image/svg+xml')
|
|
||||||
|
|
||||||
|
# Serialize path objects, iterating over each three-tuple in the path
|
||||||
for near_end, cable, far_end in obj.trace():
|
for near_end, cable, far_end in obj.trace():
|
||||||
if near_end is None:
|
if near_end is not None:
|
||||||
# Split paths
|
serializer_a = get_serializer_for_model(near_end[0], prefix='Nested')
|
||||||
break
|
near_end = serializer_a(near_end, many=True, context={'request': request}).data
|
||||||
|
|
||||||
# Serialize each object
|
|
||||||
serializer_a = get_serializer_for_model(near_end[0], prefix='Nested')
|
|
||||||
x = serializer_a(near_end, many=True, context={'request': request}).data
|
|
||||||
if cable is not None:
|
|
||||||
y = serializers.TracedCableSerializer(cable[0], context={'request': request}).data
|
|
||||||
else:
|
else:
|
||||||
y = None
|
# Path is split; stop here
|
||||||
|
break
|
||||||
|
if cable is not None:
|
||||||
|
cable = serializers.TracedCableSerializer(cable[0], context={'request': request}).data
|
||||||
if far_end is not None:
|
if far_end is not None:
|
||||||
serializer_b = get_serializer_for_model(far_end[0], prefix='Nested')
|
serializer_b = get_serializer_for_model(far_end[0], prefix='Nested')
|
||||||
z = serializer_b(far_end, many=True, context={'request': request}).data
|
far_end = serializer_b(far_end, many=True, context={'request': request}).data
|
||||||
else:
|
|
||||||
z = None
|
|
||||||
|
|
||||||
path.append((x, y, z))
|
path.append((near_end, cable, far_end))
|
||||||
|
|
||||||
return Response(path)
|
return Response(path)
|
||||||
|
|
||||||
|
@ -1294,7 +1294,7 @@ class CableEndChoices(ChoiceSet):
|
|||||||
CHOICES = (
|
CHOICES = (
|
||||||
(SIDE_A, 'A'),
|
(SIDE_A, 'A'),
|
||||||
(SIDE_B, 'B'),
|
(SIDE_B, 'B'),
|
||||||
('', ''),
|
# ('', ''),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
5
netbox/dcim/graphql/mixins.py
Normal file
5
netbox/dcim/graphql/mixins.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
class CabledObjectMixin:
|
||||||
|
|
||||||
|
def resolve_cable_end(self, info):
|
||||||
|
# Handle empty values
|
||||||
|
return self.cable_end or None
|
@ -7,6 +7,7 @@ from extras.graphql.mixins import (
|
|||||||
from ipam.graphql.mixins import IPAddressesMixin, VLANGroupsMixin
|
from ipam.graphql.mixins import IPAddressesMixin, VLANGroupsMixin
|
||||||
from netbox.graphql.scalars import BigInt
|
from netbox.graphql.scalars import BigInt
|
||||||
from netbox.graphql.types import BaseObjectType, OrganizationalObjectType, NetBoxObjectType
|
from netbox.graphql.types import BaseObjectType, OrganizationalObjectType, NetBoxObjectType
|
||||||
|
from .mixins import CabledObjectMixin
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'CableType',
|
'CableType',
|
||||||
@ -107,7 +108,7 @@ class CableTerminationType(NetBoxObjectType):
|
|||||||
filterset_class = filtersets.CableTerminationFilterSet
|
filterset_class = filtersets.CableTerminationFilterSet
|
||||||
|
|
||||||
|
|
||||||
class ConsolePortType(ComponentObjectType):
|
class ConsolePortType(ComponentObjectType, CabledObjectMixin):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.ConsolePort
|
model = models.ConsolePort
|
||||||
@ -129,7 +130,7 @@ class ConsolePortTemplateType(ComponentTemplateObjectType):
|
|||||||
return self.type or None
|
return self.type or None
|
||||||
|
|
||||||
|
|
||||||
class ConsoleServerPortType(ComponentObjectType):
|
class ConsoleServerPortType(ComponentObjectType, CabledObjectMixin):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.ConsoleServerPort
|
model = models.ConsoleServerPort
|
||||||
@ -211,7 +212,7 @@ class DeviceTypeType(NetBoxObjectType):
|
|||||||
return self.airflow or None
|
return self.airflow or None
|
||||||
|
|
||||||
|
|
||||||
class FrontPortType(ComponentObjectType):
|
class FrontPortType(ComponentObjectType, CabledObjectMixin):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.FrontPort
|
model = models.FrontPort
|
||||||
@ -227,7 +228,7 @@ class FrontPortTemplateType(ComponentTemplateObjectType):
|
|||||||
filterset_class = filtersets.FrontPortTemplateFilterSet
|
filterset_class = filtersets.FrontPortTemplateFilterSet
|
||||||
|
|
||||||
|
|
||||||
class InterfaceType(IPAddressesMixin, ComponentObjectType):
|
class InterfaceType(IPAddressesMixin, ComponentObjectType, CabledObjectMixin):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Interface
|
model = models.Interface
|
||||||
@ -330,7 +331,7 @@ class PlatformType(OrganizationalObjectType):
|
|||||||
filterset_class = filtersets.PlatformFilterSet
|
filterset_class = filtersets.PlatformFilterSet
|
||||||
|
|
||||||
|
|
||||||
class PowerFeedType(NetBoxObjectType):
|
class PowerFeedType(NetBoxObjectType, CabledObjectMixin):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.PowerFeed
|
model = models.PowerFeed
|
||||||
@ -338,7 +339,7 @@ class PowerFeedType(NetBoxObjectType):
|
|||||||
filterset_class = filtersets.PowerFeedFilterSet
|
filterset_class = filtersets.PowerFeedFilterSet
|
||||||
|
|
||||||
|
|
||||||
class PowerOutletType(ComponentObjectType):
|
class PowerOutletType(ComponentObjectType, CabledObjectMixin):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.PowerOutlet
|
model = models.PowerOutlet
|
||||||
@ -374,7 +375,7 @@ class PowerPanelType(NetBoxObjectType):
|
|||||||
filterset_class = filtersets.PowerPanelFilterSet
|
filterset_class = filtersets.PowerPanelFilterSet
|
||||||
|
|
||||||
|
|
||||||
class PowerPortType(ComponentObjectType):
|
class PowerPortType(ComponentObjectType, CabledObjectMixin):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.PowerPort
|
model = models.PowerPort
|
||||||
@ -426,7 +427,7 @@ class RackRoleType(OrganizationalObjectType):
|
|||||||
filterset_class = filtersets.RackRoleFilterSet
|
filterset_class = filtersets.RackRoleFilterSet
|
||||||
|
|
||||||
|
|
||||||
class RearPortType(ComponentObjectType):
|
class RearPortType(ComponentObjectType, CabledObjectMixin):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.RearPort
|
model = models.RearPort
|
||||||
|
@ -27,7 +27,7 @@ class Migration(migrations.Migration):
|
|||||||
),
|
),
|
||||||
migrations.AddConstraint(
|
migrations.AddConstraint(
|
||||||
model_name='cabletermination',
|
model_name='cabletermination',
|
||||||
constraint=models.UniqueConstraint(fields=('termination_type', 'termination_id'), name='unique_termination'),
|
constraint=models.UniqueConstraint(fields=('termination_type', 'termination_id'), name='dcim_cable_termination_unique_termination'),
|
||||||
),
|
),
|
||||||
|
|
||||||
# Update CablePath model
|
# Update CablePath model
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import itertools
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||||
@ -11,15 +12,14 @@ from django.urls import reverse
|
|||||||
from dcim.choices import *
|
from dcim.choices import *
|
||||||
from dcim.constants import *
|
from dcim.constants import *
|
||||||
from dcim.fields import PathField
|
from dcim.fields import PathField
|
||||||
from dcim.utils import decompile_path_node, flatten_path, object_to_path_node, path_node_to_object
|
from dcim.utils import decompile_path_node, object_to_path_node, path_node_to_object
|
||||||
from netbox.models import NetBoxModel
|
from netbox.models import NetBoxModel
|
||||||
from utilities.fields import ColorField
|
from utilities.fields import ColorField
|
||||||
from utilities.querysets import RestrictedQuerySet
|
from utilities.querysets import RestrictedQuerySet
|
||||||
from utilities.utils import to_meters
|
from utilities.utils import to_meters
|
||||||
from wireless.models import WirelessLink
|
from wireless.models import WirelessLink
|
||||||
from .devices import Device
|
|
||||||
from .device_components import FrontPort, RearPort
|
from .device_components import FrontPort, RearPort
|
||||||
|
from .devices import Device
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'Cable',
|
'Cable',
|
||||||
@ -110,7 +110,8 @@ class Cable(NetBoxModel):
|
|||||||
# Cache the original status so we can check later if it's been changed
|
# Cache the original status so we can check later if it's been changed
|
||||||
self._orig_status = self.status
|
self._orig_status = self.status
|
||||||
|
|
||||||
# Assign associated CableTerminations (if any)
|
# Assign any *new* CableTerminations for the instance. These will replace any existing
|
||||||
|
# terminations on save().
|
||||||
if a_terminations is not None:
|
if a_terminations is not None:
|
||||||
self.a_terminations = a_terminations
|
self.a_terminations = a_terminations
|
||||||
if b_terminations is not None:
|
if b_terminations is not None:
|
||||||
@ -133,28 +134,25 @@ class Cable(NetBoxModel):
|
|||||||
self.length_unit = ''
|
self.length_unit = ''
|
||||||
|
|
||||||
a_terminations = [
|
a_terminations = [
|
||||||
CableTermination(cable=self, cable_end='A', termination=t) for t in getattr(self, 'a_terminations', [])
|
CableTermination(cable=self, cable_end='A', termination=t)
|
||||||
|
for t in getattr(self, 'a_terminations', [])
|
||||||
]
|
]
|
||||||
b_terminations = [
|
b_terminations = [
|
||||||
CableTermination(cable=self, cable_end='B', termination=t) for t in getattr(self, 'b_terminations', [])
|
CableTermination(cable=self, cable_end='B', termination=t)
|
||||||
|
for t in getattr(self, 'b_terminations', [])
|
||||||
]
|
]
|
||||||
|
|
||||||
# Check that all termination objects for either end are of the same type
|
# Check that all termination objects for either end are of the same type
|
||||||
for terms in (a_terminations, b_terminations):
|
for terms in (a_terminations, b_terminations):
|
||||||
if terms and len(terms) > 1:
|
if len(terms) > 1 and not all(t.termination_type == terms[0].termination_type for t in terms[1:]):
|
||||||
if not all(t.termination_type == terms[0].termination_type for t in terms[1:]):
|
raise ValidationError("Cannot connect different termination types to same end of cable.")
|
||||||
raise ValidationError(
|
|
||||||
"Cannot connect different termination types to same end of cable."
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check that termination types are compatible
|
# Check that termination types are compatible
|
||||||
if a_terminations and b_terminations:
|
if a_terminations and b_terminations:
|
||||||
a_type = a_terminations[0].termination_type.model
|
a_type = a_terminations[0].termination_type.model
|
||||||
b_type = b_terminations[0].termination_type.model
|
b_type = b_terminations[0].termination_type.model
|
||||||
if b_type not in COMPATIBLE_TERMINATION_TYPES.get(a_type):
|
if b_type not in COMPATIBLE_TERMINATION_TYPES.get(a_type):
|
||||||
raise ValidationError(
|
raise ValidationError(f"Incompatible termination types: {a_type} and {b_type}")
|
||||||
f"Incompatible termination types: {a_type} and {b_type}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Run clean() on any new CableTerminations
|
# Run clean() on any new CableTerminations
|
||||||
for cabletermination in [*a_terminations, *b_terminations]:
|
for cabletermination in [*a_terminations, *b_terminations]:
|
||||||
@ -169,6 +167,7 @@ class Cable(NetBoxModel):
|
|||||||
else:
|
else:
|
||||||
self._abs_length = None
|
self._abs_length = None
|
||||||
|
|
||||||
|
# TODO: Need to come with a proper solution for filtering by termination parent
|
||||||
# Store the parent Device for the A and B terminations (if applicable) to enable filtering
|
# Store the parent Device for the A and B terminations (if applicable) to enable filtering
|
||||||
if hasattr(self, 'a_terminations'):
|
if hasattr(self, 'a_terminations'):
|
||||||
self._termination_a_device = getattr(self.a_terminations[0], 'device', None)
|
self._termination_a_device = getattr(self.a_terminations[0], 'device', None)
|
||||||
@ -210,13 +209,15 @@ class Cable(NetBoxModel):
|
|||||||
return LinkStatusChoices.colors.get(self.status)
|
return LinkStatusChoices.colors.get(self.status)
|
||||||
|
|
||||||
def get_a_terminations(self):
|
def get_a_terminations(self):
|
||||||
|
# Query self.terminations.all() to leverage cached results
|
||||||
return [
|
return [
|
||||||
term.termination for term in CableTermination.objects.filter(cable=self, cable_end='A')
|
ct.termination for ct in self.terminations.all() if ct.cable_end == CableEndChoices.SIDE_A
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_b_terminations(self):
|
def get_b_terminations(self):
|
||||||
|
# Query self.terminations.all() to leverage cached results
|
||||||
return [
|
return [
|
||||||
term.termination for term in CableTermination.objects.filter(cable=self, cable_end='B')
|
ct.termination for ct in self.terminations.all() if ct.cable_end == CableEndChoices.SIDE_B
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -253,7 +254,7 @@ class CableTermination(models.Model):
|
|||||||
constraints = (
|
constraints = (
|
||||||
models.UniqueConstraint(
|
models.UniqueConstraint(
|
||||||
fields=('termination_type', 'termination_id'),
|
fields=('termination_type', 'termination_id'),
|
||||||
name='unique_termination'
|
name='dcim_cable_termination_unique_termination'
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -289,34 +290,48 @@ class CableTermination(models.Model):
|
|||||||
|
|
||||||
# Delete the cable association on the terminating object
|
# Delete the cable association on the terminating object
|
||||||
termination_model = self.termination._meta.model
|
termination_model = self.termination._meta.model
|
||||||
termination_model.objects.filter(pk=self.termination_id).update(cable=None, cable_end='', _path=None)
|
termination_model.objects.filter(pk=self.termination_id).update(
|
||||||
|
cable=None,
|
||||||
|
cable_end=''
|
||||||
|
)
|
||||||
|
|
||||||
super().delete(*args, **kwargs)
|
super().delete(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class CablePath(models.Model):
|
class CablePath(models.Model):
|
||||||
"""
|
"""
|
||||||
A CablePath instance represents the physical path from an origin to a destination, including all intermediate
|
A CablePath instance represents the physical path from a set of origin nodes to a set of destination nodes,
|
||||||
elements in the path. Every instance must specify an `origin`, whereas `destination` may be null (for paths which do
|
including all intermediate elements.
|
||||||
not terminate on a PathEndpoint).
|
|
||||||
|
|
||||||
`path` contains a list of nodes within the path, each represented by a tuple of (type, ID). The first element in the
|
`path` contains the ordered set of nodes, arranged in lists of (type, ID) tuples. (Each cable in the path can
|
||||||
path must be a Cable instance, followed by a pair of pass-through ports. For example, consider the following
|
terminate to one or more objects.) For example, consider the following
|
||||||
topology:
|
topology:
|
||||||
|
|
||||||
1 2 3
|
A B C
|
||||||
Interface A --- Front Port A | Rear Port A --- Rear Port B | Front Port B --- Interface B
|
Interface 1 --- Front Port 1 | Rear Port 1 --- Rear Port 2 | Front Port 3 --- Interface 2
|
||||||
|
Front Port 2 Front Port 4
|
||||||
|
|
||||||
This path would be expressed as:
|
This path would be expressed as:
|
||||||
|
|
||||||
CablePath(
|
CablePath(
|
||||||
origin = Interface A
|
path = [
|
||||||
destination = Interface B
|
[Interface 1],
|
||||||
path = [Cable 1, Front Port A, Rear Port A, Cable 2, Rear Port B, Front Port B, Cable 3]
|
[Cable A],
|
||||||
|
[Front Port 1, Front Port 2],
|
||||||
|
[Rear Port 1],
|
||||||
|
[Cable B],
|
||||||
|
[Rear Port 2],
|
||||||
|
[Front Port 3, Front Port 4],
|
||||||
|
[Cable C],
|
||||||
|
[Interface 2],
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
`is_active` is set to True only if 1) `destination` is not null, and 2) every Cable within the path has a status of
|
`is_active` is set to True only if every Cable within the path has a status of "connected". `is_complete` is True
|
||||||
"connected".
|
if the instance represents a complete end-to-end path from origin(s) to destination(s). `is_split` is True if the
|
||||||
|
path diverges across multiple cables.
|
||||||
|
|
||||||
|
`_nodes` retains a flattened list of all nodes within the path to enable simple filtering.
|
||||||
"""
|
"""
|
||||||
path = models.JSONField(
|
path = models.JSONField(
|
||||||
default=list
|
default=list
|
||||||
@ -332,36 +347,32 @@ class CablePath(models.Model):
|
|||||||
)
|
)
|
||||||
_nodes = PathField()
|
_nodes = PathField()
|
||||||
|
|
||||||
class Meta:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
status = ' (active)' if self.is_active else ' (split)' if self.is_split else ''
|
return f"Path #{self.pk}: {len(self.path)} hops"
|
||||||
return f"Path #{self.pk}: {len(self.path)} nodes{status}"
|
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
|
|
||||||
# Save the flattened nodes list
|
# Save the flattened nodes list
|
||||||
self._nodes = flatten_path(self.path)
|
self._nodes = list(itertools.chain(*self.path))
|
||||||
|
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
# Record a direct reference to this CablePath on its originating object(s)
|
# Record a direct reference to this CablePath on its originating object(s)
|
||||||
origin_model = self.origins[0]._meta.model
|
origin_model = self.origin_type.model_class()
|
||||||
origin_ids = [o.id for o in self.origins]
|
origin_ids = [decompile_path_node(node)[1] for node in self.path[0]]
|
||||||
origin_model.objects.filter(pk__in=origin_ids).update(_path=self.pk)
|
origin_model.objects.filter(pk__in=origin_ids).update(_path=self.pk)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def origin_type(self):
|
def origin_type(self):
|
||||||
ct_id, _ = decompile_path_node(self.path[0][0])
|
if self.path:
|
||||||
return ContentType.objects.get_for_id(ct_id)
|
ct_id, _ = decompile_path_node(self.path[0][0])
|
||||||
|
return ContentType.objects.get_for_id(ct_id)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def destination_type(self):
|
def destination_type(self):
|
||||||
if not self.is_complete:
|
if self.is_complete:
|
||||||
return None
|
ct_id, _ = decompile_path_node(self.path[-1][0])
|
||||||
ct_id, _ = decompile_path_node(self.path[-1][0])
|
return ContentType.objects.get_for_id(ct_id)
|
||||||
return ContentType.objects.get_for_id(ct_id)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path_objects(self):
|
def path_objects(self):
|
||||||
@ -375,7 +386,7 @@ class CablePath(models.Model):
|
|||||||
@property
|
@property
|
||||||
def origins(self):
|
def origins(self):
|
||||||
"""
|
"""
|
||||||
Return the list of originating objects (from cache, if available).
|
Return the list of originating objects.
|
||||||
"""
|
"""
|
||||||
if hasattr(self, '_path_objects'):
|
if hasattr(self, '_path_objects'):
|
||||||
return self.path_objects[0]
|
return self.path_objects[0]
|
||||||
@ -386,7 +397,7 @@ class CablePath(models.Model):
|
|||||||
@property
|
@property
|
||||||
def destinations(self):
|
def destinations(self):
|
||||||
"""
|
"""
|
||||||
Return the list of destination objects (from cache, if available), if the path is complete.
|
Return the list of destination objects, if the path is complete.
|
||||||
"""
|
"""
|
||||||
if not self.is_complete:
|
if not self.is_complete:
|
||||||
return []
|
return []
|
||||||
|
@ -10,7 +10,6 @@ from mptt.models import MPTTModel, TreeForeignKey
|
|||||||
from dcim.choices import *
|
from dcim.choices import *
|
||||||
from dcim.constants import *
|
from dcim.constants import *
|
||||||
from dcim.fields import MACAddressField, WWNField
|
from dcim.fields import MACAddressField, WWNField
|
||||||
from dcim.svg import CableTraceSVG
|
|
||||||
from netbox.models import OrganizationalModel, NetBoxModel
|
from netbox.models import OrganizationalModel, NetBoxModel
|
||||||
from utilities.choices import ColorChoices
|
from utilities.choices import ColorChoices
|
||||||
from utilities.fields import ColorField, NaturalOrderingField
|
from utilities.fields import ColorField, NaturalOrderingField
|
||||||
@ -105,7 +104,8 @@ class ModularComponentModel(ComponentModel):
|
|||||||
|
|
||||||
class CabledObjectModel(models.Model):
|
class CabledObjectModel(models.Model):
|
||||||
"""
|
"""
|
||||||
An abstract model inherited by all models to which a Cable can terminate.
|
An abstract model inherited by all models to which a Cable can terminate. Provides the `cable` and `cable_end`
|
||||||
|
fields for caching cable associations, as well as `mark_connected` to designate "fake" connections.
|
||||||
"""
|
"""
|
||||||
cable = models.ForeignKey(
|
cable = models.ForeignKey(
|
||||||
to='dcim.Cable',
|
to='dcim.Cable',
|
||||||
@ -134,8 +134,11 @@ class CabledObjectModel(models.Model):
|
|||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
"cable_end": "Must specify cable end (A or B) when attaching a cable."
|
"cable_end": "Must specify cable end (A or B) when attaching a cable."
|
||||||
})
|
})
|
||||||
|
if self.cable_end and not self.cable:
|
||||||
if self.mark_connected and self.cable_id:
|
raise ValidationError({
|
||||||
|
"cable_end": "Cable end must not be set without a cable."
|
||||||
|
})
|
||||||
|
if self.mark_connected and self.cable:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
"mark_connected": "Cannot mark as connected with a cable attached."
|
"mark_connected": "Cannot mark as connected with a cable attached."
|
||||||
})
|
})
|
||||||
@ -167,12 +170,13 @@ class CabledObjectModel(models.Model):
|
|||||||
"""
|
"""
|
||||||
Generic wrapper for a Cable, WirelessLink, or some other relation to a connected termination.
|
Generic wrapper for a Cable, WirelessLink, or some other relation to a connected termination.
|
||||||
"""
|
"""
|
||||||
|
# TODO: Support WirelessLinks
|
||||||
return self.cable
|
return self.cable
|
||||||
|
|
||||||
|
|
||||||
class PathEndpoint(models.Model):
|
class PathEndpoint(models.Model):
|
||||||
"""
|
"""
|
||||||
An abstract model inherited by any CableTermination subclass which represents the end of a CablePath; specifically,
|
An abstract model inherited by any CabledObjectModel subclass which represents the end of a CablePath; specifically,
|
||||||
these include ConsolePort, ConsoleServerPort, PowerPort, PowerOutlet, Interface, and PowerFeed.
|
these include ConsolePort, ConsoleServerPort, PowerPort, PowerOutlet, Interface, and PowerFeed.
|
||||||
|
|
||||||
`_path` references the CablePath originating from this instance, if any. It is set or cleared by the receivers in
|
`_path` references the CablePath originating from this instance, if any. It is set or cleared by the receivers in
|
||||||
@ -215,14 +219,6 @@ class PathEndpoint(models.Model):
|
|||||||
# Return the path as a list of three-tuples (A termination(s), cable(s), B termination(s))
|
# Return the path as a list of three-tuples (A termination(s), cable(s), B termination(s))
|
||||||
return list(zip(*[iter(path)] * 3))
|
return list(zip(*[iter(path)] * 3))
|
||||||
|
|
||||||
def get_trace_svg(self, base_url=None, width=CABLE_TRACE_SVG_DEFAULT_WIDTH):
|
|
||||||
trace = CableTraceSVG(self, base_url=base_url, width=width)
|
|
||||||
return trace.render()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def path(self):
|
|
||||||
return self._path
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def connected_endpoints(self):
|
def connected_endpoints(self):
|
||||||
"""
|
"""
|
||||||
@ -338,7 +334,15 @@ class PowerPort(ModularComponentModel, CabledObjectModel, PathEndpoint):
|
|||||||
|
|
||||||
def get_downstream_powerports(self, leg=None):
|
def get_downstream_powerports(self, leg=None):
|
||||||
"""
|
"""
|
||||||
Return a queryset of all PowerPorts connected via cable to a child PowerOutlet.
|
Return a queryset of all PowerPorts connected via cable to a child PowerOutlet. For example, in the topology
|
||||||
|
below, PP1.get_downstream_powerports() would return PP2-4.
|
||||||
|
|
||||||
|
---- PO1 <---> PP2
|
||||||
|
/
|
||||||
|
PP1 ------- PO2 <---> PP3
|
||||||
|
\
|
||||||
|
---- PO3 <---> PP4
|
||||||
|
|
||||||
"""
|
"""
|
||||||
poweroutlets = self.poweroutlets.filter(cable__isnull=False)
|
poweroutlets = self.poweroutlets.filter(cable__isnull=False)
|
||||||
if leg:
|
if leg:
|
||||||
|
@ -438,9 +438,9 @@ class Rack(NetBoxModel):
|
|||||||
peer for peer in powerfeed.link_peers if isinstance(peer, PowerPort)
|
peer for peer in powerfeed.link_peers if isinstance(peer, PowerPort)
|
||||||
])
|
])
|
||||||
|
|
||||||
allocated_draw = 0
|
allocated_draw = sum([
|
||||||
for powerport in powerports:
|
powerport.get_power_draw()['allocated'] for powerport in powerports
|
||||||
allocated_draw += powerport.get_power_draw()['allocated']
|
])
|
||||||
|
|
||||||
return int(allocated_draw / available_power_total * 100)
|
return int(allocated_draw / available_power_total * 100)
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ import logging
|
|||||||
from django.db.models.signals import post_save, post_delete, pre_delete
|
from django.db.models.signals import post_save, post_delete, pre_delete
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
|
|
||||||
from .choices import LinkStatusChoices
|
from .choices import CableEndChoices, LinkStatusChoices
|
||||||
from .models import Cable, CablePath, CableTermination, Device, PathEndpoint, PowerPanel, Rack, Location, VirtualChassis
|
from .models import Cable, CablePath, CableTermination, Device, PathEndpoint, PowerPanel, Rack, Location, VirtualChassis
|
||||||
from .models.cables import trace_paths
|
from .models.cables import trace_paths
|
||||||
from .utils import create_cablepath, rebuild_paths
|
from .utils import create_cablepath, rebuild_paths
|
||||||
@ -83,7 +83,7 @@ def update_connected_endpoints(instance, created, raw=False, **kwargs):
|
|||||||
a_terminations = []
|
a_terminations = []
|
||||||
b_terminations = []
|
b_terminations = []
|
||||||
for t in instance.terminations.all():
|
for t in instance.terminations.all():
|
||||||
if t.cable_end == 'A':
|
if t.cable_end == CableEndChoices.SIDE_A:
|
||||||
a_terminations.append(t.termination)
|
a_terminations.append(t.termination)
|
||||||
else:
|
else:
|
||||||
b_terminations.append(t.termination)
|
b_terminations.append(t.termination)
|
||||||
|
@ -13,21 +13,17 @@ CABLE_LENGTH = """
|
|||||||
{% if record.length %}{{ record.length|simplify_decimal }} {{ record.length_unit }}{% endif %}
|
{% if record.length %}{{ record.length|simplify_decimal }} {{ record.length_unit }}{% endif %}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
CABLE_TERMINATION = """
|
# CABLE_TERMINATION_PARENT = """
|
||||||
{{ value|join:", " }}
|
# {% with value.0 as termination %}
|
||||||
"""
|
# {% if termination.device %}
|
||||||
|
# <a href="{{ termination.device.get_absolute_url }}">{{ termination.device }}</a>
|
||||||
CABLE_TERMINATION_PARENT = """
|
# {% elif termination.circuit %}
|
||||||
{% with value.0 as termination %}
|
# <a href="{{ termination.circuit.get_absolute_url }}">{{ termination.circuit }}</a>
|
||||||
{% if termination.device %}
|
# {% elif termination.power_panel %}
|
||||||
<a href="{{ termination.device.get_absolute_url }}">{{ termination.device }}</a>
|
# <a href="{{ termination.power_panel.get_absolute_url }}">{{ termination.power_panel }}</a>
|
||||||
{% elif termination.circuit %}
|
# {% endif %}
|
||||||
<a href="{{ termination.circuit.get_absolute_url }}">{{ termination.circuit }}</a>
|
# {% endwith %}
|
||||||
{% elif termination.power_panel %}
|
# """
|
||||||
<a href="{{ termination.power_panel.get_absolute_url }}">{{ termination.power_panel }}</a>
|
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
|
||||||
"""
|
|
||||||
|
|
||||||
DEVICE_LINK = """
|
DEVICE_LINK = """
|
||||||
<a href="{% url 'dcim:device' pk=record.pk %}">
|
<a href="{% url 'dcim:device' pk=record.pk %}">
|
||||||
|
@ -3,6 +3,7 @@ from django.test import TestCase
|
|||||||
from circuits.models import *
|
from circuits.models import *
|
||||||
from dcim.choices import LinkStatusChoices
|
from dcim.choices import LinkStatusChoices
|
||||||
from dcim.models import *
|
from dcim.models import *
|
||||||
|
from dcim.svg import CableTraceSVG
|
||||||
from dcim.utils import object_to_path_node
|
from dcim.utils import object_to_path_node
|
||||||
|
|
||||||
|
|
||||||
@ -107,7 +108,7 @@ class CablePathTestCase(TestCase):
|
|||||||
self.assertPathIsSet(interface2, path2)
|
self.assertPathIsSet(interface2, path2)
|
||||||
|
|
||||||
# Test SVG generation
|
# Test SVG generation
|
||||||
interface1.get_trace_svg()
|
CableTraceSVG(interface1).render()
|
||||||
|
|
||||||
# Delete cable 1
|
# Delete cable 1
|
||||||
cable1.delete()
|
cable1.delete()
|
||||||
@ -146,7 +147,7 @@ class CablePathTestCase(TestCase):
|
|||||||
self.assertPathIsSet(consoleserverport1, path2)
|
self.assertPathIsSet(consoleserverport1, path2)
|
||||||
|
|
||||||
# Test SVG generation
|
# Test SVG generation
|
||||||
consoleport1.get_trace_svg()
|
CableTraceSVG(consoleport1).render()
|
||||||
|
|
||||||
# Delete cable 1
|
# Delete cable 1
|
||||||
cable1.delete()
|
cable1.delete()
|
||||||
@ -185,7 +186,7 @@ class CablePathTestCase(TestCase):
|
|||||||
self.assertPathIsSet(poweroutlet1, path2)
|
self.assertPathIsSet(poweroutlet1, path2)
|
||||||
|
|
||||||
# Test SVG generation
|
# Test SVG generation
|
||||||
powerport1.get_trace_svg()
|
CableTraceSVG(powerport1).render()
|
||||||
|
|
||||||
# Delete cable 1
|
# Delete cable 1
|
||||||
cable1.delete()
|
cable1.delete()
|
||||||
@ -224,7 +225,7 @@ class CablePathTestCase(TestCase):
|
|||||||
self.assertPathIsSet(powerfeed1, path2)
|
self.assertPathIsSet(powerfeed1, path2)
|
||||||
|
|
||||||
# Test SVG generation
|
# Test SVG generation
|
||||||
powerport1.get_trace_svg()
|
CableTraceSVG(powerport1).render()
|
||||||
|
|
||||||
# Delete cable 1
|
# Delete cable 1
|
||||||
cable1.delete()
|
cable1.delete()
|
||||||
@ -267,7 +268,7 @@ class CablePathTestCase(TestCase):
|
|||||||
self.assertPathIsSet(interface3, path2)
|
self.assertPathIsSet(interface3, path2)
|
||||||
|
|
||||||
# Test SVG generation
|
# Test SVG generation
|
||||||
interface1.get_trace_svg()
|
CableTraceSVG(interface1).render()
|
||||||
|
|
||||||
# Delete cable 1
|
# Delete cable 1
|
||||||
cable1.delete()
|
cable1.delete()
|
||||||
@ -319,7 +320,7 @@ class CablePathTestCase(TestCase):
|
|||||||
self.assertPathIsSet(interface4, path2)
|
self.assertPathIsSet(interface4, path2)
|
||||||
|
|
||||||
# Test SVG generation
|
# Test SVG generation
|
||||||
interface1.get_trace_svg()
|
CableTraceSVG(interface1).render()
|
||||||
|
|
||||||
# Delete cable 1
|
# Delete cable 1
|
||||||
cable1.delete()
|
cable1.delete()
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import itertools
|
||||||
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
|
||||||
@ -29,16 +31,6 @@ def path_node_to_object(repr):
|
|||||||
return ct.model_class().objects.get(pk=object_id)
|
return ct.model_class().objects.get(pk=object_id)
|
||||||
|
|
||||||
|
|
||||||
def flatten_path(path):
|
|
||||||
"""
|
|
||||||
Flatten a two-dimensional array (list of lists) into a flat list.
|
|
||||||
"""
|
|
||||||
ret = []
|
|
||||||
for step in path:
|
|
||||||
ret.extend(step)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
def create_cablepath(terminations):
|
def create_cablepath(terminations):
|
||||||
"""
|
"""
|
||||||
Create CablePaths for all paths originating from the specified set of nodes.
|
Create CablePaths for all paths originating from the specified set of nodes.
|
||||||
@ -54,7 +46,7 @@ def create_cablepath(terminations):
|
|||||||
|
|
||||||
def rebuild_paths(terminations):
|
def rebuild_paths(terminations):
|
||||||
"""
|
"""
|
||||||
Rebuild all CablePaths which traverse the specified node
|
Rebuild all CablePaths which traverse the specified nodes.
|
||||||
"""
|
"""
|
||||||
from dcim.models import CablePath
|
from dcim.models import CablePath
|
||||||
|
|
||||||
|
@ -28,6 +28,18 @@ from .choices import DeviceFaceChoices
|
|||||||
from .constants import NONCONNECTABLE_IFACE_TYPES
|
from .constants import NONCONNECTABLE_IFACE_TYPES
|
||||||
from .models import *
|
from .models import *
|
||||||
|
|
||||||
|
CABLE_TERMINATION_TYPES = {
|
||||||
|
'dcim.consoleport': ConsolePort,
|
||||||
|
'dcim.consoleserverport': ConsoleServerPort,
|
||||||
|
'dcim.powerport': PowerPort,
|
||||||
|
'dcim.poweroutlet': PowerOutlet,
|
||||||
|
'dcim.interface': Interface,
|
||||||
|
'dcim.frontport': FrontPort,
|
||||||
|
'dcim.rearport': RearPort,
|
||||||
|
'dcim.powerfeed': PowerFeed,
|
||||||
|
'circuits.circuittermination': CircuitTermination,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class DeviceComponentsView(generic.ObjectChildrenView):
|
class DeviceComponentsView(generic.ObjectChildrenView):
|
||||||
queryset = Device.objects.all()
|
queryset = Device.objects.all()
|
||||||
@ -2818,22 +2830,10 @@ class CableEditView(generic.ObjectEditView):
|
|||||||
|
|
||||||
# If creating a new Cable, initialize the form class using URL query params
|
# If creating a new Cable, initialize the form class using URL query params
|
||||||
if 'pk' not in kwargs:
|
if 'pk' not in kwargs:
|
||||||
termination_types = {
|
self.form = forms.get_cable_form(
|
||||||
'dcim.consoleport': ConsolePort,
|
a_type=CABLE_TERMINATION_TYPES.get(request.GET.get('a_terminations_type')),
|
||||||
'dcim.consoleserverport': ConsoleServerPort,
|
b_type=CABLE_TERMINATION_TYPES.get(request.GET.get('b_terminations_type'))
|
||||||
'dcim.powerport': PowerPort,
|
)
|
||||||
'dcim.poweroutlet': PowerOutlet,
|
|
||||||
'dcim.interface': Interface,
|
|
||||||
'dcim.frontport': FrontPort,
|
|
||||||
'dcim.rearport': RearPort,
|
|
||||||
'dcim.powerfeed': PowerFeed,
|
|
||||||
'circuits.circuittermination': CircuitTermination,
|
|
||||||
}
|
|
||||||
|
|
||||||
a_type = termination_types.get(request.GET.get('a_terminations_type'))
|
|
||||||
b_type = termination_types.get(request.GET.get('b_terminations_type'))
|
|
||||||
|
|
||||||
self.form = forms.get_cable_form(a_type, b_type)
|
|
||||||
|
|
||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
|
@ -179,8 +179,8 @@ class ExceptionHandlingMiddleware:
|
|||||||
def process_exception(self, request, exception):
|
def process_exception(self, request, exception):
|
||||||
|
|
||||||
# Handle exceptions that occur from REST API requests
|
# Handle exceptions that occur from REST API requests
|
||||||
if is_api_request(request):
|
# if is_api_request(request):
|
||||||
return rest_api_server_error(request)
|
# return rest_api_server_error(request)
|
||||||
|
|
||||||
# Don't catch exceptions when in debug mode
|
# Don't catch exceptions when in debug mode
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
|
@ -3,7 +3,6 @@ import sys
|
|||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.db.models import F
|
|
||||||
from django.http import HttpResponseServerError
|
from django.http import HttpResponseServerError
|
||||||
from django.shortcuts import redirect, render
|
from django.shortcuts import redirect, render
|
||||||
from django.template import loader
|
from django.template import loader
|
||||||
@ -37,13 +36,13 @@ class HomeView(View):
|
|||||||
return redirect("login")
|
return redirect("login")
|
||||||
|
|
||||||
connected_consoleports = ConsolePort.objects.restrict(request.user, 'view').prefetch_related('_path').filter(
|
connected_consoleports = ConsolePort.objects.restrict(request.user, 'view').prefetch_related('_path').filter(
|
||||||
_path__is_active=True
|
_path__is_complete=True
|
||||||
)
|
)
|
||||||
connected_powerports = PowerPort.objects.restrict(request.user, 'view').prefetch_related('_path').filter(
|
connected_powerports = PowerPort.objects.restrict(request.user, 'view').prefetch_related('_path').filter(
|
||||||
_path__is_active=True
|
_path__is_complete=True
|
||||||
)
|
)
|
||||||
connected_interfaces = Interface.objects.restrict(request.user, 'view').prefetch_related('_path').filter(
|
connected_interfaces = Interface.objects.restrict(request.user, 'view').prefetch_related('_path').filter(
|
||||||
_path__is_active=True
|
_path__is_complete=True
|
||||||
)
|
)
|
||||||
|
|
||||||
def build_stats():
|
def build_stats():
|
||||||
|
Loading…
Reference in New Issue
Block a user