1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Replace nested serializers with primary serializers where possible

This commit is contained in:
Jeremy Stretch
2024-02-26 16:49:30 -05:00
parent d042e6f69d
commit c146f5e1b5
16 changed files with 1525 additions and 1437 deletions

View File

@ -2,13 +2,12 @@ from rest_framework import serializers
from circuits.choices import CircuitStatusChoices from circuits.choices import CircuitStatusChoices
from circuits.models import * from circuits.models import *
from dcim.api.nested_serializers import NestedSiteSerializer from dcim.api.serializers import CabledObjectSerializer, SiteSerializer
from dcim.api.serializers import CabledObjectSerializer
from ipam.api.nested_serializers import NestedASNSerializer from ipam.api.nested_serializers import NestedASNSerializer
from ipam.models import ASN from ipam.models import ASN
from netbox.api.fields import ChoiceField, RelatedObjectCountField, SerializedPKRelatedField from netbox.api.fields import ChoiceField, RelatedObjectCountField, SerializedPKRelatedField
from netbox.api.serializers import NetBoxModelSerializer, WritableNestedSerializer from netbox.api.serializers import NetBoxModelSerializer, WritableNestedSerializer
from tenancy.api.nested_serializers import NestedTenantSerializer from tenancy.api.serializers import TenantSerializer
from .nested_serializers import * from .nested_serializers import *
@ -49,7 +48,7 @@ class ProviderSerializer(NetBoxModelSerializer):
class ProviderAccountSerializer(NetBoxModelSerializer): class ProviderAccountSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:provideraccount-detail') url = serializers.HyperlinkedIdentityField(view_name='circuits-api:provideraccount-detail')
provider = NestedProviderSerializer() provider = ProviderSerializer(nested=True)
class Meta: class Meta:
model = ProviderAccount model = ProviderAccount
@ -66,7 +65,7 @@ class ProviderAccountSerializer(NetBoxModelSerializer):
class ProviderNetworkSerializer(NetBoxModelSerializer): class ProviderNetworkSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:providernetwork-detail') url = serializers.HyperlinkedIdentityField(view_name='circuits-api:providernetwork-detail')
provider = NestedProviderSerializer() provider = ProviderSerializer(nested=True)
class Meta: class Meta:
model = ProviderNetwork model = ProviderNetwork
@ -98,8 +97,8 @@ class CircuitTypeSerializer(NetBoxModelSerializer):
class CircuitCircuitTerminationSerializer(WritableNestedSerializer): class CircuitCircuitTerminationSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuittermination-detail') url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuittermination-detail')
site = NestedSiteSerializer(allow_null=True) site = SiteSerializer(nested=True, allow_null=True)
provider_network = NestedProviderNetworkSerializer(allow_null=True) provider_network = ProviderNetworkSerializer(nested=True, allow_null=True)
class Meta: class Meta:
model = CircuitTermination model = CircuitTermination
@ -111,11 +110,11 @@ class CircuitCircuitTerminationSerializer(WritableNestedSerializer):
class CircuitSerializer(NetBoxModelSerializer): class CircuitSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuit-detail') url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuit-detail')
provider = NestedProviderSerializer() provider = ProviderSerializer(nested=True)
provider_account = NestedProviderAccountSerializer(required=False, allow_null=True) provider_account = ProviderAccountSerializer(nested=True, required=False, allow_null=True)
status = ChoiceField(choices=CircuitStatusChoices, required=False) status = ChoiceField(choices=CircuitStatusChoices, required=False)
type = NestedCircuitTypeSerializer() type = CircuitTypeSerializer(nested=True)
tenant = NestedTenantSerializer(required=False, allow_null=True) tenant = TenantSerializer(nested=True, required=False, allow_null=True)
termination_a = CircuitCircuitTerminationSerializer(read_only=True, allow_null=True) termination_a = CircuitCircuitTerminationSerializer(read_only=True, allow_null=True)
termination_z = CircuitCircuitTerminationSerializer(read_only=True, allow_null=True) termination_z = CircuitCircuitTerminationSerializer(read_only=True, allow_null=True)
@ -131,9 +130,9 @@ class CircuitSerializer(NetBoxModelSerializer):
class CircuitTerminationSerializer(NetBoxModelSerializer, CabledObjectSerializer): class CircuitTerminationSerializer(NetBoxModelSerializer, CabledObjectSerializer):
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuittermination-detail') url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuittermination-detail')
circuit = NestedCircuitSerializer() circuit = CircuitSerializer(nested=True)
site = NestedSiteSerializer(required=False, allow_null=True) site = SiteSerializer(nested=True, required=False, allow_null=True)
provider_network = NestedProviderNetworkSerializer(required=False, allow_null=True) provider_network = ProviderNetworkSerializer(nested=True, required=False, allow_null=True)
class Meta: class Meta:
model = CircuitTermination model = CircuitTermination

View File

@ -4,7 +4,7 @@ from core.choices import JobStatusChoices
from core.models import * from core.models import *
from netbox.api.fields import ChoiceField from netbox.api.fields import ChoiceField
from netbox.api.serializers import WritableNestedSerializer from netbox.api.serializers import WritableNestedSerializer
from users.api.nested_serializers import NestedUserSerializer from users.api.serializers import UserSerializer
__all__ = ( __all__ = (
'NestedDataFileSerializer', 'NestedDataFileSerializer',
@ -32,7 +32,8 @@ class NestedDataFileSerializer(WritableNestedSerializer):
class NestedJobSerializer(serializers.ModelSerializer): class NestedJobSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='core-api:job-detail') url = serializers.HyperlinkedIdentityField(view_name='core-api:job-detail')
status = ChoiceField(choices=JobStatusChoices) status = ChoiceField(choices=JobStatusChoices)
user = NestedUserSerializer( user = UserSerializer(
nested=True,
read_only=True read_only=True
) )

View File

@ -5,8 +5,7 @@ from core.models import *
from netbox.api.fields import ChoiceField, ContentTypeField, RelatedObjectCountField from netbox.api.fields import ChoiceField, ContentTypeField, RelatedObjectCountField
from netbox.api.serializers import BaseModelSerializer, NetBoxModelSerializer from netbox.api.serializers import BaseModelSerializer, NetBoxModelSerializer
from netbox.utils import get_data_backend_choices from netbox.utils import get_data_backend_choices
from users.api.nested_serializers import NestedUserSerializer from users.api.serializers import UserSerializer
from .nested_serializers import *
__all__ = ( __all__ = (
'DataFileSerializer', 'DataFileSerializer',
@ -43,7 +42,8 @@ class DataFileSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField( url = serializers.HyperlinkedIdentityField(
view_name='core-api:datafile-detail' view_name='core-api:datafile-detail'
) )
source = NestedDataSourceSerializer( source = DataSourceSerializer(
nested=True,
read_only=True read_only=True
) )
@ -57,7 +57,8 @@ class DataFileSerializer(NetBoxModelSerializer):
class JobSerializer(BaseModelSerializer): class JobSerializer(BaseModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='core-api:job-detail') url = serializers.HyperlinkedIdentityField(view_name='core-api:job-detail')
user = NestedUserSerializer( user = UserSerializer(
nested=True,
read_only=True read_only=True
) )
status = ChoiceField(choices=JobStatusChoices, read_only=True) status = ChoiceField(choices=JobStatusChoices, read_only=True)

View File

@ -6,8 +6,6 @@ from netbox.api.fields import RelatedObjectCountField
from netbox.api.serializers import WritableNestedSerializer from netbox.api.serializers import WritableNestedSerializer
__all__ = [ __all__ = [
'ComponentNestedModuleSerializer',
'ModuleBayNestedModuleSerializer',
'NestedCableSerializer', 'NestedCableSerializer',
'NestedConsolePortSerializer', 'NestedConsolePortSerializer',
'NestedConsolePortTemplateSerializer', 'NestedConsolePortTemplateSerializer',
@ -311,26 +309,6 @@ class ModuleNestedModuleBaySerializer(WritableNestedSerializer):
fields = ['id', 'url', 'display', 'name'] fields = ['id', 'url', 'display', 'name']
class ModuleBayNestedModuleSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:module-detail')
class Meta:
model = models.Module
fields = ['id', 'url', 'display', 'serial']
class ComponentNestedModuleSerializer(WritableNestedSerializer):
"""
Used by device component serializers.
"""
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:module-detail')
module_bay = ModuleNestedModuleBaySerializer(read_only=True)
class Meta:
model = models.Module
fields = ['id', 'url', 'display', 'device', 'module_bay']
class NestedModuleSerializer(WritableNestedSerializer): class NestedModuleSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:module-detail') url = serializers.HyperlinkedIdentityField(view_name='dcim-api:module-detail')
device = NestedDeviceSerializer(read_only=True) device = NestedDeviceSerializer(read_only=True)

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@ from rest_framework.response import Response
from rest_framework.status import HTTP_400_BAD_REQUEST from rest_framework.status import HTTP_400_BAD_REQUEST
from netbox.api.renderers import TextRenderer from netbox.api.renderers import TextRenderer
from .nested_serializers import NestedConfigTemplateSerializer from .serializers import ConfigTemplateSerializer
__all__ = ( __all__ = (
'ConfigContextQuerySetMixin', 'ConfigContextQuerySetMixin',
@ -52,7 +52,7 @@ class ConfigTemplateRenderMixin:
if request.accepted_renderer.format == 'txt': if request.accepted_renderer.format == 'txt':
return Response(output) return Response(output)
template_serializer = NestedConfigTemplateSerializer(configtemplate, context={'request': request}) template_serializer = ConfigTemplateSerializer(configtemplate, nested=True, context={'request': request})
return Response({ return Response({
'configtemplate': template_serializer.data, 'configtemplate': template_serializer.data,

View File

@ -5,8 +5,7 @@ from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import extend_schema_field from drf_spectacular.utils import extend_schema_field
from rest_framework import serializers from rest_framework import serializers
from core.api.nested_serializers import NestedDataSourceSerializer, NestedDataFileSerializer, NestedJobSerializer from core.api.serializers import DataFileSerializer, DataSourceSerializer, JobSerializer
from core.api.serializers import JobSerializer
from core.models import ContentType from core.models import ContentType
from dcim.api.nested_serializers import ( from dcim.api.nested_serializers import (
NestedDeviceRoleSerializer, NestedDeviceTypeSerializer, NestedLocationSerializer, NestedPlatformSerializer, NestedDeviceRoleSerializer, NestedDeviceTypeSerializer, NestedLocationSerializer, NestedPlatformSerializer,
@ -22,7 +21,7 @@ from netbox.api.serializers.features import TaggableModelSerializer
from netbox.constants import NESTED_SERIALIZER_PREFIX from netbox.constants import NESTED_SERIALIZER_PREFIX
from tenancy.api.nested_serializers import NestedTenantSerializer, NestedTenantGroupSerializer from tenancy.api.nested_serializers import NestedTenantSerializer, NestedTenantGroupSerializer
from tenancy.models import Tenant, TenantGroup from tenancy.models import Tenant, TenantGroup
from users.api.nested_serializers import NestedUserSerializer from users.api.serializers import UserSerializer
from utilities.api import get_serializer_for_model from utilities.api import get_serializer_for_model
from virtualization.api.nested_serializers import ( from virtualization.api.nested_serializers import (
NestedClusterGroupSerializer, NestedClusterSerializer, NestedClusterTypeSerializer, NestedClusterGroupSerializer, NestedClusterSerializer, NestedClusterTypeSerializer,
@ -115,6 +114,28 @@ class WebhookSerializer(NetBoxModelSerializer):
# Custom fields # Custom fields
# #
class CustomFieldChoiceSetSerializer(ValidatedModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='extras-api:customfieldchoiceset-detail')
base_choices = ChoiceField(
choices=CustomFieldChoiceSetBaseChoices,
required=False
)
extra_choices = serializers.ListField(
child=serializers.ListField(
min_length=2,
max_length=2
)
)
class Meta:
model = CustomFieldChoiceSet
fields = [
'id', 'url', 'display', 'name', 'description', 'base_choices', 'extra_choices', 'order_alphabetically',
'choices_count', 'created', 'last_updated',
]
brief_fields = ('id', 'url', 'display', 'name', 'description', 'choices_count')
class CustomFieldSerializer(ValidatedModelSerializer): class CustomFieldSerializer(ValidatedModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='extras-api:customfield-detail') url = serializers.HyperlinkedIdentityField(view_name='extras-api:customfield-detail')
content_types = ContentTypeField( content_types = ContentTypeField(
@ -129,7 +150,8 @@ class CustomFieldSerializer(ValidatedModelSerializer):
) )
filter_logic = ChoiceField(choices=CustomFieldFilterLogicChoices, required=False) filter_logic = ChoiceField(choices=CustomFieldFilterLogicChoices, required=False)
data_type = serializers.SerializerMethodField() data_type = serializers.SerializerMethodField()
choice_set = NestedCustomFieldChoiceSetSerializer( choice_set = CustomFieldChoiceSetSerializer(
nested=True,
required=False, required=False,
allow_null=True allow_null=True
) )
@ -168,28 +190,6 @@ class CustomFieldSerializer(ValidatedModelSerializer):
return 'string' return 'string'
class CustomFieldChoiceSetSerializer(ValidatedModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='extras-api:customfieldchoiceset-detail')
base_choices = ChoiceField(
choices=CustomFieldChoiceSetBaseChoices,
required=False
)
extra_choices = serializers.ListField(
child=serializers.ListField(
min_length=2,
max_length=2
)
)
class Meta:
model = CustomFieldChoiceSet
fields = [
'id', 'url', 'display', 'name', 'description', 'base_choices', 'extra_choices', 'order_alphabetically',
'choices_count', 'created', 'last_updated',
]
brief_fields = ('id', 'url', 'display', 'name', 'description', 'choices_count')
# #
# Custom links # Custom links
# #
@ -220,10 +220,12 @@ class ExportTemplateSerializer(ValidatedModelSerializer):
queryset=ContentType.objects.with_feature('export_templates'), queryset=ContentType.objects.with_feature('export_templates'),
many=True many=True
) )
data_source = NestedDataSourceSerializer( data_source = DataSourceSerializer(
nested=True,
required=False required=False
) )
data_file = NestedDataFileSerializer( data_file = DataFileSerializer(
nested=True,
read_only=True read_only=True
) )
@ -267,7 +269,7 @@ class BookmarkSerializer(ValidatedModelSerializer):
queryset=ContentType.objects.with_feature('bookmarks'), queryset=ContentType.objects.with_feature('bookmarks'),
) )
object = serializers.SerializerMethodField(read_only=True) object = serializers.SerializerMethodField(read_only=True)
user = NestedUserSerializer() user = UserSerializer(nested=True)
class Meta: class Meta:
model = Bookmark model = Bookmark
@ -482,10 +484,12 @@ class ConfigContextSerializer(ValidatedModelSerializer):
required=False, required=False,
many=True many=True
) )
data_source = NestedDataSourceSerializer( data_source = DataSourceSerializer(
nested=True,
required=False required=False
) )
data_file = NestedDataFileSerializer( data_file = DataFileSerializer(
nested=True,
read_only=True read_only=True
) )
@ -506,10 +510,12 @@ class ConfigContextSerializer(ValidatedModelSerializer):
class ConfigTemplateSerializer(TaggableModelSerializer, ValidatedModelSerializer): class ConfigTemplateSerializer(TaggableModelSerializer, ValidatedModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='extras-api:configtemplate-detail') url = serializers.HyperlinkedIdentityField(view_name='extras-api:configtemplate-detail')
data_source = NestedDataSourceSerializer( data_source = DataSourceSerializer(
nested=True,
required=False required=False
) )
data_file = NestedDataFileSerializer( data_file = DataFileSerializer(
nested=True,
required=False required=False
) )
@ -530,7 +536,7 @@ class ScriptSerializer(ValidatedModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='extras-api:script-detail') url = serializers.HyperlinkedIdentityField(view_name='extras-api:script-detail')
description = serializers.SerializerMethodField(read_only=True) description = serializers.SerializerMethodField(read_only=True)
vars = serializers.SerializerMethodField(read_only=True) vars = serializers.SerializerMethodField(read_only=True)
result = NestedJobSerializer(read_only=True) result = JobSerializer(nested=True, read_only=True)
class Meta: class Meta:
model = Script model = Script
@ -596,7 +602,8 @@ class ScriptInputSerializer(serializers.Serializer):
class ObjectChangeSerializer(BaseModelSerializer): class ObjectChangeSerializer(BaseModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='extras-api:objectchange-detail') url = serializers.HyperlinkedIdentityField(view_name='extras-api:objectchange-detail')
user = NestedUserSerializer( user = UserSerializer(
nested=True,
read_only=True read_only=True
) )
action = ChoiceField( action = ChoiceField(

View File

@ -2,29 +2,48 @@ from django.contrib.contenttypes.models import ContentType
from drf_spectacular.utils import extend_schema_field from drf_spectacular.utils import extend_schema_field
from rest_framework import serializers from rest_framework import serializers
from dcim.api.nested_serializers import NestedDeviceSerializer, NestedSiteSerializer from dcim.api.serializers import DeviceSerializer, SiteSerializer
from ipam.choices import * from ipam.choices import *
from ipam.constants import IPADDRESS_ASSIGNMENT_MODELS, VLANGROUP_SCOPE_TYPES from ipam.constants import IPADDRESS_ASSIGNMENT_MODELS, VLANGROUP_SCOPE_TYPES
from ipam.models import * from ipam.models import *
from netbox.api.fields import ChoiceField, ContentTypeField, RelatedObjectCountField, SerializedPKRelatedField from netbox.api.fields import ChoiceField, ContentTypeField, RelatedObjectCountField, SerializedPKRelatedField
from netbox.api.serializers import NetBoxModelSerializer from netbox.api.serializers import NetBoxModelSerializer
from netbox.constants import NESTED_SERIALIZER_PREFIX from netbox.constants import NESTED_SERIALIZER_PREFIX
from tenancy.api.nested_serializers import NestedTenantSerializer from tenancy.api.serializers import TenantSerializer
from utilities.api import get_serializer_for_model from utilities.api import get_serializer_for_model
from virtualization.api.nested_serializers import NestedVirtualMachineSerializer from virtualization.api.serializers import VirtualMachineSerializer
from vpn.api.nested_serializers import NestedL2VPNTerminationSerializer from vpn.api.nested_serializers import NestedL2VPNTerminationSerializer
from .field_serializers import IPAddressField, IPNetworkField from .field_serializers import IPAddressField, IPNetworkField
from .nested_serializers import * from .nested_serializers import *
#
# RIRs
#
class RIRSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='ipam-api:rir-detail')
# Related object counts
aggregate_count = RelatedObjectCountField('aggregates')
class Meta:
model = RIR
fields = [
'id', 'url', 'display', 'name', 'slug', 'is_private', 'description', 'tags', 'custom_fields', 'created',
'last_updated', 'aggregate_count',
]
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description', 'aggregate_count')
# #
# ASN ranges # ASN ranges
# #
class ASNRangeSerializer(NetBoxModelSerializer): class ASNRangeSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='ipam-api:asnrange-detail') url = serializers.HyperlinkedIdentityField(view_name='ipam-api:asnrange-detail')
rir = NestedRIRSerializer() rir = RIRSerializer(nested=True)
tenant = NestedTenantSerializer(required=False, allow_null=True) tenant = TenantSerializer(nested=True, required=False, allow_null=True)
asn_count = serializers.IntegerField(read_only=True) asn_count = serializers.IntegerField(read_only=True)
class Meta: class Meta:
@ -42,8 +61,8 @@ class ASNRangeSerializer(NetBoxModelSerializer):
class ASNSerializer(NetBoxModelSerializer): class ASNSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='ipam-api:asn-detail') url = serializers.HyperlinkedIdentityField(view_name='ipam-api:asn-detail')
rir = NestedRIRSerializer(required=False, allow_null=True) rir = RIRSerializer(nested=True, required=False, allow_null=True)
tenant = NestedTenantSerializer(required=False, allow_null=True) tenant = TenantSerializer(nested=True, required=False, allow_null=True)
# Related object counts # Related object counts
site_count = RelatedObjectCountField('sites') site_count = RelatedObjectCountField('sites')
@ -66,7 +85,7 @@ class AvailableASNSerializer(serializers.Serializer):
description = serializers.CharField(required=False) description = serializers.CharField(required=False)
def to_representation(self, asn): def to_representation(self, asn):
rir = NestedRIRSerializer(self.context['range'].rir, context={ rir = RIRSerializer(self.context['range'].rir, nested=True, context={
'request': self.context['request'] 'request': self.context['request']
}).data }).data
return { return {
@ -81,7 +100,7 @@ class AvailableASNSerializer(serializers.Serializer):
class VRFSerializer(NetBoxModelSerializer): class VRFSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vrf-detail') url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vrf-detail')
tenant = NestedTenantSerializer(required=False, allow_null=True) tenant = TenantSerializer(nested=True, required=False, allow_null=True)
import_targets = SerializedPKRelatedField( import_targets = SerializedPKRelatedField(
queryset=RouteTarget.objects.all(), queryset=RouteTarget.objects.all(),
serializer=NestedRouteTargetSerializer, serializer=NestedRouteTargetSerializer,
@ -109,13 +128,9 @@ class VRFSerializer(NetBoxModelSerializer):
brief_fields = ('id', 'url', 'display', 'name', 'rd', 'description', 'prefix_count') brief_fields = ('id', 'url', 'display', 'name', 'rd', 'description', 'prefix_count')
#
# Route targets
#
class RouteTargetSerializer(NetBoxModelSerializer): class RouteTargetSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='ipam-api:routetarget-detail') url = serializers.HyperlinkedIdentityField(view_name='ipam-api:routetarget-detail')
tenant = NestedTenantSerializer(required=False, allow_null=True) tenant = TenantSerializer(nested=True, required=False, allow_null=True)
class Meta: class Meta:
model = RouteTarget model = RouteTarget
@ -127,29 +142,14 @@ class RouteTargetSerializer(NetBoxModelSerializer):
# #
# RIRs/aggregates # Aggregates
# #
class RIRSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='ipam-api:rir-detail')
# Related object counts
aggregate_count = RelatedObjectCountField('aggregates')
class Meta:
model = RIR
fields = [
'id', 'url', 'display', 'name', 'slug', 'is_private', 'description', 'tags', 'custom_fields', 'created',
'last_updated', 'aggregate_count',
]
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description', 'aggregate_count')
class AggregateSerializer(NetBoxModelSerializer): class AggregateSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='ipam-api:aggregate-detail') url = serializers.HyperlinkedIdentityField(view_name='ipam-api:aggregate-detail')
family = ChoiceField(choices=IPAddressFamilyChoices, read_only=True) family = ChoiceField(choices=IPAddressFamilyChoices, read_only=True)
rir = NestedRIRSerializer() rir = RIRSerializer(nested=True)
tenant = NestedTenantSerializer(required=False, allow_null=True) tenant = TenantSerializer(nested=True, required=False, allow_null=True)
prefix = IPNetworkField() prefix = IPNetworkField()
class Meta: class Meta:
@ -180,7 +180,7 @@ class FHRPGroupSerializer(NetBoxModelSerializer):
class FHRPGroupAssignmentSerializer(NetBoxModelSerializer): class FHRPGroupAssignmentSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='ipam-api:fhrpgroupassignment-detail') url = serializers.HyperlinkedIdentityField(view_name='ipam-api:fhrpgroupassignment-detail')
group = NestedFHRPGroupSerializer() group = FHRPGroupSerializer(nested=True)
interface_type = ContentTypeField( interface_type = ContentTypeField(
queryset=ContentType.objects.all() queryset=ContentType.objects.all()
) )
@ -261,11 +261,11 @@ class VLANGroupSerializer(NetBoxModelSerializer):
class VLANSerializer(NetBoxModelSerializer): class VLANSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vlan-detail') url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vlan-detail')
site = NestedSiteSerializer(required=False, allow_null=True) site = SiteSerializer(nested=True, required=False, allow_null=True)
group = NestedVLANGroupSerializer(required=False, allow_null=True, default=None) group = VLANGroupSerializer(nested=True, required=False, allow_null=True, default=None)
tenant = NestedTenantSerializer(required=False, allow_null=True) tenant = TenantSerializer(nested=True, required=False, allow_null=True)
status = ChoiceField(choices=VLANStatusChoices, required=False) status = ChoiceField(choices=VLANStatusChoices, required=False)
role = NestedRoleSerializer(required=False, allow_null=True) role = RoleSerializer(nested=True, required=False, allow_null=True)
l2vpn_termination = NestedL2VPNTerminationSerializer(read_only=True, allow_null=True) l2vpn_termination = NestedL2VPNTerminationSerializer(read_only=True, allow_null=True)
# Related object counts # Related object counts
@ -285,23 +285,24 @@ class AvailableVLANSerializer(serializers.Serializer):
Representation of a VLAN which does not exist in the database. Representation of a VLAN which does not exist in the database.
""" """
vid = serializers.IntegerField(read_only=True) vid = serializers.IntegerField(read_only=True)
group = NestedVLANGroupSerializer(read_only=True) group = VLANGroupSerializer(nested=True, read_only=True)
def to_representation(self, instance): def to_representation(self, instance):
return { return {
'vid': instance, 'vid': instance,
'group': NestedVLANGroupSerializer( 'group': VLANGroupSerializer(
self.context['group'], self.context['group'],
nested=True,
context={'request': self.context['request']} context={'request': self.context['request']}
).data, ).data,
} }
class CreateAvailableVLANSerializer(NetBoxModelSerializer): class CreateAvailableVLANSerializer(NetBoxModelSerializer):
site = NestedSiteSerializer(required=False, allow_null=True) site = SiteSerializer(nested=True, required=False, allow_null=True)
tenant = NestedTenantSerializer(required=False, allow_null=True) tenant = TenantSerializer(nested=True, required=False, allow_null=True)
status = ChoiceField(choices=VLANStatusChoices, required=False) status = ChoiceField(choices=VLANStatusChoices, required=False)
role = NestedRoleSerializer(required=False, allow_null=True) role = RoleSerializer(nested=True, required=False, allow_null=True)
class Meta: class Meta:
model = VLAN model = VLAN
@ -321,12 +322,12 @@ class CreateAvailableVLANSerializer(NetBoxModelSerializer):
class PrefixSerializer(NetBoxModelSerializer): class PrefixSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='ipam-api:prefix-detail') url = serializers.HyperlinkedIdentityField(view_name='ipam-api:prefix-detail')
family = ChoiceField(choices=IPAddressFamilyChoices, read_only=True) family = ChoiceField(choices=IPAddressFamilyChoices, read_only=True)
site = NestedSiteSerializer(required=False, allow_null=True) site = SiteSerializer(nested=True, required=False, allow_null=True)
vrf = NestedVRFSerializer(required=False, allow_null=True) vrf = VRFSerializer(nested=True, required=False, allow_null=True)
tenant = NestedTenantSerializer(required=False, allow_null=True) tenant = TenantSerializer(nested=True, required=False, allow_null=True)
vlan = NestedVLANSerializer(required=False, allow_null=True) vlan = VLANSerializer(nested=True, required=False, allow_null=True)
status = ChoiceField(choices=PrefixStatusChoices, required=False) status = ChoiceField(choices=PrefixStatusChoices, required=False)
role = NestedRoleSerializer(required=False, allow_null=True) role = RoleSerializer(nested=True, required=False, allow_null=True)
children = serializers.IntegerField(read_only=True) children = serializers.IntegerField(read_only=True)
_depth = serializers.IntegerField(read_only=True) _depth = serializers.IntegerField(read_only=True)
prefix = IPNetworkField() prefix = IPNetworkField()
@ -374,11 +375,11 @@ class AvailablePrefixSerializer(serializers.Serializer):
""" """
family = serializers.IntegerField(read_only=True) family = serializers.IntegerField(read_only=True)
prefix = serializers.CharField(read_only=True) prefix = serializers.CharField(read_only=True)
vrf = NestedVRFSerializer(read_only=True) vrf = VRFSerializer(nested=True, read_only=True)
def to_representation(self, instance): def to_representation(self, instance):
if self.context.get('vrf'): if self.context.get('vrf'):
vrf = NestedVRFSerializer(self.context['vrf'], context={'request': self.context['request']}).data vrf = VRFSerializer(self.context['vrf'], nested=True, context={'request': self.context['request']}).data
else: else:
vrf = None vrf = None
return { return {
@ -397,10 +398,10 @@ class IPRangeSerializer(NetBoxModelSerializer):
family = ChoiceField(choices=IPAddressFamilyChoices, read_only=True) family = ChoiceField(choices=IPAddressFamilyChoices, read_only=True)
start_address = IPAddressField() start_address = IPAddressField()
end_address = IPAddressField() end_address = IPAddressField()
vrf = NestedVRFSerializer(required=False, allow_null=True) vrf = VRFSerializer(nested=True, required=False, allow_null=True)
tenant = NestedTenantSerializer(required=False, allow_null=True) tenant = TenantSerializer(nested=True, required=False, allow_null=True)
status = ChoiceField(choices=IPRangeStatusChoices, required=False) status = ChoiceField(choices=IPRangeStatusChoices, required=False)
role = NestedRoleSerializer(required=False, allow_null=True) role = RoleSerializer(nested=True, required=False, allow_null=True)
class Meta: class Meta:
model = IPRange model = IPRange
@ -420,8 +421,8 @@ class IPAddressSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='ipam-api:ipaddress-detail') url = serializers.HyperlinkedIdentityField(view_name='ipam-api:ipaddress-detail')
family = ChoiceField(choices=IPAddressFamilyChoices, read_only=True) family = ChoiceField(choices=IPAddressFamilyChoices, read_only=True)
address = IPAddressField() address = IPAddressField()
vrf = NestedVRFSerializer(required=False, allow_null=True) vrf = VRFSerializer(nested=True, required=False, allow_null=True)
tenant = NestedTenantSerializer(required=False, allow_null=True) tenant = TenantSerializer(nested=True, required=False, allow_null=True)
status = ChoiceField(choices=IPAddressStatusChoices, required=False) status = ChoiceField(choices=IPAddressStatusChoices, required=False)
role = ChoiceField(choices=IPAddressRoleChoices, allow_blank=True, required=False) role = ChoiceField(choices=IPAddressRoleChoices, allow_blank=True, required=False)
assigned_object_type = ContentTypeField( assigned_object_type = ContentTypeField(
@ -457,12 +458,12 @@ class AvailableIPSerializer(serializers.Serializer):
""" """
family = serializers.IntegerField(read_only=True) family = serializers.IntegerField(read_only=True)
address = serializers.CharField(read_only=True) address = serializers.CharField(read_only=True)
vrf = NestedVRFSerializer(read_only=True) vrf = VRFSerializer(nested=True, read_only=True)
description = serializers.CharField(required=False) description = serializers.CharField(required=False)
def to_representation(self, instance): def to_representation(self, instance):
if self.context.get('vrf'): if self.context.get('vrf'):
vrf = NestedVRFSerializer(self.context['vrf'], context={'request': self.context['request']}).data vrf = VRFSerializer(self.context['vrf'], nested=True, context={'request': self.context['request']}).data
else: else:
vrf = None vrf = None
return { return {
@ -491,8 +492,8 @@ class ServiceTemplateSerializer(NetBoxModelSerializer):
class ServiceSerializer(NetBoxModelSerializer): class ServiceSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='ipam-api:service-detail') url = serializers.HyperlinkedIdentityField(view_name='ipam-api:service-detail')
device = NestedDeviceSerializer(required=False, allow_null=True) device = DeviceSerializer(nested=True, required=False, allow_null=True)
virtual_machine = NestedVirtualMachineSerializer(required=False, allow_null=True) virtual_machine = VirtualMachineSerializer(nested=True, required=False, allow_null=True)
protocol = ChoiceField(choices=ServiceProtocolChoices, required=False) protocol = ChoiceField(choices=ServiceProtocolChoices, required=False)
ipaddresses = SerializedPKRelatedField( ipaddresses = SerializedPKRelatedField(
queryset=IPAddress.objects.all(), queryset=IPAddress.objects.all(),

View File

@ -1,8 +1,9 @@
from django.db.models import ManyToManyField
from rest_framework import serializers from rest_framework import serializers
from drf_spectacular.utils import extend_schema_field from drf_spectacular.utils import extend_schema_field
from drf_spectacular.types import OpenApiTypes from drf_spectacular.types import OpenApiTypes
from utilities.api import get_related_object_by_attrs
__all__ = ( __all__ = (
'BaseModelSerializer', 'BaseModelSerializer',
'ValidatedModelSerializer', 'ValidatedModelSerializer',
@ -12,15 +13,30 @@ __all__ = (
class BaseModelSerializer(serializers.ModelSerializer): class BaseModelSerializer(serializers.ModelSerializer):
display = serializers.SerializerMethodField(read_only=True) display = serializers.SerializerMethodField(read_only=True)
def __init__(self, *args, requested_fields=None, **kwargs): def __init__(self, *args, nested=False, requested_fields=None, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.nested = nested
if nested and not requested_fields:
requested_fields = getattr(self.Meta, 'brief_fields', None)
# If specific fields have been requested, omit the others # If specific fields have been requested, omit the others
if requested_fields: if requested_fields:
for field in list(self.fields.keys()): for field in list(self.fields.keys()):
if field not in requested_fields: if field not in requested_fields:
self.fields.pop(field) self.fields.pop(field)
def to_internal_value(self, data):
# If initialized as a nested serializer, we should expect to receive the attrs or PK
# identifying a related object.
if self.nested:
queryset = self.Meta.model.objects.all()
return get_related_object_by_attrs(queryset, data)
return super().to_internal_value(data)
@extend_schema_field(OpenApiTypes.STR) @extend_schema_field(OpenApiTypes.STR)
def get_display(self, obj): def get_display(self, obj):
return str(obj) return str(obj)
@ -32,6 +48,11 @@ class ValidatedModelSerializer(BaseModelSerializer):
validation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144) validation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)
""" """
def validate(self, data): def validate(self, data):
# Skip validation if we're being used to represent a nested object
if self.nested:
return data
attrs = data.copy() attrs = data.copy()
# Remove custom field data (if any) prior to model validation # Remove custom field data (if any) prior to model validation

View File

@ -1,10 +1,7 @@
from django.core.exceptions import FieldError, MultipleObjectsReturned, ObjectDoesNotExist
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from extras.models import Tag from extras.models import Tag
from utilities.utils import dict_to_filter_params from utilities.api import get_related_object_by_attrs
from .base import BaseModelSerializer from .base import BaseModelSerializer
__all__ = ( __all__ = (
@ -20,43 +17,8 @@ class WritableNestedSerializer(BaseModelSerializer):
subclassed to return a full representation of the related object on read. subclassed to return a full representation of the related object on read.
""" """
def to_internal_value(self, data): def to_internal_value(self, data):
queryset = self.Meta.model.objects.all()
if data is None: return get_related_object_by_attrs(queryset, data)
return None
# Dictionary of related object attributes
if isinstance(data, dict):
params = dict_to_filter_params(data)
queryset = self.Meta.model.objects
try:
return queryset.get(**params)
except ObjectDoesNotExist:
raise ValidationError(
_("Related object not found using the provided attributes: {params}").format(params=params))
except MultipleObjectsReturned:
raise ValidationError(
_("Multiple objects match the provided attributes: {params}").format(params=params)
)
except FieldError as e:
raise ValidationError(e)
# Integer PK of related object
try:
# Cast as integer in case a PK was mistakenly sent as a string
pk = int(data)
except (TypeError, ValueError):
raise ValidationError(
_(
"Related objects must be referenced by numeric ID or by dictionary of attributes. Received an "
"unrecognized value: {value}"
).format(value=data)
)
# Look up object by PK
try:
return self.Meta.model.objects.get(pk=pk)
except ObjectDoesNotExist:
raise ValidationError(_("Related object not found using the provided numeric ID: {id}").format(id=pk))
# Declared here for use by PrimaryModelSerializer, but should be imported from extras.api.nested_serializers # Declared here for use by PrimaryModelSerializer, but should be imported from extras.api.nested_serializers

View File

@ -32,7 +32,7 @@ class TenantGroupSerializer(NestedGroupModelSerializer):
class TenantSerializer(NetBoxModelSerializer): class TenantSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='tenancy-api:tenant-detail') url = serializers.HyperlinkedIdentityField(view_name='tenancy-api:tenant-detail')
group = NestedTenantGroupSerializer(required=False, allow_null=True) group = TenantGroupSerializer(nested=True, required=False, allow_null=True)
# Related object counts # Related object counts
circuit_count = RelatedObjectCountField('circuits') circuit_count = RelatedObjectCountField('circuits')
@ -87,7 +87,7 @@ class ContactRoleSerializer(NetBoxModelSerializer):
class ContactSerializer(NetBoxModelSerializer): class ContactSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='tenancy-api:contact-detail') url = serializers.HyperlinkedIdentityField(view_name='tenancy-api:contact-detail')
group = NestedContactGroupSerializer(required=False, allow_null=True, default=None) group = ContactGroupSerializer(nested=True, required=False, allow_null=True, default=None)
class Meta: class Meta:
model = Contact model = Contact
@ -104,8 +104,8 @@ class ContactAssignmentSerializer(NetBoxModelSerializer):
queryset=ContentType.objects.all() queryset=ContentType.objects.all()
) )
object = serializers.SerializerMethodField(read_only=True) object = serializers.SerializerMethodField(read_only=True)
contact = NestedContactSerializer() contact = ContactSerializer(nested=True)
role = NestedContactRoleSerializer(required=False, allow_null=True) role = ContactRoleSerializer(nested=True, required=False, allow_null=True)
priority = ChoiceField(choices=ContactPriorityChoices, allow_blank=True, required=False, default=lambda: '') priority = ChoiceField(choices=ContactPriorityChoices, allow_blank=True, required=False, default=lambda: '')
class Meta: class Meta:

View File

@ -89,7 +89,7 @@ class TokenSerializer(ValidatedModelSerializer):
required=False, required=False,
write_only=not settings.ALLOW_TOKEN_RETRIEVAL write_only=not settings.ALLOW_TOKEN_RETRIEVAL
) )
user = NestedUserSerializer() user = UserSerializer(nested=True)
allowed_ips = serializers.ListField( allowed_ips = serializers.ListField(
child=IPNetworkSerializer(), child=IPNetworkSerializer(),
required=False, required=False,
@ -122,7 +122,8 @@ class TokenSerializer(ValidatedModelSerializer):
class TokenProvisionSerializer(TokenSerializer): class TokenProvisionSerializer(TokenSerializer):
user = NestedUserSerializer( user = UserSerializer(
nested=True,
read_only=True read_only=True
) )
username = serializers.CharField( username = serializers.CharField(

View File

@ -3,23 +3,26 @@ import sys
from django.conf import settings from django.conf import settings
from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.fields import GenericForeignKey
from django.core.exceptions import FieldDoesNotExist from django.core.exceptions import (
FieldDoesNotExist, FieldError, MultipleObjectsReturned, ObjectDoesNotExist, ValidationError,
)
from django.db.models.fields.related import ManyToOneRel, RelatedField from django.db.models.fields.related import ManyToOneRel, RelatedField
from django.http import JsonResponse from django.http import JsonResponse
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from rest_framework import status from rest_framework import status
from rest_framework.serializers import Serializer from rest_framework.serializers import Serializer
from rest_framework.utils import formatting from rest_framework.utils import formatting
from netbox.api.fields import RelatedObjectCountField from netbox.api.fields import RelatedObjectCountField
from netbox.api.exceptions import GraphQLTypeNotFound, SerializerNotFound from netbox.api.exceptions import GraphQLTypeNotFound, SerializerNotFound
from utilities.utils import count_related from .utils import count_related, dict_to_filter_params, dynamic_import
from .utils import dynamic_import
__all__ = ( __all__ = (
'get_annotations_for_serializer', 'get_annotations_for_serializer',
'get_graphql_type_for_model', 'get_graphql_type_for_model',
'get_prefetches_for_serializer', 'get_prefetches_for_serializer',
'get_related_object_by_attrs',
'get_serializer_for_model', 'get_serializer_for_model',
'get_view_name', 'get_view_name',
'is_api_request', 'is_api_request',
@ -103,7 +106,7 @@ def get_prefetches_for_serializer(serializer_class, fields_to_include=None):
""" """
model = serializer_class.Meta.model model = serializer_class.Meta.model
# If specific fields are not specified, default to all # If fields are not specified, default to all
if not fields_to_include: if not fields_to_include:
fields_to_include = serializer_class.Meta.fields fields_to_include = serializer_class.Meta.fields
@ -128,7 +131,9 @@ def get_prefetches_for_serializer(serializer_class, fields_to_include=None):
# for the related object. # for the related object.
if serializer_field: if serializer_field:
if issubclass(type(serializer_field), Serializer): if issubclass(type(serializer_field), Serializer):
for subfield in get_prefetches_for_serializer(type(serializer_field)): # Determine which fields to prefetch for the nested object
subfields = serializer_field.Meta.brief_fields if serializer_field.nested else None
for subfield in get_prefetches_for_serializer(type(serializer_field), subfields):
prefetch_fields.append(f'{field_name}__{subfield}') prefetch_fields.append(f'{field_name}__{subfield}')
return prefetch_fields return prefetch_fields
@ -154,6 +159,48 @@ def get_annotations_for_serializer(serializer_class, fields_to_include=None):
return annotations return annotations
def get_related_object_by_attrs(queryset, attrs):
"""
Return an object identified by either a dictionary of attributes or its numeric primary key (ID). This is used
for referencing related objects when creating/updating objects via the REST API.
"""
if attrs is None:
return None
# Dictionary of related object attributes
if isinstance(attrs, dict):
params = dict_to_filter_params(attrs)
try:
return queryset.get(**params)
except ObjectDoesNotExist:
raise ValidationError(
_("Related object not found using the provided attributes: {params}").format(params=params))
except MultipleObjectsReturned:
raise ValidationError(
_("Multiple objects match the provided attributes: {params}").format(params=params)
)
except FieldError as e:
raise ValidationError(e)
# Integer PK of related object
try:
# Cast as integer in case a PK was mistakenly sent as a string
pk = int(attrs)
except (TypeError, ValueError):
raise ValidationError(
_(
"Related objects must be referenced by numeric ID or by dictionary of attributes. Received an "
"unrecognized value: {value}"
).format(value=attrs)
)
# Look up object by PK
try:
return queryset.get(pk=pk)
except ObjectDoesNotExist:
raise ValidationError(_("Related object not found using the provided numeric ID: {id}").format(id=pk))
def rest_api_server_error(request, *args, **kwargs): def rest_api_server_error(request, *args, **kwargs):
""" """
Handle exceptions and return a useful error message for REST API requests. Handle exceptions and return a useful error message for REST API requests.

View File

@ -1,16 +1,14 @@
from drf_spectacular.utils import extend_schema_field from drf_spectacular.utils import extend_schema_field
from rest_framework import serializers from rest_framework import serializers
from dcim.api.nested_serializers import ( from dcim.api.serializers import DeviceSerializer, DeviceRoleSerializer, PlatformSerializer, SiteSerializer
NestedDeviceSerializer, NestedDeviceRoleSerializer, NestedPlatformSerializer, NestedSiteSerializer,
)
from dcim.choices import InterfaceModeChoices from dcim.choices import InterfaceModeChoices
from extras.api.nested_serializers import NestedConfigTemplateSerializer from extras.api.serializers import ConfigTemplateSerializer
from ipam.api.nested_serializers import NestedIPAddressSerializer, NestedVLANSerializer, NestedVRFSerializer from ipam.api.nested_serializers import NestedIPAddressSerializer, NestedVLANSerializer, NestedVRFSerializer
from ipam.models import VLAN from ipam.models import VLAN
from netbox.api.fields import ChoiceField, RelatedObjectCountField, SerializedPKRelatedField from netbox.api.fields import ChoiceField, RelatedObjectCountField, SerializedPKRelatedField
from netbox.api.serializers import NetBoxModelSerializer from netbox.api.serializers import NetBoxModelSerializer
from tenancy.api.nested_serializers import NestedTenantSerializer from tenancy.api.serializers import TenantSerializer
from virtualization.choices import * from virtualization.choices import *
from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualDisk, VirtualMachine, VMInterface from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualDisk, VirtualMachine, VMInterface
from vpn.api.nested_serializers import NestedL2VPNTerminationSerializer from vpn.api.nested_serializers import NestedL2VPNTerminationSerializer
@ -53,11 +51,11 @@ class ClusterGroupSerializer(NetBoxModelSerializer):
class ClusterSerializer(NetBoxModelSerializer): class ClusterSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:cluster-detail') url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:cluster-detail')
type = NestedClusterTypeSerializer() type = ClusterTypeSerializer(nested=True)
group = NestedClusterGroupSerializer(required=False, allow_null=True, default=None) group = ClusterGroupSerializer(nested=True, required=False, allow_null=True, default=None)
status = ChoiceField(choices=ClusterStatusChoices, required=False) status = ChoiceField(choices=ClusterStatusChoices, required=False)
tenant = NestedTenantSerializer(required=False, allow_null=True) tenant = TenantSerializer(nested=True, required=False, allow_null=True)
site = NestedSiteSerializer(required=False, allow_null=True, default=None) site = SiteSerializer(nested=True, required=False, allow_null=True, default=None)
# Related object counts # Related object counts
device_count = RelatedObjectCountField('devices') device_count = RelatedObjectCountField('devices')
@ -79,16 +77,16 @@ class ClusterSerializer(NetBoxModelSerializer):
class VirtualMachineSerializer(NetBoxModelSerializer): class VirtualMachineSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:virtualmachine-detail') url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:virtualmachine-detail')
status = ChoiceField(choices=VirtualMachineStatusChoices, required=False) status = ChoiceField(choices=VirtualMachineStatusChoices, required=False)
site = NestedSiteSerializer(required=False, allow_null=True) site = SiteSerializer(nested=True, required=False, allow_null=True)
cluster = NestedClusterSerializer(required=False, allow_null=True) cluster = ClusterSerializer(nested=True, required=False, allow_null=True)
device = NestedDeviceSerializer(required=False, allow_null=True) device = DeviceSerializer(nested=True, required=False, allow_null=True)
role = NestedDeviceRoleSerializer(required=False, allow_null=True) role = DeviceRoleSerializer(nested=True, required=False, allow_null=True)
tenant = NestedTenantSerializer(required=False, allow_null=True) tenant = TenantSerializer(nested=True, required=False, allow_null=True)
platform = NestedPlatformSerializer(required=False, allow_null=True) platform = PlatformSerializer(nested=True, required=False, allow_null=True)
primary_ip = NestedIPAddressSerializer(read_only=True) primary_ip = NestedIPAddressSerializer(read_only=True)
primary_ip4 = NestedIPAddressSerializer(required=False, allow_null=True) primary_ip4 = NestedIPAddressSerializer(required=False, allow_null=True)
primary_ip6 = NestedIPAddressSerializer(required=False, allow_null=True) primary_ip6 = NestedIPAddressSerializer(required=False, allow_null=True)
config_template = NestedConfigTemplateSerializer(required=False, allow_null=True, default=None) config_template = ConfigTemplateSerializer(nested=True, required=False, allow_null=True, default=None)
# Counter fields # Counter fields
interface_count = serializers.IntegerField(read_only=True) interface_count = serializers.IntegerField(read_only=True)
@ -128,7 +126,7 @@ class VirtualMachineWithConfigContextSerializer(VirtualMachineSerializer):
class VMInterfaceSerializer(NetBoxModelSerializer): class VMInterfaceSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:vminterface-detail') url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:vminterface-detail')
virtual_machine = NestedVirtualMachineSerializer() virtual_machine = VirtualMachineSerializer(nested=True)
parent = NestedVMInterfaceSerializer(required=False, allow_null=True) parent = NestedVMInterfaceSerializer(required=False, allow_null=True)
bridge = NestedVMInterfaceSerializer(required=False, allow_null=True) bridge = NestedVMInterfaceSerializer(required=False, allow_null=True)
mode = ChoiceField(choices=InterfaceModeChoices, allow_blank=True, required=False) mode = ChoiceField(choices=InterfaceModeChoices, allow_blank=True, required=False)
@ -178,7 +176,7 @@ class VMInterfaceSerializer(NetBoxModelSerializer):
class VirtualDiskSerializer(NetBoxModelSerializer): class VirtualDiskSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:virtualdisk-detail') url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:virtualdisk-detail')
virtual_machine = NestedVirtualMachineSerializer() virtual_machine = VirtualMachineSerializer(nested=True)
class Meta: class Meta:
model = VirtualDisk model = VirtualDisk

View File

@ -2,12 +2,13 @@ from django.contrib.contenttypes.models import ContentType
from drf_spectacular.utils import extend_schema_field from drf_spectacular.utils import extend_schema_field
from rest_framework import serializers from rest_framework import serializers
from ipam.api.nested_serializers import NestedIPAddressSerializer, NestedRouteTargetSerializer from ipam.api.serializers import IPAddressSerializer
from ipam.api.nested_serializers import NestedRouteTargetSerializer
from ipam.models import RouteTarget from ipam.models import RouteTarget
from netbox.api.fields import ChoiceField, ContentTypeField, RelatedObjectCountField, SerializedPKRelatedField from netbox.api.fields import ChoiceField, ContentTypeField, RelatedObjectCountField, SerializedPKRelatedField
from netbox.api.serializers import NetBoxModelSerializer from netbox.api.serializers import NetBoxModelSerializer
from netbox.constants import NESTED_SERIALIZER_PREFIX from netbox.constants import NESTED_SERIALIZER_PREFIX
from tenancy.api.nested_serializers import NestedTenantSerializer from tenancy.api.serializers import TenantSerializer
from utilities.api import get_serializer_for_model from utilities.api import get_serializer_for_model
from vpn.choices import * from vpn.choices import *
from vpn.models import * from vpn.models import *
@ -27,90 +28,6 @@ __all__ = (
) )
class TunnelGroupSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='vpn-api:tunnelgroup-detail')
# Related object counts
tunnel_count = RelatedObjectCountField('tunnels')
class Meta:
model = TunnelGroup
fields = [
'id', 'url', 'display', 'name', 'slug', 'description', 'tags', 'custom_fields', 'created', 'last_updated',
'tunnel_count',
]
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description', 'tunnel_count')
class TunnelSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='vpn-api:tunnel-detail'
)
status = ChoiceField(
choices=TunnelStatusChoices
)
group = NestedTunnelGroupSerializer(
required=False,
allow_null=True
)
encapsulation = ChoiceField(
choices=TunnelEncapsulationChoices
)
ipsec_profile = NestedIPSecProfileSerializer(
required=False,
allow_null=True
)
tenant = NestedTenantSerializer(
required=False,
allow_null=True
)
# Related object counts
terminations_count = RelatedObjectCountField('terminations')
class Meta:
model = Tunnel
fields = (
'id', 'url', 'display', 'name', 'status', 'group', 'encapsulation', 'ipsec_profile', 'tenant', 'tunnel_id',
'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'terminations_count',
)
brief_fields = ('id', 'url', 'display', 'name', 'description')
class TunnelTerminationSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='vpn-api:tunneltermination-detail'
)
tunnel = NestedTunnelSerializer()
role = ChoiceField(
choices=TunnelTerminationRoleChoices
)
termination_type = ContentTypeField(
queryset=ContentType.objects.all()
)
termination = serializers.SerializerMethodField(
read_only=True
)
outside_ip = NestedIPAddressSerializer(
required=False,
allow_null=True
)
class Meta:
model = TunnelTermination
fields = (
'id', 'url', 'display', 'tunnel', 'role', 'termination_type', 'termination_id', 'termination', 'outside_ip',
'tags', 'custom_fields', 'created', 'last_updated',
)
brief_fields = ('id', 'url', 'display')
@extend_schema_field(serializers.JSONField(allow_null=True))
def get_termination(self, obj):
serializer = get_serializer_for_model(obj.termination, prefix=NESTED_SERIALIZER_PREFIX)
context = {'request': self.context['request']}
return serializer(obj.termination, context=context).data
class IKEProposalSerializer(NetBoxModelSerializer): class IKEProposalSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField( url = serializers.HyperlinkedIdentityField(
view_name='vpn-api:ikeproposal-detail' view_name='vpn-api:ikeproposal-detail'
@ -215,8 +132,12 @@ class IPSecProfileSerializer(NetBoxModelSerializer):
mode = ChoiceField( mode = ChoiceField(
choices=IPSecModeChoices choices=IPSecModeChoices
) )
ike_policy = NestedIKEPolicySerializer() ike_policy = IKEPolicySerializer(
ipsec_policy = NestedIPSecPolicySerializer() nested=True
)
ipsec_policy = IPSecPolicySerializer(
nested=True
)
class Meta: class Meta:
model = IPSecProfile model = IPSecProfile
@ -227,6 +148,100 @@ class IPSecProfileSerializer(NetBoxModelSerializer):
brief_fields = ('id', 'url', 'display', 'name', 'description') brief_fields = ('id', 'url', 'display', 'name', 'description')
#
# Tunnels
#
class TunnelGroupSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='vpn-api:tunnelgroup-detail')
# Related object counts
tunnel_count = RelatedObjectCountField('tunnels')
class Meta:
model = TunnelGroup
fields = [
'id', 'url', 'display', 'name', 'slug', 'description', 'tags', 'custom_fields', 'created', 'last_updated',
'tunnel_count',
]
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description', 'tunnel_count')
class TunnelSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='vpn-api:tunnel-detail'
)
status = ChoiceField(
choices=TunnelStatusChoices
)
group = TunnelGroupSerializer(
nested=True,
required=False,
allow_null=True
)
encapsulation = ChoiceField(
choices=TunnelEncapsulationChoices
)
ipsec_profile = IPSecProfileSerializer(
nested=True,
required=False,
allow_null=True
)
tenant = TenantSerializer(
nested=True,
required=False,
allow_null=True
)
# Related object counts
terminations_count = RelatedObjectCountField('terminations')
class Meta:
model = Tunnel
fields = (
'id', 'url', 'display', 'name', 'status', 'group', 'encapsulation', 'ipsec_profile', 'tenant', 'tunnel_id',
'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'terminations_count',
)
brief_fields = ('id', 'url', 'display', 'name', 'description')
class TunnelTerminationSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='vpn-api:tunneltermination-detail'
)
tunnel = TunnelSerializer(
nested=True
)
role = ChoiceField(
choices=TunnelTerminationRoleChoices
)
termination_type = ContentTypeField(
queryset=ContentType.objects.all()
)
termination = serializers.SerializerMethodField(
read_only=True
)
outside_ip = IPAddressSerializer(
nested=True,
required=False,
allow_null=True
)
class Meta:
model = TunnelTermination
fields = (
'id', 'url', 'display', 'tunnel', 'role', 'termination_type', 'termination_id', 'termination', 'outside_ip',
'tags', 'custom_fields', 'created', 'last_updated',
)
brief_fields = ('id', 'url', 'display')
@extend_schema_field(serializers.JSONField(allow_null=True))
def get_termination(self, obj):
serializer = get_serializer_for_model(obj.termination, prefix=NESTED_SERIALIZER_PREFIX)
context = {'request': self.context['request']}
return serializer(obj.termination, context=context).data
# #
# L2VPN # L2VPN
# #
@ -246,7 +261,7 @@ class L2VPNSerializer(NetBoxModelSerializer):
required=False, required=False,
many=True many=True
) )
tenant = NestedTenantSerializer(required=False, allow_null=True) tenant = TenantSerializer(nested=True, required=False, allow_null=True)
class Meta: class Meta:
model = L2VPN model = L2VPN
@ -259,7 +274,9 @@ class L2VPNSerializer(NetBoxModelSerializer):
class L2VPNTerminationSerializer(NetBoxModelSerializer): class L2VPNTerminationSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='vpn-api:l2vpntermination-detail') url = serializers.HyperlinkedIdentityField(view_name='vpn-api:l2vpntermination-detail')
l2vpn = NestedL2VPNSerializer() l2vpn = L2VPNSerializer(
nested=True
)
assigned_object_type = ContentTypeField( assigned_object_type = ContentTypeField(
queryset=ContentType.objects.all() queryset=ContentType.objects.all()
) )

View File

@ -1,11 +1,11 @@
from rest_framework import serializers from rest_framework import serializers
from dcim.choices import LinkStatusChoices from dcim.choices import LinkStatusChoices
from dcim.api.serializers import NestedInterfaceSerializer from dcim.api.serializers import InterfaceSerializer
from ipam.api.serializers import NestedVLANSerializer from ipam.api.serializers import VLANSerializer
from netbox.api.fields import ChoiceField from netbox.api.fields import ChoiceField
from netbox.api.serializers import NestedGroupModelSerializer, NetBoxModelSerializer from netbox.api.serializers import NestedGroupModelSerializer, NetBoxModelSerializer
from tenancy.api.nested_serializers import NestedTenantSerializer from tenancy.api.serializers import TenantSerializer
from wireless.choices import * from wireless.choices import *
from wireless.models import * from wireless.models import *
from .nested_serializers import * from .nested_serializers import *
@ -33,10 +33,10 @@ class WirelessLANGroupSerializer(NestedGroupModelSerializer):
class WirelessLANSerializer(NetBoxModelSerializer): class WirelessLANSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='wireless-api:wirelesslan-detail') url = serializers.HyperlinkedIdentityField(view_name='wireless-api:wirelesslan-detail')
group = NestedWirelessLANGroupSerializer(required=False, allow_null=True) group = WirelessLANGroupSerializer(nested=True, required=False, allow_null=True)
status = ChoiceField(choices=WirelessLANStatusChoices, required=False, allow_blank=True) status = ChoiceField(choices=WirelessLANStatusChoices, required=False, allow_blank=True)
vlan = NestedVLANSerializer(required=False, allow_null=True) vlan = VLANSerializer(nested=True, required=False, allow_null=True)
tenant = NestedTenantSerializer(required=False, allow_null=True) tenant = TenantSerializer(nested=True, required=False, allow_null=True)
auth_type = ChoiceField(choices=WirelessAuthTypeChoices, required=False, allow_blank=True) auth_type = ChoiceField(choices=WirelessAuthTypeChoices, required=False, allow_blank=True)
auth_cipher = ChoiceField(choices=WirelessAuthCipherChoices, required=False, allow_blank=True) auth_cipher = ChoiceField(choices=WirelessAuthCipherChoices, required=False, allow_blank=True)
@ -52,9 +52,9 @@ class WirelessLANSerializer(NetBoxModelSerializer):
class WirelessLinkSerializer(NetBoxModelSerializer): class WirelessLinkSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='wireless-api:wirelesslink-detail') url = serializers.HyperlinkedIdentityField(view_name='wireless-api:wirelesslink-detail')
status = ChoiceField(choices=LinkStatusChoices, required=False) status = ChoiceField(choices=LinkStatusChoices, required=False)
interface_a = NestedInterfaceSerializer() interface_a = InterfaceSerializer(nested=True)
interface_b = NestedInterfaceSerializer() interface_b = InterfaceSerializer(nested=True)
tenant = NestedTenantSerializer(required=False, allow_null=True) tenant = TenantSerializer(nested=True, required=False, allow_null=True)
auth_type = ChoiceField(choices=WirelessAuthTypeChoices, required=False, allow_blank=True) auth_type = ChoiceField(choices=WirelessAuthTypeChoices, required=False, allow_blank=True)
auth_cipher = ChoiceField(choices=WirelessAuthCipherChoices, required=False, allow_blank=True) auth_cipher = ChoiceField(choices=WirelessAuthCipherChoices, required=False, allow_blank=True)