47

I am new to DRF and I am trying to write custom view / serializer that I can use to update just one field of user object.

I need to make logic just to update the "name" of the user.

I wrote serializer:

class ClientNameSerializer(serializers.ModelSerializer):
    class Meta:
        model = ClientUser
        fields = ('name',)

    def update(self, instance, validated_data):
        instance.name = validated_data.get('name', instance.name)
        instance.save()
        return instance

This method is never called. I tried setting breakpoint there and debug it, but it is never called, even if I use PUT, POST or PATCH methods. If I add create method it is being called when I use POST.

This is how my view looks like:

class UpdateName(generics.CreateAPIView):
    queryset = ClientUser.objects.all()
    serializer_class = ClientNameSerializer
    permission_classes = (permissions.IsAuthenticated,)

Does anyone have some suggestion? Thanks!

My models.py looks like this

class ClientUser(models.Model):
    owner = models.OneToOneField(User,unique=True,primary_key=True)

    phone_number = models.CharField(validators=[PHONE_REGEX],max_length=20,unique=True)

    name = models.CharField(max_length=100,blank=True)
    status = models.IntegerField(default=1)
    member_from = models.DateTimeField('member from',auto_now_add=True)
    is_member = models.BooleanField(default=False)
vabada
  • 1,738
  • 4
  • 29
  • 37
Bob
  • 8,392
  • 12
  • 55
  • 96

3 Answers3

44

The definition of what methods the endpoint can accept are done in the view, not in the serializer.

The update method you have under your serializer needs to be moved into your view so you'll have something like:

serializers.py

class ClientNameSerializer(serializers.ModelSerializer):
    class Meta:
        model = ClientUser

views.py

class UpdateName(generics.UpdateAPIView):
    queryset = ClientUser.objects.all()
    serializer_class = ClientNameSerializer
    permission_classes = (permissions.IsAuthenticated,)

    def update(self, request, *args, **kwargs):
        instance = self.get_object()
        instance.name = request.data.get("name")
        instance.save()

        serializer = self.get_serializer(instance)
        serializer.is_valid(raise_exception=True)
        self.perform_update(serializer)

        return Response(serializer.data)

Take note that you're overriding the UpdateModelMixin and you might need to change the above code a little bit to get it right.

Rakmo
  • 1,926
  • 3
  • 19
  • 37
awwester
  • 9,623
  • 13
  • 45
  • 72
  • Hi, update method breaks on this line: `instance = self.get_object()` – Bob Jul 02 '15 at 21:20
  • 1
    This is the error that I received: `Expected view UpdateName to be called with a URL keyword argument named "pk". Fix your URL conf, or set the .lookup_field attribute on the view correctly` – Bob Jul 02 '15 at 21:21
  • @bla0009 how did you solve the issue? url keyword argument pk? – momokjaaaaa Apr 17 '16 at 23:56
  • 1
    @momokjaaaaa the best way to update some attribute is actually if you use the queryset, so you can write for example `Person.objects.filter(....).update(name="some name")` – Bob Apr 18 '16 at 08:35
  • 1
    Whats the different between override the update method inside view and inside serializers. ? – Karesh A Oct 26 '16 at 09:37
  • I know this is old but what happens if the serializer throws an error? The `instance.save()` is rolled back or It's left persisted in the database? – Marcos Dec 31 '19 at 16:42
  • as the error suggests add the ```lookup_field``` primarily it is already set to ```pk``` you can update from what you are using like if you are using ```id``` then ```lookup_field=id``` – Pradyum Gupta Sep 27 '20 at 03:02
6

One other approach might be the following one:

serializer.py

class ClientNameSerializer(serializers.ModelSerializer):
   class Meta:
        model = ClientUser
        fields = ('name',)

   def update(self, instance, validated_data): 
        instance.name = validated_data.get('name', instance.name)
        instance.save()
        return instance

views.py

class UpdateName(generics.UpdateAPIView):
    queryset = ClientUser.objects.all()
    serializer_class = ClientNameSerializer
    permission_classes = (permissions.IsAuthenticated,)

    def update(self, request, *args, **kwargs):
        data_to_change = {'name': request.data.get("name")}
        # Partial update of the data
        serializer = self.serializer_class(request.user, data=data_to_change, partial=True)
        if serializer.is_valid():
            self.perform_update(serializer)

        return Response(serializer.data)
Franci Gjeci
  • 141
  • 2
  • 3
5

If you use class UpdateName(generics.CreateAPIView), this will only call a create() method on the serializer.

You should subclass generics.UpdateAPIView instead. And that's it. You do not have to move your method to the view as suggested in this answer (it is basically copying/duplicating the UpdateModelMixin's update method)

For more information how serializers work regarding saving/updating see the docs here:

Tadej Krevh
  • 417
  • 6
  • 7