10

I am trying to extend the Django rest framework (version 3.x.x) with gender, created_at, updated_at fields that are defined in a separate model called UserProfile. When I try to update the UserProfile model instance including the nested User model instance, it updates only the UserProfile instance (gender, updated_at fields). So basically what I want to achieve is to be able to update also the email, first_name and last_name fields from the User model

models.py:

class UserProfile(models.Model):
        user = models.OneToOneField(User, primary_key = True, related_name = 'profile')
        gender = models.CharField(choices = GENDERS, default = 2, max_length = 64)
        created_at = models.DateTimeField(auto_now_add = True)
        updated_at = models.DateTimeField(auto_now = True)

        def __unicode__(self):
                return self.user.username

        @receiver(post_save, sender = User)
        def create_profile_for_user(sender, instance = None, created = False, **kwargs):
                if created:
                        UserProfile.objects.get_or_create(user = instance)

        @receiver(pre_delete, sender = User)
        def delete_profile_for_user(sender, instance = None, **kwargs):
                if instance:
                        user_profile = UserProfile.objects.get(user = instance)
                        user_profile.delete()

serializers.py:

class UserProfileSerializer(serializers.ModelSerializer):

        id = serializers.IntegerField(source = 'pk', read_only = True)
        username = serializers.CharField(source = 'user.username', read_only = True)
        email = serializers.CharField(source = 'user.email')
        first_name = serializers.CharField(source = 'user.first_name')
        last_name = serializers.CharField(source = 'user.last_name')

        class Meta:
                model = UserProfile
                fields = (
                        'id', 'username', 'email', 'first_name', 'last_name',
                        'created_at', 'updated_at', 'gender',
                )
                read_only_fields = ('created_at', 'updated_at',)

        def update(self, instance, validated_data):
                #user = User.objects.get(pk = instance.user.pk);        
                user = instance.user
                user.email = validated_data.get('user.email', user.email)
                user.first_name = validated_data.get('user.first_name', user.first_name)
                user.last_name = validated_data.get('user.last_name', user.last_name)
                user.save()

                instance.gender = validated_data.get('gender', instance.gender)
                instance.save()

                return instance

        def create(self, validated_data):

                user_data = validated_data.pop('user')
                user = User.objects.create(**user_data)

                profile = UserProfile.objects.create(user = user, **validated_data)
                return profile

views.py:

class UserViewSet(viewsets.ModelViewSet):
        queryset = User.objects.all()
        serializer_class = UserSerializer

class UserProfileViewSet(viewsets.ModelViewSet):
        queryset = UserProfile.objects.all()
        serializer_class = UserProfileSerializer
Adam
  • 1,054
  • 1
  • 12
  • 26

2 Answers2

6

Here's how I did it:

def update(self, instance, validated_data):
    # First, update the User
    user_data = validated_data.pop('user', None)
    for attr, value in user_data.items():
            setattr(instance.user, attr, value)
    # Then, update UserProfile
    for attr, value in validated_data.items():
        setattr(instance, attr, value)
    instance.save()
    return instance

Basically, I looked into the original code for serializers (here, line 800) and modified it so that it would fit my needs.

Good luck.

AdelaN
  • 3,366
  • 2
  • 25
  • 45
  • is there an error because it give me that `user` is not defined, may be you mean `instance.user = user_data` – L3K0V Aug 13 '15 at 19:05
  • Actually, that line is not even needed, since all attributes are set directly on the user instance. Remove it and it should work just fine :) – AdelaN Aug 18 '15 at 09:23
3

A slight modification to the above code replacing the None with {}

Because 'NoneType' object has no attribute 'items'

def update(self, instance, validated_data):
    # First, update the User
    user_data = validated_data.pop('user', {})
    for attr, value in user_data.items():
        setattr(instance.user, attr, value)
    # Then, update UserProfile
    for attr, value in validated_data.items():
        setattr(instance, attr, value)
    instance.save()
    return instance

There is another way that I am implementing which takes care of validation. I am implementing it for handling PATCH

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

class ProfileSerializer(serializers.ModelSerializer):
    email = serializers.CharField(source='user.email')
    first_name = serializers.CharField(source='user.first_name')
    last_name = serializers.CharField(source='user.last_name')

    class Meta:
        model = Profile
        exclude = ('user',)

    def update(self, instance, validated_data):
        user_data = validated_data.pop('user', {})
        user_serializer = UserSerializer(instance.user, data=user_data, partial=True)
        user_serializer.is_valid(raise_exception=True)
        user_serializer.update(instance.user, user_data)
        super(ProfileSerializer, self).update(instance, validated_data)
        return instance