diff --git a/netbox/ipam/api/serializers_/ip.py b/netbox/ipam/api/serializers_/ip.py index b515838ff..1efbdec70 100644 --- a/netbox/ipam/api/serializers_/ip.py +++ b/netbox/ipam/api/serializers_/ip.py @@ -67,11 +67,17 @@ 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', 'family', 'aggregate', 'parent', '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') + brief_fields = ('id', 'url', 'display', 'family', 'aggregate', 'parent', 'prefix', 'description', '_depth') + + def get_fields(self): + fields = super(PrefixSerializer, self).get_fields() + fields['parent'] = PrefixSerializer(nested=True, read_only=True) + + return fields @extend_schema_field(serializers.JSONField(allow_null=True)) def get_scope(self, obj): @@ -146,12 +152,12 @@ class IPRangeSerializer(NetBoxModelSerializer): class Meta: model = IPRange fields = [ - 'id', 'url', 'display_url', 'display', 'family', 'start_address', 'end_address', 'size', 'vrf', 'tenant', - 'status', 'role', 'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', + 'id', 'url', 'display_url', 'display', 'family', 'prefix', 'start_address', 'end_address', 'size', 'vrf', + 'tenant', 'status', 'role', 'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'mark_populated', 'mark_utilized', 'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', ] - brief_fields = ('id', 'url', 'display', 'family', 'start_address', 'end_address', 'description') + brief_fields = ('id', 'url', 'display', 'family', 'prefix', 'start_address', 'end_address', 'description') # @@ -178,11 +184,11 @@ class IPAddressSerializer(NetBoxModelSerializer): class Meta: model = IPAddress fields = [ - 'id', 'url', 'display_url', 'display', 'family', 'address', 'vrf', 'tenant', 'status', 'role', + 'id', 'url', 'display_url', 'display', 'family', 'prefix', 'address', 'vrf', 'tenant', 'status', 'role', 'assigned_object_type', 'assigned_object_id', 'assigned_object', 'nat_inside', 'nat_outside', 'dns_name', 'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', ] - brief_fields = ('id', 'url', 'display', 'family', 'address', 'description') + brief_fields = ('id', 'url', 'display', 'family', 'prefix', 'address', 'description') @extend_schema_field(serializers.JSONField(allow_null=True)) def get_assigned_object(self, obj): diff --git a/netbox/ipam/graphql/types.py b/netbox/ipam/graphql/types.py index b35d44df5..39eec74e5 100644 --- a/netbox/ipam/graphql/types.py +++ b/netbox/ipam/graphql/types.py @@ -169,6 +169,7 @@ class IPAddressType(NetBoxObjectType, ContactsMixin, BaseIPAddressFamilyType): pagination=True ) class IPRangeType(NetBoxObjectType, ContactsMixin): + prefix: Annotated["PrefixType", strawberry.lazy('ipam.graphql.types')] | None start_address: str end_address: str vrf: Annotated["VRFType", strawberry.lazy('ipam.graphql.types')] | None @@ -183,6 +184,8 @@ class IPRangeType(NetBoxObjectType, ContactsMixin): pagination=True ) class PrefixType(NetBoxObjectType, ContactsMixin, BaseIPAddressFamilyType): + aggregate: Annotated["AggregateType", strawberry.lazy('ipam.graphql.types')] | None + parent: Annotated["PrefixType", strawberry.lazy('ipam.graphql.types')] | None prefix: str vrf: Annotated["VRFType", strawberry.lazy('ipam.graphql.types')] | None tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None diff --git a/netbox/ipam/signals.py b/netbox/ipam/signals.py index 450e89758..d7085f8b5 100644 --- a/netbox/ipam/signals.py +++ b/netbox/ipam/signals.py @@ -195,6 +195,7 @@ def handle_prefix_saved(instance, created, **kwargs): if created or instance.vrf_id != instance._vrf_id or instance.prefix != instance._prefix: update_ipaddress_prefix(instance) + update_iprange_prefix(instance) update_prefix_parents(instance) update_parents_children(instance) update_children_depth(instance) @@ -209,6 +210,7 @@ def handle_prefix_saved(instance, created, **kwargs): @receiver(pre_delete, sender=Prefix) def pre_handle_prefix_deleted(instance, **kwargs): update_ipaddress_prefix(instance, True) + update_iprange_prefix(instance, True) update_prefix_parents(instance, delete=True) @@ -218,6 +220,7 @@ def handle_prefix_deleted(instance, **kwargs): update_parents_children(instance) update_children_depth(instance) update_ipaddress_prefix(instance, delete=True) + update_iprange_prefix(instance, delete=True) update_prefix_parents(instance, delete=True) diff --git a/netbox/ipam/tests/test_api.py b/netbox/ipam/tests/test_api.py index 907924f25..79616ff4e 100644 --- a/netbox/ipam/tests/test_api.py +++ b/netbox/ipam/tests/test_api.py @@ -357,7 +357,7 @@ class RoleTest(APIViewTestCases.APIViewTestCase): class PrefixTest(APIViewTestCases.APIViewTestCase): model = Prefix # TODO: Alter for parent prefix - brief_fields = ['_depth', 'description', 'display', 'family', 'id', 'prefix', 'url'] + brief_fields = ['_depth', 'aggregate', 'description', 'display', 'family', 'id', 'parent', 'prefix', 'url'] create_data = [ { 'prefix': '192.168.4.0/24', @@ -537,7 +537,7 @@ class PrefixTest(APIViewTestCases.APIViewTestCase): class IPRangeTest(APIViewTestCases.APIViewTestCase): model = IPRange # TODO: Alter for parent prefix - brief_fields = ['description', 'display', 'end_address', 'family', 'id', 'start_address', 'url'] + brief_fields = ['description', 'display', 'end_address', 'family', 'id', 'prefix', 'start_address', 'url'] create_data = [ { 'start_address': '192.168.4.10/24', @@ -637,7 +637,7 @@ class IPRangeTest(APIViewTestCases.APIViewTestCase): class IPAddressTest(APIViewTestCases.APIViewTestCase): model = IPAddress # TODO: Alter for parent prefix - brief_fields = ['address', 'description', 'display', 'family', 'id', 'url'] + brief_fields = ['address', 'description', 'display', 'family', 'id', 'prefix', 'url'] create_data = [ { 'address': '192.168.0.4/24', diff --git a/netbox/ipam/tests/test_models.py b/netbox/ipam/tests/test_models.py index c5efb8efa..764573b48 100644 --- a/netbox/ipam/tests/test_models.py +++ b/netbox/ipam/tests/test_models.py @@ -252,10 +252,10 @@ class TestPrefix(TestCase): prefix=IPNetwork('10.0.0.0/16'), status=PrefixStatusChoices.STATUS_CONTAINER ) ips = IPAddress.objects.bulk_create(( - IPAddress(address=IPNetwork('10.0.0.1/24'), vrf=None), - IPAddress(address=IPNetwork('10.0.1.1/24'), vrf=vrfs[0]), - IPAddress(address=IPNetwork('10.0.2.1/24'), vrf=vrfs[1]), - IPAddress(address=IPNetwork('10.0.3.1/24'), vrf=vrfs[2]), + IPAddress(prefix=parent_prefix, address=IPNetwork('10.0.0.1/24'), vrf=None), + IPAddress(prefix=parent_prefix, address=IPNetwork('10.0.1.1/24'), vrf=vrfs[0]), + IPAddress(prefix=parent_prefix, address=IPNetwork('10.0.2.1/24'), vrf=vrfs[1]), + IPAddress(prefix=parent_prefix, address=IPNetwork('10.0.3.1/24'), vrf=vrfs[2]), )) child_ip_pks = {p.pk for p in parent_prefix.ip_addresses.all()} diff --git a/netbox/ipam/tests/test_views.py b/netbox/ipam/tests/test_views.py index a9b0dd227..ba739779a 100644 --- a/netbox/ipam/tests/test_views.py +++ b/netbox/ipam/tests/test_views.py @@ -481,9 +481,9 @@ class PrefixTestCase(ViewTestCases.PrimaryObjectViewTestCase): def test_prefix_ipranges(self): prefix = Prefix.objects.create(prefix=IPNetwork('192.168.0.0/16')) ip_ranges = ( - IPRange(start_address='192.168.0.1/24', end_address='192.168.0.100/24', size=99), - IPRange(start_address='192.168.1.1/24', end_address='192.168.1.100/24', size=99), - IPRange(start_address='192.168.2.1/24', end_address='192.168.2.100/24', size=99), + IPRange(prefix=prefix, start_address='192.168.0.1/24', end_address='192.168.0.100/24', size=99), + IPRange(prefix=prefix, start_address='192.168.1.1/24', end_address='192.168.1.100/24', size=99), + IPRange(prefix=prefix, start_address='192.168.2.1/24', end_address='192.168.2.100/24', size=99), ) IPRange.objects.bulk_create(ip_ranges) self.assertEqual(prefix.get_child_ranges().count(), 3) @@ -495,9 +495,9 @@ class PrefixTestCase(ViewTestCases.PrimaryObjectViewTestCase): def test_prefix_ipaddresses(self): prefix = Prefix.objects.create(prefix=IPNetwork('192.168.0.0/16')) ip_addresses = ( - IPAddress(address=IPNetwork('192.168.0.1/16')), - IPAddress(address=IPNetwork('192.168.0.2/16')), - IPAddress(address=IPNetwork('192.168.0.3/16')), + IPAddress(prefix=prefix, address=IPNetwork('192.168.0.1/16')), + IPAddress(prefix=prefix, address=IPNetwork('192.168.0.2/16')), + IPAddress(prefix=prefix, address=IPNetwork('192.168.0.3/16')), ) IPAddress.objects.bulk_create(ip_addresses) self.assertEqual(prefix.ip_addresses.all().count(), 3)