Reputation: 886
Let's say I have three models in my app that lets user write a review for a book. The requirement is that every user can only do one review for each book.
Review
model has two foreign keys to User
and Book
models. I want to be able to validate user input before creating an instance of Review
model to ensure a user can not create more than one review per book.
I know how to do it if I ask the user to provide the User
and Book
information in the data that's sent to DRF version 3.+.
But the url to post a new Review
has the Book
id in it, and the user is authenticated:
url used to list
and create
reviews: /book/{book_id}/reviews/
Right now when I do a POST
operation on the url, DRF complains that the book
field is required, even though I tried to send keyword arguments to the overloaded perform_create
function (see snippets below)
My guess is I should send the information about book and review that's embedded in the url and request
to the default UniqueTogetherValidator
but I don't know how to do it!
I have seen a partial solution in this question, but my problem is that I not only have to provide the current authenticated user to the validator but also the book
information, since I am requiring a unique_together
constraint.
default
kwarg to the fields.So my question is how to validate unique_together
of input data from user's request when the book
information is embedded in the url
and the user
information is part of the incoming request
(while both of those are related foreign keys and not local attributes of Review
model)?
Here are my models and serializers:
class Review(models.Model):
# Relationships
book = models.ForeignKey(Book)
user = models.ForeignKey(User)
comment = models.TextField(null=True, blank=True)
class Meta:
unique_together = (("user", "book"),)
class ReviewSerializer(serializers.ModelSerializer):
book = serializers.PrimaryKeyRelatedField(read_only=True)
user = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = Review
class ReviewList(generics.ListCreateAPIView):
serializer_class = ReviewSerializer
def perform_create(self, serializer):
serializer.save(user=self.request.user, book=self.kwargs['book_id'])
url(r'^book/(?P<book_id>\d+)/reviews/$', view=ReviewList.as_view(), name='review-list')
Upvotes: 5
Views: 2113
Reputation: 20996
You are trying to make things harder than they should.
My feeling on this issue is that the unique together constraint is part of the business rules. Therefore I'd go with removing that constraint on the serializer. After the serializer has validated the data you'll be able to check the constraint on the review (owner + book) and raise a validation error if there's already one. Doing this in the perform_create before saving the serializer seems sensible.
To remove the unique together constraint you'll have to explicitly set the validators on the serializer:
class ReviewSerializer(serializers.ModelSerializer):
book = serializers.PrimaryKeyRelatedField(read_only=True)
user = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = Review
validators = []
Make sure to print a serializer instance before doing that change so you can make sure you're not removing other contraints.
Upvotes: 2