diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index 452ebb15d..c01fd330b 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -5,7 +5,7 @@ from taggit_serializer.serializers import TaggitSerializer, TagListSerializerFie from circuits.models import Circuit, CircuitTermination from dcim.constants import * from dcim.models import ( - ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, + Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, DeviceBayTemplate, DeviceType, DeviceRole, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate, Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site, VirtualChassis, @@ -15,8 +15,8 @@ from ipam.models import IPAddress, VLAN from tenancy.api.serializers import NestedTenantSerializer from users.api.serializers import NestedUserSerializer from utilities.api import ( - ChoiceField, SerializedPKRelatedField, TimeZoneField, ValidatedModelSerializer, - WritableNestedSerializer, + ChoiceField, ContentTypeField, SerializedPKRelatedField, TimeZoneField, ValidatedModelSerializer, + WritableNestedSerializer, get_serializer_for_model, ) from virtualization.models import Cluster @@ -717,11 +717,12 @@ class RearPortSerializer(ValidatedModelSerializer): class NestedRearPortSerializer(WritableNestedSerializer): + device = NestedDeviceSerializer(read_only=True) url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rearport-detail') class Meta: model = RearPort - fields = ['id', 'url', 'name'] + fields = ['id', 'url', 'device', 'name'] # @@ -740,11 +741,12 @@ class FrontPortSerializer(ValidatedModelSerializer): class NestedFrontPortSerializer(WritableNestedSerializer): + device = NestedDeviceSerializer(read_only=True) url = serializers.HyperlinkedIdentityField(view_name='dcim-api:frontport-detail') class Meta: model = FrontPort - fields = ['id', 'url', 'name'] + fields = ['id', 'url', 'device', 'name'] # @@ -807,6 +809,47 @@ class InterfaceConnectionSerializer(ValidatedModelSerializer): return NestedInterfaceSerializer(instance=obj, context=context).data +# +# Cables +# + +class CableSerializer(ValidatedModelSerializer): + termination_a_type = ContentTypeField() + termination_b_type = ContentTypeField() + termination_a = serializers.SerializerMethodField(read_only=True) + termination_b = serializers.SerializerMethodField(read_only=True) + status = ChoiceField(choices=CONNECTION_STATUS_CHOICES) + length_unit = ChoiceField(choices=LENGTH_UNIT_CHOICES) + + class Meta: + model = Cable + fields = [ + 'id', 'termination_a_type', 'termination_a_id', 'termination_a', 'termination_b_type', 'termination_b_id', + 'termination_b', 'type', 'status', 'label', 'color', 'length', 'length_unit', + ] + + def _get_termination(self, obj, side): + """ + Serialize a nested representation of a termination. + """ + if side.lower() not in ['a', 'b']: + raise ValueError("Termination side must be either A or B.") + termination = getattr(obj, 'termination_{}'.format(side.lower())) + if termination is None: + return None + serializer = get_serializer_for_model(termination, prefix='Nested') + context = {'request': self.context['request']} + data = serializer(termination, context=context).data + + return data + + def get_termination_a(self, obj): + return self._get_termination(obj, 'a') + + def get_termination_b(self, obj): + return self._get_termination(obj, 'b') + + # # Virtual chassis # diff --git a/netbox/dcim/api/urls.py b/netbox/dcim/api/urls.py index 9a6f00c18..67f648733 100644 --- a/netbox/dcim/api/urls.py +++ b/netbox/dcim/api/urls.py @@ -62,6 +62,9 @@ router.register(r'console-connections', views.ConsoleConnectionViewSet, base_nam router.register(r'power-connections', views.PowerConnectionViewSet, base_name='powerconnections') router.register(r'interface-connections', views.InterfaceConnectionViewSet, base_name='interfaceconnections') +# Cables +router.register(r'cables', views.CableViewSet) + # Virtual chassis router.register(r'virtual-chassis', views.VirtualChassisViewSet) diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index 9586bbcf6..aa9c3e226 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -14,7 +14,7 @@ from rest_framework.viewsets import GenericViewSet, ViewSet from dcim import filters from dcim.models import ( - ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, + Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate, Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site, VirtualChassis, @@ -428,6 +428,16 @@ class InterfaceConnectionViewSet(ModelViewSet): filter_class = filters.InterfaceConnectionFilter +# +# Cables +# + +class CableViewSet(ModelViewSet): + queryset = Cable.objects.prefetch_related('termination_a__device', 'termination_b__device') + serializer_class = serializers.CableSerializer + filter_class = filters.CableFilter + + # # Virtual chassis # diff --git a/netbox/utilities/api.py b/netbox/utilities/api.py index 5b6dcb24e..a474725e5 100644 --- a/netbox/utilities/api.py +++ b/netbox/utilities/api.py @@ -69,6 +69,8 @@ class ChoiceField(Field): super(ChoiceField, self).__init__(**kwargs) def to_representation(self, obj): + if obj is '': + return None return {'value': obj, 'label': self._choices[obj]} def to_internal_value(self, data):