Django REST框架序列化器:显示反向关系的最新对象

时间:2015-07-15 07:38:39

标签: django django-rest-framework

ListAPIView(下面的代码)的默认行为是为每个Report对象序列化所有Report对象和嵌套的Log对象。如果我只希望每个报告显示最新的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的回答更清楚。

2 个答案:

答案 0 :(得分:5)

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

实现此目标的最简单方法是prefetching每个logs report。这种方法的缺点是你每页logsreport加载一次内存。如果一个report得到类似5-10-15 logs的内容,那就不是那么糟糕了。这意味着对于50 reports的页面,您将加载50 * 10 = 500 logs,这不是什么大问题。如果每logs {更多report(假设为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
    
  4. 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'
    ))
    

答案 1 :(得分:-1)

您可以使用select related参数。它只使用JOIN命中数据库一次。

class ReportListView(generics.ListAPIView):

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