2

I'm trying to extend the user model with a profile using the method in this answer.

My idea is to only send the profile object whenever a request about a user is made to the api.

I have accomplished this as follows:

# views.py
class ProfileDetail(APIView):
    """
    Retrieve or update a profile instance.
    """
    permission_classes = (permissions.IsAuthenticatedOrReadOnly, )
    ...
    def get(self, request, pk, format=None):
        ...

    # Only allow owners to update a profile
    def put(self, request, pk, format=None):
        profile = self.get_object(pk)
        if request.user != profile.user:
            raise PermissionDenied
        serializer = OwnerProfileSerializer(
            profile,
            data=request.data,
            context={"request": request},
        )
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

And my serializer looks like this:

#serializers.py
# Serializer for the owner of profile object
class OwnerProfileSerializer(serializers.HyperlinkedModelSerializer):
    user = serializers.ReadOnlyField(source='user.id')
    first_name = serializers.ReadOnlyField(source='user.first_name')
    last_name = serializers.ReadOnlyField(source='user.last_name')
    email = serializers.(source='user.email')

    class Meta:
        model = Profile
        fields = (
            'url',
            'id',
            'dob',
            'bio',
            'town',
            'gender',
            'image',
            'user',
            'first_name',
            'last_name',
            'email',
        )

I would like the owner to also be able to modify user object fields, such as first_name, last_name, email, etc.

However I have not found a (e.g.) ReadWriteField in the DRF serializers docs.

How could I accomplish this? If possible I want all the modification to be carried out through the Profile view/serializer and not the User.

--EDIT--

As a test I replaced the ReadOnlyField with last_name = serializers.CharField(source='user.last_name'). When I tried to update it, DRF gave me this error:

The .update() method does not support writable dotted-source fields by default. Write an explicit .update() method for serializer profiles.serializers.OwnerProfileSerializer, or set read_only=True on dotted-source serializer fields.

So it looks like I could implement my own update method. But I'm not sure how to proceed.

sonarforte
  • 1,588
  • 15
  • 18

2 Answers2

1

I managed to fix it by extending the update method on the serializer:

class OwnerProfileSerializer(serializers.HyperlinkedModelSerializer):

    user = serializers.ReadOnlyField(source='user.id')
    first_name = serializers.CharField(source='user.first_name')
    last_name = serializers.CharField(source='user.last_name')
    email = serializers.ReadOnlyField(source='user.email')

    def update(self, instance, validated_data):

        user = instance.user
        # pprint(vars(user))
        print(validated_data)
        for updated_field in validated_data['user']:
            value = validated_data['user'][updated_field]
            setattr(user, updated_field, value)

        for updated_field in validated_data:
            if updated_field == 'user':
                for user_field in validated_data['user']:
                    value = validated_data['user'][user_field]
                    setattr(user, user_field, value)
            else:
                value = validated_data[updated_field]
                setattr(instance, updated_field, value)

        user.save()
        instance.save()
        return instance

    class Meta:
        model = Profile
        fields = (
            'url',
            'id',
            ....

However, this might give me problems later on when I need to validate an email address change. In that case, writing a user serialzer as per @Kostas' suggestion might work

sonarforte
  • 1,588
  • 15
  • 18
0

One option would be to include your UserSerializer in your app, and make it editable. For example this should look like this:

class OwnerProfileSerializer(serializers.HyperlinkedModelSerializer):
    user = CustomUserSerializer(read_only=False).

This also will affect a bit the structure of your api response, but I believe this can work in your case.

Kostas Livieratos
  • 965
  • 14
  • 33