Search code examples
djangopython-2.7serializationdjango-rest-frameworkdjango-rest-auth

Update user profile and user in one request


Thanks in advance for looking.

The goal:

In short: Update django user object and custom user profile object only if there is a change from a single view.

The problem:

For starters, this is my first time trying to do anything like this, so it may be something really simple and I am just overlooking it.

It seems to be failing on validation steps and I am getting an HTTP 400 error saying 'username must be unique', even when I am not sending a change that would be changing the current users username. Now I could probably use the view that is built into django-rest-auth to update just the user portion, then create just a custom view that only updates the profile. But that seems hacky and silly, but if it is the way to go then I will do it.

Things I have tried:

  • Tried both puts and patches. Same result.
  • Tried overriding the put and patch methods to remove the username field from request.data before validation if it is the same, but that gave another error saying it is required.
  • Tried overriding the update method in the serializer, came into a problem where I have no idea how to update a nested field.
  • Googling to no end.
  • Considered writing a custom validator. But I have no idea where to start on that and I wanted to make this as simple as possible and prefer built-in stuff.

Serializers:

class UserSerializer(ModelSerializer):

    class Meta(UserDetailsSerializer.Meta):
        model = User
        fields = ('username', 'email', 'first_name', 'last_name')
        read_only_fields = ('email', )

class UserProfileSerializer(ModelSerializer):
    user = UserSerializer(required=True, many=False)
    games = UserGameProfileSerializer(required=False, many=True)

    class Meta:
        model = UserProfile
        fields = ('premium', 'user', 'games')

View:

class UserProfileUpdateView(generics.UpdateAPIView):
    # authentication_classes = (authentication.TokenAuthentication,)
    # permission_classes = (permissions.IsAuthenticated,)
    serializer_class = UserProfileSerializer

    def get_queryset(self):
        return UserProfile.objects.filter(user__username__exact=self.request.user).all()

    def get_object(self):
        return UserProfile.objects.filter(user__username__exact=self.request.user).get()

Solution

  • Disclaimer

    I was finally able to figure it out and I am only answering for future reference. I am not sure if this is best practice or the easiest way to accomplish the task. However, this is what I was able to come up with.

    I changed the view to be simpler instead of that nasty looking filter query I found I could do a get object and just say user=self.request.user and that would get what I needed.

    View

    class UserProfileUpdateView(generics.UpdateAPIView):
        authentication_classes = (authentication.TokenAuthentication,)
        permission_classes = (permissions.IsAuthenticated,)
        serializer_class = UserProfileSerializer
    
        def get_object(self):
            return UserProfile.objects.get(user=self.request.user)
    

    Then in the serializer I figured out how to update nested objects which is exactly what needed to be done. Now I realize that currently I am only really updating the built in django object. But soon I will be adding more fields to the user profile and will need to update those and if there are changes to the user object I wanted to update them in the same request.

    In the update function you can access the request data with self.data and in there I was able to access the dict containing the user information. With that I was able to query for the user object, then using the UserSerializer I was able to validate the data and call update passing the user I queried for and the validated data from the serializer.

    Serializer

    class UserProfileSerializer(ModelSerializer):
        user = UserSerializer(required=True, many=False)
        games = UserGameProfileSerializer(required=False, many=True)
    
        class Meta:
            model = UserProfile
            fields = ('premium', 'user', 'games')
            read_only_fields = ('premium', )
    
        def update(self, instance, validated_data):
            user_data = validated_data.pop('user')
            game_data = validated_data.pop('games')
            username = self.data['user']['username']
            user = User.objects.get(username=username)
            print user
            user_serializer = UserSerializer(data=user_data)
            if user_serializer.is_valid():
                user_serializer.update(user, user_data)
            instance.save()
            return instance