Django serializer to show different fields based on the side of a foreign key relation list

I’m writing a Django API (using the Django REST framework) for movies and actors and have both of them in separate tables with another table to store the list of actors for each film storing the keys of both. I have different models and serializers for all of them. I am wondering if it is possible to show a different selection of fields for the connection between the two (the table for storing the list of actors by film) depending on if I get the film or the actor. In other terms, when I get the film I only want the actor and role fields but when I get the actor I only want the film and role fields. Getting the field relating to the currently selected object would be redundant and gets obnoxious and messy (why would I need to know that Anthony Hopkins is in each movie when I get the list of roles for Anthony Hopkins’ movies?).

Here are simplified versions of the models:

class Film(models.Model):
    title = models.CharField(max_length=100, null=False, blank=False)
    ...Plus other fields

    def __str__(self):
        return f"{self.title}"

    class Meta:
        ordering = ['title']

class Person(models.Model):
    name = models.CharField(max_length=100, null=False, blank=False)
    ...Plus other fields

    def __str__(self):
        return f"{self.name}"

    class Meta:
        ordering = ['name']

class ActorRole(models.Model):
    film = models.ForeignKey(
        Film, on_delete=models.CASCADE, related_name='cast', null=False, blank=False)
    person = models.ForeignKey(
        Person, on_delete=models.CASCADE, related_name='filmCredits', null=False, blank=False)
    role = models.CharField(max_length=100, null=False, blank=False)

    def __str__(self):
        return f"{self.film}: {self.person.name} - {self.role}"

    class Meta:
        ordering = ['film__title']
        unique_together = ['film', 'person', 'role']

Here are simplified versions of the serializers.

class FilmSerializer(serializers.ModelSerializer):
    cast = ActorRoleSerializer(many=True, read_only=True)

    class Meta:
        model = Film
        fields = ['id', 'title', 'cast']

class PersonSerializer(serializers.ModelSerializer):
    filmCredits = ActorRoleSerializer(many=True, read_only=True)

    class Meta:
        model = Person
        fields = ['id', 'name', 'filmCredits']

class ActorRoleSerializer(serializers.ModelSerializer):
    film = serializers.CharField(source='film.title')
    actor = serializers.CharField(source='person.name')

    class Meta:
        model = ActorRole
        fields = ['actor', 'role', 'film']

Here is what I am hoping to get when I retrieve the film:

{
  "title": "Doctor Strange (2016)"
  ...other fields

  "cast": [
    {
      "actor": "Pumpernickle Samsquanch",
      "role": "Dr. Stephen Strange"
    }
    {
      "actor": "Benedict Wong",
      "role": "Wong"
    }
    {
      "actor": "Mads Mikkelsen",
      "role": "Kaecilius"
    }
    ...etc
  ]
}

Here is what I am hoping to get when I retrieve the actor:

{
  "name": "Mads Mikkelsen",
  ...other fields

  "filmCredits": [
    {
      "film": "Casino Royale (2006)",
      "role": "Le Chiffre"
    },
    {
      "film": "Hunt, The (2012)",
      "role": "Lucas"
    }
    {
      "film": "Doctor Strange (2016)",
      "role": "Kaecilius"
    }
    ...etc
  ]
}

Notice how the film gets the actor and role fields in the cast list but not the film field and the actor gets the film and role fields in the filmCredits list but not the actor field.

I imagine this is possible in some way but I’m stumped on how. I’ve contemplated making two separate serializers, one for each side of the relationship but that seems a bit silly. I imagine I may need to do some reworking of my models so I’m open to that. I also fully expect someone to point our that I’m going about the whole thing all wrong. Either way, I want to get that desired output without adding a crazy amount of extra boilerplate code.

Answer

You can support modifying the fields of a serializer dynamically by using this nifty class found in the DRF docs:

class DynamicFieldsModelSerializer(serializers.ModelSerializer):
    """
    A ModelSerializer that takes an additional `fields` argument that
    controls which fields should be displayed.
    """

    def __init__(self, *args, **kwargs):
        # Don't pass the 'fields' arg up to the superclass
        fields = kwargs.pop('fields', None)

        # Instantiate the superclass normally
        super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)

        if fields is not None:
            # Drop any fields that are not specified in the `fields` argument.
            allowed = set(fields)
            existing = set(self.fields)
            for field_name in existing - allowed:
                self.fields.pop(field_name)

With that you can do something like this:

class FilmSerializer(serializers.ModelSerializer):
    cast = ActorRoleSerializer(many=True, read_only=True, fields=['actor', 'role'])

    class Meta:
        model = Film
        fields = ['id', 'title', 'cast']


class PersonSerializer(serializers.ModelSerializer):
    filmCredits = ActorRoleSerializer(many=True, read_only=True, fields=['film', 'role'])

    class Meta:
        model = Person
        fields = ['id', 'name', 'filmCredits']


class ActorRoleSerializer(DynamicFieldsModelSerializer):
    film = serializers.CharField(source='film.title')
    actor = serializers.CharField(source='person.name')

    class Meta:
        model = ActorRole
        fields = ['actor', 'role', 'film']

You can now set the fields you want ActorRoleSerializer to use depending on which serializer is using it.