Django Rest Framework: How to let user add only one ForeignKey object (for example review)

My solution I decided to write up to have reference in the future.

When building my API that would list places (that offer products) and users can rate both places and products I needed a way to limit user to one review per place and one review per product.

I couldn't find any solution online so I came up with my own. If you know better way, please let me know. Thanks!

This problem likely should be handled on database level (or rather ORM) level but I wanted to explore Django Rest Framework so I created validation in the serializer.

You can implement validate method on your serializer to validate the data. With this I faced issues with not having all the available data available for validation. Turns out, you can provide additional context for your serializer inside view with get_serializer_context.

I used this method to add product_id and the request itself to the context for use in validate method like so:

def get_serializer_context(self):
    return {'product_id': self.kwargs['product_id'], 'request': self.request}

With this ready, the validate method can be implemented:

def validate(self, data):
    product_id = self.context["product_id"]
    if Review.objects.filter(product_id=product_id, author=self.context["request"].user).exists():
        raise serializers.ValidationError("This user has already added review for this product")
    return data

I first get product_id from the context and then check whether Review object for this product exists by this user which I can get from request. If it does, we can raise ValidationError and explain what is wrong.

Users can still add reviews but in case they decide to add another, the validate method will prevent them from doing so.

This particular API is for mobile app which would do this validation on its part. AKA when user enters reviews section for product/place it would already load this user's review and display it for possible edit. So the above is more like additional check to prevent unwanted data in the database.

Thanks for reading! And do let me know if you have better solution.

Filip Němeček profile photo


Filip Němeček @nemecek_f

iOS blogger and developer with interest in Python/Django. Telling other devs' stories with iOS Chat.