我的数据库结构很复杂,编写相应的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
提供的每个玩家的数据库,我觉得我做错了。
如何改进此解决方案?
答案 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')