zerg .
zerg .

Reputation: 169

Django rest framework nested serializer partial update

Serializer:

class ProfileSerializer(serializers.ModelSerializer):
    class Meta:
        model = UserProfile
        fields = ('foo', 'bar')


class UserSerializer(serializers.ModelSerializer):
    userprofile = ProfileSerializer(partial=True)

    class Meta:
        model = User
        fields = ('username', 'password', 'email', 'userprofile')

    def create(self, validated_data):
        profile_data = validated_data.pop('userprofile')
        user = User.objects.create(**validated_data)
        UserProfile.objects.create(user=user, **profile_data)

        return user

    def update(self, instance, validated_data):
        profile_data = validated_data.pop('userprofile')
        profile = instance.userprofile

        instance.username = validated_data.get('username', instance.username)
        instance.email = validated_data.get('email', instance.email)
        instance.save()

        profile.foo = profile_data.get('foo', profile.foo)
        profile.bar = profile_data.get('bar', profile.bar)
        profile.save()

        return instance

View:

class UsersViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    authentication_classes = (TokenAuthentication,)
    permission_classes = (IsAuthenticated,)

Both create and update are working just fine, problem is with partial updates. The django User model has as required username and I would like to make that optional. Is there a way to enable partial updates for this scenario?

For instance I would like to update with PUT just "foo".

Upvotes: 7

Views: 7799

Answers (5)

fatima
fatima

Reputation: 1

I think it is enough to enforce partial=True when you define and override the update() method in serializers.py, i.e.:

def update(self, instance, validated_data, partial=True):
...

Upvotes: 0

zerg .
zerg .

Reputation: 169

I ended up overriding get_serializer inside UsersViewSet:

    def get_serializer(self, instance=None, data=None, many=False, partial=False):
        """If request is not PUT, allow partial updates."""
        if self.request.method == 'PUT':
            return UserSerializer(instance=instance, data=data, many=many, partial=True)
        else:
            return UserSerializer(instance=instance, data=data, many=many, partial=partial)

Forcing partial to True if request.method is PUT. Not sure if this is the most elegant solution but it works. If any one has a better solution please do share :)

Upvotes: 4

Scofield77
Scofield77

Reputation: 917

Actually, the following code already supports the partial update:

instance.username = validated_data.get('username', instance.username)

This 'get' function will get the 'username' field from validated_data. If it didn't exist, then instance.username would be returned.

Upvotes: 1

semicolon
semicolon

Reputation: 2580

By default PUT is expected to supply all required arguments. But PATCH is not. So as long as you are OK with using PATCH instead of PUT you don't have to change your code at all.

Personally I think it is a little weird how that works, PUT requires all arguments that aren't optional, but will leave optional arguments alone. So you can edit optional arguments while leaving other optional arguments alone but can't do the same with required arguments (I mean you can obviously just supply the existing values, but if they changed whilst you were editing you are screwed and will force change them back). PATCH makes a lot more sense to me. Any argument you supply will be changed, and any you don't won't. IMO PUT should wipe out any optional arguments that aren't supplied, so that it is a true replace rather than simply a replace required and update (PUT) optional.

Upvotes: 6

jmoz
jmoz

Reputation: 8006

The user seriliazer needs to change to something like

username = fields.CharField(required=False)

Upvotes: 0

Related Questions