From a705b64c222e1329882ee4ecd43e4f411809d0d8 Mon Sep 17 00:00:00 2001 From: Louis Date: Wed, 16 Sep 2026 09:21:08 +0200 Subject: [PATCH] Fix AssertionError on API load: PrimaryKeyRelatedField needs a real queryset DRF's RelatedField asserts queryset is not None (unless read_only) at __init__ time, i.e. when the class body of the Serializer is evaluated - not when the field is later used. The previous code passed queryset=None and tried to patch it in the serializer's own __init__, which never ran in time because construction of the field itself already raised. Pass the real querysets directly at class-body level instead, matching the standard DRF/NetBox pattern; the deferred per-instance model imports are no longer needed since serializers are only imported once all apps are ready anyway. Co-Authored-By: Claude Sonnet 5 --- netbox_rack_concept/api/serializers.py | 35 ++++++-------------------- 1 file changed, 8 insertions(+), 27 deletions(-) diff --git a/netbox_rack_concept/api/serializers.py b/netbox_rack_concept/api/serializers.py index 4a34d09..c433590 100644 --- a/netbox_rack_concept/api/serializers.py +++ b/netbox_rack_concept/api/serializers.py @@ -4,8 +4,10 @@ from dcim.api.serializers import ( DeviceRoleSerializer, DeviceTypeSerializer, LocationSerializer, PlatformSerializer, RackRoleSerializer, RackSerializer, RackTypeSerializer, SiteSerializer, ) +from dcim.models import Location, Rack, Site from netbox.api.serializers import NetBoxModelSerializer from tenancy.api.serializers import TenantGroupSerializer, TenantSerializer +from tenancy.models import Tenant from ..choices import ConceptStatusChoices from ..models import ConceptDeployment, ConceptDevice, RackConcept @@ -84,43 +86,31 @@ class ConceptDeploymentSerializer(serializers.ModelSerializer): class RackCopySerializer(serializers.Serializer): """POST body for copying a real rack into a new concept.""" - rack = serializers.PrimaryKeyRelatedField(queryset=None) + rack = serializers.PrimaryKeyRelatedField(queryset=Rack.objects.all()) name = serializers.CharField(max_length=100) status = serializers.ChoiceField(choices=ConceptStatusChoices, default=ConceptStatusChoices.STATUS_DRAFT) - tenant = serializers.PrimaryKeyRelatedField(queryset=None, required=False, allow_null=True) + tenant = serializers.PrimaryKeyRelatedField(queryset=Tenant.objects.all(), required=False, allow_null=True) description = serializers.CharField(max_length=200, required=False, allow_blank=True, default='') include_devices = serializers.BooleanField(default=True) include_device_names = serializers.BooleanField(default=True) - def __init__(self, *args, **kwargs): - from dcim.models import Rack - from tenancy.models import Tenant - super().__init__(*args, **kwargs) - self.fields['rack'].queryset = Rack.objects.all() - self.fields['tenant'].queryset = Tenant.objects.all() - class RackConceptCloneSerializer(serializers.Serializer): """POST body for duplicating a concept.""" name = serializers.CharField(max_length=100) status = serializers.ChoiceField(choices=ConceptStatusChoices, default=ConceptStatusChoices.STATUS_DRAFT) - tenant = serializers.PrimaryKeyRelatedField(queryset=None, required=False, allow_null=True) + tenant = serializers.PrimaryKeyRelatedField(queryset=Tenant.objects.all(), required=False, allow_null=True) include_devices = serializers.BooleanField(default=True) - def __init__(self, *args, **kwargs): - from tenancy.models import Tenant - super().__init__(*args, **kwargs) - self.fields['tenant'].queryset = Tenant.objects.all() - class ConceptDeploySerializer(serializers.Serializer): """POST body for materialising a concept.""" name_pattern = serializers.CharField(max_length=100) count = serializers.IntegerField(min_value=1, max_value=100, default=1) start_index = serializers.IntegerField(default=1) - site = serializers.PrimaryKeyRelatedField(queryset=None) - location = serializers.PrimaryKeyRelatedField(queryset=None, required=False, allow_null=True) - tenant = serializers.PrimaryKeyRelatedField(queryset=None, required=False, allow_null=True) + site = serializers.PrimaryKeyRelatedField(queryset=Site.objects.all()) + location = serializers.PrimaryKeyRelatedField(queryset=Location.objects.all(), required=False, allow_null=True) + tenant = serializers.PrimaryKeyRelatedField(queryset=Tenant.objects.all(), required=False, allow_null=True) create_devices = serializers.BooleanField(default=True) device_name_pattern = serializers.CharField(max_length=100, required=False, allow_blank=True, default='') skip_unnamed_devices = serializers.BooleanField(default=True) @@ -129,16 +119,7 @@ class ConceptDeploySerializer(serializers.Serializer): help_text='Return the planned rack names and warnings without creating anything' ) - def __init__(self, *args, **kwargs): - from dcim.models import Location, Site - from tenancy.models import Tenant - super().__init__(*args, **kwargs) - self.fields['site'].queryset = Site.objects.all() - self.fields['location'].queryset = Location.objects.all() - self.fields['tenant'].queryset = Tenant.objects.all() - def validate(self, attrs): - from dcim.models import Rack try: names = [ attrs['name_pattern'].format(n=i)