diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index cc218c61c..f84ceacae 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -51,32 +51,64 @@ class LinkTerminationSerializer(serializers.ModelSerializer): return obj._occupied -class ConnectedEndpointSerializer(serializers.ModelSerializer): - connected_endpoint_type = serializers.SerializerMethodField(read_only=True) - connected_endpoint = serializers.SerializerMethodField(read_only=True) - connected_endpoint_reachable = serializers.SerializerMethodField(read_only=True) +# class ConnectedEndpointSerializer(serializers.ModelSerializer): +# """ +# Legacy serializer for pre-v3.3 connections +# """ +# connected_endpoint_type = serializers.SerializerMethodField(read_only=True) +# connected_endpoint = serializers.SerializerMethodField(read_only=True) +# connected_endpoint_reachable = serializers.SerializerMethodField(read_only=True) +# +# def get_connected_endpoint_type(self, obj): +# if obj._path is not None and obj._path.destination is not None: +# return f'{obj._path.destination._meta.app_label}.{obj._path.destination._meta.model_name}' +# return None +# +# @swagger_serializer_method(serializer_or_field=serializers.DictField) +# def get_connected_endpoint(self, obj): +# """ +# Return the appropriate serializer for the type of connected object. +# """ +# if obj._path is not None and obj._path.destination is not None: +# serializer = get_serializer_for_model(obj._path.destination, prefix='Nested') +# context = {'request': self.context['request']} +# return serializer(obj._path.destination, context=context).data +# return None +# +# @swagger_serializer_method(serializer_or_field=serializers.BooleanField) +# def get_connected_endpoint_reachable(self, obj): +# if obj._path is not None: +# return obj._path.is_active +# return None - def get_connected_endpoint_type(self, obj): - if obj._path is not None and obj._path.destination is not None: - return f'{obj._path.destination._meta.app_label}.{obj._path.destination._meta.model_name}' - return None + +class ConnectedEndpointsSerializer(serializers.ModelSerializer): + """ + Legacy serializer for pre-v3.3 connections + """ + connected_endpoints_type = serializers.SerializerMethodField(read_only=True) + connected_endpoints = serializers.SerializerMethodField(read_only=True) + connected_endpoints_reachable = serializers.SerializerMethodField(read_only=True) + + def get_connected_endpoints_type(self, obj): + endpoints = obj.connected_endpoints + if endpoints: + return f'{endpoints[0]._meta.app_label}.{endpoints[0]._meta.model_name}' @swagger_serializer_method(serializer_or_field=serializers.DictField) - def get_connected_endpoint(self, obj): + def get_connected_endpoints(self, obj): """ Return the appropriate serializer for the type of connected object. """ - if obj._path is not None and obj._path.destination is not None: - serializer = get_serializer_for_model(obj._path.destination, prefix='Nested') + endpoints = obj.connected_endpoints + if endpoints: + serializer = get_serializer_for_model(endpoints[0], prefix='Nested') context = {'request': self.context['request']} - return serializer(obj._path.destination, context=context).data - return None + return serializer(endpoints, many=True, context=context).data @swagger_serializer_method(serializer_or_field=serializers.BooleanField) - def get_connected_endpoint_reachable(self, obj): - if obj._path is not None: - return obj._path.is_active - return None + def get_connected_endpoints_reachable(self, obj): + return obj._path and obj._path.is_complete and obj._path.is_active # @@ -659,7 +691,7 @@ class DeviceNAPALMSerializer(serializers.Serializer): # Device components # -class ConsoleServerPortSerializer(NetBoxModelSerializer, LinkTerminationSerializer, ConnectedEndpointSerializer): +class ConsoleServerPortSerializer(NetBoxModelSerializer, LinkTerminationSerializer, ConnectedEndpointsSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:consoleserverport-detail') device = NestedDeviceSerializer() module = ComponentNestedModuleSerializer( @@ -682,12 +714,12 @@ class ConsoleServerPortSerializer(NetBoxModelSerializer, LinkTerminationSerializ model = ConsoleServerPort fields = [ 'id', 'url', 'display', 'device', 'module', 'name', 'label', 'type', 'speed', 'description', - 'mark_connected', 'cable', 'link_peer', 'link_peer_type', 'connected_endpoint', 'connected_endpoint_type', - 'connected_endpoint_reachable', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied', + 'mark_connected', 'cable', 'link_peer', 'link_peer_type', 'connected_endpoints', 'connected_endpoints_type', + 'connected_endpoints_reachable', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied', ] -class ConsolePortSerializer(NetBoxModelSerializer, LinkTerminationSerializer, ConnectedEndpointSerializer): +class ConsolePortSerializer(NetBoxModelSerializer, LinkTerminationSerializer, ConnectedEndpointsSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:consoleport-detail') device = NestedDeviceSerializer() module = ComponentNestedModuleSerializer( @@ -710,12 +742,12 @@ class ConsolePortSerializer(NetBoxModelSerializer, LinkTerminationSerializer, Co model = ConsolePort fields = [ 'id', 'url', 'display', 'device', 'module', 'name', 'label', 'type', 'speed', 'description', - 'mark_connected', 'cable', 'link_peer', 'link_peer_type', 'connected_endpoint', 'connected_endpoint_type', - 'connected_endpoint_reachable', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied', + 'mark_connected', 'cable', 'link_peer', 'link_peer_type', 'connected_endpoints', 'connected_endpoints_type', + 'connected_endpoints_reachable', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied', ] -class PowerOutletSerializer(NetBoxModelSerializer, LinkTerminationSerializer, ConnectedEndpointSerializer): +class PowerOutletSerializer(NetBoxModelSerializer, LinkTerminationSerializer, ConnectedEndpointsSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:poweroutlet-detail') device = NestedDeviceSerializer() module = ComponentNestedModuleSerializer( @@ -744,13 +776,13 @@ class PowerOutletSerializer(NetBoxModelSerializer, LinkTerminationSerializer, Co model = PowerOutlet fields = [ 'id', 'url', 'display', 'device', 'module', 'name', 'label', 'type', 'power_port', 'feed_leg', - 'description', 'mark_connected', 'cable', 'link_peer', 'link_peer_type', 'connected_endpoint', - 'connected_endpoint_type', 'connected_endpoint_reachable', 'tags', 'custom_fields', 'created', + 'description', 'mark_connected', 'cable', 'link_peer', 'link_peer_type', 'connected_endpoints', + 'connected_endpoints_type', 'connected_endpoints_reachable', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied', ] -class PowerPortSerializer(NetBoxModelSerializer, LinkTerminationSerializer, ConnectedEndpointSerializer): +class PowerPortSerializer(NetBoxModelSerializer, LinkTerminationSerializer, ConnectedEndpointsSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:powerport-detail') device = NestedDeviceSerializer() module = ComponentNestedModuleSerializer( @@ -768,13 +800,13 @@ class PowerPortSerializer(NetBoxModelSerializer, LinkTerminationSerializer, Conn model = PowerPort fields = [ 'id', 'url', 'display', 'device', 'module', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', - 'description', 'mark_connected', 'cable', 'link_peer', 'link_peer_type', 'connected_endpoint', - 'connected_endpoint_type', 'connected_endpoint_reachable', 'tags', 'custom_fields', 'created', + 'description', 'mark_connected', 'cable', 'link_peer', 'link_peer_type', 'connected_endpoints', + 'connected_endpoints_type', 'connected_endpoints_reachable', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied', ] -class InterfaceSerializer(NetBoxModelSerializer, LinkTerminationSerializer, ConnectedEndpointSerializer): +class InterfaceSerializer(NetBoxModelSerializer, LinkTerminationSerializer, ConnectedEndpointsSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:interface-detail') device = NestedDeviceSerializer() module = ComponentNestedModuleSerializer( @@ -814,8 +846,8 @@ class InterfaceSerializer(NetBoxModelSerializer, LinkTerminationSerializer, Conn 'id', 'url', 'display', 'device', 'module', 'name', 'label', 'type', 'enabled', 'parent', 'bridge', 'lag', 'mtu', 'mac_address', 'speed', 'duplex', 'wwn', 'mgmt_only', 'description', 'mode', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'untagged_vlan', 'tagged_vlans', 'mark_connected', - 'cable', 'wireless_link', 'link_peer', 'link_peer_type', 'wireless_lans', 'vrf', 'connected_endpoint', - 'connected_endpoint_type', 'connected_endpoint_reachable', 'tags', 'custom_fields', 'created', + 'cable', 'wireless_link', 'link_peer', 'link_peer_type', 'wireless_lans', 'vrf', 'connected_endpoints', + 'connected_endpoints_type', 'connected_endpoints_reachable', 'tags', 'custom_fields', 'created', 'last_updated', 'count_ipaddresses', 'count_fhrp_groups', '_occupied', ] @@ -1092,7 +1124,7 @@ class PowerPanelSerializer(NetBoxModelSerializer): ] -class PowerFeedSerializer(NetBoxModelSerializer, LinkTerminationSerializer, ConnectedEndpointSerializer): +class PowerFeedSerializer(NetBoxModelSerializer, LinkTerminationSerializer, ConnectedEndpointsSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:powerfeed-detail') power_panel = NestedPowerPanelSerializer() rack = NestedRackSerializer( @@ -1123,6 +1155,6 @@ class PowerFeedSerializer(NetBoxModelSerializer, LinkTerminationSerializer, Conn fields = [ 'id', 'url', 'display', 'power_panel', 'rack', 'name', 'status', 'type', 'supply', 'phase', 'voltage', 'amperage', 'max_utilization', 'comments', 'mark_connected', 'cable', 'link_peer', 'link_peer_type', - 'connected_endpoint', 'connected_endpoint_type', 'connected_endpoint_reachable', 'tags', 'custom_fields', + 'connected_endpoints', 'connected_endpoints_type', 'connected_endpoints_reachable', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied', ] diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index 1ee6082f0..fd5bf13c2 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -93,7 +93,7 @@ class PassThroughPortMixin(object): Return all CablePaths which traverse a given pass-through port. """ obj = get_object_or_404(self.queryset, pk=pk) - cablepaths = CablePath.objects.filter(_nodes__contains=obj).prefetch_related('origin', 'destination') + cablepaths = CablePath.objects.filter(_nodes__contains=obj) serializer = serializers.CablePathSerializer(cablepaths, context={'request': request}, many=True) return Response(serializer.data) diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py index ec82e6606..92c8dc09f 100644 --- a/netbox/dcim/models/device_components.py +++ b/netbox/dcim/models/device_components.py @@ -224,13 +224,13 @@ class PathEndpoint(models.Model): return self._path @property - def connected_endpoint(self): + def connected_endpoints(self): """ Caching accessor for the attached CablePath's destination (if any) """ - if not hasattr(self, '_connected_endpoint'): - self._connected_endpoint = self._path.get_destination() - return self._connected_endpoint + if not hasattr(self, '_connected_endpoints'): + self._connected_endpoints = self._path.get_destination() if self._path else [] + return self._connected_endpoints # diff --git a/netbox/dcim/tests/test_api.py b/netbox/dcim/tests/test_api.py index 9855c5b6d..aaad18577 100644 --- a/netbox/dcim/tests/test_api.py +++ b/netbox/dcim/tests/test_api.py @@ -45,7 +45,7 @@ class Mixins: device=peer_device, name='Peer Termination' ) - cable = Cable(termination_a=obj, b_terminations=[peer_obj], label='Cable 1') + cable = Cable(a_terminations=[obj], b_terminations=[peer_obj], label='Cable 1') cable.save() self.add_permissions(f'dcim.view_{self.model._meta.model_name}') diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 96f72b309..0c7f38a4c 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -2771,7 +2771,7 @@ class PathTraceView(generic.ObjectView): # Otherwise, find all CablePaths which traverse the specified object else: - related_paths = CablePath.objects.filter(_nodes__contains=instance).prefetch_related('origin') + related_paths = CablePath.objects.filter(_nodes__contains=instance) # Check for specification of a particular path (when tracing pass-through ports) try: path_id = int(request.GET.get('cablepath_id'))