From 1777d4228e995230f4b6eeb9df733d0479d9ec80 Mon Sep 17 00:00:00 2001 From: Daniel Sheppard Date: Mon, 17 Mar 2025 21:52:08 -0500 Subject: [PATCH] Work on 11507 * Add `rir` property method to model * Add `aggregate` property method to model * Add `rir` attribute to serializer * Add `aggregate` attribute to serializer * Add test for both rir and aggregate fields on API --- netbox/ipam/api/serializers_/ip.py | 8 +++++--- netbox/ipam/models/ip.py | 9 +++++++++ netbox/ipam/tests/test_api.py | 27 +++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/netbox/ipam/api/serializers_/ip.py b/netbox/ipam/api/serializers_/ip.py index bfc7ac5465c..23ed7c21e21 100644 --- a/netbox/ipam/api/serializers_/ip.py +++ b/netbox/ipam/api/serializers_/ip.py @@ -44,6 +44,8 @@ class Meta: class PrefixSerializer(NetBoxModelSerializer): + aggregate = AggregateSerializer(nested=True, read_only=True, allow_null=True) + rir = RIRSerializer(nested=True, read_only=True, allow_null=True) family = ChoiceField(choices=IPAddressFamilyChoices, read_only=True) vrf = VRFSerializer(nested=True, required=False, allow_null=True) scope_type = ContentTypeField( @@ -67,9 +69,9 @@ class PrefixSerializer(NetBoxModelSerializer): class Meta: model = Prefix fields = [ - 'id', 'url', 'display_url', 'display', 'family', 'prefix', 'vrf', 'scope_type', 'scope_id', 'scope', - 'tenant', 'vlan', 'status', 'role', 'is_pool', 'mark_utilized', 'description', 'comments', 'tags', - 'custom_fields', 'created', 'last_updated', 'children', '_depth', + 'id', 'url', 'display_url', 'display', 'aggregate', 'rir', 'family', 'prefix', 'vrf', 'scope_type', + 'scope_id', 'scope', 'tenant', 'vlan', 'status', 'role', 'is_pool', 'mark_utilized', 'description', + 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'children', '_depth', ] brief_fields = ('id', 'url', 'display', 'family', 'prefix', 'description', '_depth') diff --git a/netbox/ipam/models/ip.py b/netbox/ipam/models/ip.py index e1a8d91e307..b22920739a3 100644 --- a/netbox/ipam/models/ip.py +++ b/netbox/ipam/models/ip.py @@ -322,6 +322,15 @@ def save(self, *args, **kwargs): super().save(*args, **kwargs) + @property + def aggregate(self): + return Aggregate.objects.filter(prefix__net_contains_or_equals=self.prefix).first() + + @property + def rir(self): + aggregate = self.aggregate + return aggregate.rir if aggregate else None + @property def family(self): return self.prefix.version if self.prefix else None diff --git a/netbox/ipam/tests/test_api.py b/netbox/ipam/tests/test_api.py index e9dcacc169d..b6771b54318 100644 --- a/netbox/ipam/tests/test_api.py +++ b/netbox/ipam/tests/test_api.py @@ -532,6 +532,33 @@ def test_create_multiple_available_ips(self): self.assertHttpStatus(response, status.HTTP_201_CREATED) self.assertEqual(len(response.data), 8) + def test_get_prefix_with_aggregate_and_rir(self): + self.add_permissions('ipam.view_prefix') + rir = RIR.objects.create(name='RFC 1918', slug='rfc-1918') + aggregate = Aggregate.objects.create(prefix=IPNetwork('192.168.0.0/16'), rir=rir) + prefixes = [ + Prefix.objects.filter(prefix=IPNetwork('192.168.2.0/24')).first(), + Prefix.objects.create(prefix=IPNetwork('10.0.0.0/24')) + ] + + self.assertIsNotNone(prefixes[0]) + + url = self._get_detail_url(prefixes[0]) + response = self.client.get(url, **self.header) + self.assertIsNotNone(prefixes[0].aggregate) + self.assertHttpStatus(response, status.HTTP_200_OK) + self.assertIsNotNone(response.data.get('aggregate')) + self.assertIsNotNone(response.data.get('rir')) + self.assertEqual(response.data.get('aggregate').get('id', None), aggregate.pk) + self.assertEqual(response.data.get('rir').get('id', None), rir.pk) + + url = self._get_detail_url(prefixes[1]) + response = self.client.get(url, **self.header) + self.assertIsNone(prefixes[1].aggregate) + self.assertHttpStatus(response, status.HTTP_200_OK) + self.assertIsNone(response.data.get('aggregate')) + self.assertIsNone(response.data.get('rir')) + class IPRangeTest(APIViewTestCases.APIViewTestCase): model = IPRange