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

Add dynamic nesting support to SerializedPKRelatedField

This commit is contained in:
Jeremy Stretch
2024-02-27 14:00:42 -05:00
parent c382ba0ae0
commit ca56c8b9ef
19 changed files with 137 additions and 111 deletions

View File

@ -1,7 +1,7 @@
from rest_framework import serializers
from circuits.models import Provider, ProviderAccount, ProviderNetwork
from ipam.api.nested_serializers import NestedASNSerializer
from ipam.api.serializers_.asns import ASNSerializer
from ipam.models import ASN
from netbox.api.fields import RelatedObjectCountField, SerializedPKRelatedField
from netbox.api.serializers import NetBoxModelSerializer
@ -24,7 +24,8 @@ class ProviderSerializer(NetBoxModelSerializer):
)
asns = SerializedPKRelatedField(
queryset=ASN.objects.all(),
serializer=NestedASNSerializer,
serializer=ASNSerializer,
nested=True,
required=False,
many=True
)

View File

@ -10,21 +10,21 @@ from dcim.models import (
)
from ipam.api.serializers_.vlans import VLANSerializer
from ipam.api.serializers_.vrfs import VRFSerializer
from ipam.api.nested_serializers import NestedVLANSerializer
from ipam.models import VLAN
from netbox.api.fields import ChoiceField, ContentTypeField, SerializedPKRelatedField
from netbox.api.serializers import NetBoxModelSerializer, WritableNestedSerializer
from netbox.constants import NESTED_SERIALIZER_PREFIX
from utilities.api import get_serializer_for_model
from vpn.api.serializers_.l2vpn import L2VPNTerminationSerializer
from wireless.api.nested_serializers import NestedWirelessLANSerializer, NestedWirelessLinkSerializer
from wireless.api.nested_serializers import NestedWirelessLinkSerializer
from wireless.choices import *
from wireless.models import WirelessLAN
from .base import ConnectedEndpointsSerializer
from .cables import CabledObjectSerializer
from .devices import DeviceSerializer, ModuleSerializer
from .devices import DeviceSerializer, ModuleSerializer, VirtualDeviceContextSerializer
from .manufacturers import ManufacturerSerializer
from .roles import InventoryItemRoleSerializer
from wireless.api.serializers_.wirelesslans import WirelessLANSerializer
from ..nested_serializers import *
__all__ = (
@ -173,7 +173,8 @@ class InterfaceSerializer(NetBoxModelSerializer, CabledObjectSerializer, Connect
device = DeviceSerializer(nested=True)
vdcs = SerializedPKRelatedField(
queryset=VirtualDeviceContext.objects.all(),
serializer=NestedVirtualDeviceContextSerializer,
serializer=VirtualDeviceContextSerializer,
nested=True,
required=False,
many=True
)
@ -196,7 +197,8 @@ class InterfaceSerializer(NetBoxModelSerializer, CabledObjectSerializer, Connect
untagged_vlan = VLANSerializer(nested=True, required=False, allow_null=True)
tagged_vlans = SerializedPKRelatedField(
queryset=VLAN.objects.all(),
serializer=NestedVLANSerializer,
serializer=VLANSerializer,
nested=True,
required=False,
many=True
)
@ -205,7 +207,8 @@ class InterfaceSerializer(NetBoxModelSerializer, CabledObjectSerializer, Connect
wireless_link = NestedWirelessLinkSerializer(read_only=True, allow_null=True)
wireless_lans = SerializedPKRelatedField(
queryset=WirelessLAN.objects.all(),
serializer=NestedWirelessLANSerializer,
serializer=WirelessLANSerializer,
nested=True,
required=False,
many=True
)

View File

@ -6,7 +6,7 @@ from rest_framework import serializers
from dcim.choices import *
from dcim.models import Device, DeviceBay, Module, VirtualDeviceContext
from extras.api.serializers_.provisioning import ConfigTemplateSerializer
from extras.api.serializers_.configtemplates import ConfigTemplateSerializer
from ipam.api.serializers_.ip import IPAddressSerializer
from netbox.api.fields import ChoiceField, RelatedObjectCountField
from netbox.api.serializers import NetBoxModelSerializer

View File

@ -1,7 +1,7 @@
from rest_framework import serializers
from dcim.models import Platform
from extras.api.serializers_.provisioning import ConfigTemplateSerializer
from extras.api.serializers_.configtemplates import ConfigTemplateSerializer
from netbox.api.fields import RelatedObjectCountField
from netbox.api.serializers import NetBoxModelSerializer
from .manufacturers import ManufacturerSerializer

View File

@ -1,7 +1,7 @@
from rest_framework import serializers
from dcim.models import DeviceRole, InventoryItemRole
from extras.api.serializers_.provisioning import ConfigTemplateSerializer
from extras.api.serializers_.configtemplates import ConfigTemplateSerializer
from netbox.api.fields import RelatedObjectCountField
from netbox.api.serializers import NetBoxModelSerializer

View File

@ -3,7 +3,7 @@ from timezone_field.rest_framework import TimeZoneSerializerField
from dcim.choices import *
from dcim.models import Location, Region, Site, SiteGroup
from ipam.api.nested_serializers import NestedASNSerializer
from ipam.api.serializers_.asns import ASNSerializer
from ipam.models import ASN
from netbox.api.fields import ChoiceField, RelatedObjectCountField, SerializedPKRelatedField
from netbox.api.serializers import NestedGroupModelSerializer, NetBoxModelSerializer
@ -55,7 +55,8 @@ class SiteSerializer(NetBoxModelSerializer):
time_zone = TimeZoneSerializerField(required=False, allow_null=True)
asns = SerializedPKRelatedField(
queryset=ASN.objects.all(),
serializer=NestedASNSerializer,
serializer=ASNSerializer,
nested=True,
required=False,
many=True
)

View File

@ -8,7 +8,8 @@ from .serializers_.dashboard import *
from .serializers_.events import *
from .serializers_.exporttemplates import *
from .serializers_.journaling import *
from .serializers_.provisioning import *
from .serializers_.configcontexts import *
from .serializers_.configtemplates import *
from .serializers_.savedfilters import *
from .serializers_.scripts import *
from .serializers_.tags import *

View File

@ -1,25 +1,21 @@
from rest_framework import serializers
from core.api.serializers_.data import DataFileSerializer, DataSourceSerializer
from dcim.api.nested_serializers import (
NestedDeviceRoleSerializer, NestedDeviceTypeSerializer, NestedLocationSerializer, NestedPlatformSerializer,
NestedRegionSerializer, NestedSiteSerializer, NestedSiteGroupSerializer,
)
from dcim.api.serializers_.devicetypes import DeviceTypeSerializer
from dcim.api.serializers_.platforms import PlatformSerializer
from dcim.api.serializers_.roles import DeviceRoleSerializer
from dcim.api.serializers_.sites import LocationSerializer, RegionSerializer, SiteSerializer, SiteGroupSerializer
from dcim.models import DeviceRole, DeviceType, Location, Platform, Region, Site, SiteGroup
from extras.models import ConfigContext, ConfigTemplate, Tag
from extras.models import ConfigContext, Tag
from netbox.api.fields import SerializedPKRelatedField
from netbox.api.serializers import ValidatedModelSerializer
from netbox.api.serializers.features import TaggableModelSerializer
from tenancy.api.nested_serializers import NestedTenantSerializer, NestedTenantGroupSerializer
from tenancy.api.serializers_.tenants import TenantSerializer, TenantGroupSerializer
from tenancy.models import Tenant, TenantGroup
from virtualization.api.nested_serializers import (
NestedClusterGroupSerializer, NestedClusterSerializer, NestedClusterTypeSerializer,
)
from virtualization.api.serializers_.clusters import ClusterSerializer, ClusterGroupSerializer, ClusterTypeSerializer
from virtualization.models import Cluster, ClusterGroup, ClusterType
__all__ = (
'ConfigContextSerializer',
'ConfigTemplateSerializer',
)
@ -27,73 +23,85 @@ class ConfigContextSerializer(ValidatedModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='extras-api:configcontext-detail')
regions = SerializedPKRelatedField(
queryset=Region.objects.all(),
serializer=NestedRegionSerializer,
serializer=RegionSerializer,
nested=True,
required=False,
many=True
)
site_groups = SerializedPKRelatedField(
queryset=SiteGroup.objects.all(),
serializer=NestedSiteGroupSerializer,
serializer=SiteGroupSerializer,
nested=True,
required=False,
many=True
)
sites = SerializedPKRelatedField(
queryset=Site.objects.all(),
serializer=NestedSiteSerializer,
serializer=SiteSerializer,
nested=True,
required=False,
many=True
)
locations = SerializedPKRelatedField(
queryset=Location.objects.all(),
serializer=NestedLocationSerializer,
serializer=LocationSerializer,
nested=True,
required=False,
many=True
)
device_types = SerializedPKRelatedField(
queryset=DeviceType.objects.all(),
serializer=NestedDeviceTypeSerializer,
serializer=DeviceTypeSerializer,
nested=True,
required=False,
many=True
)
roles = SerializedPKRelatedField(
queryset=DeviceRole.objects.all(),
serializer=NestedDeviceRoleSerializer,
serializer=DeviceRoleSerializer,
nested=True,
required=False,
many=True
)
platforms = SerializedPKRelatedField(
queryset=Platform.objects.all(),
serializer=NestedPlatformSerializer,
serializer=PlatformSerializer,
nested=True,
required=False,
many=True
)
cluster_types = SerializedPKRelatedField(
queryset=ClusterType.objects.all(),
serializer=NestedClusterTypeSerializer,
serializer=ClusterTypeSerializer,
nested=True,
required=False,
many=True
)
cluster_groups = SerializedPKRelatedField(
queryset=ClusterGroup.objects.all(),
serializer=NestedClusterGroupSerializer,
serializer=ClusterGroupSerializer,
nested=True,
required=False,
many=True
)
clusters = SerializedPKRelatedField(
queryset=Cluster.objects.all(),
serializer=NestedClusterSerializer,
serializer=ClusterSerializer,
nested=True,
required=False,
many=True
)
tenant_groups = SerializedPKRelatedField(
queryset=TenantGroup.objects.all(),
serializer=NestedTenantGroupSerializer,
serializer=TenantGroupSerializer,
nested=True,
required=False,
many=True
)
tenants = SerializedPKRelatedField(
queryset=Tenant.objects.all(),
serializer=NestedTenantSerializer,
serializer=TenantSerializer,
nested=True,
required=False,
many=True
)
@ -121,27 +129,3 @@ class ConfigContextSerializer(ValidatedModelSerializer):
'created', 'last_updated',
]
brief_fields = ('id', 'url', 'display', 'name', 'description')
#
# Config templates
#
class ConfigTemplateSerializer(TaggableModelSerializer, ValidatedModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='extras-api:configtemplate-detail')
data_source = DataSourceSerializer(
nested=True,
required=False
)
data_file = DataFileSerializer(
nested=True,
required=False
)
class Meta:
model = ConfigTemplate
fields = [
'id', 'url', 'display', 'name', 'description', 'environment_params', 'template_code', 'data_source',
'data_path', 'data_file', 'data_synced', 'tags', 'created', 'last_updated',
]
brief_fields = ('id', 'url', 'display', 'name', 'description')

View File

@ -0,0 +1,30 @@
from rest_framework import serializers
from core.api.serializers_.data import DataFileSerializer, DataSourceSerializer
from extras.models import ConfigTemplate
from netbox.api.serializers import ValidatedModelSerializer
from netbox.api.serializers.features import TaggableModelSerializer
__all__ = (
'ConfigTemplateSerializer',
)
class ConfigTemplateSerializer(TaggableModelSerializer, ValidatedModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='extras-api:configtemplate-detail')
data_source = DataSourceSerializer(
nested=True,
required=False
)
data_file = DataFileSerializer(
nested=True,
required=False
)
class Meta:
model = ConfigTemplate
fields = [
'id', 'url', 'display', 'name', 'description', 'environment_params', 'template_code', 'data_source',
'data_path', 'data_file', 'data_synced', 'tags', 'created', 'last_updated',
]
brief_fields = ('id', 'url', 'display', 'name', 'description')

View File

@ -9,7 +9,7 @@ from netbox.api.fields import ChoiceField, ContentTypeField
from netbox.api.serializers import NetBoxModelSerializer
from netbox.constants import NESTED_SERIALIZER_PREFIX
from utilities.api import get_serializer_for_model
from ..nested_serializers import *
from .scripts import ScriptSerializer
__all__ = (
'EventRuleSerializer',
@ -49,7 +49,7 @@ class EventRuleSerializer(NetBoxModelSerializer):
if instance.action_type == EventRuleActionChoices.SCRIPT:
script = instance.action_object
instance = script.python_class() if script.python_class else None
return NestedScriptSerializer(instance, context=context).data
return ScriptSerializer(instance, nested=True, context=context).data
else:
serializer = get_serializer_for_model(
model=instance.action_object_type.model_class(),

View File

@ -7,7 +7,7 @@ from netbox.api.fields import ContentTypeField
from netbox.api.serializers import NetBoxModelSerializer
from netbox.constants import NESTED_SERIALIZER_PREFIX
from utilities.api import get_serializer_for_model
from ..nested_serializers import *
from .ip import IPAddressSerializer
__all__ = (
'FHRPGroupAssignmentSerializer',
@ -17,7 +17,7 @@ __all__ = (
class FHRPGroupSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='ipam-api:fhrpgroup-detail')
ip_addresses = NestedIPAddressSerializer(many=True, read_only=True)
ip_addresses = IPAddressSerializer(nested=True, many=True, read_only=True)
class Meta:
model = FHRPGroup

View File

@ -6,7 +6,7 @@ from ipam.models import IPAddress, Service, ServiceTemplate
from netbox.api.fields import ChoiceField, SerializedPKRelatedField
from netbox.api.serializers import NetBoxModelSerializer
from virtualization.api.serializers_.virtualmachines import VirtualMachineSerializer
from ..nested_serializers import *
from .ip import IPAddressSerializer
__all__ = (
'ServiceSerializer',
@ -34,7 +34,8 @@ class ServiceSerializer(NetBoxModelSerializer):
protocol = ChoiceField(choices=ServiceProtocolChoices, required=False)
ipaddresses = SerializedPKRelatedField(
queryset=IPAddress.objects.all(),
serializer=NestedIPAddressSerializer,
serializer=IPAddressSerializer,
nested=True,
required=False,
many=True
)

View File

@ -4,7 +4,6 @@ from ipam.models import RouteTarget, VRF
from netbox.api.fields import RelatedObjectCountField, SerializedPKRelatedField
from netbox.api.serializers import NetBoxModelSerializer
from tenancy.api.serializers_.tenants import TenantSerializer
from ..nested_serializers import *
__all__ = (
'RouteTargetSerializer',
@ -12,18 +11,31 @@ __all__ = (
)
class RouteTargetSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='ipam-api:routetarget-detail')
tenant = TenantSerializer(nested=True, required=False, allow_null=True)
class Meta:
model = RouteTarget
fields = [
'id', 'url', 'display', 'name', 'tenant', 'description', 'comments', 'tags', 'custom_fields', 'created',
'last_updated',
]
brief_fields = ('id', 'url', 'display', 'name', 'description')
class VRFSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vrf-detail')
tenant = TenantSerializer(nested=True, required=False, allow_null=True)
import_targets = SerializedPKRelatedField(
queryset=RouteTarget.objects.all(),
serializer=NestedRouteTargetSerializer,
serializer=RouteTargetSerializer,
required=False,
many=True
)
export_targets = SerializedPKRelatedField(
queryset=RouteTarget.objects.all(),
serializer=NestedRouteTargetSerializer,
serializer=RouteTargetSerializer,
required=False,
many=True
)
@ -40,16 +52,3 @@ class VRFSerializer(NetBoxModelSerializer):
'prefix_count',
]
brief_fields = ('id', 'url', 'display', 'name', 'rd', 'description', 'prefix_count')
class RouteTargetSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='ipam-api:routetarget-detail')
tenant = TenantSerializer(nested=True, required=False, allow_null=True)
class Meta:
model = RouteTarget
fields = [
'id', 'url', 'display', 'name', 'tenant', 'description', 'comments', 'tags', 'custom_fields', 'created',
'last_updated',
]
brief_fields = ('id', 'url', 'display', 'name', 'description')

View File

@ -132,13 +132,15 @@ class SerializedPKRelatedField(PrimaryKeyRelatedField):
Extends PrimaryKeyRelatedField to return a serialized object on read. This is useful for representing related
objects in a ManyToManyField while still allowing a set of primary keys to be written.
"""
def __init__(self, serializer, **kwargs):
def __init__(self, serializer, nested=False, **kwargs):
self.serializer = serializer
self.nested = nested
self.pk_field = kwargs.pop('pk_field', None)
super().__init__(**kwargs)
def to_representation(self, value):
return self.serializer(value, context={'request': self.context['request']}).data
return self.serializer(value, nested=self.nested, context={'request': self.context['request']}).data
@extend_schema_field(OpenApiTypes.INT64)

View File

@ -6,7 +6,7 @@ from rest_framework import serializers
from netbox.api.fields import ContentTypeField, SerializedPKRelatedField
from netbox.api.serializers import ValidatedModelSerializer
from users.models import ObjectPermission
from ..nested_serializers import *
from .users import GroupSerializer, UserSerializer
__all__ = (
'ObjectPermissionSerializer',
@ -21,13 +21,15 @@ class ObjectPermissionSerializer(ValidatedModelSerializer):
)
groups = SerializedPKRelatedField(
queryset=Group.objects.all(),
serializer=NestedGroupSerializer,
serializer=GroupSerializer,
nested=True,
required=False,
many=True
)
users = SerializedPKRelatedField(
queryset=get_user_model().objects.all(),
serializer=NestedUserSerializer,
serializer=UserSerializer,
nested=True,
required=False,
many=True
)

View File

@ -6,7 +6,6 @@ from rest_framework import serializers
from netbox.api.fields import SerializedPKRelatedField
from netbox.api.serializers import ValidatedModelSerializer
from ..nested_serializers import *
__all__ = (
'GroupSerializer',
@ -14,11 +13,22 @@ __all__ = (
)
class GroupSerializer(ValidatedModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='users-api:group-detail')
user_count = serializers.IntegerField(read_only=True)
class Meta:
model = Group
fields = ('id', 'url', 'display', 'name', 'user_count')
brief_fields = ('id', 'url', 'display', 'name')
class UserSerializer(ValidatedModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='users-api:user-detail')
groups = SerializedPKRelatedField(
queryset=Group.objects.all(),
serializer=NestedGroupSerializer,
serializer=GroupSerializer,
nested=True,
required=False,
many=True
)
@ -60,13 +70,3 @@ class UserSerializer(ValidatedModelSerializer):
if full_name := obj.get_full_name():
return f"{obj.username} ({full_name})"
return obj.username
class GroupSerializer(ValidatedModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='users-api:group-detail')
user_count = serializers.IntegerField(read_only=True)
class Meta:
model = Group
fields = ('id', 'url', 'display', 'name', 'user_count')
brief_fields = ('id', 'url', 'display', 'name')

View File

@ -6,8 +6,7 @@ from dcim.api.serializers_.platforms import PlatformSerializer
from dcim.api.serializers_.roles import DeviceRoleSerializer
from dcim.api.serializers_.sites import SiteSerializer
from dcim.choices import InterfaceModeChoices
from extras.api.serializers_.provisioning import ConfigTemplateSerializer
from ipam.api.nested_serializers import NestedVLANSerializer
from extras.api.serializers_.configtemplates import ConfigTemplateSerializer
from ipam.api.serializers_.ip import IPAddressSerializer
from ipam.api.serializers_.vlans import VLANSerializer
from ipam.api.serializers_.vrfs import VRFSerializer
@ -18,9 +17,8 @@ from tenancy.api.serializers_.tenants import TenantSerializer
from virtualization.choices import *
from virtualization.models import VirtualDisk, VirtualMachine, VMInterface
from vpn.api.serializers_.l2vpn import L2VPNTerminationSerializer
from ..nested_serializers import *
from .clusters import ClusterSerializer
from ..nested_serializers import *
__all__ = (
'VMInterfaceSerializer',
@ -89,7 +87,8 @@ class VMInterfaceSerializer(NetBoxModelSerializer):
untagged_vlan = VLANSerializer(nested=True, required=False, allow_null=True)
tagged_vlans = SerializedPKRelatedField(
queryset=VLAN.objects.all(),
serializer=NestedVLANSerializer,
serializer=VLANSerializer,
nested=True,
required=False,
many=True
)

View File

@ -4,7 +4,6 @@ from netbox.api.fields import ChoiceField, SerializedPKRelatedField
from netbox.api.serializers import NetBoxModelSerializer
from vpn.choices import *
from vpn.models import IKEPolicy, IKEProposal, IPSecPolicy, IPSecProfile, IPSecProposal
from ..nested_serializers import *
__all__ = (
'IKEPolicySerializer',
@ -54,7 +53,8 @@ class IKEPolicySerializer(NetBoxModelSerializer):
)
proposals = SerializedPKRelatedField(
queryset=IKEProposal.objects.all(),
serializer=NestedIKEProposalSerializer,
serializer=IKEProposalSerializer,
nested=True,
required=False,
many=True
)
@ -94,7 +94,8 @@ class IPSecPolicySerializer(NetBoxModelSerializer):
)
proposals = SerializedPKRelatedField(
queryset=IPSecProposal.objects.all(),
serializer=NestedIPSecProposalSerializer,
serializer=IPSecProposalSerializer,
nested=True,
required=False,
many=True
)

View File

@ -2,7 +2,7 @@ from django.contrib.contenttypes.models import ContentType
from drf_spectacular.utils import extend_schema_field
from rest_framework import serializers
from ipam.api.nested_serializers import NestedRouteTargetSerializer
from ipam.api.serializers_.vrfs import RouteTargetSerializer
from ipam.models import RouteTarget
from netbox.api.fields import ChoiceField, ContentTypeField, SerializedPKRelatedField
from netbox.api.serializers import NetBoxModelSerializer
@ -23,13 +23,15 @@ class L2VPNSerializer(NetBoxModelSerializer):
type = ChoiceField(choices=L2VPNTypeChoices, required=False)
import_targets = SerializedPKRelatedField(
queryset=RouteTarget.objects.all(),
serializer=NestedRouteTargetSerializer,
serializer=RouteTargetSerializer,
nested=True,
required=False,
many=True
)
export_targets = SerializedPKRelatedField(
queryset=RouteTarget.objects.all(),
serializer=NestedRouteTargetSerializer,
serializer=RouteTargetSerializer,
nested=True,
required=False,
many=True
)