Сериализатор 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
использовать в зависимости от того, какой сериализатор их использует.