From 7a55832a223ec8f92ec0b2514251a0ef13ee9e63 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Wed, 3 Nov 2021 15:15:14 -0400 Subject: [PATCH] #6732: Add asns relationship to SiteSerializer and extend tests --- netbox/dcim/api/serializers.py | 18 +++++++++------ netbox/dcim/api/views.py | 3 +-- netbox/dcim/tests/test_api.py | 12 +++++++++- netbox/dcim/tests/test_views.py | 40 ++++++++++----------------------- netbox/ipam/api/serializers.py | 1 - netbox/ipam/models/ip.py | 10 ++++----- 6 files changed, 40 insertions(+), 44 deletions(-) diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index 6fd67bf69..b85283bdb 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -1,4 +1,3 @@ -from django.conf import settings from django.contrib.contenttypes.models import ContentType from drf_yasg.utils import swagger_serializer_method from rest_framework import serializers @@ -7,8 +6,8 @@ from timezone_field.rest_framework import TimeZoneSerializerField from dcim.choices import * from dcim.constants import * from dcim.models import * -from ipam.api.nested_serializers import NestedIPAddressSerializer, NestedVLANSerializer, NestedASNSerializer -from ipam.models import VLAN +from ipam.api.nested_serializers import NestedASNSerializer, NestedIPAddressSerializer, NestedVLANSerializer +from ipam.models import ASN, VLAN from netbox.api import ChoiceField, ContentTypeField, SerializedPKRelatedField from netbox.api.serializers import ( NestedGroupModelSerializer, PrimaryModelSerializer, ValidatedModelSerializer, WritableNestedSerializer, @@ -113,13 +112,19 @@ class SiteSerializer(PrimaryModelSerializer): region = NestedRegionSerializer(required=False, allow_null=True) group = NestedSiteGroupSerializer(required=False, allow_null=True) tenant = NestedTenantSerializer(required=False, allow_null=True) - asns = NestedASNSerializer(many=True, required=False, allow_null=True) time_zone = TimeZoneSerializerField(required=False) + asns = SerializedPKRelatedField( + queryset=ASN.objects.all(), + serializer=NestedASNSerializer, + required=False, + many=True + ) + + # Related object counts circuit_count = serializers.IntegerField(read_only=True) device_count = serializers.IntegerField(read_only=True) prefix_count = serializers.IntegerField(read_only=True) rack_count = serializers.IntegerField(read_only=True) - asn_count = serializers.IntegerField(read_only=True) virtualmachine_count = serializers.IntegerField(read_only=True) vlan_count = serializers.IntegerField(read_only=True) @@ -129,8 +134,7 @@ class SiteSerializer(PrimaryModelSerializer): 'id', 'url', 'display', 'name', 'slug', 'status', 'region', 'group', 'tenant', 'facility', 'asn', 'asns', 'time_zone', 'description', 'physical_address', 'shipping_address', 'latitude', 'longitude', 'contact_name', 'contact_phone', 'contact_email', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', - 'asn_count', 'circuit_count', 'device_count', 'prefix_count', 'rack_count', 'virtualmachine_count', - 'vlan_count', + 'circuit_count', 'device_count', 'prefix_count', 'rack_count', 'virtualmachine_count', 'vlan_count', ] diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index e05ccaed2..b6685aba8 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -137,9 +137,8 @@ class SiteGroupViewSet(CustomFieldModelViewSet): class SiteViewSet(CustomFieldModelViewSet): queryset = Site.objects.prefetch_related( - 'region', 'tenant', 'tags' + 'region', 'tenant', 'asns', 'tags' ).annotate( - asn_count=count_related(ASN, 'sites'), device_count=count_related(Device, 'site'), rack_count=count_related(Rack, 'site'), prefix_count=count_related(Prefix, 'site'), diff --git a/netbox/dcim/tests/test_api.py b/netbox/dcim/tests/test_api.py index 042b4ce28..0c733cc18 100644 --- a/netbox/dcim/tests/test_api.py +++ b/netbox/dcim/tests/test_api.py @@ -6,7 +6,7 @@ from rest_framework import status from dcim.choices import * from dcim.constants import * from dcim.models import * -from ipam.models import VLAN +from ipam.models import ASN, RIR, VLAN from utilities.testing import APITestCase, APIViewTestCases from virtualization.models import Cluster, ClusterType @@ -143,6 +143,13 @@ class SiteTest(APIViewTestCases.APIViewTestCase): ) Site.objects.bulk_create(sites) + rir = RIR.objects.create(name='RFC 6996', is_private=True) + + asns = [ + ASN(asn=65000 + i, rir=rir) for i in range(8) + ] + ASN.objects.bulk_create(asns) + cls.create_data = [ { 'name': 'Site 4', @@ -150,6 +157,7 @@ class SiteTest(APIViewTestCases.APIViewTestCase): 'region': regions[1].pk, 'group': groups[1].pk, 'status': SiteStatusChoices.STATUS_ACTIVE, + 'asns': [asns[0].pk, asns[1].pk], }, { 'name': 'Site 5', @@ -157,6 +165,7 @@ class SiteTest(APIViewTestCases.APIViewTestCase): 'region': regions[1].pk, 'group': groups[1].pk, 'status': SiteStatusChoices.STATUS_ACTIVE, + 'asns': [asns[2].pk, asns[3].pk], }, { 'name': 'Site 6', @@ -164,6 +173,7 @@ class SiteTest(APIViewTestCases.APIViewTestCase): 'region': regions[1].pk, 'group': groups[1].pk, 'status': SiteStatusChoices.STATUS_ACTIVE, + 'asns': [asns[4].pk, asns[5].pk], }, ] diff --git a/netbox/dcim/tests/test_views.py b/netbox/dcim/tests/test_views.py index dc22b18a0..ac1b93274 100644 --- a/netbox/dcim/tests/test_views.py +++ b/netbox/dcim/tests/test_views.py @@ -11,7 +11,7 @@ from netaddr import EUI from dcim.choices import * from dcim.constants import * from dcim.models import * -from ipam.models import ASN, VLAN, RIR +from ipam.models import ASN, RIR, VLAN from tenancy.models import Tenant from utilities.testing import ViewTestCases, create_tags, create_test_device @@ -110,41 +110,24 @@ class SiteTestCase(ViewTestCases.PrimaryObjectViewTestCase): for group in groups: group.save() + rir = RIR.objects.create(name='RFC 6996', is_private=True) + + asns = [ + ASN(asn=65000 + i, rir=rir) for i in range(8) + ] + ASN.objects.bulk_create(asns) + sites = Site.objects.bulk_create([ Site(name='Site 1', slug='site-1', region=regions[0], group=groups[1]), Site(name='Site 2', slug='site-2', region=regions[0], group=groups[1]), Site(name='Site 3', slug='site-3', region=regions[0], group=groups[1]), ]) + sites[0].asns.set([asns[0], asns[1]]) + sites[1].asns.set([asns[2], asns[3]]) + sites[2].asns.set([asns[4], asns[5]]) tags = create_tags('Alpha', 'Bravo', 'Charlie') - rir = RIR.objects.create(name='RFC 6996', is_private=True) - - asns = [ - ASN(asn=65000, rir=rir), - ASN(asn=65001, rir=rir), - ASN(asn=65002, rir=rir), - ASN(asn=65003, rir=rir), - ASN(asn=65004, rir=rir), - ASN(asn=65005, rir=rir), - ASN(asn=65006, rir=rir), - ASN(asn=65007, rir=rir), - ASN(asn=65008, rir=rir), - ASN(asn=65009, rir=rir), - ASN(asn=65010, rir=rir), - ] - ASN.objects.bulk_create(asns) - - asns[0].sites.set([sites[0]]) - asns[2].sites.set([sites[0]]) - asns[3].sites.set([sites[1]]) - asns[4].sites.set([sites[2]]) - asns[5].sites.set([sites[1]]) - asns[6].sites.set([sites[2]]) - asns[7].sites.set([sites[2]]) - asns[8].sites.set([sites[2]]) - asns[10].sites.set([sites[0]]) - cls.form_data = { 'name': 'Site X', 'slug': 'site-x', @@ -153,6 +136,7 @@ class SiteTestCase(ViewTestCases.PrimaryObjectViewTestCase): 'group': groups[1].pk, 'tenant': None, 'facility': 'Facility X', + 'asns': [asns[6].pk, asns[7].pk], 'time_zone': pytz.UTC, 'description': 'Site description', 'physical_address': '742 Evergreen Terrace, Springfield, USA', diff --git a/netbox/ipam/api/serializers.py b/netbox/ipam/api/serializers.py index eae653ad7..28be07334 100644 --- a/netbox/ipam/api/serializers.py +++ b/netbox/ipam/api/serializers.py @@ -23,7 +23,6 @@ from .nested_serializers import * class ASNSerializer(PrimaryModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='ipam-api:asn-detail') tenant = NestedTenantSerializer(required=False, allow_null=True) - site_count = serializers.IntegerField(read_only=True) class Meta: diff --git a/netbox/ipam/models/ip.py b/netbox/ipam/models/ip.py index ad707dda1..b6c0a1b6b 100644 --- a/netbox/ipam/models/ip.py +++ b/netbox/ipam/models/ip.py @@ -73,11 +73,12 @@ class RIR(OrganizationalModel): @extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks') class ASN(PrimaryModel): - + """ + An autonomous system (AS) number is typically used to represent an independent routing domain. A site can have + one or more ASNs assigned to it. + """ asn = ASNField( unique=True, - blank=False, - null=False, verbose_name='ASN', help_text='32-bit autonomous system number' ) @@ -89,8 +90,7 @@ class ASN(PrimaryModel): to='ipam.RIR', on_delete=models.PROTECT, related_name='asns', - blank=False, - null=False + verbose_name='RIR' ) tenant = models.ForeignKey( to='tenancy.Tenant',