Django计数聚合与相关字段上的过滤器不起作用

时间:2018-04-01 17:32:15

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

假设我有2个模型Coupon和UserRedemption(当然还有用户模型)。他们看起来像:

Coupon(models.Model):
    offer = models.TextField() # not important to this task
    limited_use = models.BooleanField(default=False)
    max_uses = models.IntegerField(default=0)

UserRedemption(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL)
    coupon = models.ForeignKey(Coupon)

非常基本。我的应用程序中有优惠券,每张优惠券都有各种用户兑换的优惠券清单。有些优惠券是限制使用",即它们只能由给定用户使用一定次数。

我的目标:返回优惠券列表,不包括用户提出请求时使用过最多次数的优惠券。

我尝试通过使用当前用户兑换的次数(n_user_redemptions)来注释优惠券并过滤优惠券的列表,其中limited_use = False或max_uses> n_user_redemptions。这是我在优惠券类上尝试的方法:

@classmethod
def get_available_user_coupons(cls, user):
    coupon_redemption_count = Count('userredemption', filter=Q(userredemption__user=user))
    return cls.objects.annotate(n_user_redemptions=coupon_redemption_count)
                      .filter(Q(limited_use=False) | Q(max_uses__gt=F('n_user_redemptions')))

但是,对于从请求传入的用户,它没有正确过滤。它总是将n_user_redemptions设置为所有用户的所有兑换优惠券的计数,而不仅仅是给定用户的兑换。我已经测试过,如果limited_use设置为False,并且该优惠券已被兑换的总次数(所有用户数)超过max_uses,则会正确返回优惠券。我已经确认我正确地传递了用户。

我不确定我在这里失踪了什么。我已经非常广泛地浏览了django聚合文档,看起来这应该可行。任何帮助将不胜感激。

进一步更新:

我检查了生成的SQL,并注意到无论我是否设置了查询都是相同的:

coupon_redemption_count = Count('userredemption', filter=Q(userredemption__user=user))

OR

coupon_redemption_count = Count('userredemption')

不确定过滤器参数的行为应该在Count中。

相关的SQL:

SELECT "coupon_coupon"."id", "coupon_coupon"."offer", "coupon_coupon"."limited_use", "coupon_coupon"."max_uses", 
   COUNT("userredemption_userredemption"."id") AS "n_user_redemptions" 
FROM "coupon_coupon" 
LEFT OUTER JOIN "userredemption_userredemption" 
    ON ("coupon_coupon"."id" = "userredemption_userredemption"."coupon_id")
GROUP BY "coupon_coupon"."id" 
ORDER BY "coupon_coupon"."id" DESC

在任何地方都没有提到用户?

2 个答案:

答案 0 :(得分:1)

你不能这样做:

def return_list_of_coupons(user):
    list_of_coupons = [coupon for coupon in Coupon.objects.all()
                       if UserRedemption.objects.filter(user=user.pk, coupon=coupon.pk).count() < coupon.max_uses]
    return list_of_coupons

它更简单,有点粗糙,但它的工作做得很好。

答案 1 :(得分:0)

所以我从来没有得到答案或弄清楚为什么聚合过滤器不起作用,但我确实找到了一个不太漂亮的解决方法,使用一个子查询,至少不是原始的sql,这将提出维护噩梦而不是强迫我评估多个查询。我更喜欢使用聚合,因为它使用引擎下的连接而不是子查询,这将是更有效的方法,但现在将服务,我将与Django团队一起登录以查看聚合过滤器的内容。

以下是解决方法:

coupon_redemption_count = Count(Subquery(UserRedemption.objects.filter(user=user, coupon=OuterRef(‘pk’)).values_list(‘id’, flat=True)))

然后其他一切都是一样的。

保留此问题以防万一找到它并告诉我为什么聚合过滤器无效。