DRF Get All Fields of OneToOne Model

Viewed 461

I'm relatively new to DRF and currently stuck at an issue. After lot of trials I tried looking around but couldn't find something what I want. In case this question has already been answered please do direct me to the original question. Thanks in advance for the help!

Problem: I've a profile model that has one to one relation with User model. When I create a new user object the profile object is auto created as expected. I would like to create an API such that when I execute it like api/profile/ in addition to returning the profile model fields it should also return all the fields from User model i.e. first_name, last_name, username, email etc...

class Profile(models.Model):
    rater = models.OneToOneField(User, on_delete=models.CASCADE, related_name='approval')
    certified = models.ManyToManyField(Protocol, blank=True, default=None, related_name='trained')
    is_approved = models.BooleanField(default=False)

My ProfileSerializer is as follows:

class ProfileSerializer(serializers.ModelSerializer):
    approval = UserSerializer(read_only=True)

    class Meta:
        model = Profile
        fields = '__all__'

When I run the API I get the following response:

[
    {
        "id": 1,
        "rater": 3,
        "certified": [],
        "is_approved": false
    }
]

How can I get the following response:

[
    {
        "id": 1,
        "rater": 3,
        "certified": [],
        "is_approved": false,
        "first_name": xyz,
        "last_name": abc,
        "username": 123,
        "email": abc@gmail.com,
    }
]
2 Answers

You can do this a few ways. The best way would be to make a serializer that contains fields from both models. What you have is close, but because your related field name is rater on the Profile side of the relationship. (e.g. you use profile.rater.email, not profile.approval)

You should either use the name rater or specify source keyword argument.

class ProfileSerializer(serializers.ModelSerializer):
    approval = UserSerializer(read_only=True, source='rater')
    class Meta:
        model = Profile
        fields = '__all__'

If you want the result to be flat, rather than nested, you can specify a to_representation method.

    def to_representation(self, instance):
        # get the usual response as a dictionary
        representation = super().to_representation(instance)
        # pop the nested field and flatten
        user_details = representation.pop('approval')
        representation.update(user_details)
        return representation

You could also 'cheat' and do this straight from the to_representation method altogether, although it requires a bit more verbosity and you don't get the advantage of all the definitions from the UserSerializer. But this may be useful if you don't have a serializer and don't want to make one for some reason.

   def to_representation(self, instance):
       representation = super().to_representation(instance)
       user = instance.rater
       representation.update({'email': user.email,
                              'first_name', user.first_name,
                              ...
                              })
       return representation

Try to change approval to rater:

class ProfileSerializer(serializers.ModelSerializer):
    rater = UserSerializer(read_only=True)

    class Meta:
        model = Profile
        fields = '__all__'

This will automatically pick up the rater field from Profile and pass it on to UserSerializer.

This way you will end up with:

[
    {
        "id": 1,
        "certified": [],
        "is_approved": false,
        "rater": {
            "id": 3,
            "first_name": "xyz",
            "last_name": "abc",
            "username": "123",
            "email": "abc@gmail.com"
        }
    }
]
Related