diff --git a/CHANGELOG.md b/CHANGELOG.md index 63326838b..db325bb08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ NetBox now supports modeling physical cables for console, power, and interface c * [#2572](https://github.com/digitalocean/netbox/issues/2572) - Add button to disconnect cable from circuit termination * [#2573](https://github.com/digitalocean/netbox/issues/2573) - Fix bulk console/power/interface disconnections * [#2574](https://github.com/digitalocean/netbox/issues/2574) - Remove duplicate interface links from topology maps +* [#2578](https://github.com/digitalocean/netbox/issues/2578) - Reorganized nested serializers * [#2579](https://github.com/digitalocean/netbox/issues/2579) - Add missing cable disconnect buttons for front/rear ports ## API Changes diff --git a/netbox/circuits/api/nested_serializers.py b/netbox/circuits/api/nested_serializers.py new file mode 100644 index 000000000..211dc4007 --- /dev/null +++ b/netbox/circuits/api/nested_serializers.py @@ -0,0 +1,52 @@ +from rest_framework import serializers + +from circuits.models import Circuit, CircuitTermination, CircuitType, Provider +from utilities.api import WritableNestedSerializer + +__all__ = [ + 'NestedCircuitSerializer', + 'NestedCircuitTerminationSerializer', + 'NestedCircuitTypeSerializer', + 'NestedProviderSerializer', +] + + +# +# Providers +# + +class NestedProviderSerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='circuits-api:provider-detail') + + class Meta: + model = Provider + fields = ['id', 'url', 'name', 'slug'] + + +# +# Circuits +# + +class NestedCircuitTypeSerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuittype-detail') + + class Meta: + model = CircuitType + fields = ['id', 'url', 'name', 'slug'] + + +class NestedCircuitSerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuit-detail') + + class Meta: + model = Circuit + fields = ['id', 'url', 'cid'] + + +class NestedCircuitTerminationSerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuittermination-detail') + circuit = NestedCircuitSerializer() + + class Meta: + model = CircuitTermination + fields = ['id', 'url', 'circuit', 'term_side'] diff --git a/netbox/circuits/api/serializers.py b/netbox/circuits/api/serializers.py index da2bed53a..6ac2587c6 100644 --- a/netbox/circuits/api/serializers.py +++ b/netbox/circuits/api/serializers.py @@ -1,12 +1,12 @@ -from rest_framework import serializers from taggit_serializer.serializers import TaggitSerializer, TagListSerializerField from circuits.constants import CIRCUIT_STATUS_CHOICES from circuits.models import Provider, Circuit, CircuitTermination, CircuitType -from dcim.api.serializers import NestedCableSerializer, NestedInterfaceSerializer, NestedSiteSerializer +from dcim.api.nested_serializers import NestedCableSerializer, NestedInterfaceSerializer, NestedSiteSerializer from extras.api.customfields import CustomFieldModelSerializer -from tenancy.api.serializers import NestedTenantSerializer -from utilities.api import ChoiceField, ValidatedModelSerializer, WritableNestedSerializer +from tenancy.api.nested_serializers import NestedTenantSerializer +from utilities.api import ChoiceField, ValidatedModelSerializer +from .nested_serializers import * # @@ -24,16 +24,8 @@ class ProviderSerializer(TaggitSerializer, CustomFieldModelSerializer): ] -class NestedProviderSerializer(WritableNestedSerializer): - url = serializers.HyperlinkedIdentityField(view_name='circuits-api:provider-detail') - - class Meta: - model = Provider - fields = ['id', 'url', 'name', 'slug'] - - # -# Circuit types +# Circuits # class CircuitTypeSerializer(ValidatedModelSerializer): @@ -43,18 +35,6 @@ class CircuitTypeSerializer(ValidatedModelSerializer): fields = ['id', 'name', 'slug'] -class NestedCircuitTypeSerializer(WritableNestedSerializer): - url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuittype-detail') - - class Meta: - model = CircuitType - fields = ['id', 'url', 'name', 'slug'] - - -# -# Circuits -# - class CircuitSerializer(TaggitSerializer, CustomFieldModelSerializer): provider = NestedProviderSerializer() status = ChoiceField(choices=CIRCUIT_STATUS_CHOICES, required=False) @@ -70,18 +50,6 @@ class CircuitSerializer(TaggitSerializer, CustomFieldModelSerializer): ] -class NestedCircuitSerializer(WritableNestedSerializer): - url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuit-detail') - - class Meta: - model = Circuit - fields = ['id', 'url', 'cid'] - - -# -# Circuit Terminations -# - class CircuitTerminationSerializer(ValidatedModelSerializer): circuit = NestedCircuitSerializer() site = NestedSiteSerializer() @@ -94,12 +62,3 @@ class CircuitTerminationSerializer(ValidatedModelSerializer): 'id', 'circuit', 'term_side', 'site', 'port_speed', 'upstream_speed', 'xconnect_id', 'pp_info', 'description', 'connected_endpoint', 'cable', ] - - -class NestedCircuitTerminationSerializer(WritableNestedSerializer): - url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuittermination-detail') - circuit = NestedCircuitSerializer() - - class Meta: - model = CircuitTermination - fields = ['id', 'url', 'circuit', 'term_side'] diff --git a/netbox/dcim/api/nested_serializers.py b/netbox/dcim/api/nested_serializers.py new file mode 100644 index 000000000..b8dd403b5 --- /dev/null +++ b/netbox/dcim/api/nested_serializers.py @@ -0,0 +1,243 @@ +from rest_framework import serializers + +from dcim.models import ( + Cable, ConsolePort, ConsoleServerPort, Device, DeviceBay, DeviceType, DeviceRole, FrontPort, FrontPortTemplate, + Interface, Manufacturer, Platform, PowerOutlet, PowerPort, Rack, RackGroup, RackRole, RearPort, RearPortTemplate, + Region, Site, VirtualChassis, +) +from utilities.api import WritableNestedSerializer + +__all__ = [ + 'NestedCableSerializer', + 'NestedConsolePortSerializer', + 'NestedConsoleServerPortSerializer', + 'NestedDeviceBaySerializer', + 'NestedDeviceRoleSerializer', + 'NestedDeviceSerializer', + 'NestedDeviceTypeSerializer', + 'NestedFrontPortSerializer', + 'NestedFrontPortTemplateSerializer', + 'NestedInterfaceSerializer', + 'NestedManufacturerSerializer', + 'NestedPlatformSerializer', + 'NestedPowerOutletSerializer', + 'NestedPowerPortSerializer', + 'NestedRackGroupSerializer', + 'NestedRackRoleSerializer', + 'NestedRackSerializer', + 'NestedRearPortSerializer', + 'NestedRearPortTemplateSerializer', + 'NestedRegionSerializer', + 'NestedSiteSerializer', + 'NestedVirtualChassisSerializer', +] + + +# +# Regions/sites +# + +class NestedRegionSerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='dcim-api:region-detail') + + class Meta: + model = Region + fields = ['id', 'url', 'name', 'slug'] + + +class NestedSiteSerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='dcim-api:site-detail') + + class Meta: + model = Site + fields = ['id', 'url', 'name', 'slug'] + + +# +# Racks +# + +class NestedRackGroupSerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rackgroup-detail') + + class Meta: + model = RackGroup + fields = ['id', 'url', 'name', 'slug'] + + +class NestedRackRoleSerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rackrole-detail') + + class Meta: + model = RackRole + fields = ['id', 'url', 'name', 'slug'] + + +class NestedRackSerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rack-detail') + + class Meta: + model = Rack + fields = ['id', 'url', 'name', 'display_name'] + + +# +# Device types +# + +class NestedManufacturerSerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='dcim-api:manufacturer-detail') + + class Meta: + model = Manufacturer + fields = ['id', 'url', 'name', 'slug'] + + +class NestedDeviceTypeSerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicetype-detail') + manufacturer = NestedManufacturerSerializer(read_only=True) + + class Meta: + model = DeviceType + fields = ['id', 'url', 'manufacturer', 'model', 'slug'] + + +class NestedRearPortTemplateSerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rearporttemplate-detail') + + class Meta: + model = RearPortTemplate + fields = ['id', 'url', 'name'] + + +class NestedFrontPortTemplateSerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='dcim-api:frontporttemplate-detail') + + class Meta: + model = FrontPortTemplate + fields = ['id', 'url', 'name'] + + +# +# Devices +# + +class NestedDeviceRoleSerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicerole-detail') + + class Meta: + model = DeviceRole + fields = ['id', 'url', 'name', 'slug'] + + +class NestedPlatformSerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='dcim-api:platform-detail') + + class Meta: + model = Platform + fields = ['id', 'url', 'name', 'slug'] + + +class NestedDeviceSerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='dcim-api:device-detail') + + class Meta: + model = Device + fields = ['id', 'url', 'name', 'display_name'] + + +class NestedConsoleServerPortSerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='dcim-api:consoleserverport-detail') + device = NestedDeviceSerializer(read_only=True) + + class Meta: + model = ConsoleServerPort + fields = ['id', 'url', 'device', 'name', 'cable'] + + +class NestedConsolePortSerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='dcim-api:consoleport-detail') + device = NestedDeviceSerializer(read_only=True) + + class Meta: + model = ConsolePort + fields = ['id', 'url', 'device', 'name', 'cable'] + + +class NestedPowerOutletSerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='dcim-api:poweroutlet-detail') + device = NestedDeviceSerializer(read_only=True) + + class Meta: + model = PowerOutlet + fields = ['id', 'url', 'device', 'name', 'cable'] + + +class NestedPowerPortSerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='dcim-api:powerport-detail') + device = NestedDeviceSerializer(read_only=True) + + class Meta: + model = PowerPort + fields = ['id', 'url', 'device', 'name', 'cable'] + + +class NestedInterfaceSerializer(WritableNestedSerializer): + device = NestedDeviceSerializer(read_only=True) + url = serializers.HyperlinkedIdentityField(view_name='dcim-api:interface-detail') + + class Meta: + model = Interface + fields = ['id', 'url', 'device', 'name', 'cable'] + + +class NestedRearPortSerializer(WritableNestedSerializer): + device = NestedDeviceSerializer(read_only=True) + url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rearport-detail') + + class Meta: + model = RearPort + fields = ['id', 'url', 'device', 'name', 'cable'] + + +class NestedFrontPortSerializer(WritableNestedSerializer): + device = NestedDeviceSerializer(read_only=True) + url = serializers.HyperlinkedIdentityField(view_name='dcim-api:frontport-detail') + + class Meta: + model = FrontPort + fields = ['id', 'url', 'device', 'name', 'cable'] + + +class NestedDeviceBaySerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rearport-detail') + device = NestedDeviceSerializer(read_only=True) + + class Meta: + model = DeviceBay + fields = ['id', 'url', 'device', 'name'] + + +# +# Cables +# + +class NestedCableSerializer(serializers.ModelSerializer): + url = serializers.HyperlinkedIdentityField(view_name='dcim-api:cable-detail') + + class Meta: + model = Cable + fields = ['id', 'url', 'label'] + + +# +# Virtual chassis +# + +class NestedVirtualChassisSerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='dcim-api:virtualchassis-detail') + master = NestedDeviceSerializer() + + class Meta: + model = VirtualChassis + fields = ['id', 'url', 'master'] diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index bc0995d49..23d870864 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -2,7 +2,6 @@ from rest_framework import serializers from rest_framework.validators import UniqueTogetherValidator from taggit_serializer.serializers import TaggitSerializer, TagListSerializerField -from circuits.models import Circuit, CircuitTermination from dcim.constants import * from dcim.models import ( Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, @@ -11,28 +10,22 @@ from dcim.models import ( RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site, VirtualChassis, ) from extras.api.customfields import CustomFieldModelSerializer -from ipam.models import IPAddress, VLAN -from tenancy.api.serializers import NestedTenantSerializer -from users.api.serializers import NestedUserSerializer +from ipam.api.nested_serializers import NestedIPAddressSerializer, NestedVLANSerializer +from ipam.models import VLAN +from tenancy.api.nested_serializers import NestedTenantSerializer +from users.api.nested_serializers import NestedUserSerializer from utilities.api import ( ChoiceField, ContentTypeField, SerializedPKRelatedField, TimeZoneField, ValidatedModelSerializer, WritableNestedSerializer, get_serializer_for_model, ) -from virtualization.models import Cluster +from virtualization.api.nested_serializers import NestedClusterSerializer +from .nested_serializers import * # -# Regions +# Regions/sites # -class NestedRegionSerializer(WritableNestedSerializer): - url = serializers.HyperlinkedIdentityField(view_name='dcim-api:region-detail') - - class Meta: - model = Region - fields = ['id', 'url', 'name', 'slug'] - - class RegionSerializer(serializers.ModelSerializer): parent = NestedRegionSerializer(required=False, allow_null=True) @@ -41,10 +34,6 @@ class RegionSerializer(serializers.ModelSerializer): fields = ['id', 'name', 'slug', 'parent'] -# -# Sites -# - class SiteSerializer(TaggitSerializer, CustomFieldModelSerializer): status = ChoiceField(choices=SITE_STATUS_CHOICES, required=False) region = NestedRegionSerializer(required=False, allow_null=True) @@ -62,16 +51,8 @@ class SiteSerializer(TaggitSerializer, CustomFieldModelSerializer): ] -class NestedSiteSerializer(WritableNestedSerializer): - url = serializers.HyperlinkedIdentityField(view_name='dcim-api:site-detail') - - class Meta: - model = Site - fields = ['id', 'url', 'name', 'slug'] - - # -# Rack groups +# Racks # class RackGroupSerializer(ValidatedModelSerializer): @@ -82,18 +63,6 @@ class RackGroupSerializer(ValidatedModelSerializer): fields = ['id', 'name', 'slug', 'site'] -class NestedRackGroupSerializer(WritableNestedSerializer): - url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rackgroup-detail') - - class Meta: - model = RackGroup - fields = ['id', 'url', 'name', 'slug'] - - -# -# Rack roles -# - class RackRoleSerializer(ValidatedModelSerializer): class Meta: @@ -101,18 +70,6 @@ class RackRoleSerializer(ValidatedModelSerializer): fields = ['id', 'name', 'slug', 'color'] -class NestedRackRoleSerializer(WritableNestedSerializer): - url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rackrole-detail') - - class Meta: - model = RackRole - fields = ['id', 'url', 'name', 'slug'] - - -# -# Racks -# - class RackSerializer(TaggitSerializer, CustomFieldModelSerializer): site = NestedSiteSerializer() group = NestedRackGroupSerializer(required=False, allow_null=True, default=None) @@ -151,26 +108,6 @@ class RackSerializer(TaggitSerializer, CustomFieldModelSerializer): return data -class NestedRackSerializer(WritableNestedSerializer): - url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rack-detail') - - class Meta: - model = Rack - fields = ['id', 'url', 'name', 'display_name'] - - -# -# Rack units -# - -class NestedDeviceSerializer(WritableNestedSerializer): - url = serializers.HyperlinkedIdentityField(view_name='dcim-api:device-detail') - - class Meta: - model = Device - fields = ['id', 'url', 'name', 'display_name'] - - class RackUnitSerializer(serializers.Serializer): """ A rack unit is an abstraction formed by the set (rack, position, face); it does not exist as a row in the database. @@ -181,10 +118,6 @@ class RackUnitSerializer(serializers.Serializer): device = NestedDeviceSerializer(read_only=True) -# -# Rack reservations -# - class RackReservationSerializer(ValidatedModelSerializer): rack = NestedRackSerializer() user = NestedUserSerializer() @@ -196,7 +129,7 @@ class RackReservationSerializer(ValidatedModelSerializer): # -# Manufacturers +# Device types # class ManufacturerSerializer(ValidatedModelSerializer): @@ -206,18 +139,6 @@ class ManufacturerSerializer(ValidatedModelSerializer): fields = ['id', 'name', 'slug'] -class NestedManufacturerSerializer(WritableNestedSerializer): - url = serializers.HyperlinkedIdentityField(view_name='dcim-api:manufacturer-detail') - - class Meta: - model = Manufacturer - fields = ['id', 'url', 'name', 'slug'] - - -# -# Device types -# - class DeviceTypeSerializer(TaggitSerializer, CustomFieldModelSerializer): manufacturer = NestedManufacturerSerializer() subdevice_role = ChoiceField(choices=SUBDEVICE_ROLE_CHOICES, required=False, allow_null=True) @@ -232,19 +153,6 @@ class DeviceTypeSerializer(TaggitSerializer, CustomFieldModelSerializer): ] -class NestedDeviceTypeSerializer(WritableNestedSerializer): - url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicetype-detail') - manufacturer = NestedManufacturerSerializer(read_only=True) - - class Meta: - model = DeviceType - fields = ['id', 'url', 'manufacturer', 'model', 'slug'] - - -# -# Console port templates -# - class ConsolePortTemplateSerializer(ValidatedModelSerializer): device_type = NestedDeviceTypeSerializer() @@ -253,10 +161,6 @@ class ConsolePortTemplateSerializer(ValidatedModelSerializer): fields = ['id', 'device_type', 'name'] -# -# Console server port templates -# - class ConsoleServerPortTemplateSerializer(ValidatedModelSerializer): device_type = NestedDeviceTypeSerializer() @@ -265,10 +169,6 @@ class ConsoleServerPortTemplateSerializer(ValidatedModelSerializer): fields = ['id', 'device_type', 'name'] -# -# Power port templates -# - class PowerPortTemplateSerializer(ValidatedModelSerializer): device_type = NestedDeviceTypeSerializer() @@ -277,10 +177,6 @@ class PowerPortTemplateSerializer(ValidatedModelSerializer): fields = ['id', 'device_type', 'name'] -# -# Power outlet templates -# - class PowerOutletTemplateSerializer(ValidatedModelSerializer): device_type = NestedDeviceTypeSerializer() @@ -289,10 +185,6 @@ class PowerOutletTemplateSerializer(ValidatedModelSerializer): fields = ['id', 'device_type', 'name'] -# -# Interface templates -# - class InterfaceTemplateSerializer(ValidatedModelSerializer): device_type = NestedDeviceTypeSerializer() form_factor = ChoiceField(choices=IFACE_FF_CHOICES, required=False) @@ -302,10 +194,6 @@ class InterfaceTemplateSerializer(ValidatedModelSerializer): fields = ['id', 'device_type', 'name', 'form_factor', 'mgmt_only'] -# -# Rear port templates -# - class RearPortTemplateSerializer(ValidatedModelSerializer): device_type = NestedDeviceTypeSerializer() type = ChoiceField(choices=PORT_TYPE_CHOICES) @@ -315,18 +203,6 @@ class RearPortTemplateSerializer(ValidatedModelSerializer): fields = ['id', 'device_type', 'name', 'type', 'positions'] -class NestedRearPortTemplateSerializer(WritableNestedSerializer): - url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rearporttemplate-detail') - - class Meta: - model = RearPortTemplate - fields = ['id', 'url', 'name'] - - -# -# Front port templates -# - class FrontPortTemplateSerializer(ValidatedModelSerializer): device_type = NestedDeviceTypeSerializer() type = ChoiceField(choices=PORT_TYPE_CHOICES) @@ -337,18 +213,6 @@ class FrontPortTemplateSerializer(ValidatedModelSerializer): fields = ['id', 'device_type', 'name', 'type', 'rear_port', 'rear_port_position'] -class NestedFrontPortTemplateSerializer(WritableNestedSerializer): - url = serializers.HyperlinkedIdentityField(view_name='dcim-api:frontporttemplate-detail') - - class Meta: - model = FrontPortTemplate - fields = ['id', 'url', 'name'] - - -# -# Device bay templates -# - class DeviceBayTemplateSerializer(ValidatedModelSerializer): device_type = NestedDeviceTypeSerializer() @@ -358,7 +222,7 @@ class DeviceBayTemplateSerializer(ValidatedModelSerializer): # -# Device roles +# Devices # class DeviceRoleSerializer(ValidatedModelSerializer): @@ -368,18 +232,6 @@ class DeviceRoleSerializer(ValidatedModelSerializer): fields = ['id', 'name', 'slug', 'color', 'vm_role'] -class NestedDeviceRoleSerializer(WritableNestedSerializer): - url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicerole-detail') - - class Meta: - model = DeviceRole - fields = ['id', 'url', 'name', 'slug'] - - -# -# Platforms -# - class PlatformSerializer(ValidatedModelSerializer): manufacturer = NestedManufacturerSerializer(required=False, allow_null=True) @@ -388,46 +240,6 @@ class PlatformSerializer(ValidatedModelSerializer): fields = ['id', 'name', 'slug', 'manufacturer', 'napalm_driver', 'napalm_args'] -class NestedPlatformSerializer(WritableNestedSerializer): - url = serializers.HyperlinkedIdentityField(view_name='dcim-api:platform-detail') - - class Meta: - model = Platform - fields = ['id', 'url', 'name', 'slug'] - - -# -# Devices -# - -# Cannot import ipam.api.NestedIPAddressSerializer due to circular dependency -class DeviceIPAddressSerializer(WritableNestedSerializer): - url = serializers.HyperlinkedIdentityField(view_name='ipam-api:ipaddress-detail') - - class Meta: - model = IPAddress - fields = ['id', 'url', 'family', 'address'] - - -# Cannot import virtualization.api.NestedClusterSerializer due to circular dependency -class NestedClusterSerializer(WritableNestedSerializer): - url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:cluster-detail') - - class Meta: - model = Cluster - fields = ['id', 'url', 'name'] - - -# Cannot import NestedVirtualChassisSerializer due to circular dependency -class DeviceVirtualChassisSerializer(WritableNestedSerializer): - url = serializers.HyperlinkedIdentityField(view_name='dcim-api:virtualchassis-detail') - master = NestedDeviceSerializer() - - class Meta: - model = VirtualChassis - fields = ['id', 'url', 'master'] - - class DeviceSerializer(TaggitSerializer, CustomFieldModelSerializer): device_type = NestedDeviceTypeSerializer() device_role = NestedDeviceRoleSerializer() @@ -437,12 +249,12 @@ class DeviceSerializer(TaggitSerializer, CustomFieldModelSerializer): rack = NestedRackSerializer(required=False, allow_null=True) face = ChoiceField(choices=RACK_FACE_CHOICES, required=False, allow_null=True) status = ChoiceField(choices=DEVICE_STATUS_CHOICES, required=False) - primary_ip = DeviceIPAddressSerializer(read_only=True) - primary_ip4 = DeviceIPAddressSerializer(required=False, allow_null=True) - primary_ip6 = DeviceIPAddressSerializer(required=False, allow_null=True) + primary_ip = NestedIPAddressSerializer(read_only=True) + primary_ip4 = NestedIPAddressSerializer(required=False, allow_null=True) + primary_ip6 = NestedIPAddressSerializer(required=False, allow_null=True) parent_device = serializers.SerializerMethodField() cluster = NestedClusterSerializer(required=False, allow_null=True) - virtual_chassis = DeviceVirtualChassisSerializer(required=False, allow_null=True) + virtual_chassis = NestedVirtualChassisSerializer(required=False, allow_null=True) tags = TagListSerializerField(required=False) class Meta: @@ -450,8 +262,8 @@ class DeviceSerializer(TaggitSerializer, CustomFieldModelSerializer): fields = [ 'id', 'name', 'display_name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag', 'site', 'rack', 'position', 'face', 'parent_device', 'status', 'primary_ip', 'primary_ip4', 'primary_ip6', - 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments', 'tags', 'custom_fields', 'created', - 'last_updated', 'local_context_data', + 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments', 'local_context_data', 'tags', + 'custom_fields', 'created', 'last_updated', ] validators = [] @@ -486,14 +298,178 @@ class DeviceWithConfigContextSerializer(DeviceSerializer): fields = [ 'id', 'name', 'display_name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag', 'site', 'rack', 'position', 'face', 'parent_device', 'status', 'primary_ip', 'primary_ip4', 'primary_ip6', - 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments', 'tags', 'custom_fields', - 'config_context', 'created', 'last_updated', 'local_context_data', + 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments', 'local_context_data', 'tags', + 'custom_fields', 'config_context', 'created', 'last_updated', ] def get_config_context(self, obj): return obj.get_config_context() +class ConsoleServerPortSerializer(TaggitSerializer, ValidatedModelSerializer): + device = NestedDeviceSerializer() + cable = NestedCableSerializer(read_only=True) + tags = TagListSerializerField(required=False) + + class Meta: + model = ConsoleServerPort + fields = ['id', 'device', 'name', 'connected_endpoint', 'cable', 'tags'] + read_only_fields = ['connected_endpoint'] + + +class ConsolePortSerializer(TaggitSerializer, ValidatedModelSerializer): + device = NestedDeviceSerializer() + connected_endpoint = NestedConsoleServerPortSerializer(read_only=True) + cable = NestedCableSerializer(read_only=True) + tags = TagListSerializerField(required=False) + + class Meta: + model = ConsolePort + fields = ['id', 'device', 'name', 'connected_endpoint', 'connection_status', 'cable', 'tags'] + + +class PowerOutletSerializer(TaggitSerializer, ValidatedModelSerializer): + device = NestedDeviceSerializer() + cable = NestedCableSerializer(read_only=True) + tags = TagListSerializerField(required=False) + + class Meta: + model = PowerOutlet + fields = ['id', 'device', 'name', 'connected_endpoint', 'cable', 'tags'] + read_only_fields = ['connected_endpoint'] + + +class PowerPortSerializer(TaggitSerializer, ValidatedModelSerializer): + device = NestedDeviceSerializer() + connected_endpoint = NestedPowerOutletSerializer(read_only=True) + cable = NestedCableSerializer(read_only=True) + tags = TagListSerializerField(required=False) + + class Meta: + model = PowerPort + fields = ['id', 'device', 'name', 'connected_endpoint', 'connection_status', 'cable', 'tags'] + + +class InterfaceSerializer(TaggitSerializer, ValidatedModelSerializer): + device = NestedDeviceSerializer() + form_factor = ChoiceField(choices=IFACE_FF_CHOICES, required=False) + lag = NestedInterfaceSerializer(required=False, allow_null=True) + connected_endpoint = serializers.SerializerMethodField(read_only=True) + mode = ChoiceField(choices=IFACE_MODE_CHOICES, required=False, allow_null=True) + untagged_vlan = NestedVLANSerializer(required=False, allow_null=True) + tagged_vlans = SerializedPKRelatedField( + queryset=VLAN.objects.all(), + serializer=NestedVLANSerializer, + required=False, + many=True + ) + cable = NestedCableSerializer(read_only=True) + tags = TagListSerializerField(required=False) + + class Meta: + model = Interface + fields = [ + 'id', 'device', 'name', 'form_factor', 'enabled', 'lag', 'mtu', 'mac_address', 'mgmt_only', 'description', + 'connected_endpoint', 'cable', 'mode', 'untagged_vlan', 'tagged_vlans', 'tags', 'count_ipaddresses', + ] + + # TODO: This validation should be handled by Interface.clean() + def validate(self, data): + + # All associated VLANs be global or assigned to the parent device's site. + device = self.instance.device if self.instance else data.get('device') + untagged_vlan = data.get('untagged_vlan') + if untagged_vlan and untagged_vlan.site not in [device.site, None]: + raise serializers.ValidationError({ + 'untagged_vlan': "VLAN {} must belong to the same site as the interface's parent device, or it must be " + "global.".format(untagged_vlan) + }) + for vlan in data.get('tagged_vlans', []): + if vlan.site not in [device.site, None]: + raise serializers.ValidationError({ + 'tagged_vlans': "VLAN {} must belong to the same site as the interface's parent device, or it must " + "be global.".format(vlan) + }) + + return super(InterfaceSerializer, self).validate(data) + + def get_connected_endpoint(self, obj): + """ + Return the appropriate serializer for the type of connected object. + """ + if obj.connected_endpoint is None: + return None + + serializer = get_serializer_for_model(obj.connected_endpoint, prefix='Nested') + context = {'request': self.context['request']} + data = serializer(obj.connected_endpoint, context=context).data + + return data + + +class RearPortSerializer(ValidatedModelSerializer): + device = NestedDeviceSerializer() + type = ChoiceField(choices=PORT_TYPE_CHOICES) + cable = NestedCableSerializer(read_only=True) + tags = TagListSerializerField(required=False) + + class Meta: + model = RearPort + fields = ['id', 'device', 'name', 'type', 'positions', 'cable', 'tags'] + + +class FrontPortRearPortSerializer(WritableNestedSerializer): + """ + NestedRearPortSerializer but with parent device omitted (since front and rear ports must belong to same device) + """ + url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rearport-detail') + + class Meta: + model = RearPort + fields = ['id', 'url', 'name'] + + +class FrontPortSerializer(ValidatedModelSerializer): + device = NestedDeviceSerializer() + type = ChoiceField(choices=PORT_TYPE_CHOICES) + rear_port = FrontPortRearPortSerializer() + cable = NestedCableSerializer(read_only=True) + tags = TagListSerializerField(required=False) + + class Meta: + model = FrontPort + fields = ['id', 'device', 'name', 'type', 'rear_port', 'rear_port_position', 'cable', 'tags'] + + +class DeviceBaySerializer(TaggitSerializer, ValidatedModelSerializer): + device = NestedDeviceSerializer() + installed_device = NestedDeviceSerializer(required=False, allow_null=True) + tags = TagListSerializerField(required=False) + + class Meta: + model = DeviceBay + fields = ['id', 'device', 'name', 'installed_device', 'tags'] + + +# +# Inventory items +# + +class InventoryItemSerializer(TaggitSerializer, ValidatedModelSerializer): + device = NestedDeviceSerializer() + # Provide a default value to satisfy UniqueTogetherValidator + parent = serializers.PrimaryKeyRelatedField(queryset=InventoryItem.objects.all(), allow_null=True, default=None) + manufacturer = NestedManufacturerSerializer(required=False, allow_null=True, default=None) + tags = TagListSerializerField(required=False) + + class Meta: + model = InventoryItem + fields = [ + 'id', 'device', 'parent', 'name', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered', + 'description', 'tags', + ] + + # # Cables # @@ -548,305 +524,6 @@ class TracedCableSerializer(serializers.ModelSerializer): ] -class NestedCableSerializer(serializers.ModelSerializer): - url = serializers.HyperlinkedIdentityField(view_name='dcim-api:cable-detail') - - class Meta: - model = Cable - fields = ['id', 'url', 'label'] - - -# -# Console server ports -# - -class ConsoleServerPortSerializer(TaggitSerializer, ValidatedModelSerializer): - device = NestedDeviceSerializer() - cable = NestedCableSerializer(read_only=True) - tags = TagListSerializerField(required=False) - - class Meta: - model = ConsoleServerPort - fields = ['id', 'device', 'name', 'connected_endpoint', 'cable', 'tags'] - read_only_fields = ['connected_endpoint'] - - -class NestedConsoleServerPortSerializer(WritableNestedSerializer): - url = serializers.HyperlinkedIdentityField(view_name='dcim-api:consoleserverport-detail') - device = NestedDeviceSerializer(read_only=True) - - class Meta: - model = ConsoleServerPort - fields = ['id', 'url', 'device', 'name', 'cable'] - - -# -# Console ports -# - -class ConsolePortSerializer(TaggitSerializer, ValidatedModelSerializer): - device = NestedDeviceSerializer() - connected_endpoint = NestedConsoleServerPortSerializer(read_only=True) - cable = NestedCableSerializer(read_only=True) - tags = TagListSerializerField(required=False) - - class Meta: - model = ConsolePort - fields = ['id', 'device', 'name', 'connected_endpoint', 'connection_status', 'cable', 'tags'] - - -class NestedConsolePortSerializer(TaggitSerializer, ValidatedModelSerializer): - url = serializers.HyperlinkedIdentityField(view_name='dcim-api:consoleport-detail') - device = NestedDeviceSerializer(read_only=True) - - class Meta: - model = ConsolePort - fields = ['id', 'url', 'device', 'name', 'cable'] - - -# -# Power outlets -# - -class PowerOutletSerializer(TaggitSerializer, ValidatedModelSerializer): - device = NestedDeviceSerializer() - cable = NestedCableSerializer(read_only=True) - tags = TagListSerializerField(required=False) - - class Meta: - model = PowerOutlet - fields = ['id', 'device', 'name', 'connected_endpoint', 'cable', 'tags'] - read_only_fields = ['connected_endpoint'] - - -class NestedPowerOutletSerializer(WritableNestedSerializer): - url = serializers.HyperlinkedIdentityField(view_name='dcim-api:poweroutlet-detail') - device = NestedDeviceSerializer(read_only=True) - - class Meta: - model = PowerOutlet - fields = ['id', 'url', 'device', 'name', 'cable'] - - -# -# Power ports -# - -class PowerPortSerializer(TaggitSerializer, ValidatedModelSerializer): - device = NestedDeviceSerializer() - connected_endpoint = NestedPowerOutletSerializer(read_only=True) - cable = NestedCableSerializer(read_only=True) - tags = TagListSerializerField(required=False) - - class Meta: - model = PowerPort - fields = ['id', 'device', 'name', 'connected_endpoint', 'connection_status', 'cable', 'tags'] - - -class NestedPowerPortSerializer(TaggitSerializer, ValidatedModelSerializer): - url = serializers.HyperlinkedIdentityField(view_name='dcim-api:powerport-detail') - device = NestedDeviceSerializer(read_only=True) - - class Meta: - model = PowerPort - fields = ['id', 'url', 'device', 'name', 'cable'] - - -# -# Interfaces -# - -class NestedInterfaceSerializer(WritableNestedSerializer): - device = NestedDeviceSerializer(read_only=True) - url = serializers.HyperlinkedIdentityField(view_name='dcim-api:interface-detail') - - class Meta: - model = Interface - fields = ['id', 'url', 'device', 'name', 'cable'] - - -class InterfaceNestedCircuitSerializer(serializers.ModelSerializer): - url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuit-detail') - - class Meta: - model = Circuit - fields = ['id', 'url', 'cid'] - - -class InterfaceCircuitTerminationSerializer(WritableNestedSerializer): - circuit = InterfaceNestedCircuitSerializer(read_only=True) - - class Meta: - model = CircuitTermination - fields = [ - 'id', 'circuit', 'term_side', 'port_speed', 'upstream_speed', 'xconnect_id', 'pp_info', - ] - - -# Cannot import ipam.api.NestedVLANSerializer due to circular dependency -class InterfaceVLANSerializer(WritableNestedSerializer): - url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vlan-detail') - - class Meta: - model = VLAN - fields = ['id', 'url', 'vid', 'name', 'display_name'] - - -class InterfaceSerializer(TaggitSerializer, ValidatedModelSerializer): - device = NestedDeviceSerializer() - form_factor = ChoiceField(choices=IFACE_FF_CHOICES, required=False) - lag = NestedInterfaceSerializer(required=False, allow_null=True) - connected_endpoint = serializers.SerializerMethodField(read_only=True) - mode = ChoiceField(choices=IFACE_MODE_CHOICES, required=False, allow_null=True) - untagged_vlan = InterfaceVLANSerializer(required=False, allow_null=True) - tagged_vlans = SerializedPKRelatedField( - queryset=VLAN.objects.all(), - serializer=InterfaceVLANSerializer, - required=False, - many=True - ) - cable = NestedCableSerializer(read_only=True) - tags = TagListSerializerField(required=False) - - class Meta: - model = Interface - fields = [ - 'id', 'device', 'name', 'form_factor', 'enabled', 'lag', 'mtu', 'mac_address', 'mgmt_only', 'description', - 'connected_endpoint', 'cable', 'mode', 'untagged_vlan', 'tagged_vlans', 'tags', 'count_ipaddresses', - ] - - def validate(self, data): - - # All associated VLANs be global or assigned to the parent device's site. - device = self.instance.device if self.instance else data.get('device') - untagged_vlan = data.get('untagged_vlan') - if untagged_vlan and untagged_vlan.site not in [device.site, None]: - raise serializers.ValidationError({ - 'untagged_vlan': "VLAN {} must belong to the same site as the interface's parent device, or it must be " - "global.".format(untagged_vlan) - }) - for vlan in data.get('tagged_vlans', []): - if vlan.site not in [device.site, None]: - raise serializers.ValidationError({ - 'tagged_vlans': "VLAN {} must belong to the same site as the interface's parent device, or it must " - "be global.".format(vlan) - }) - - return super(InterfaceSerializer, self).validate(data) - - def get_connected_endpoint(self, obj): - """ - Return the appropriate serializer for the type of connected object. - """ - if obj.connected_endpoint is None: - return None - - serializer = get_serializer_for_model(obj.connected_endpoint, prefix='Nested') - context = {'request': self.context['request']} - data = serializer(obj.connected_endpoint, context=context).data - - return data - - -# -# Rear ports -# - -class RearPortSerializer(ValidatedModelSerializer): - device = NestedDeviceSerializer() - type = ChoiceField(choices=PORT_TYPE_CHOICES) - cable = NestedCableSerializer(read_only=True) - tags = TagListSerializerField(required=False) - - class Meta: - model = RearPort - fields = ['id', 'device', 'name', 'type', 'positions', 'cable', 'tags'] - - -class NestedRearPortSerializer(WritableNestedSerializer): - device = NestedDeviceSerializer(read_only=True) - url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rearport-detail') - - class Meta: - model = RearPort - fields = ['id', 'url', 'device', 'name', 'cable'] - - -# -# Front ports -# - -class FrontPortRearPortSerializer(WritableNestedSerializer): - url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rearport-detail') - - class Meta: - model = RearPort - fields = ['id', 'url', 'name'] - - -class FrontPortSerializer(ValidatedModelSerializer): - device = NestedDeviceSerializer() - type = ChoiceField(choices=PORT_TYPE_CHOICES) - rear_port = FrontPortRearPortSerializer() - cable = NestedCableSerializer(read_only=True) - tags = TagListSerializerField(required=False) - - class Meta: - model = FrontPort - fields = ['id', 'device', 'name', 'type', 'rear_port', 'rear_port_position', 'cable', 'tags'] - - -class NestedFrontPortSerializer(WritableNestedSerializer): - device = NestedDeviceSerializer(read_only=True) - url = serializers.HyperlinkedIdentityField(view_name='dcim-api:frontport-detail') - - class Meta: - model = FrontPort - fields = ['id', 'url', 'device', 'name', 'cable'] - - -# -# Device bays -# - -class DeviceBaySerializer(TaggitSerializer, ValidatedModelSerializer): - device = NestedDeviceSerializer() - installed_device = NestedDeviceSerializer(required=False, allow_null=True) - tags = TagListSerializerField(required=False) - - class Meta: - model = DeviceBay - fields = ['id', 'device', 'name', 'installed_device', 'tags'] - - -class NestedDeviceBaySerializer(WritableNestedSerializer): - url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rearport-detail') - device = NestedDeviceSerializer(read_only=True) - - class Meta: - model = DeviceBay - fields = ['id', 'url', 'device', 'name'] - - -# -# Inventory items -# - -class InventoryItemSerializer(TaggitSerializer, ValidatedModelSerializer): - device = NestedDeviceSerializer() - # Provide a default value to satisfy UniqueTogetherValidator - parent = serializers.PrimaryKeyRelatedField(queryset=InventoryItem.objects.all(), allow_null=True, default=None) - manufacturer = NestedManufacturerSerializer(required=False, allow_null=True, default=None) - tags = TagListSerializerField(required=False) - - class Meta: - model = InventoryItem - fields = [ - 'id', 'device', 'parent', 'name', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered', - 'description', 'tags', - ] - - # # Interface connections # @@ -876,11 +553,3 @@ class VirtualChassisSerializer(TaggitSerializer, ValidatedModelSerializer): class Meta: model = VirtualChassis fields = ['id', 'master', 'domain', 'tags'] - - -class NestedVirtualChassisSerializer(WritableNestedSerializer): - url = serializers.HyperlinkedIdentityField(view_name='dcim-api:virtualchassis-detail') - - class Meta: - model = VirtualChassis - fields = ['id', 'url'] diff --git a/netbox/dcim/tests/test_api.py b/netbox/dcim/tests/test_api.py index 5c5c413e0..a40886f21 100644 --- a/netbox/dcim/tests/test_api.py +++ b/netbox/dcim/tests/test_api.py @@ -3433,7 +3433,7 @@ class VirtualChassisTest(APITestCase): self.assertEqual( sorted(response.data['results'][0]), - ['id', 'url'] + ['id', 'master', 'url'] ) def test_create_virtualchassis(self): diff --git a/netbox/extras/api/nested_serializers.py b/netbox/extras/api/nested_serializers.py new file mode 100644 index 000000000..11367aba9 --- /dev/null +++ b/netbox/extras/api/nested_serializers.py @@ -0,0 +1,23 @@ +from rest_framework import serializers + +from extras.models import ReportResult + +__all__ = [ + 'NestedReportResultSerializer', +] + + +# +# Reports +# + +class NestedReportResultSerializer(serializers.ModelSerializer): + url = serializers.HyperlinkedIdentityField( + view_name='extras-api:report-detail', + lookup_field='report', + lookup_url_kwarg='pk' + ) + + class Meta: + model = ReportResult + fields = ['url', 'created', 'user', 'failed'] diff --git a/netbox/extras/api/serializers.py b/netbox/extras/api/serializers.py index c62fab635..db468ceba 100644 --- a/netbox/extras/api/serializers.py +++ b/netbox/extras/api/serializers.py @@ -2,7 +2,7 @@ from django.core.exceptions import ObjectDoesNotExist from rest_framework import serializers from taggit.models import Tag -from dcim.api.serializers import ( +from dcim.api.nested_serializers import ( NestedDeviceSerializer, NestedDeviceRoleSerializer, NestedPlatformSerializer, NestedRackSerializer, NestedRegionSerializer, NestedSiteSerializer, ) @@ -11,12 +11,13 @@ from extras.constants import * from extras.models import ( ConfigContext, ExportTemplate, Graph, ImageAttachment, ObjectChange, ReportResult, TopologyMap, ) -from tenancy.api.serializers import NestedTenantSerializer, NestedTenantGroupSerializer +from tenancy.api.nested_serializers import NestedTenantSerializer, NestedTenantGroupSerializer from tenancy.models import Tenant, TenantGroup -from users.api.serializers import NestedUserSerializer +from users.api.nested_serializers import NestedUserSerializer from utilities.api import ( ChoiceField, ContentTypeField, get_serializer_for_model, SerializedPKRelatedField, ValidatedModelSerializer, ) +from .nested_serializers import * # @@ -187,18 +188,6 @@ class ReportResultSerializer(serializers.ModelSerializer): fields = ['created', 'user', 'failed', 'data'] -class NestedReportResultSerializer(serializers.ModelSerializer): - url = serializers.HyperlinkedIdentityField( - view_name='extras-api:report-detail', - lookup_field='report', - lookup_url_kwarg='pk' - ) - - class Meta: - model = ReportResult - fields = ['url', 'created', 'user', 'failed'] - - class ReportSerializer(serializers.Serializer): module = serializers.CharField(max_length=255) name = serializers.CharField(max_length=255) diff --git a/netbox/ipam/api/nested_serializers.py b/netbox/ipam/api/nested_serializers.py new file mode 100644 index 000000000..2ffaa0ae2 --- /dev/null +++ b/netbox/ipam/api/nested_serializers.py @@ -0,0 +1,100 @@ +from rest_framework import serializers + +from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, VLAN, VLANGroup, VRF +from utilities.api import WritableNestedSerializer + +__all__ = [ + 'NestedAggregateSerializer', + 'NestedIPAddressSerializer', + 'NestedPrefixSerializer', + 'NestedRIRSerializer', + 'NestedRoleSerializer', + 'NestedVLANGroupSerializer', + 'NestedVLANSerializer', + 'NestedVRFSerializer', +] + + +# +# VRFs +# + +class NestedVRFSerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vrf-detail') + + class Meta: + model = VRF + fields = ['id', 'url', 'name', 'rd'] + + +# +# RIRs/aggregates +# + +class NestedRIRSerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='ipam-api:rir-detail') + + class Meta: + model = RIR + fields = ['id', 'url', 'name', 'slug'] + + +class NestedAggregateSerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='ipam-api:aggregate-detail') + + class Meta: + model = Aggregate + fields = ['id', 'url', 'family', 'prefix'] + + +# +# VLANs +# + +class NestedRoleSerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='ipam-api:role-detail') + + class Meta: + model = Role + fields = ['id', 'url', 'name', 'slug'] + + +class NestedVLANGroupSerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vlangroup-detail') + + class Meta: + model = VLANGroup + fields = ['id', 'url', 'name', 'slug'] + + +class NestedVLANSerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vlan-detail') + + class Meta: + model = VLAN + fields = ['id', 'url', 'vid', 'name', 'display_name'] + + +# +# Prefixes +# + +class NestedPrefixSerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='ipam-api:prefix-detail') + + class Meta: + model = Prefix + fields = ['id', 'url', 'family', 'prefix'] + + +# +# IP addresses +# + + +class NestedIPAddressSerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='ipam-api:ipaddress-detail') + + class Meta: + model = IPAddress + fields = ['id', 'url', 'family', 'address'] diff --git a/netbox/ipam/api/serializers.py b/netbox/ipam/api/serializers.py index 0581d1e6d..ec47d5d7f 100644 --- a/netbox/ipam/api/serializers.py +++ b/netbox/ipam/api/serializers.py @@ -5,18 +5,17 @@ from rest_framework.reverse import reverse from rest_framework.validators import UniqueTogetherValidator from taggit_serializer.serializers import TaggitSerializer, TagListSerializerField -from dcim.api.serializers import NestedDeviceSerializer, InterfaceSerializer, NestedSiteSerializer +from dcim.api.nested_serializers import NestedDeviceSerializer, NestedSiteSerializer from dcim.models import Interface from extras.api.customfields import CustomFieldModelSerializer -from ipam.constants import ( - IPADDRESS_ROLE_CHOICES, IPADDRESS_STATUS_CHOICES, IP_PROTOCOL_CHOICES, PREFIX_STATUS_CHOICES, VLAN_STATUS_CHOICES, -) +from ipam.constants import * from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF -from tenancy.api.serializers import NestedTenantSerializer +from tenancy.api.nested_serializers import NestedTenantSerializer from utilities.api import ( ChoiceField, SerializedPKRelatedField, ValidatedModelSerializer, WritableNestedSerializer, ) -from virtualization.api.serializers import NestedVirtualMachineSerializer +from virtualization.api.nested_serializers import NestedVirtualMachineSerializer +from .nested_serializers import * # @@ -35,35 +34,8 @@ class VRFSerializer(TaggitSerializer, CustomFieldModelSerializer): ] -class NestedVRFSerializer(WritableNestedSerializer): - url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vrf-detail') - - class Meta: - model = VRF - fields = ['id', 'url', 'name', 'rd'] - - # -# Roles -# - -class RoleSerializer(ValidatedModelSerializer): - - class Meta: - model = Role - fields = ['id', 'name', 'slug', 'weight'] - - -class NestedRoleSerializer(WritableNestedSerializer): - url = serializers.HyperlinkedIdentityField(view_name='ipam-api:role-detail') - - class Meta: - model = Role - fields = ['id', 'url', 'name', 'slug'] - - -# -# RIRs +# RIRs/aggregates # class RIRSerializer(ValidatedModelSerializer): @@ -73,18 +45,6 @@ class RIRSerializer(ValidatedModelSerializer): fields = ['id', 'name', 'slug', 'is_private'] -class NestedRIRSerializer(WritableNestedSerializer): - url = serializers.HyperlinkedIdentityField(view_name='ipam-api:rir-detail') - - class Meta: - model = RIR - fields = ['id', 'url', 'name', 'slug'] - - -# -# Aggregates -# - class AggregateSerializer(TaggitSerializer, CustomFieldModelSerializer): rir = NestedRIRSerializer() tags = TagListSerializerField(required=False) @@ -98,18 +58,17 @@ class AggregateSerializer(TaggitSerializer, CustomFieldModelSerializer): read_only_fields = ['family'] -class NestedAggregateSerializer(WritableNestedSerializer): - url = serializers.HyperlinkedIdentityField(view_name='ipam-api:aggregate-detail') - - class Meta(AggregateSerializer.Meta): - model = Aggregate - fields = ['id', 'url', 'family', 'prefix'] - - # -# VLAN groups +# VLANs # +class RoleSerializer(ValidatedModelSerializer): + + class Meta: + model = Role + fields = ['id', 'name', 'slug', 'weight'] + + class VLANGroupSerializer(ValidatedModelSerializer): site = NestedSiteSerializer(required=False, allow_null=True) @@ -133,18 +92,6 @@ class VLANGroupSerializer(ValidatedModelSerializer): return data -class NestedVLANGroupSerializer(WritableNestedSerializer): - url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vlangroup-detail') - - class Meta: - model = VLANGroup - fields = ['id', 'url', 'name', 'slug'] - - -# -# VLANs -# - class VLANSerializer(TaggitSerializer, CustomFieldModelSerializer): site = NestedSiteSerializer(required=False, allow_null=True) group = NestedVLANGroupSerializer(required=False, allow_null=True) @@ -176,14 +123,6 @@ class VLANSerializer(TaggitSerializer, CustomFieldModelSerializer): return data -class NestedVLANSerializer(WritableNestedSerializer): - url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vlan-detail') - - class Meta: - model = VLAN - fields = ['id', 'url', 'vid', 'name', 'display_name'] - - # # Prefixes # @@ -206,16 +145,10 @@ class PrefixSerializer(TaggitSerializer, CustomFieldModelSerializer): read_only_fields = ['family'] -class NestedPrefixSerializer(WritableNestedSerializer): - url = serializers.HyperlinkedIdentityField(view_name='ipam-api:prefix-detail') - - class Meta: - model = Prefix - fields = ['id', 'url', 'family', 'prefix'] - - class AvailablePrefixSerializer(serializers.Serializer): - + """ + Representation of a prefix which does not exist in the database. + """ def to_representation(self, instance): if self.context.get('vrf'): vrf = NestedVRFSerializer(self.context['vrf'], context={'request': self.context['request']}).data @@ -233,11 +166,14 @@ class AvailablePrefixSerializer(serializers.Serializer): # class IPAddressInterfaceSerializer(WritableNestedSerializer): + """ + Nested representation of an Interface which may belong to a Device *or* a VirtualMachine. + """ url = serializers.SerializerMethodField() # We're imitating a HyperlinkedIdentityField here device = NestedDeviceSerializer(read_only=True) virtual_machine = NestedVirtualMachineSerializer(read_only=True) - class Meta(InterfaceSerializer.Meta): + class Meta: model = Interface fields = [ 'id', 'url', 'device', 'virtual_machine', 'name', @@ -258,6 +194,8 @@ class IPAddressSerializer(TaggitSerializer, CustomFieldModelSerializer): status = ChoiceField(choices=IPADDRESS_STATUS_CHOICES, required=False) role = ChoiceField(choices=IPADDRESS_ROLE_CHOICES, required=False, allow_null=True) interface = IPAddressInterfaceSerializer(required=False, allow_null=True) + nat_inside = NestedIPAddressSerializer(required=False, allow_null=True) + nat_outside = NestedIPAddressSerializer(read_only=True) tags = TagListSerializerField(required=False) class Meta: @@ -269,20 +207,10 @@ class IPAddressSerializer(TaggitSerializer, CustomFieldModelSerializer): read_only_fields = ['family'] -class NestedIPAddressSerializer(WritableNestedSerializer): - url = serializers.HyperlinkedIdentityField(view_name='ipam-api:ipaddress-detail') - - class Meta: - model = IPAddress - fields = ['id', 'url', 'family', 'address'] - - -IPAddressSerializer._declared_fields['nat_inside'] = NestedIPAddressSerializer(required=False, allow_null=True) -IPAddressSerializer._declared_fields['nat_outside'] = NestedIPAddressSerializer(read_only=True) - - class AvailableIPSerializer(serializers.Serializer): - + """ + Representation of an IP address which does not exist in the database. + """ def to_representation(self, instance): if self.context.get('vrf'): vrf = NestedVRFSerializer(self.context['vrf'], context={'request': self.context['request']}).data diff --git a/netbox/secrets/api/nested_serializers.py b/netbox/secrets/api/nested_serializers.py new file mode 100644 index 000000000..819546c63 --- /dev/null +++ b/netbox/secrets/api/nested_serializers.py @@ -0,0 +1,16 @@ +from rest_framework import serializers + +from secrets.models import SecretRole +from utilities.api import WritableNestedSerializer + +__all__ = [ + 'NestedSecretRoleSerializer' +] + + +class NestedSecretRoleSerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='secrets-api:secretrole-detail') + + class Meta: + model = SecretRole + fields = ['id', 'url', 'name', 'slug'] diff --git a/netbox/secrets/api/serializers.py b/netbox/secrets/api/serializers.py index fc1a60b03..4d50a0a0b 100644 --- a/netbox/secrets/api/serializers.py +++ b/netbox/secrets/api/serializers.py @@ -2,14 +2,15 @@ from rest_framework import serializers from rest_framework.validators import UniqueTogetherValidator from taggit_serializer.serializers import TaggitSerializer, TagListSerializerField -from dcim.api.serializers import NestedDeviceSerializer +from dcim.api.nested_serializers import NestedDeviceSerializer from extras.api.customfields import CustomFieldModelSerializer from secrets.models import Secret, SecretRole -from utilities.api import ValidatedModelSerializer, WritableNestedSerializer +from utilities.api import ValidatedModelSerializer +from .nested_serializers import * # -# SecretRoles +# Secrets # class SecretRoleSerializer(ValidatedModelSerializer): @@ -19,18 +20,6 @@ class SecretRoleSerializer(ValidatedModelSerializer): fields = ['id', 'name', 'slug'] -class NestedSecretRoleSerializer(WritableNestedSerializer): - url = serializers.HyperlinkedIdentityField(view_name='secrets-api:secretrole-detail') - - class Meta: - model = SecretRole - fields = ['id', 'url', 'name', 'slug'] - - -# -# Secrets -# - class SecretSerializer(TaggitSerializer, CustomFieldModelSerializer): device = NestedDeviceSerializer() role = NestedSecretRoleSerializer() diff --git a/netbox/tenancy/api/nested_serializers.py b/netbox/tenancy/api/nested_serializers.py new file mode 100644 index 000000000..d26ac4675 --- /dev/null +++ b/netbox/tenancy/api/nested_serializers.py @@ -0,0 +1,29 @@ +from rest_framework import serializers + +from tenancy.models import Tenant, TenantGroup +from utilities.api import WritableNestedSerializer + +__all__ = [ + 'NestedTenantGroupSerializer', + 'NestedTenantSerializer', +] + + +# +# Tenants +# + +class NestedTenantGroupSerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='tenancy-api:tenantgroup-detail') + + class Meta: + model = TenantGroup + fields = ['id', 'url', 'name', 'slug'] + + +class NestedTenantSerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='tenancy-api:tenant-detail') + + class Meta: + model = Tenant + fields = ['id', 'url', 'name', 'slug'] diff --git a/netbox/tenancy/api/serializers.py b/netbox/tenancy/api/serializers.py index 08492c55d..80f3b948d 100644 --- a/netbox/tenancy/api/serializers.py +++ b/netbox/tenancy/api/serializers.py @@ -1,13 +1,13 @@ -from rest_framework import serializers from taggit_serializer.serializers import TaggitSerializer, TagListSerializerField from extras.api.customfields import CustomFieldModelSerializer from tenancy.models import Tenant, TenantGroup -from utilities.api import ValidatedModelSerializer, WritableNestedSerializer +from utilities.api import ValidatedModelSerializer +from .nested_serializers import * # -# Tenant groups +# Tenants # class TenantGroupSerializer(ValidatedModelSerializer): @@ -17,18 +17,6 @@ class TenantGroupSerializer(ValidatedModelSerializer): fields = ['id', 'name', 'slug'] -class NestedTenantGroupSerializer(WritableNestedSerializer): - url = serializers.HyperlinkedIdentityField(view_name='tenancy-api:tenantgroup-detail') - - class Meta: - model = TenantGroup - fields = ['id', 'url', 'name', 'slug'] - - -# -# Tenants -# - class TenantSerializer(TaggitSerializer, CustomFieldModelSerializer): group = NestedTenantGroupSerializer(required=False) tags = TagListSerializerField(required=False) @@ -39,11 +27,3 @@ class TenantSerializer(TaggitSerializer, CustomFieldModelSerializer): 'id', 'name', 'slug', 'group', 'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', ] - - -class NestedTenantSerializer(WritableNestedSerializer): - url = serializers.HyperlinkedIdentityField(view_name='tenancy-api:tenant-detail') - - class Meta: - model = Tenant - fields = ['id', 'url', 'name', 'slug'] diff --git a/netbox/users/api/nested_serializers.py b/netbox/users/api/nested_serializers.py new file mode 100644 index 000000000..d1b649713 --- /dev/null +++ b/netbox/users/api/nested_serializers.py @@ -0,0 +1,18 @@ +from django.contrib.auth.models import User + +from utilities.api import WritableNestedSerializer + +_all_ = [ + 'NestedUserSerializer', +] + + +# +# Users +# + +class NestedUserSerializer(WritableNestedSerializer): + + class Meta: + model = User + fields = ['id', 'username'] diff --git a/netbox/users/api/serializers.py b/netbox/users/api/serializers.py index d97ba7ed1..86d350e69 100644 --- a/netbox/users/api/serializers.py +++ b/netbox/users/api/serializers.py @@ -1,10 +1,4 @@ -from django.contrib.auth.models import User - -from utilities.api import WritableNestedSerializer +from .nested_serializers import * -class NestedUserSerializer(WritableNestedSerializer): - - class Meta: - model = User - fields = ['id', 'username'] +# Placeholder for future serializers diff --git a/netbox/virtualization/api/nested_serializers.py b/netbox/virtualization/api/nested_serializers.py new file mode 100644 index 000000000..fb6e2b0be --- /dev/null +++ b/netbox/virtualization/api/nested_serializers.py @@ -0,0 +1,62 @@ +from rest_framework import serializers + +from dcim.models import Interface +from utilities.api import WritableNestedSerializer +from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine + +__all__ = [ + 'NestedClusterGroupSerializer', + 'NestedClusterSerializer', + 'NestedClusterTypeSerializer', + 'NestedInterfaceSerializer', + 'NestedVirtualMachineSerializer', +] + +# +# Clusters +# + + +class NestedClusterTypeSerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:clustertype-detail') + + class Meta: + model = ClusterType + fields = ['id', 'url', 'name', 'slug'] + + +class NestedClusterGroupSerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:clustergroup-detail') + + class Meta: + model = ClusterGroup + fields = ['id', 'url', 'name', 'slug'] + + +class NestedClusterSerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:cluster-detail') + + class Meta: + model = Cluster + fields = ['id', 'url', 'name'] + + +# +# Virtual machines +# + +class NestedVirtualMachineSerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:virtualmachine-detail') + + class Meta: + model = VirtualMachine + fields = ['id', 'url', 'name'] + + +class NestedInterfaceSerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:interface-detail') + virtual_machine = NestedVirtualMachineSerializer(read_only=True) + + class Meta: + model = Interface + fields = ['id', 'url', 'virtual_machine', 'name'] diff --git a/netbox/virtualization/api/serializers.py b/netbox/virtualization/api/serializers.py index 0c1bf927c..1f2b957fc 100644 --- a/netbox/virtualization/api/serializers.py +++ b/netbox/virtualization/api/serializers.py @@ -1,19 +1,21 @@ from rest_framework import serializers from taggit_serializer.serializers import TaggitSerializer, TagListSerializerField -from dcim.api.serializers import NestedDeviceRoleSerializer, NestedPlatformSerializer, NestedSiteSerializer +from dcim.api.nested_serializers import NestedDeviceRoleSerializer, NestedPlatformSerializer, NestedSiteSerializer from dcim.constants import IFACE_FF_CHOICES, IFACE_FF_VIRTUAL, IFACE_MODE_CHOICES from dcim.models import Interface from extras.api.customfields import CustomFieldModelSerializer -from ipam.models import IPAddress, VLAN -from tenancy.api.serializers import NestedTenantSerializer -from utilities.api import ChoiceField, SerializedPKRelatedField, ValidatedModelSerializer, WritableNestedSerializer +from ipam.api.nested_serializers import NestedIPAddressSerializer, NestedVLANSerializer +from ipam.models import VLAN +from tenancy.api.nested_serializers import NestedTenantSerializer +from utilities.api import ChoiceField, SerializedPKRelatedField, ValidatedModelSerializer from virtualization.constants import VM_STATUS_CHOICES from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine +from .nested_serializers import * # -# Cluster types +# Clusters # class ClusterTypeSerializer(ValidatedModelSerializer): @@ -23,18 +25,6 @@ class ClusterTypeSerializer(ValidatedModelSerializer): fields = ['id', 'name', 'slug'] -class NestedClusterTypeSerializer(WritableNestedSerializer): - url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:clustertype-detail') - - class Meta: - model = ClusterType - fields = ['id', 'url', 'name', 'slug'] - - -# -# Cluster groups -# - class ClusterGroupSerializer(ValidatedModelSerializer): class Meta: @@ -42,18 +32,6 @@ class ClusterGroupSerializer(ValidatedModelSerializer): fields = ['id', 'name', 'slug'] -class NestedClusterGroupSerializer(WritableNestedSerializer): - url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:clustergroup-detail') - - class Meta: - model = ClusterGroup - fields = ['id', 'url', 'name', 'slug'] - - -# -# Clusters -# - class ClusterSerializer(TaggitSerializer, CustomFieldModelSerializer): type = NestedClusterTypeSerializer() group = NestedClusterGroupSerializer(required=False, allow_null=True) @@ -67,27 +45,10 @@ class ClusterSerializer(TaggitSerializer, CustomFieldModelSerializer): ] -class NestedClusterSerializer(WritableNestedSerializer): - url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:cluster-detail') - - class Meta: - model = Cluster - fields = ['id', 'url', 'name'] - - # # Virtual machines # -# Cannot import ipam.api.NestedIPAddressSerializer due to circular dependency -class VirtualMachineIPAddressSerializer(WritableNestedSerializer): - url = serializers.HyperlinkedIdentityField(view_name='ipam-api:ipaddress-detail') - - class Meta: - model = IPAddress - fields = ['id', 'url', 'family', 'address'] - - class VirtualMachineSerializer(TaggitSerializer, CustomFieldModelSerializer): status = ChoiceField(choices=VM_STATUS_CHOICES, required=False) site = NestedSiteSerializer(read_only=True) @@ -95,17 +56,17 @@ class VirtualMachineSerializer(TaggitSerializer, CustomFieldModelSerializer): role = NestedDeviceRoleSerializer(required=False, allow_null=True) tenant = NestedTenantSerializer(required=False, allow_null=True) platform = NestedPlatformSerializer(required=False, allow_null=True) - primary_ip = VirtualMachineIPAddressSerializer(read_only=True) - primary_ip4 = VirtualMachineIPAddressSerializer(required=False, allow_null=True) - primary_ip6 = VirtualMachineIPAddressSerializer(required=False, allow_null=True) + primary_ip = NestedIPAddressSerializer(read_only=True) + primary_ip4 = NestedIPAddressSerializer(required=False, allow_null=True) + primary_ip6 = NestedIPAddressSerializer(required=False, allow_null=True) tags = TagListSerializerField(required=False) class Meta: model = VirtualMachine fields = [ 'id', 'name', 'status', 'site', 'cluster', 'role', 'tenant', 'platform', 'primary_ip', 'primary_ip4', - 'primary_ip6', 'vcpus', 'memory', 'disk', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', - 'local_context_data', + 'primary_ip6', 'vcpus', 'memory', 'disk', 'comments', 'local_context_data', 'tags', 'custom_fields', + 'created', 'last_updated', ] @@ -114,44 +75,27 @@ class VirtualMachineWithConfigContextSerializer(VirtualMachineSerializer): class Meta(VirtualMachineSerializer.Meta): fields = [ - 'id', 'name', 'status', 'cluster', 'role', 'tenant', 'platform', 'primary_ip', 'primary_ip4', 'primary_ip6', - 'vcpus', 'memory', 'disk', 'comments', 'tags', 'custom_fields', 'config_context', 'created', 'last_updated', - 'local_context_data', + 'id', 'name', 'status', 'site', 'cluster', 'role', 'tenant', 'platform', 'primary_ip', 'primary_ip4', + 'primary_ip6', 'vcpus', 'memory', 'disk', 'comments', 'local_context_data', 'tags', 'custom_fields', + 'config_context', 'created', 'last_updated', ] def get_config_context(self, obj): return obj.get_config_context() -class NestedVirtualMachineSerializer(WritableNestedSerializer): - url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:virtualmachine-detail') - - class Meta: - model = VirtualMachine - fields = ['id', 'url', 'name'] - - # # VM interfaces # -# Cannot import ipam.api.serializers.NestedVLANSerializer due to circular dependency -class InterfaceVLANSerializer(WritableNestedSerializer): - url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vlan-detail') - - class Meta: - model = VLAN - fields = ['id', 'url', 'vid', 'name', 'display_name'] - - class InterfaceSerializer(TaggitSerializer, ValidatedModelSerializer): virtual_machine = NestedVirtualMachineSerializer() form_factor = ChoiceField(choices=IFACE_FF_CHOICES, default=IFACE_FF_VIRTUAL, required=False) mode = ChoiceField(choices=IFACE_MODE_CHOICES, required=False, allow_null=True) - untagged_vlan = InterfaceVLANSerializer(required=False, allow_null=True) + untagged_vlan = NestedVLANSerializer(required=False, allow_null=True) tagged_vlans = SerializedPKRelatedField( queryset=VLAN.objects.all(), - serializer=InterfaceVLANSerializer, + serializer=NestedVLANSerializer, required=False, many=True ) @@ -163,12 +107,3 @@ class InterfaceSerializer(TaggitSerializer, ValidatedModelSerializer): 'id', 'virtual_machine', 'name', 'form_factor', 'enabled', 'mtu', 'mac_address', 'description', 'mode', 'untagged_vlan', 'tagged_vlans', 'tags', ] - - -class NestedInterfaceSerializer(WritableNestedSerializer): - url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:interface-detail') - virtual_machine = NestedVirtualMachineSerializer(read_only=True) - - class Meta: - model = Interface - fields = ['id', 'url', 'virtual_machine', 'name']