【问题标题】:DjangoREST annotate count of related entries with filteringDjango REST通过过滤注释相关条目的计数
【发布时间】:2018-12-02 20:05:41
【问题描述】:

所以我在 Django REST 中做一个小项目,我还在习惯 ORM。我正在处理以下 Django 模型:

class Session(models.Model):
    date = models.DateTimeField(default=now, blank=False)
    owner = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='efforts', on_delete=models.CASCADE)

我对每个所有者在特定月份的会话数特别感兴趣。如下:

Session.objects.filter(date__gte=start, date__lt=end).values('owner').annotate(num_sessions=Count('owner')).order_by("owner")

此查询产生以下结构的结果:

(owner, count)

在序列化程序中,我想使用所有者来检索用户名,以构造响应。我这样做绝对没有运气。我的序列化器现在看起来像这样:

class SessionAggregateSerializer(serializers.ModelSerializer):
    num_sessions = serializers.IntegerField(read_only=True)
    owner = serializers.IntegerField(read_only=True)

    class Meta:
        model = Session
        fields = ('id', 'owner', 'num_sessions')

有人对如何解决这个问题有任何建议吗?对于常规查询,我使用以下行来检索用户名,但这在当前设置中不再适用:

owner = serializers.ReadOnlyField(source='owner.username')

【问题讨论】:

  • 如果您一次只检索一条记录,我认为使用 <owner>.efforts.count() 而不是您的注释功能更简洁。
  • 感谢您的建议。我对 ORM 的尴尬感觉感到惊讶。

标签: django orm django-rest-framework


【解决方案1】:

我认为你可以使用SerializerMethodField(documentation):

class SessionAggregateSerializer(serializers.ModelSerializer):
        ...
        username = serializers.SerializerMethodField()
        class Meta:
            model = Session
            fields = ('id', 'username', 'num_sessions')

        def get_username(self, obj):
          return obj.owner.username

更新

   class UserSerializer(serializers.ModelSerializer):
        ...
        sessions = serializers.SerializerMethodField()
        class Meta:
            model = User
            fields = ('id', 'username', 'sessions')

        def get_sessions(self, obj):
          return obj.efforts.all().count()  # or obj.efforts.filter(efforts__date__gte=....).count()

【讨论】:

  • 产生 AttributedError: 'dict' 对象没有属性 'owner'。
  • 谢谢大师!必须添加一些序列化程序上下文才能使其工作,但它完成了工作。非常感谢!
  • 使用这样的东西:User.objects.annotate(sessions=Count('efforts__id')).filter(sessions__gt=0)
  • 自己发布了另一个答案。
  • 这可能是真的,该解决方案的主要问题是(如果我没记错的话)在序列化程序中进行额外的过滤会发出额外的、单独的数据库查询。这意味着为每个用户执行一个查询,这并不是很有效。我的答案中的构造将执行单个查询,因此减少了显着的开销。仍然感谢您的时间/答案,无论如何,您让我走上了正确的道路。
【解决方案2】:

为了让用例更具体:

会话通过“努力”关系附加到用户。给定一个开始和结束时间,我想为用户显示一个列表以及会话数量——在开始和结束日期之间的日期——附加到他们身上。会话由分支进一步标识。这对用户来说是一样的,但是用户可能属于多个分支。因此,计数应该进一步向下钻取以仅将会话纳入计数,其中分支出现在请求者的用户对象的分支中。此外,应省略会话计数为零的用户以保持结果简洁。

Session 模型如下所示:

class Session(models.Model):
    date = models.DateTimeField(default=now, blank=False)
    owner = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='efforts', on_delete=models.CASCADE)
    branch = models.ForeignKey(Branch, related_name='hour_branch', on_delete=models.CASCADE)
    status = models.IntegerField(default=1, blank=False, validators=[MinValueValidator(0), MaxValueValidator(3)])

User 模型如下所示(为简洁起见,省略了各个字段)。

class User(AbstractUser):
    branches = models.ManyToManyField(Branch)

上面的答案并不令人满意,因为它不允许我根据需要过滤计入用户计数的会话。因此,为什么我最终使用conditional expression 得到以下解决方案:

相关视图的代码:

class AggregateSessionList(generics.ListAPIView):
    queryset = User.objects.all()
    serializer_class = UserSessionsSerializer
    permission_classes = (permissions.IsAdminUser,)

    def get_queryset(self):
        today=datetime.date.today()
        month=self.request.query_params.get('month', today.month)
        year=self.request.query_params.get('year', today.year)

        start = datetime.date(year=year, month=month, day=1)
        end = datetime.datetime(year=start.year + int(start.month / 12), month=int((start.month % 12) + 1), day=1)

        branches = self.request.user.branches.all()
        return User.objects.annotate(
            sessions=Count(Case(
                When(efforts__date__gt=start, efforts__date__lt=end, efforts__branch__in=branches, then=1),
                output_field=IntegerField()))).filter(sessions__gt=0)

序列化程序的代码:

class UserSessionsSerializer(serializers.ModelSerializer):
    sessions = serializers.IntegerField()

    class Meta:
        model = User
        fields = ('username', 'sessions')

我想分享这个解决方案,因为我找不到类似的东西。

编辑

这个解决方案有利于 ruddra 的回答的另一个好处是,只执行一个数据库查询。这与 ruddra 的答案形成鲜明对比,其中为每个用户执行附加查询(这不符合我的要求)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-12-23
    • 2012-03-07
    • 1970-01-01
    • 1970-01-01
    • 2013-10-30
    • 1970-01-01
    • 2017-11-13
    • 1970-01-01
    相关资源
    最近更新 更多