Alvaro Bataller
Alvaro Bataller

Reputation: 483

Django Rest Framework: Prevent duplicate objects when using CreateModelMixin

I have a Viewset where a user can create reviews for a specific product and list their all the reviews they have created for all products:

class ProductReviewViewset(CreateModelMixin, ListModelMixin, GenericViewSet):
    serializer_class = ProductReviewSerializer

    def get_queryset(self):
        # Return all reviews for user that are not marked as deleted
        user = self.request.user
        return ProductReview.objects.filter(user=user, deleted=False).order_by('review__name')

    def perform_create(self, serializer):
        # Add user to review object before saving
        user = self.request.user
        serializer.save(user=self.request.user)

The problem is that this allows the user to create multiple reviews for the same object. I wanted to prevent this, and only allow to create a review if there is no other review already created for that product (reviews marked as "deleted" will not be counted).

So far I was able to override the .create() method and do the serialization, validation and checking for duplicates in there.

class ProductReviewViewset(CreateModelMixin, ListModelMixin, GenericViewSet):
    serializer_class = ProductReviewSerializer

    def get_queryset(self):
        # Return all reviews for user that are not marked as deleted
        user = self.request.user
        return ProductReview.objects.filter(user=user, deleted=False).order_by('review__name')

    def create(self, request, *args, **kwargs):
        # Override create method to prevent duplicate object creation
        serializer = ProductReviewSerializer(data=self.request.data)
        serializer.is_valid(raise_exception=True)
        user = self.request.user
        product = serializer.validated_data['product']
        exists = ProductReview.objects.filter(user=user, product=product, deleted=False).exists()
        if not exists:
            serializer.save(user=user)
            return Response(status=status.HTTP_201_CREATED)
        else:
            return Response(status=status.HTTP_409_CONFLICT)

Is there an easier or better way of doing this? Or is this the way to do it?

Thanks!

Upvotes: 2

Views: 3734

Answers (1)

Hari
Hari

Reputation: 1623

An alternative would be using get_or_create queryset method : get_or_create

def create(self, request, *args, **kwargs):
    # Override create method to prevent duplicate object creation
    serializer = ProductReviewSerializer(data=self.request.data)
    serializer.is_valid(raise_exception=True)
    user = self.request.user
    product = serializer.validated_data['product']
    obj, created = ProductReview.objects.get_or_create(user=user, product=product, deleted=False, defaults=seriaizer.validated_data)
    if not created:
        serializer.save(user=user)
        return Response(status=status.HTTP_201_CREATED)
    else:
        return Response(status=status.HTTP_409_CONFLICT)
            

the get_or_create will prevent race conditions, when request made in parallel

Upvotes: 2

Related Questions