1

I want to make it so a user is able to update only certain attributes with a PUT request in my Django Rest API. As an example, if I had the following model and only wanted the user to be able to update their first and last name, how would I go about doing this?

models.py:

class User(models.Model):
    email = models.EmailField('email address', unique = True)
    first_name = models.TextField(max_length = 10)
    last_name = models.TextField(max_length = 20)

(noting that they should not be able to change the 'id' field either which is automatically set)

serializers.py:

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['id', 'email', 'first_name', 'last_name']

views.py:

class SingleUser(APIView):
    def put(self, request, user_id):
        user = User.objects.get(pk = user_id)
        serializer = UserEditSerializer(user, data = request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status = status.HTTP_200_OK)
        return Response(serializer.errors, status = status.HTTP_400_BAD_REQUEST)

What is the best way to enforce that users can only change a subset of these attributes?

Thanks, Grae

Peter Jordanson
  • 61
  • 2
  • 16
  • 1
    you can define a serializer which only has the fields you want to allow the user to change, then use that serializer when you're handling `put` requests – Sajad Oct 05 '20 at 15:01

3 Answers3

3

You can set read-only fields on a serializer:

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['id', 'email', 'first_name', 'last_name']
        read_only_fields = ['id', 'email']

Make sure to set your serializer to partial in the put function:

serializer = UserEditSerializer(user, data = request.data, partial=True)

You could also omit fields from the serializer altogether, which will make the fields not available to the user at all.

Nico Griffioen
  • 5,143
  • 2
  • 27
  • 36
2

Create a separate serializer and use it

# serializers.py
class UserPutSerializer(serializers.ModelSerializer): # new serializer class
    class Meta:
        model = User
        fields = ['first_name', 'last_name'] # define required fields

#views.py
class SingleUser(APIView):
    def put(self, request, user_id):
        user = User.objects.get(pk=user_id)
        serializer = UserPutSerializer(user, data=request.data) # use new serializer here
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_200_OK)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
JPG
  • 82,442
  • 19
  • 127
  • 206
  • Imo, this gets messy pretty quickly. If you add one field to your model, now you'll need to update one more serializer. And what if you want different behaviour for put, post and get requests? Before you know it, you're maintaining 3 different serializers per model. – Nico Griffioen Oct 05 '20 at 15:07
  • It's perfectly possible to create one serializer that behaves differently based on whether you are creating, reading or updating a model instance. – Nico Griffioen Oct 05 '20 at 15:08
  • 1
    Yes, it is possible. I will also use a single class if my field requirements aren't like this, for example, `id` and `username` for `HTTP GET, `id`, and `first_name` for HTTP POST, and `email` and `last_name` for HTTP PUT. In these situations, we can't rely on a single serializer class. (Yes it is possible to use it ***"somehow"***, but, I don't think it is a ***clean*** way to handle different fields for each situation – JPG Oct 05 '20 at 15:24
  • Apart from that, I would stick with "***Single Responsibility Principle***" in this case. @NicoGriffioen It will also help us to *"debug"* things very easily – JPG Oct 05 '20 at 15:25
0

You can use the read_only_fields, a serializer's attribute.

Another approach is to use a "serializer per action"

gmacro
  • 397
  • 3
  • 6