2 votes

Comment interroger efficacement la base de données pour un champ connexe dans un sérialiseur REST ?

J'ai une architecture complexe pour ma base de données et j'ai des difficultés à écrire l'API REST correspondante.

Le contexte : Je demande diverses informations sur une API publique toutes les heures, y compris des données dont je veux conserver l'historique.

Voici les modèles que j'utilise

class Player(models.Model):
    name = models.CharField(max_length=255)

class PlayerStatsHistory(models.Model):
    player = models.ForeignKey('Player', null=True, on_delete=models.CASCADE)
    last_refresh = models.DateTimeField(null=True)
    ...

De cette façon, je peux stocker chaque changement dans les statistiques de chaque utilisateur. J'ai écrit 2 sérialiseurs, un pour Player et un pour PlayerStatsHistory . Le plus simple fonctionne bien

class PlayerStatsSerializer(HyperlinkedModelSerializer):
    class Meta:
        model = PlayerStatsHistory
        fields = ('last_refresh', ...)

Mais lorsque j'ai besoin de demander les dernières statistiques d'un joueur, je ne sais plus où j'en suis :

class PlayerSerializer(HyperlinkedModelSerializer):
    details = SerializerMethodField()

    def get_details(self, obj):
        return PlayerStatsSerializer(PlayerStatsHistory.objects.filter(player=obj).order_by('-last_refresh').first()).data

    class Meta:
        model = Player
        fields = ('name', 'details')

Cela fonctionne bien, mais la base de données sera frappée à chaque joueur fourni à la base de données de l'UE. PlayerSerializer et j'ai l'impression que je m'y prends mal.

Comment puis-je améliorer cette solution ?

4voto

Ehsan Nouri Points 1536

Je pense que le mieux que vous puissiez faire est d'obtenir l'identité du dernier PlayerStatsHistory de chaque joueur (en utilisant group_by) :

latest_stats_history_pks = PlayerStatsHistory.objects.values('player').annotate(max_id=models.Max('id')).values_list('max_id', flat=True)

(il y a un problème, il utilise tous les lecteurs ; si vous utilisez la pagination, vous n'avez pas besoin de tous les lecteurs, dans ce cas, prefetch sans filtrer son queryset devrait convenir)

puis ne préférez que ces valeurs sur votre Player queryset, donc :

       queryset = Player.objects.all().prefetch_related(models.Prefetch(
        'playerstatshistory_set',
        queryset=PlayerStatsHistory.objects.filter(pk__in=latest_stats_history_pks), to_attr='last_stat_list'))

donc finalement votre get_queryset dans votre vue devrait être comme :

def get_queryset(self):

    latest_stats_history_pks = PlayerStatsHistory.objects.values('player').annotate(max_id=models.Max('id')).values_list('max_id', flat=True)

    queryset = Player.objects.all().prefetch_related(models.Prefetch(
        'playerstatshistory_set',
        queryset=PlayerStatsHistory.objects.filter(pk__in=latest_stats_history_pks), to_attr='last_stat_list'))

    return queryset

et si vous utilisez FBV, faites quelque chose comme ça :

from rest_framework.decorators import api_view
from rest_framework.response import Response

@api_view(['GET'])
def player_list(request, format=None):
    if request.method == 'GET':
        latest_stats_history_pks = PlayerStatsHistory.objects.values('player').annotate(max_id=models.Max('id')).values_list('max_id', flat=True)

        players = Player.objects.all().prefetch_related(models.Prefetch(
        'playerstatshistory_set',
        queryset=PlayerStatsHistory.objects.filter(pk__in=latest_stats_history_pks), to_attr='last_stat_list'))

        serializer = PlayerSerializer(players, many=True)
        return Response(serializer.data)

également dans votre sérialiseur, modifiez ce champ comme ci-dessous :

class PlayerSerializer(HyperlinkedModelSerializer):
    details = SerializerMethodField()

    def get_details(self, obj):
        return {} if not obj.last_stat_list else PlayerStatsSerializer(obj.last_stat_list[-1]).data

    class Meta:
        model = Player
        fields = ('name', 'details')

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X