Reputation: 3830
Having issues with a serializer.is_valid()
returning True
if the serializer instance fails a unique_together
constraint on the model side.
Is there a way for me to specify in the serializer to enforce a unique_together
constraint?
Upvotes: 29
Views: 33213
Reputation: 5507
I Needed this to override default message. Solved it by this.
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers, validators
class SomeSerializer(serializers.ModelSerializer):
"""
Demostrating How to Override DRF UniqueTogetherValidator Message
"""
class Meta:
model = Some
validators = [
validators.UniqueTogetherValidator(
queryset=model.objects.all(),
fields=('field1', 'field2'),
message=_("Some custom message.")
)
]
Similarly, you can specify fields.
Upvotes: 25
Reputation: 712
Had the same problem and from this answer https://stackoverflow.com/a/26027788/6473175 I was able to get it to work but had to use self.instance
instead of self.object
.
def validate(self, data):
field1 = data.get('field1',None)
field2 = data.get('field2',None)
try:
obj = self.Meta.model.objects.get(field1=field1, field2=field2)
except self.Meta.model.DoesNotExist:
return data
if self.instance and obj.id == self.instance.id:
return data
else:
raise serializers.ValidationError('custom error message')
Upvotes: 2
Reputation: 2970
Well this is kinda stupid and it's very unlikely that anyone but me would make this mistake, but I had put the same model in two serializers class, as a consequence I had this problem
Hope my mistake help someone!
Upvotes: -1
Reputation: 436
The ModelSerializer class has this functionality build-in, at least in djangorestframework>=3.0.0
, however if you are using a serializer
which doesn't include all of the fields which are affected by your unique_together
constrain, then you'll get an IntegrityError
when saving an instance which violates it. For example, using the following model:
class Foo(models.Model):
class Meta:
unique_together = ('foo_a', 'foo_b')
a = models.TextField(blank=True)
b = models.TextField(blank=True)
foo_a = models.IntegerField()
foo_b = models.IntegerField(default=2)
and the following serializer and ViewSet:
class FooSerializer(serializers.ModelSerializer):
class Meta:
model = models.Foo
fields = ('a', 'b', 'foo_a')
class FooViewSet(viewsets.ModelViewSet):
queryset = models.Foo.objects.all()
serializer_class = FooSerializer
routes = routers.DefaultRouter()
routes.register(r'foo', FooViewSet)
if you try to save two instances with the same foo_a
and foo_b
set, you'll get an IntegrityError
. However, if we modify the serializer like this:
class FooSerializer(serializers.ModelSerializer):
class Meta:
model = models.Foo
fields = ('a', 'b', 'foo_a', 'foo_b')
you'll then get a proper HTTP 400 BAD REQUEST
status code, and the corresponding JSON descriptive message in the response body:
HTTP 400 BAD REQUEST
Content-Type: application/json
Vary: Accept
Allow: GET, POST, HEAD, OPTIONS
{
"non_field_errors": [
"The fields foo_a, foo_b must make a unique set."
]
}
I hope this to result helpful for you, even when this is a somewhat old-posted question ;-)
Upvotes: 30
Reputation: 2737
Unfortunately, Andreas's answer is not quite complete, as it will not work in the case of an update.
Instead, you would want something more like:
def validate(self, attrs):
field1 = attrs.get('field1', self.object.field1)
field2 = attrs.get('field2', self.object.field2)
try:
obj = Model.objects.get(field1=field1, field2=field2)
except StateWithholdingForm.DoesNotExist:
return attrs
if self.object and obj.id == self.object.id:
return attrs
else:
raise serializers.ValidationError('field1 with field2 already exists')
This will work for PUT, PATCH, and POST.
Upvotes: 13
Reputation: 61
Yes, you can do it in the .validate()
method of the serializer.
def validate(self, attrs):
try:
Model.objects.get(field1=attrs['field1'], field2=attrs['field2'])
except Model.DoesNotExist:
pass
else:
raise serializers.ValidationError('field1 with field2 already exists')
return attrs
The unique constraint that you set in your model is for creating database constraints, not for validating.
Upvotes: 4