fix(validators): guard UniqueConstraint validators against queryset instance in ListSerializer (fixes #9484)#10000
Open
AdarshJ173 wants to merge 1 commit into
Conversation
…nstance Fixes encode#9484 When a ModelSerializer is used with many=True and an instance queryset (e.g. for bulk create via ListSerializer), the child serializer receives the parent queryset as its `.instance` instead of a model instance. This causes a hard crash in `UniqueTogetherValidator.exclude_current_instance` and `UniqueValidator.exclude_current_instance` when they call `queryset.exclude(pk=instance.pk)` — because a QuerySet has no `.pk` attribute: AttributeError: 'BaseQuerySet' object has no attribute 'pk' Reproducing case (from issue encode#9484): class PetSerializer(serializers.ModelSerializer): class Meta: model = Pet # has UniqueConstraint on (name, animal_type) fields = ('name', 'animal_type', 'can_fly') # This crashes on .is_valid() serializer = PetListSerializer( instances, # <- queryset, not a single instance data=[{"name": "Polly", "animal_type": "Parrot", "can_fly": True}, ...], many=True, ) Root cause: `many_init` correctly passes `instance` to the ListSerializer, but the child serializer (PetSerializer) inherits the same `instance` reference — which is the queryset. When the UniqueConstraint validators run on the child, `serializer.instance` is the queryset, and `.pk` is undefined on it. Fix: Add a `_is_single_instance` helper that returns True only when `instance` is a non-None, non-queryset, non-list object that has a `.pk` attribute. Both `UniqueValidator.exclude_current_instance` and `UniqueTogetherValidator.exclude_current_instance` now call this helper before accessing `instance.pk`, treating a queryset/list as "no instance" (i.e. a create operation) for the purposes of the uniqueness exclusion. This is the minimal, backward-compatible fix: - Single-instance update: behaviour unchanged (instance.pk still used) - Bulk create (ListSerializer + queryset): validators no longer crash, correctly treat each item as a create - Existing tests: all pass unchanged
auvipy
requested changes
Jul 22, 2026
auvipy
left a comment
Collaborator
There was a problem hiding this comment.
this will also need appropriate test coverage to verify
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Fixes #9484 —
AttributeError: 'BaseQuerySet' object has no attribute 'pk'when usingModelSerializerwithmany=Trueon a model that has aUniqueConstraint.Root Cause
When
many=Trueis passed to aModelSerializer,many_initcreates aListSerializerwrapping the child serializer. Theinstancekwarg (aQuerySet) is forwarded to both theListSerializerand thechildserializer. When the child'sUniqueTogetherValidator(orUniqueValidator) runs during validation, it calls:Because
instanceis the entireQuerySet, not a model instance,.pkraisesAttributeError.Reproducer (from #9484)
Fix
Introduces a small private helper
_is_single_instance(instance)that returnsTrueonly ifinstanceis a non-None, non-QuerySet, non-list object that has a.pkattribute. BothUniqueValidator.exclude_current_instanceandUniqueTogetherValidator.exclude_current_instance(andBaseUniqueForValidator.exclude_current_instance) now use this helper instead ofif instance is not None.This is minimal and fully backward-compatible:
instance.pkstill used to exclude the current object.instance): validators no longer crash; treat each child item as a create.Noneinstance (plain create): unchanged.The fix also corrects
UniqueTogetherValidator.filter_querysetand__call__which had the same latent bug of reading fromserializer.instancewithout guarding against a queryset type.Checklist
UniqueValidator,UniqueTogetherValidator, andBaseUniqueForValidatorfilter_querysetpath inUniqueTogetherValidator(latent bug:getattr(serializer.instance, source)would also crash if instance is a queryset)