【问题标题】:Django rest_framework: child object count in serializerDjango rest_framework:序列化程序中的子对象计数
【发布时间】:2018-03-01 18:16:57
【问题描述】:

我需要计算一个对象拥有的子对象的数量,并通过对象序列化程序在我的 API 中返回这个值。我还需要计算这些子对象的子集。

我有一个包含子受让人的任务对象。在我的 API 中,当我查询我希望返回以下数据集的任务时:

[
    { label: "Cross the bridge",
      count_assigned: 5,
      count_completed: 3 },
    { label: "Build a fire",
      count_assigned: 5,
      count_completed: 2 }
]

我该怎么做?我找到了.annotate() 方法,但该结果在序列化程序类中不可用。

models.py

class Task(models.Model):
    label         = models.CharField(max_length=255,null=False,blank=False)

class Assignee(models.model):
    task         = models.ForeignKey(Task, related_name='assignees', on_delete=models.CASCADE, blank=True, null=True) 
    person       = models.ForeignKey(Person, on_delete=models.CASCADE, blank=True, null=True)
    completed    = models.DateTimeField(null=True,blank=True)

序列化器.py

from rest_framework import serializers

from .models import Task, Assignee
from people.serializers import PersonSerializer

class AssigneeSerializer(serializers.ModelSerializer):
    id = serializers.ReadOnlyField()
    person = PersonSerializer(read_only=True)

    class Meta:
        model = Assignee

        fields = ('id','task','person','completed')
        read_only_fields = ['id']


class TaskSerializer(serializers.ModelSerializer):
    id = serializers.ReadOnlyField()

    class Meta:
        model = Task

        fields = ('id', 'label')
        read_only_fields = ['id']

【问题讨论】:

  • 您的 API 视图是什么样的?你在使用查看器吗?如果是这样this answer(注意,不是该问题的公认答案)会做你想做的事。

标签: python django django-models django-rest-framework serialization


【解决方案1】:

建议的方式

class TaskSerializer(serializers.ModelSerializer):
     id = serializers.ReadOnlyField()
     count_assigned = serializers.SerializerMethodField()
     count_completed = serializers.SerializerMethodField()

     class Meta:
         model = Task
         fields = ('id', 'label', 'count_assigned', 'count_completed')

    def get_count_assigned(self, obj):
        return obj.assignees.count()

    def get_count_completed(self, obj):
        return obj.assignees.exclude(completed__isnull=True).count()

http://www.django-rest-framework.org/api-guide/fields/#serializermethodfield

【讨论】:

  • 好的,这很好,但又引发了另一个问题。我以指定的格式获取数据。现在我想修改客户端说的这个javascript对象,例如更改标签,然后将相同的 javascript 对象发回以更新数据库,我得到一个错误:django.core.exceptions.FieldDoesNotExist: Task has no field named 'count_assigned'
  • 字段(“count_assigned”、“count_completed”)是只读字段。这意味着它们仅用于生成序列化器数据表示。它们不能用于修改数据库记录。在尝试更新之前,最好从 javascript 中的 API 参数中删除这些字段。
  • 但是,在列表操作的情况下,将为每个任务调用 obj.assignees.count(),这会导致 N+1 个查询。我宁愿建议@brown-bear 的解决方案,它会进行 1 次查询并嵌入所有计数。
【解决方案2】:

如果我理解你的逻辑正确,你可以试试

在序列化程序中

class TaskSerializer(serializers.ModelSerializer):
    count_assigned = serializers.IntegerField(read_only=True)
    count_completed = serializers.IntegerField(read_only=True)

然后通过查询集:

from django.db.models import Count, Case, When, IntegerField

qs = Task.objects.annotate(
        count_completed=Count(Case(
            When(assignees__completed__isnull=False, then=1),
            output_field=IntegerField(),
        ))
    ).annotate(count_assigned=Count('assignees'))

serializer = TaskSerializer(qs, many=True)

或者模型效率极低:

from django.utils.functional import cached_property

class Task(models.Model):

@cached_property
def all_assignee(self):
    return self.assignees.all()

def count_assigned(self):
    return self.all_assignee.count()

def count_completed(self):
    return self.all_assignee.filter(completed__isnull=False).count()

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-07-09
    • 2014-09-30
    • 1970-01-01
    • 2011-01-24
    • 2019-10-20
    • 1970-01-01
    • 2016-10-22
    相关资源
    最近更新 更多