DRF Serializer Read Nested Data and Write Primary Key

Usually, my goal with a JSON response is to not nest (too many) things. I prefer small, versatile, and reusable endpoints over bulky, complicated and overly nested ones. However, sometimes one might want to "read" a nested object and "write" a primary key while still using a single serializer and Django REST Framework's amazing ViewSets.

The easiest way I could think of, is to declare a nested serializer for the related object on the root serializer, as one normally would. This will return the nested data. To realize "write" we need to "undo" the nested serializer by telling the root serializer to use serializers.PrimaryKeyRelatedField instead, which is the default field for foreign keys. This can easily be achieved by overwriting the to_internal_value method on the root serializer.

Let's say we have two models: Category and BlogPost.

class Category(models.Model):
    name = models.CharField() 
    description = models.TextField()


class BlogPost(models.Model):
    title = models.CharField()
    content = models.TextField()
    category = models.ForeignKey(Category, on_delete=models.Cascade)

In this example, we want the BlogPost's ModelSerializer BlogPostModelSerializer to return information about the nested category, while still allowing to link to an existing category to a blog post by using the category's id. This can easily be achieved by the following code:

class CategoryModelSerializer(serializers.ModelSerializer):

    class Meta:
        model = Category
        fields = ('name', 'description')


class BlogPostModelSerializer(serializers.ModelSerializer):
    category = CategoryModelSerializer()

    class Meta:
        model = BlogPost
        fields = ('title', 'content', 'category')

    def to_internal_value(self, data):
        self.fields['category'] = serializers.PrimaryKeyRelatedField(
            queryset=Category.objects.all()
        )
        return super().to_internal_value(data)