diff --git a/docs/release-notes/version-2.10.md b/docs/release-notes/version-2.10.md index d62646bc4..d5df2ae49 100644 --- a/docs/release-notes/version-2.10.md +++ b/docs/release-notes/version-2.10.md @@ -4,6 +4,7 @@ ### Bug Fixes +* [#5450](https://github.com/netbox-community/netbox/issues/5450) - API serializer foreign count fields do not have a default value * [#5453](https://github.com/netbox-community/netbox/issues/5453) - Correct change log representation when creating a cable * [#5458](https://github.com/netbox-community/netbox/issues/5458) - Creating a component template throws an exception * [#5461](https://github.com/netbox-community/netbox/issues/5461) - Rack Elevations throw reverse match exception diff --git a/netbox/circuits/api/views.py b/netbox/circuits/api/views.py index ad497ee5f..ef5a944e2 100644 --- a/netbox/circuits/api/views.py +++ b/netbox/circuits/api/views.py @@ -1,4 +1,5 @@ from django.db.models import Prefetch +from django.db.models.functions import Coalesce from rest_framework.routers import APIRootView from circuits import filters @@ -24,7 +25,7 @@ class CircuitsRootView(APIRootView): class ProviderViewSet(CustomFieldModelViewSet): queryset = Provider.objects.prefetch_related('tags').annotate( - circuit_count=get_subquery(Circuit, 'provider') + circuit_count=Coalesce(get_subquery(Circuit, 'provider'), 0) ) serializer_class = serializers.ProviderSerializer filterset_class = filters.ProviderFilterSet @@ -36,7 +37,7 @@ class ProviderViewSet(CustomFieldModelViewSet): class CircuitTypeViewSet(ModelViewSet): queryset = CircuitType.objects.annotate( - circuit_count=get_subquery(Circuit, 'type') + circuit_count=Coalesce(get_subquery(Circuit, 'type'), 0) ) serializer_class = serializers.CircuitTypeSerializer filterset_class = filters.CircuitTypeFilterSet diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index 071174c76..db36c3176 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -3,6 +3,7 @@ from collections import OrderedDict from django.conf import settings from django.db.models import F +from django.db.models.functions import Coalesce from django.http import HttpResponseForbidden, HttpResponse from django.shortcuts import get_object_or_404 from drf_yasg import openapi @@ -119,12 +120,12 @@ class SiteViewSet(CustomFieldModelViewSet): queryset = Site.objects.prefetch_related( 'region', 'tenant', 'tags' ).annotate( - device_count=get_subquery(Device, 'site'), - rack_count=get_subquery(Rack, 'site'), - prefix_count=get_subquery(Prefix, 'site'), - vlan_count=get_subquery(VLAN, 'site'), - circuit_count=get_subquery(Circuit, 'terminations__site'), - virtualmachine_count=get_subquery(VirtualMachine, 'cluster__site'), + device_count=Coalesce(get_subquery(Device, 'site'), 0), + rack_count=Coalesce(get_subquery(Rack, 'site'), 0), + prefix_count=Coalesce(get_subquery(Prefix, 'site'), 0), + vlan_count=Coalesce(get_subquery(VLAN, 'site'), 0), + circuit_count=Coalesce(get_subquery(Circuit, 'terminations__site'), 0), + virtualmachine_count=Coalesce(get_subquery(VirtualMachine, 'cluster__site'), 0), ) serializer_class = serializers.SiteSerializer filterset_class = filters.SiteFilterSet @@ -152,7 +153,7 @@ class RackGroupViewSet(ModelViewSet): class RackRoleViewSet(ModelViewSet): queryset = RackRole.objects.annotate( - rack_count=get_subquery(Rack, 'role') + rack_count=Coalesce(get_subquery(Rack, 'role'), 0) ) serializer_class = serializers.RackRoleSerializer filterset_class = filters.RackRoleFilterSet @@ -166,8 +167,8 @@ class RackViewSet(CustomFieldModelViewSet): queryset = Rack.objects.prefetch_related( 'site', 'group__site', 'role', 'tenant', 'tags' ).annotate( - device_count=get_subquery(Device, 'rack'), - powerfeed_count=get_subquery(PowerFeed, 'rack') + device_count=Coalesce(get_subquery(Device, 'rack'), 0), + powerfeed_count=Coalesce(get_subquery(PowerFeed, 'rack'), 0) ) serializer_class = serializers.RackSerializer filterset_class = filters.RackFilterSet @@ -240,9 +241,9 @@ class RackReservationViewSet(ModelViewSet): class ManufacturerViewSet(ModelViewSet): queryset = Manufacturer.objects.annotate( - devicetype_count=get_subquery(DeviceType, 'manufacturer'), - inventoryitem_count=get_subquery(InventoryItem, 'manufacturer'), - platform_count=get_subquery(Platform, 'manufacturer') + devicetype_count=Coalesce(get_subquery(DeviceType, 'manufacturer'), 0), + inventoryitem_count=Coalesce(get_subquery(InventoryItem, 'manufacturer'), 0), + platform_count=Coalesce(get_subquery(Platform, 'manufacturer'), 0) ) serializer_class = serializers.ManufacturerSerializer filterset_class = filters.ManufacturerFilterSet @@ -254,7 +255,7 @@ class ManufacturerViewSet(ModelViewSet): class DeviceTypeViewSet(CustomFieldModelViewSet): queryset = DeviceType.objects.prefetch_related('manufacturer', 'tags').annotate( - device_count=get_subquery(Device, 'device_type') + device_count=Coalesce(get_subquery(Device, 'device_type'), 0) ) serializer_class = serializers.DeviceTypeSerializer filterset_class = filters.DeviceTypeFilterSet @@ -318,8 +319,8 @@ class DeviceBayTemplateViewSet(ModelViewSet): class DeviceRoleViewSet(ModelViewSet): queryset = DeviceRole.objects.annotate( - device_count=get_subquery(Device, 'device_role'), - virtualmachine_count=get_subquery(VirtualMachine, 'role') + device_count=Coalesce(get_subquery(Device, 'device_role'), 0), + virtualmachine_count=Coalesce(get_subquery(VirtualMachine, 'role'), 0) ) serializer_class = serializers.DeviceRoleSerializer filterset_class = filters.DeviceRoleFilterSet @@ -331,8 +332,8 @@ class DeviceRoleViewSet(ModelViewSet): class PlatformViewSet(ModelViewSet): queryset = Platform.objects.annotate( - device_count=get_subquery(Device, 'platform'), - virtualmachine_count=get_subquery(VirtualMachine, 'platform') + device_count=Coalesce(get_subquery(Device, 'platform'), 0), + virtualmachine_count=Coalesce(get_subquery(VirtualMachine, 'platform'), 0) ) serializer_class = serializers.PlatformSerializer filterset_class = filters.PlatformFilterSet @@ -596,7 +597,7 @@ class CableViewSet(ModelViewSet): class VirtualChassisViewSet(ModelViewSet): queryset = VirtualChassis.objects.prefetch_related('tags').annotate( - member_count=get_subquery(Device, 'virtual_chassis') + member_count=Coalesce(get_subquery(Device, 'virtual_chassis'), 0) ) serializer_class = serializers.VirtualChassisSerializer filterset_class = filters.VirtualChassisFilterSet @@ -610,7 +611,7 @@ class PowerPanelViewSet(ModelViewSet): queryset = PowerPanel.objects.prefetch_related( 'site', 'rack_group' ).annotate( - powerfeed_count=get_subquery(PowerFeed, 'power_panel') + powerfeed_count=Coalesce(get_subquery(PowerFeed, 'power_panel'), 0) ) serializer_class = serializers.PowerPanelSerializer filterset_class = filters.PowerPanelFilterSet diff --git a/netbox/extras/api/views.py b/netbox/extras/api/views.py index 38077c89a..fcd9add7c 100644 --- a/netbox/extras/api/views.py +++ b/netbox/extras/api/views.py @@ -1,4 +1,5 @@ from django.contrib.contenttypes.models import ContentType +from django.db.models.functions import Coalesce from django.http import Http404 from django_rq.queues import get_connection from rest_framework import status @@ -102,7 +103,7 @@ class ExportTemplateViewSet(ModelViewSet): class TagViewSet(ModelViewSet): queryset = Tag.objects.annotate( - tagged_items=get_subquery(TaggedItem, 'tag') + tagged_items=Coalesce(get_subquery(TaggedItem, 'tag'), 0) ) serializer_class = serializers.TagSerializer filterset_class = filters.TagFilterSet diff --git a/netbox/ipam/api/views.py b/netbox/ipam/api/views.py index 9d09bbe03..fb38edf46 100644 --- a/netbox/ipam/api/views.py +++ b/netbox/ipam/api/views.py @@ -1,4 +1,5 @@ from django.conf import settings +from django.db.models.functions import Coalesce from django.shortcuts import get_object_or_404 from django_pglocks import advisory_lock from drf_yasg.utils import swagger_auto_schema @@ -32,8 +33,8 @@ class VRFViewSet(CustomFieldModelViewSet): queryset = VRF.objects.prefetch_related('tenant').prefetch_related( 'import_targets', 'export_targets', 'tags' ).annotate( - ipaddress_count=get_subquery(IPAddress, 'vrf'), - prefix_count=get_subquery(Prefix, 'vrf') + ipaddress_count=Coalesce(get_subquery(IPAddress, 'vrf'), 0), + prefix_count=Coalesce(get_subquery(Prefix, 'vrf'), 0) ) serializer_class = serializers.VRFSerializer filterset_class = filters.VRFFilterSet @@ -55,7 +56,7 @@ class RouteTargetViewSet(CustomFieldModelViewSet): class RIRViewSet(ModelViewSet): queryset = RIR.objects.annotate( - aggregate_count=get_subquery(Aggregate, 'rir') + aggregate_count=Coalesce(get_subquery(Aggregate, 'rir'), 0) ) serializer_class = serializers.RIRSerializer filterset_class = filters.RIRFilterSet @@ -77,8 +78,8 @@ class AggregateViewSet(CustomFieldModelViewSet): class RoleViewSet(ModelViewSet): queryset = Role.objects.annotate( - prefix_count=get_subquery(Prefix, 'role'), - vlan_count=get_subquery(VLAN, 'role') + prefix_count=Coalesce(get_subquery(Prefix, 'role'), 0), + vlan_count=Coalesce(get_subquery(VLAN, 'role'), 0) ) serializer_class = serializers.RoleSerializer filterset_class = filters.RoleFilterSet @@ -272,7 +273,7 @@ class IPAddressViewSet(CustomFieldModelViewSet): class VLANGroupViewSet(ModelViewSet): queryset = VLANGroup.objects.prefetch_related('site').annotate( - vlan_count=get_subquery(VLAN, 'group') + vlan_count=Coalesce(get_subquery(VLAN, 'group'), 0) ) serializer_class = serializers.VLANGroupSerializer filterset_class = filters.VLANGroupFilterSet @@ -286,7 +287,7 @@ class VLANViewSet(CustomFieldModelViewSet): queryset = VLAN.objects.prefetch_related( 'site', 'group', 'tenant', 'role', 'tags' ).annotate( - prefix_count=get_subquery(Prefix, 'vlan') + prefix_count=Coalesce(get_subquery(Prefix, 'vlan'), 0) ) serializer_class = serializers.VLANSerializer filterset_class = filters.VLANFilterSet diff --git a/netbox/secrets/api/views.py b/netbox/secrets/api/views.py index 1153b0508..617da5c6e 100644 --- a/netbox/secrets/api/views.py +++ b/netbox/secrets/api/views.py @@ -1,6 +1,7 @@ import base64 from Crypto.PublicKey import RSA +from django.db.models.functions import Coalesce from django.http import HttpResponseBadRequest from rest_framework.exceptions import ValidationError from rest_framework.permissions import IsAuthenticated @@ -35,7 +36,7 @@ class SecretsRootView(APIRootView): class SecretRoleViewSet(ModelViewSet): queryset = SecretRole.objects.annotate( - secret_count=get_subquery(Secret, 'role') + secret_count=Coalesce(get_subquery(Secret, 'role'), 0) ) serializer_class = serializers.SecretRoleSerializer filterset_class = filters.SecretRoleFilterSet diff --git a/netbox/tenancy/api/views.py b/netbox/tenancy/api/views.py index 34be4991e..142203b58 100644 --- a/netbox/tenancy/api/views.py +++ b/netbox/tenancy/api/views.py @@ -1,3 +1,4 @@ +from django.db.models.functions import Coalesce from rest_framework.routers import APIRootView from circuits.models import Circuit @@ -46,13 +47,13 @@ class TenantViewSet(CustomFieldModelViewSet): ).annotate( circuit_count=get_subquery(Circuit, 'tenant'), device_count=get_subquery(Device, 'tenant'), - ipaddress_count=get_subquery(IPAddress, 'tenant'), - prefix_count=get_subquery(Prefix, 'tenant'), - rack_count=get_subquery(Rack, 'tenant'), - site_count=get_subquery(Site, 'tenant'), - virtualmachine_count=get_subquery(VirtualMachine, 'tenant'), - vlan_count=get_subquery(VLAN, 'tenant'), - vrf_count=get_subquery(VRF, 'tenant') + ipaddress_count=Coalesce(get_subquery(IPAddress, 'tenant'), 0), + prefix_count=Coalesce(get_subquery(Prefix, 'tenant'), 0), + rack_count=Coalesce(get_subquery(Rack, 'tenant'), 0), + site_count=Coalesce(get_subquery(Site, 'tenant'), 0), + virtualmachine_count=Coalesce(get_subquery(VirtualMachine, 'tenant'), 0), + vlan_count=Coalesce(get_subquery(VLAN, 'tenant'), 0), + vrf_count=Coalesce(get_subquery(VRF, 'tenant'), 0) ) serializer_class = serializers.TenantSerializer filterset_class = filters.TenantFilterSet diff --git a/netbox/virtualization/api/views.py b/netbox/virtualization/api/views.py index 2a2a149d4..e2d3d5ea5 100644 --- a/netbox/virtualization/api/views.py +++ b/netbox/virtualization/api/views.py @@ -1,3 +1,4 @@ +from django.db.models.functions import Coalesce from rest_framework.routers import APIRootView from dcim.models import Device @@ -22,7 +23,7 @@ class VirtualizationRootView(APIRootView): class ClusterTypeViewSet(ModelViewSet): queryset = ClusterType.objects.annotate( - cluster_count=get_subquery(Cluster, 'type') + cluster_count=Coalesce(get_subquery(Cluster, 'type'), 0) ) serializer_class = serializers.ClusterTypeSerializer filterset_class = filters.ClusterTypeFilterSet @@ -30,7 +31,7 @@ class ClusterTypeViewSet(ModelViewSet): class ClusterGroupViewSet(ModelViewSet): queryset = ClusterGroup.objects.annotate( - cluster_count=get_subquery(Cluster, 'group') + cluster_count=Coalesce(get_subquery(Cluster, 'group'), 0) ) serializer_class = serializers.ClusterGroupSerializer filterset_class = filters.ClusterGroupFilterSet @@ -40,8 +41,8 @@ class ClusterViewSet(CustomFieldModelViewSet): queryset = Cluster.objects.prefetch_related( 'type', 'group', 'tenant', 'site', 'tags' ).annotate( - device_count=get_subquery(Device, 'cluster'), - virtualmachine_count=get_subquery(VirtualMachine, 'cluster') + device_count=Coalesce(get_subquery(Device, 'cluster'), 0), + virtualmachine_count=Coalesce(get_subquery(VirtualMachine, 'cluster'), 0) ) serializer_class = serializers.ClusterSerializer filterset_class = filters.ClusterFilterSet