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 <noreply@anthropic.com>
This commit is contained in:
2026-09-16 09:21:08 +02:00
co-authored by Claude Sonnet 5
parent f0d62bafd9
commit a705b64c22
+8 -27
View File
@@ -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)