Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django REST Framework: make a serializer behave different for request payload vs response body?

I'm trying to come up with an easy way to make my serializers and views work the way I want to, while preventing a bunch of manual boilerplate.

My simplified model:

class Character(models.Model):
    name = models.CharField(max_length=255)
    location = models.ForeignKey(Location, on_delete=models.SET_NULL, blank=True, null=True)
    factions = models.ManyToManyField(Faction, blank=True)

So: character can have one location, and multiple factions.

Now, when I GET a character, I'd like those submodels (Location and Faction) to be expanded to their full representation, so my serializer looks like this:

class CharacterSerializer(serializers.ModelSerializer):
    location = LocationSerializer(read_only=True)
    factions = FactionSerializer(many=True, read_only=True)

    class Meta:
        model = Character
        fields = "__all__"

And this works perfectly fine so far. The thing is, when I POST or PUT a character, I'd like to just send the id(s).. but still get the full location and faction objects back in the response.

In other words, when I create a character with a payload like this:

{"name":"Saga","location":1,"factions":[1]}

I'd like the response to look like this:

{
  "id": 1,
  "location": {
    "id": 1,
    "name": "Location 1"
  },
  "factions": [
    {
      "id": 1,
      "name": "Faction 1"
    }
  ],
  "name": "Saga"
}

Is this possible at all without overriding the create and update methods of my ModelViewSet subclass? I was hoping I could slightly modify the serializer itself to only apply those location and factions field serializers on the response, not on the request.

like image 318
Kevin Renskers Avatar asked Oct 27 '25 12:10

Kevin Renskers


1 Answers

You can override the create and update methods of the serializer to work with primary keys.

This thus means that we first pop the data from the validated_data and then work with the remaining data to create/update an object. We can then update the many-to-many relation by using .set(…) and .add(…):

class CharacterSerializer(serializers.ModelSerializer):
    location = LocationSerializer(read_only=True)
    factions = FactionSerializer(many=True, read_only=True)

    def create(self, validated_data):
        location_data = validated_data.pop('location')
        factions_data = validated_data.pop('factions')
        character = Character.objects.create(
            **validated_data,
            location_id=location_data
        )
        character.factions.add(*factions_data)

    def update(self, instance, validated_data):
        location_data = validated_data.pop('location')
        factions_data = validated_data.pop('factions')
        instance.location_id = location_data
        result = super().update(instance, validated_data)
        instance.factions.set(faction_data)
        return result

    class Meta:
        model = Character
        fields = '__all__'
like image 151
Willem Van Onsem Avatar answered Oct 29 '25 07:10

Willem Van Onsem



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!