如何在REST序列化程序中有效地访问数据库以获取相关字段?

时间:2018-08-26 18:27:20

标签: python django python-3.x django-rest-framework

我的数据库结构很复杂,编写相应的REST API时遇到了麻烦。

上下文:我每小时都会请求公共API上的各种信息,包括我想保留其历史记录的数据。

这是我正在使用的模型

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)
    ...

这样,我可以将每个用户统计信息中的所有更改存储起来。 我写了2个串行器,一个用于Player,一个用于PlayerStatsHistory。最简单的一个很好用

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

但是当我需要为一名球员索要最新数据时,我会感到困惑:

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

这很好,但是会打到PlayerSerializer提供的每个玩家的数据库,我觉得我做错了。

如何改进此解决方案?

1 个答案:

答案 0 :(得分:5)

我认为您能做的最好的就是首先获取每个玩家的最后一个PlayerStatsHistory的ID(使用group_by):

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

(这是一个问题,如果您使用的是分页,则不需要所有的播放器,它将使用所有播放器,在这种情况下,无需过滤其查询集的预取就可以了)

然后仅在Player查询集中预取此值,因此:

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

因此,最终您在视图中使用的get_queryset方法应类似于:

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

如果您正在使用FBV,请执行以下操作:

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)

也在序列化器中,如下更改该字段:

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