Django REST Framework嵌套序列化器更新时无法关闭Slug唯一验证的问题排查
解决嵌套序列化器中更新唯一Slug字段的验证问题
我明白你遇到的问题了——当更新包含唯一slug字段的嵌套序列化器时,即使尝试关闭验证器,还是会触发“slug已存在”的错误。这是因为默认的UniqueValidator在验证时不会自动排除当前正在更新的实例,而且你之前在视图里设置的extra_kwargs并没有生效(那不是ViewSet的属性,而是序列化器的配置项)。下面给你两种可行的解决方案:
方案一:自定义Slug字段的验证逻辑
通过重写序列化器的validate_slug方法,让验证器在更新操作时排除当前实例,既保留创建时的唯一性检查,又允许更新时使用自身的slug或未被占用的slug。
修改AnswerSerializer
from rest_framework import serializers from .models import Question, Answer class AnswerSerializer(serializers.ModelSerializer): """Serialize Answer model""" slug = serializers.SlugField() class Meta: model = Answer fields = ('title', 'body', 'slug', 'author', 'question') read_only_fields = ('question',) lookup_field = 'slug' def validate_slug(self, value): # 获取当前更新的Answer实例(仅更新操作时有值) instance = self.instance if instance: # 检查除当前实例外,是否有其他Answer使用该slug if Answer.objects.exclude(pk=instance.pk).filter(slug=value).exists(): raise serializers.ValidationError("This slug already exists") else: # 创建操作时正常检查全局唯一性 if Answer.objects.filter(slug=value).exists(): raise serializers.ValidationError("This slug already exists") return value
修改QuestionSerializer
同样给Question的slug字段添加自定义验证:
class QuestionSerializer(serializers.ModelSerializer): """Serialize Question model""" answer = AnswerSerializer(read_only=False, source='answers', many=True,) slug = serializers.SlugField() class Meta: model = Question fields = ('title', 'body', 'slug', 'author', 'category', 'answer',) lookup_field = 'slug' def validate_slug(self, value): instance = self.instance if instance: if Question.objects.exclude(pk=instance.pk).filter(slug=value).exists(): raise serializers.ValidationError("This slug already exists") else: if Question.objects.filter(slug=value).exists(): raise serializers.ValidationError("This slug already exists") return value # 保留原有的create和update方法 def create(self, validated_data): answers_data = validated_data.pop('answers') question = Question.objects.create(**validated_data) for answer_data in answers_data: Answer.objects.create(question=question, **answer_data) return question def update(self, instance, validated_data): instance.title = validated_data.get('title', instance.title) instance.body = validated_data.get('body', instance.body) instance.slug = validated_data.get('slug', instance.slug) instance.author = validated_data.get('author', instance.author) instance.category = validated_data.get('category', instance.category) instance.save() answers_data = validated_data.pop('answers', []) existing_answers = list(instance.answers.all()) # 更新现有Answer for answer_data, existing_answer in zip(answers_data, existing_answers): existing_answer.title = answer_data.get('title', existing_answer.title) existing_answer.body = answer_data.get('body', existing_answer.body) existing_answer.slug = answer_data.get('slug', existing_answer.slug) existing_answer.author = answer_data.get('author', existing_answer.author) existing_answer.save() # 处理新增的Answer for answer_data in answers_data[len(existing_answers):]: Answer.objects.create(question=instance, **answer_data) # 处理多余的Answer(可选,根据需求决定是否删除) for existing_answer in existing_answers[len(answers_data):]: existing_answer.delete() return instance
方案二:动态调整UniqueValidator的查询集
利用序列化器的get_fields方法,在更新操作时自动修改UniqueValidator的查询集,排除当前实例:
修改AnswerSerializer
class AnswerSerializer(serializers.ModelSerializer): """Serialize Answer model""" class Meta: model = Answer fields = ('title', 'body', 'slug', 'author', 'question') read_only_fields = ('question',) lookup_field = 'slug' def get_fields(self): fields = super().get_fields() instance = self.instance if instance: # 找到UniqueValidator并修改其查询集 for validator in fields['slug'].validators: if isinstance(validator, serializers.UniqueValidator): validator.queryset = validator.queryset.exclude(pk=instance.pk) return fields
修改QuestionSerializer
class QuestionSerializer(serializers.ModelSerializer): """Serialize Question model""" answer = AnswerSerializer(read_only=False, source='answers', many=True,) class Meta: model = Question fields = ('title', 'body', 'slug', 'author', 'category', 'answer',) lookup_field = 'slug' def get_fields(self): fields = super().get_fields() instance = self.instance if instance: for validator in fields['slug'].validators: if isinstance(validator, serializers.UniqueValidator): validator.queryset = validator.queryset.exclude(pk=instance.pk) return fields # 保留原有的create和优化后的update方法
为什么你之前的方法无效?
你在QuestionViewSet中设置的extra_kwargs = {'slug': {'validators': []}}是无效的——extra_kwargs是序列化器Meta类的配置项,不是ViewSet的属性,所以验证器并没有被移除。而且完全移除验证器会导致创建时也不检查slug唯一性,这通常不是我们想要的结果。
内容的提问来源于stack exchange,提问作者Kentypop
相关产品推荐
相关产品推荐

