Используйте to_representation для объединения полей из связанных моделей в Django Rest Framework
У меня есть следующие модели:-
class Player(models.Model):
first_name = models.CharField()
etc.
class Game(models.Model):
date = models.DateField()
etc.
class GameMembership(models.Model):
player = models.ForeignKey(Player, related_name="memberships")
game = models.ForeignKey(Game, related_name="memberships")
available = models.BooleanField(default=False)
Я создал ModelViewSet для возврата всех игроков, но я хотел бы иметь возможность для каждого игрока в списке возвращать список их игровых членств. Я могу сделать это, довольно легко, но данные, которые он возвращает, выглядят следующим образом:-
{
"id": "1",
"memberships": [
{
"available": True,
"game": {
"date": "a date",
etc.
}
},
{
"available": False,
"game": {
"date": "a date",
etc.
}
}
]
}
но я хотел бы скрыть аспект "членства" моей базы данных от пользователей API и вместо этого вернуть что-то вроде этого:-
{
"id": "1",
"games": [
{
"available": True,
"date": "a date",
etc.
},
{
"available": False,
"date": "a date",
etc.
}
]
},
Итак, я хочу взять поле (или два) из модели GameMembership и объединить его со всеми полями из модели Game, но, что очень важно, я хочу, чтобы все это было в одном словаре в возвращаемых результатах. Я знаю, что могу просто сериализовать Game на GameMembershipSerializer, но это означает, что я буду возвращать:-
{
"id": "1",
"games": [
{
"available": True,
"game": {
"date": "a date",
etc.
}
},
{
"available": False,
"game": {
"date": "a date",
etc.
}
}
]
}
что на самом деле не имеет смысла, поскольку пользователю придется обращаться к таким вещам, как results['games'][1]['game'], что кажется неправильным.
Я думал, что смогу сделать это, используя to_representation в GameMembershipSerializer, но я не могу понять этого.
Есть идеи?
Другой подход, не переопределяя to_representation
, заключается в использовании SerializerMethodField
и выполнении обработки следующим образом:
class GameModelSerializer(models.ModelSerializer):
class Meta:
model = Game
fields = '__all__'
class PlayerModelSerializer(models.ModelSerializer):
games = serializers.SerializerMethodField()
class Meta:
model = Player
fields = ('id', 'games')
def get_games(self, player)
return [{
'available': membership.available,
**GameModelSerializer(membership.game).data,
} for membership in player.memberships.all()]
Для того, чтобы убедиться, что сериализатор не обращается к db за членством только для получения игры, вы можете предварительно получить членство с выбранной игрой следующим образом:
Player.objects.prefetch_related(
Prefetch('memberships', queryset=GameMembership.objects.select_related('game'))
)
Для тех, кому интересно, как я решил эту проблему, мне нужно было использовать to_representation, так как это должно было быть расширяемое поле (используя drf-flex-fields). Я также обнаружил, что следующая реализация была намного быстрее, чем код, упомянутый выше, поэтому я использовал ее вместо этого:-
# PlayerSerializer
games = GameMembershipGameSerializer(many=True)
class GameMembershipGameSerializer(serializers.ModelSerializer):
class Meta:
model = GameMembership
fields = ["available", "game"]
def to_representation(self, membership):
representation = super().to_representation(membership)
game = representation.pop("game")
for key in game:
representation[key] = game[key]
return representation