【问题标题】:Django REST Framework Serializers: Display the latest object of a reverse relationshipDjango REST Framework Serializers:显示反向关系的最新对象
【发布时间】:2015-10-04 03:07:58
【问题描述】:

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 单独提供的链接并没有完全为我清除图片。 Todor 的回答更加明确。

【问题讨论】:

  • 嘿,马克!我在您发布的链接中支持您的评论和您的答案,但我将 Todor 的答案标记为正确答案,因为它更完整。

标签: django django-rest-framework


【解决方案1】:

您需要以某种方式在ReportQuerySet 中注释latest_log,以便序列化程序可以使用它而无需进行任何额外的查询。

实现这一点的最简单方法是通过prefetching 所有logsreport。这种方法的缺点是您将所有logs 每页report 加载到内存中。如果一个 report 得到类似 5-10-15 logs 的东西,那还不错。这意味着对于 50 reports 的页面,您将加载 50*10=500 logs,这没什么大不了的。如果每个report 有更多logs(比如说100 个),那么您需要对queryset 进行额外过滤。

下面是一些示例代码:

  1. 预取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
    
  2. 创建一个帮助方法以便于访问 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
    
  3. 最后序列化器只使用属性

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

【讨论】:

  • 像魅力一样工作!今天学习了一项新技术!谢谢!
【解决方案2】:

您可以使用select related 参数。它只会使用 JOIN 访问数据库一次。

class ReportListView(generics.ListAPIView):

    queryset = Report.objects.select_related('log');
    serializer_class = ReportSerializer

【讨论】:

    猜你喜欢
    • 2012-12-25
    • 2023-03-05
    • 1970-01-01
    • 1970-01-01
    • 2018-08-04
    • 2013-10-20
    • 2020-08-15
    • 2015-12-14
    • 1970-01-01
    相关资源
    最近更新 更多