Django REST Framework Serializers:显示反向关系的最新对象
Django REST Framework Serializers: Display the latest object of a reverse relationship
ListAPIView(下面的代码)的默认行为是序列化所有 Report 对象和每个 Report 对象的嵌套 Log 对象。如果我只希望每个报告显示最新的日志对象怎么办?我该怎么做?
# models.py
class Log(models.Model):
# ...
report = models.ForeignKey(Report)
timestamp = models.DateTimeField(default=datetime.datetime.now)
class Report(models.Model):
code = models.CharField(max_length=32, unique=True)
description = models.TextField()
# serializers.py
class LogSerializer(serializers.ModelSerializer):
class Meta:
model = Log
class ReportSerializer(serializers.ModelSerializer):
log_set = LogSerializer(many=True, read_only=True)
class Meta:
model = Report
fields = ('code', 'description', 'log_set')
# views.py
class ReportListView(generics.ListAPIView):
queryset = Report.objects.all()
serializer_class = ReportSerializer
我知道我可以通过使用 SerializerMethodField 来做到这一点,但这可能是一个潜在的昂贵操作,因为会有一个额外的 SQL 查询来为每个 Report 对象检索适当的 Log 对象。
class ReportSerializer(serializers.ModelSerializer):
latest_log = serializers.SerializerMethodField()
class Meta:
model = Report
def get_latest_log(self, obj):
try:
latest_log = Log.objects.filter(report_id=obj.id).latest('timestamp')
except Log.DoesNotExist:
latest_log = None
return latest_log
如果我有 1000 个报表对象,如果我想全部呈现它们,将有 1000 个额外的查询。除了使用分页之外,我如何避免这些额外的查询?谁能指出我正确的方向?谢谢!
编辑: 关于可能的重复标签,仅由 Mark 提供的 link 并没有为我完全清除图片。 Todor的回答更明确。
您可以使用 select related 参数。它只会使用 JOIN 访问数据库一次。
class ReportListView(generics.ListAPIView):
queryset = Report.objects.select_related('log');
serializer_class = ReportSerializer
您需要以某种方式在 ReportQuerySet
中注释 latest_log
,以便序列化程序可以使用它而无需进行任何额外查询。
实现此目的的最简单方法是 prefetching
所有 logs
每个 report
。这种方法的缺点是您在内存中加载所有 logs
每 report
每页。如果一个 report
得到类似 5-10-15 logs
的结果,这还不错。这意味着对于包含 50 reports
的页面,您将加载 50*10=500 logs
,这没什么大不了的。如果每个 report
有更多的 logs
(比如说 100),那么您需要对 queryset
进行额外的过滤。
下面是一些示例代码:
预取 logs
.
# views.py
class ReportListView(generics.ListAPIView):
queryset = Report.objects.all()\
.prefetch_related(Prefetch('log_set',
queryset=Log.objects.all().order_by('-timestamp'),
to_attr='latest_logs'
))
serializer_class = ReportSerializer
创建一个辅助方法以便于访问 latest_log
class Report(models.Model):
#...
@property
def latest_log(self):
if hasattr(self, 'latest_logs') and len(self.latest_logs) > 0:
return self.latest_logs[0]
#you can eventually implement some fallback logic here
#to get the latest log with a query if there is no cached latest_logs
return None
最后序列化器只使用 属性
class ReportSerializer(serializers.ModelSerializer):
latest_log = serializers.LogSerializer()
class Meta:
model = Report
logs
更高级过滤的示例如下所示:
Report.objects.all().prefetch_related(Prefetch('log_set', queryset=Log.objects.all().extra(where=[
"`myapp_log`.`timestamp` = (\
SELECT max(timestamp) \
FROM `myapp_log` l2 \
WHERE l2.report == `myapp_log`.`report`\
)"]
), to_attr='latest_logs'
))
ListAPIView(下面的代码)的默认行为是序列化所有 Report 对象和每个 Report 对象的嵌套 Log 对象。如果我只希望每个报告显示最新的日志对象怎么办?我该怎么做?
# models.py
class Log(models.Model):
# ...
report = models.ForeignKey(Report)
timestamp = models.DateTimeField(default=datetime.datetime.now)
class Report(models.Model):
code = models.CharField(max_length=32, unique=True)
description = models.TextField()
# serializers.py
class LogSerializer(serializers.ModelSerializer):
class Meta:
model = Log
class ReportSerializer(serializers.ModelSerializer):
log_set = LogSerializer(many=True, read_only=True)
class Meta:
model = Report
fields = ('code', 'description', 'log_set')
# views.py
class ReportListView(generics.ListAPIView):
queryset = Report.objects.all()
serializer_class = ReportSerializer
我知道我可以通过使用 SerializerMethodField 来做到这一点,但这可能是一个潜在的昂贵操作,因为会有一个额外的 SQL 查询来为每个 Report 对象检索适当的 Log 对象。
class ReportSerializer(serializers.ModelSerializer):
latest_log = serializers.SerializerMethodField()
class Meta:
model = Report
def get_latest_log(self, obj):
try:
latest_log = Log.objects.filter(report_id=obj.id).latest('timestamp')
except Log.DoesNotExist:
latest_log = None
return latest_log
如果我有 1000 个报表对象,如果我想全部呈现它们,将有 1000 个额外的查询。除了使用分页之外,我如何避免这些额外的查询?谁能指出我正确的方向?谢谢!
编辑: 关于可能的重复标签,仅由 Mark 提供的 link 并没有为我完全清除图片。 Todor的回答更明确。
您可以使用 select related 参数。它只会使用 JOIN 访问数据库一次。
class ReportListView(generics.ListAPIView):
queryset = Report.objects.select_related('log');
serializer_class = ReportSerializer
您需要以某种方式在 ReportQuerySet
中注释 latest_log
,以便序列化程序可以使用它而无需进行任何额外查询。
实现此目的的最简单方法是 prefetching
所有 logs
每个 report
。这种方法的缺点是您在内存中加载所有 logs
每 report
每页。如果一个 report
得到类似 5-10-15 logs
的结果,这还不错。这意味着对于包含 50 reports
的页面,您将加载 50*10=500 logs
,这没什么大不了的。如果每个 report
有更多的 logs
(比如说 100),那么您需要对 queryset
进行额外的过滤。
下面是一些示例代码:
预取
logs
.# views.py class ReportListView(generics.ListAPIView): queryset = Report.objects.all()\ .prefetch_related(Prefetch('log_set', queryset=Log.objects.all().order_by('-timestamp'), to_attr='latest_logs' )) serializer_class = ReportSerializer
创建一个辅助方法以便于访问 latest_log
class Report(models.Model): #... @property def latest_log(self): if hasattr(self, 'latest_logs') and len(self.latest_logs) > 0: return self.latest_logs[0] #you can eventually implement some fallback logic here #to get the latest log with a query if there is no cached latest_logs return None
最后序列化器只使用 属性
class ReportSerializer(serializers.ModelSerializer): latest_log = serializers.LogSerializer() class Meta: model = Report
logs
更高级过滤的示例如下所示:
Report.objects.all().prefetch_related(Prefetch('log_set', queryset=Log.objects.all().extra(where=[
"`myapp_log`.`timestamp` = (\
SELECT max(timestamp) \
FROM `myapp_log` l2 \
WHERE l2.report == `myapp_log`.`report`\
)"]
), to_attr='latest_logs'
))