Сериализатор Django для отображения различных полей в зависимости от стороны списка отношений с внешним ключом

Я пишу Django API (используя Django REST framework) для фильмов и актеров и располагаю их обоих в отдельных таблицах с другой таблицей для хранения списка актеров для каждого фильма, хранящей ключи обоих. У меня есть разные модели и сериализаторы для всех этих таблиц. Мне интересно, можно ли показать разный выбор полей для связи между ними (таблица для хранения списка актеров по фильмам) в зависимости от того, получаю я фильм или актера. Другими словами, когда я получаю фильм, мне нужны только поля актера и роли, а когда я получаю актера, мне нужны только поля фильма и роли. Получение поля, относящегося к текущему выбранному объекту, было бы избыточным и становилось бы назойливым и беспорядочным (зачем мне знать, что Энтони Хопкинс снимается в каждом фильме, если я получаю список ролей для фильмов Энтони Хопкинса?).

.

Здесь представлены упрощенные версии моделей:

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']

Здесь представлены упрощенные версии сериализаторов.

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']

Вот что я надеюсь получить при извлечении пленки:

{
  "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
  ]
}

Вот что я надеюсь получить при извлечении актера:

{
  "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
  ]
}

Заметьте, как фильм получает поля актер и роль в списке cast, но не поле film, а актер получает поля film и role в списке filmCredits, но не поле actor.

Я представляю, что это возможно каким-то образом, но я в тупике, как это сделать. Я думал о создании двух отдельных сериализаторов, по одному для каждой стороны отношений, но это кажется немного глупым. Я представляю, что мне, возможно, придется немного переделать мои модели, поэтому я готов к этому. Я также ожидаю, что кто-нибудь укажет мне на то, что я все делаю неправильно. В любом случае, я хочу получить желаемый результат, не добавляя безумное количество лишнего шаблонного кода.

Вы можете поддерживать динамическое изменение полей сериализатора, используя этот замечательный класс, найденный в 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)

С помощью этого вы можете сделать что-то вроде этого:

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']

Теперь вы можете установить поля, которые вы хотите ActorRoleSerializer использовать в зависимости от того, какой сериализатор их использует.

Вернуться на верх