How can I serialize a Many to Many field with added data in Django Rest Framework, without extra keys in the response?

I am using Django 3.2 and Django Rest Framework 3.14.

I have Users that should be able to follow other Users, like on a social networking site. When serializing a full User, I would like to get a list of those following Users, as well as additional data, such as the follow datetime. However, I would like the response to stay "flat", like this:

{
    "username":"admin",
    "followers":[
        {
            "username":"testuser",
            --additional user fields--
            "follow_date":"2023-02-08 01:00:02"
        },
        --additional followers--
    ],
    --additional user fields--
}

I can only seem to go "through" my join model using an extra serializer, and end up with this:

{
    "username":"admin",
    "followers":[
        {
            "user":{
                "username":"testuser",
                --additional user fields--
            },
            "follow_date":"2023-02-08 01:00:02"
        },
        --additional followers--
    ],
    --additional user fields--
}

Note how there is an additional user key

"user":{
    "username":"testuser",
    --additional user fields--
},

I don't want that there!

My model looks something like this:

class Follower(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.RESTRICT, related_name="followers")
    follower = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.RESTRICT, related_name="following")
    follow_date = models.DateTimeField(auto_now_add=True)

My serializers look like this (extra fields trimmed for brevity):

class UserSnippetSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['username']

class FollowerSerializer(serializers.ModelSerializer):
    user = UserSnippetSerializer(source='follower')

    class Meta:
        model = Follower
        fields = ['user', 'follow_date']

class FullUserSerializer(serializers.ModelSerializer):
    followers = FollowerSerializer(source='user.followers', many=True)
    following = FollowerSerializer(source='user.following', many=True)

    class Meta:
        model = Profile
        fields = ['username', 'followers', 'following']

Given this, I understand why it's doing what it is. But I can't seem to find any way to skip the extra user key, while still including bot the User information AND the follow_date.

I've tried playing around with proxy models on User, but that didn't seem to help. I suspect that this is no configuration thing, though I would like it to be, and that I need to override some internal function. But I can't seem to locate what that would be.

What's the best (and hopefully easiest!) way to accomplish this?

One approach is to use a serializer method field on FollowerSerializer like this:

class FollowerSerializer(serializers.ModelSerializer):
    username = serializers.SerializerMethodField()

    class Meta:
        model = Follower
        fields = ['username', 'follow_date']

    def get_username(self, obj):
        return obj.username

or if you have lots of fields to display from user, you can also override to_representation like this:

class FollowerSerializer(serializers.ModelSerializer):
    user = UserSnippetSerializer(source='follower')

    class Meta:
        model = Follower
        fields = ['user', 'follow_date']


    def to_representation(self, instance):
        data = super().to_representation(instance)
        user_data = data.pop("user")

        # flatten the follower data with the user data
        return {
            **data,
            **user_data,
        }

Note that this will also affect FullUserSerializer.following

Back to Top