Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

unique_together is not allowing to update record

I am working on implemeting Like/Unlike feature in API for mobile app via Django Rest Framework.

So I have following model:

class PlaylistLikes(models.Model):
    user = models.ForeignKey(User)
    playlist = models.ForeignKey(Playlist)
    like = models.BooleanField(default=0, blank=True)

    class Meta:
        unique_together = ('user', 'playlist',)
        verbose_name = "Playlist like"
        verbose_name_plural = "Playlist likes"

I added unique_together to restrict user to like a playlist which he has already liked.

My view:

class LikesView(viewsets.ModelViewSet):
    queryset = PlaylistLikes.objects.all()
    serializer_class = LikesSerializer
    filter_backends = (filters.OrderingFilter, filters.DjangoFilterBackend, filters.SearchFilter)
    filter_class = LikesFilter

    search_fields = ('playlist', 'user',)

    def create(self, request, *args, **kwargs):
        user = request.user
        request.data['user'] = user.pk
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        return Response(serializer.data, status=status.HTTP_201_CREATED)

    @detail_route(methods=['put'])
    def put(self, request, *args, **kwargs):
        user = request.user
        request.data['user'] = user.pk
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_update(serializer)
        return Response(serializer.data, status=status.HTTP_200_OK)

And serializer:

class LikesSerializer(serializers.ModelSerializer):

    def create(self, validated_data):
        like = PlaylistLikes.objects.create(**validated_data)
        playlist = Playlist.objects.get(pk=like.playlist.id)
        playlist.likes_count += 1
        playlist.save()
        return validated_data

    def update(self, instance, validated_data):
        like = PlaylistLikes.objects.get(**validated_data)
        like.like = False
        like.save()
        playlist = Playlist.objects.get(pk=like.playlist.id)
        playlist.likes_count -= 1
        playlist.save()
        return validated_data

    class Meta:
        model = PlaylistLikes
        fields = ('user', 'playlist', 'like')

Urls:

router.register(r'likes', views.LikesView, 'likes')

To like playlist, I send POST method with body playlist = 19, user = 3, like = 1. Like is succesfully created. To unlike same playlist, I send PUT method with body: playlist = 19, user = 3, but I got following error:

"non_field_errors": [ "The fields user, playlist must make a unique set." ]

I understand that it's happening because of unique_together in model. But I am not creating a new record with same user and playlist. I just whant to update existing record. Why this happens and how to solve this problem?

like image 956
belek Avatar asked Dec 05 '25 07:12

belek


1 Answers

This happens because in the PUT method handler, you create a serializer without specifying a instance:

serializer = self.get_serializer(data=request.data)

Look at Serializer.__init__():

def __init__(self, instance=None, data=empty, **kwargs):
    self.instance = instance
    if data is not empty:
        self.initial_data = data
    self.partial = kwargs.pop('partial', False)
    self._context = kwargs.pop('context', {})
    kwargs.pop('many', None)
    super(BaseSerializer, self).__init__(**kwargs)

You didn't provide the instance argument, and it defaults to None.

For a update, unique_together validation should be skipped, only if instance attribute is not None:

class UniqueTogetherValidator(object):
    def exclude_current_instance(self, attrs, queryset):
        """
        If an instance is being updated, then do not include
        that instance itself as a uniqueness conflict.
        """
        if self.instance is not None:
            return queryset.exclude(pk=self.instance.pk)
        return queryset

To solve this problem, you can move the query of Userlikes object before initializing the serializer:

like = PlaylistLikes.objects.get(**query)
serializer = self.get_serializer(like, data=request.data)
like image 60
NeoWang Avatar answered Dec 07 '25 20:12

NeoWang



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!