drf-writable-nested icon indicating copy to clipboard operation
drf-writable-nested copied to clipboard

Nested Serializer with UniqueFieldsMixin runs '.create()' in nested serializer instead of '.update()'

Open prasetyaputraa opened this issue 4 years ago • 2 comments

When calling .save(instance, data, partial) from an outer serializer with NestedCreateMixin and NestedUpdateMixin, nested serializer wih UniqueFieldsMixin runs .create() instead of .update() thus resulting IntegritiyError being raised.

I have this outer serializer with nested UserSerializer:

class ProfileSerializer(NestedCreateMixin, NestedUpdateMixin, serializers.ModelSerializer):
    class Meta:
        model = Profile
        fields = ['user', 'address', 'cart_books']


    address = serializers.CharField(required=True)
    user = UserSerializer()
    cart_books = BookSerializer(many=True, required=False)

and this is UserSerializer:

class UserSerializer(UniqueFieldsMixin, serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['id', 'username', 'first_name', 'last_name', 'is_staff',
                        'email', 'password', 'last_login', 'date_joined']
        extra_kwargs = {
            'password': {'write_only': True},
            'is_staff': {'write_only': True},
            'date_joined': {'read_only': True},
            'last_login': {'read_only': True},
            }
        

    def create(self, validated_data):
        password = validated_data.pop('password', None)

        instance = self.Meta.model(**validated_data)

        if password is not None:
            instance.set_password(password)

        instance.save()

        return instance

    def update(self, instance, validated_data):
        for attr, value in validated_data.items():
            if attr == 'password':
                instance.set_password(value)
            else:
                setattr(instance, attr, value)

        instance.save()

        return instance

when I tried to run this, I got IntegrityError:

        user = User.objects.get(id=pk)

        if user != request.user:
            raise PermissionDenied()

        partial = kwargs.get('partial', False)

        profile_data = {
            'address': request.data.pop('address', None)
        }

        profile_data['user'] = request.data

        profile = Profile.objects.get(user=user)

        profile_serializer = ProfileSerializer(instance=profile, data=profile_data, partial=partial)

        if profile_serializer.is_valid(raise_exception=True):
            profile_serializer.save()

            return Response({
                'message': 'Updating user successful.',
                'data': profile_serializer.data
                }, status=200)

Is there something I was missing? Or is this the expected behaviour?

prasetyaputraa avatar Jan 27 '21 11:01 prasetyaputraa

Did you solve this problem? I'm also trying to add multiple objects to a M2M field, but it seems that calling save() on a partial update tries to first create the objects even though they already exist.

MatejMijoski avatar Jan 31 '21 17:01 MatejMijoski

Did you solve this problem? I'm also trying to add multiple objects to a M2M field, but it seems that calling save() on a partial update tries to first create the objects even though they already exist.

Hi, sorry for late reply. I ended up overriding the update method in the outer serializer, creating the nested serializer instance inside it with the validated_data. Like so:

    def update(self, instance, validated_data, *args, **kwargs):
        user_data = validated_data.pop('user')

        user_serializer = UserSerializer(instance.user, data=user_data, partial=self.partial)
        
        if user_serializer.is_valid():
            user = user_serializer.save()

        for key, value in validated_data.items():
            setattr(instance, key, value)
        
        instance.user = user

        instance.save()

        return instance

prasetyaputraa avatar Feb 03 '21 21:02 prasetyaputraa