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:
@@ -4,8 +4,10 @@ from dcim.api.serializers import (
|
|||||||
DeviceRoleSerializer, DeviceTypeSerializer, LocationSerializer, PlatformSerializer, RackRoleSerializer,
|
DeviceRoleSerializer, DeviceTypeSerializer, LocationSerializer, PlatformSerializer, RackRoleSerializer,
|
||||||
RackSerializer, RackTypeSerializer, SiteSerializer,
|
RackSerializer, RackTypeSerializer, SiteSerializer,
|
||||||
)
|
)
|
||||||
|
from dcim.models import Location, Rack, Site
|
||||||
from netbox.api.serializers import NetBoxModelSerializer
|
from netbox.api.serializers import NetBoxModelSerializer
|
||||||
from tenancy.api.serializers import TenantGroupSerializer, TenantSerializer
|
from tenancy.api.serializers import TenantGroupSerializer, TenantSerializer
|
||||||
|
from tenancy.models import Tenant
|
||||||
|
|
||||||
from ..choices import ConceptStatusChoices
|
from ..choices import ConceptStatusChoices
|
||||||
from ..models import ConceptDeployment, ConceptDevice, RackConcept
|
from ..models import ConceptDeployment, ConceptDevice, RackConcept
|
||||||
@@ -84,43 +86,31 @@ class ConceptDeploymentSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
class RackCopySerializer(serializers.Serializer):
|
class RackCopySerializer(serializers.Serializer):
|
||||||
"""POST body for copying a real rack into a new concept."""
|
"""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)
|
name = serializers.CharField(max_length=100)
|
||||||
status = serializers.ChoiceField(choices=ConceptStatusChoices, default=ConceptStatusChoices.STATUS_DRAFT)
|
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='')
|
description = serializers.CharField(max_length=200, required=False, allow_blank=True, default='')
|
||||||
include_devices = serializers.BooleanField(default=True)
|
include_devices = serializers.BooleanField(default=True)
|
||||||
include_device_names = 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):
|
class RackConceptCloneSerializer(serializers.Serializer):
|
||||||
"""POST body for duplicating a concept."""
|
"""POST body for duplicating a concept."""
|
||||||
name = serializers.CharField(max_length=100)
|
name = serializers.CharField(max_length=100)
|
||||||
status = serializers.ChoiceField(choices=ConceptStatusChoices, default=ConceptStatusChoices.STATUS_DRAFT)
|
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)
|
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):
|
class ConceptDeploySerializer(serializers.Serializer):
|
||||||
"""POST body for materialising a concept."""
|
"""POST body for materialising a concept."""
|
||||||
name_pattern = serializers.CharField(max_length=100)
|
name_pattern = serializers.CharField(max_length=100)
|
||||||
count = serializers.IntegerField(min_value=1, max_value=100, default=1)
|
count = serializers.IntegerField(min_value=1, max_value=100, default=1)
|
||||||
start_index = serializers.IntegerField(default=1)
|
start_index = serializers.IntegerField(default=1)
|
||||||
site = serializers.PrimaryKeyRelatedField(queryset=None)
|
site = serializers.PrimaryKeyRelatedField(queryset=Site.objects.all())
|
||||||
location = serializers.PrimaryKeyRelatedField(queryset=None, required=False, allow_null=True)
|
location = serializers.PrimaryKeyRelatedField(queryset=Location.objects.all(), required=False, allow_null=True)
|
||||||
tenant = serializers.PrimaryKeyRelatedField(queryset=None, required=False, allow_null=True)
|
tenant = serializers.PrimaryKeyRelatedField(queryset=Tenant.objects.all(), required=False, allow_null=True)
|
||||||
create_devices = serializers.BooleanField(default=True)
|
create_devices = serializers.BooleanField(default=True)
|
||||||
device_name_pattern = serializers.CharField(max_length=100, required=False, allow_blank=True, default='')
|
device_name_pattern = serializers.CharField(max_length=100, required=False, allow_blank=True, default='')
|
||||||
skip_unnamed_devices = serializers.BooleanField(default=True)
|
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'
|
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):
|
def validate(self, attrs):
|
||||||
from dcim.models import Rack
|
|
||||||
try:
|
try:
|
||||||
names = [
|
names = [
|
||||||
attrs['name_pattern'].format(n=i)
|
attrs['name_pattern'].format(n=i)
|
||||||
|
|||||||
Reference in New Issue
Block a user