DRF Serializer字段queryset未生效,图书ID验证失败问题排查
Django序列化器验证图书不存在的问题排查与解决
问题描述
现有Django模型:
from django.db import models from django.db.models import Model, CharField, ForeignKey, ManyToManyField, CASCADE class Author(Model): name = CharField(max_length=256) class Library(Model): address = CharField(max_length=256) books = ManyToManyField("Book") class Book(Model): name = CharField(max_length=256) created_by = ForeignKey(Author, on_delete=CASCADE)
需求是让作者通过ID数组创建关联图书的新Library,仅允许选择该作者创建的图书;未传入author则拒绝所有图书。编写的序列化器如下:
from rest_framework.serializers import ModelSerializer, PrimaryKeyRelatedField class LibrarySerializer(ModelSerializer): books = PrimaryKeyRelatedField(many=True, queryset=Book.objects.none()) class Meta: model = Library fields = ("books", "address") def __init__(self, *args, **kwargs): self.author = kwargs.pop("author", None) super().__init__(*args, **kwargs) if self.author: self.fields["books"].queryset = Book.objects.filter(created_by=self.author)
执行测试代码时:
author = Author(name="John") author.save() book = Book(created_by=author, name="book_one") book.save() serializer = LibrarySerializer(data={"books": [book.id], "address": "address_one"}, author=author)
调用serializer.is_valid()时,提示“给定的图书不存在”。
问题原因
核心问题是序列化器初始化时修改queryset的时机错误:PrimaryKeyRelatedField在字段初始化阶段,会将传入的queryset缓存为自身的实例变量,后续验证逻辑使用的是这个缓存的变量,而非你在__init__中修改的self.fields["books"].queryset。
简单说:你修改的是字段对象的queryset属性,但字段内部验证用的是初始化时就存好的空queryset(Book.objects.none()),自然找不到对应的图书ID,触发验证错误。
排查步骤
- 打印验证前的queryset:在调用
is_valid()前执行print(serializer.fields['books'].queryset),如果输出是空的QuerySet,说明修改的queryset没生效到验证逻辑里。
- 打印验证前的queryset:在调用
- 检查字段内部缓存:直接查看字段的
_queryset属性(print(serializer.fields['books']._queryset)),会发现它还是初始的空queryset,验证逻辑用的就是这个。
- 检查字段内部缓存:直接查看字段的
- 单独测试字段验证:手动调用
serializer.fields['books'].to_internal_value([book.id]),会直接抛出“对象不存在”的异常,确认问题出在字段验证阶段。
- 单独测试字段验证:手动调用
解决方案
方法一:自定义字段重写get_queryset(推荐)
通过自定义字段,让它动态从上下文获取author并返回正确的queryset:
class AuthorBookPrimaryKeyRelatedField(PrimaryKeyRelatedField): def get_queryset(self): author = self.context.get('author') return Book.objects.filter(created_by=author) if author else Book.objects.none() class LibrarySerializer(ModelSerializer): books = AuthorBookPrimaryKeyRelatedField(many=True) class Meta: model = Library fields = ("books", "address") # 使用时把author传入context serializer = LibrarySerializer( data={"books": [book.id], "address": "address_one"}, context={'author': author} )
方法二:在序列化器__init__中重新创建字段
如果不想自定义字段,可以直接替换原有的books字段,让它使用更新后的queryset:
class LibrarySerializer(ModelSerializer): class Meta: model = Library fields = ("books", "address") def __init__(self, *args, **kwargs): self.author = kwargs.pop("author", None) super().__init__(*args, **kwargs) # 重新创建字段并替换原有字段 queryset = Book.objects.filter(created_by=self.author) if self.author else Book.objects.none() self.fields['books'] = PrimaryKeyRelatedField(many=True, queryset=queryset)
验证修改
修改后重新运行测试代码,调用serializer.is_valid()会返回True,serializer.validated_data中能正确获取到关联的图书对象。
内容的提问来源于stack exchange,提问作者Roxell
相关产品推荐
相关产品推荐

