在 Django 中注释导致 'None' 值的 SUM 聚合函数
Annotating SUM aggregation function leading to 'None' value in Django
正在做我的第一个真正的 Django 项目,需要指导。
背景:
我的项目是一个 reddit 克隆。用户提交链接+文本。访客投票赞成或反对。有一个 social_ranking 算法,作为后台脚本每 ~2 分钟运行一次,根据净票数和内容的新鲜度重新排列所有提交。相当普通的东西。
问题:
按 votes
排序无法正常工作,因为 votes
被初始化为 None
而不是 0
。这会导致获得 None
票的提交排名低于获得反对票的提交。我已经调试这个问题好几天了 - 运气不好。
规格:
我已经超越了模型的模型管理器,为查询集注释了一个 Sum
聚合函数,然后通过 'social rank' 和投票对所述查询集进行排序。
下面是我的models.py。我使用的是 Django 1.5
,因此您在此处看到的某些内容可能不对应 1.8(例如 get_query_set
与 get_queryset
):
class LinkVoteCountManager(models.Manager):
def get_query_set(self):
return super(LinkVoteCountManager, self).get_query_set().annotate(votes=Sum('vote__value')).order_by('-rank_score', '-votes')
class Link(models.Model):
description = models.TextField(_("Write something"))
submitter = models.ForeignKey(User)
submitted_on = models.DateTimeField(auto_now_add=True)
rank_score = models.FloatField(default=0.0)
url = models.URLField(_("Link"), max_length=250, blank=True)
with_votes = LinkVoteCountManager()
objects = models.Manager()
def __unicode__(self):
return self.description
def set_rank(self):
# Based on reddit ranking algo at http://amix.dk/blog/post/19588
epoch = datetime(1970, 1, 1).replace(tzinfo=None)
netvotes = self.votes # 'NONE' votes are messing up netvotes amount.
if netvotes == None:
netvotes = 0
order = log(max(abs(netvotes), 1), 10)
sign = 1 if netvotes > 0 else -1 if netvotes < 0 else 0
unaware_submission = self.submitted_on.replace(tzinfo=None)
td = unaware_submission - epoch
epoch_submission = td.days * 86400 + td.seconds + (float(td.microseconds) / 1000000)
secs = epoch_submission - 1432201843
self.rank_score = round(sign * order + secs / 45000, 8)
self.save()
class Vote(models.Model):
voter = models.ForeignKey(User)
link = models.ForeignKey(Link)
value = models.IntegerField(null=True, blank=True, default=0)
def __unicode__(self):
return "%s gave %s to %s" % (self.voter.username, self.value, self.link.description)
如果需要,以下是我的 views.py 中的相关部分:
class LinkListView(ListView):
model = Link
queryset = Link.with_votes.all()
paginate_by = 10
def get_context_data(self, **kwargs):
context = super(LinkListView, self).get_context_data(**kwargs)
if self.request.user.is_authenticated():
voted = Vote.objects.filter(voter=self.request.user)
links_in_page = [link.id for link in context["object_list"]]
voted = voted.filter(link_id__in=links_in_page)
voted = voted.values_list('link_id', flat=True)
context["voted"] = voted
return context
class LinkCreateView(CreateView):
model = Link
form_class = LinkForm
def form_valid(self, form):
f = form.save(commit=False)
f.rank_score=0
f.with_votes = 0
f.category = '1'
f.save()
return super(CreateView, self).form_valid(form)
任何人都可以阐明我需要做什么来解决“None
”问题吗?提前致谢。
只是碰壁,尽管我选择忽略 None
个条目,将它们排除在结果之外。我猜你不想要那个。
顺便说一句,这个问题有同样的问题Annotating a Sum results in None rather than zero
至于该问题的答案中指出的使用自定义 sql 以外的解决方案,您可以改用 Django 1.8 并寻求在 Django 的错误跟踪器中打开的票证中指出的解决方案6 年 (!) https://code.djangoproject.com/ticket/10929
Coalesce(Sum('field'), 0)
所以你的经理会是:
class LinkVoteCountManager(models.Manager):
def get_query_set(self):
return super(LinkVoteCountManager, self).get_query_set().annotate(
votes=Coalesce(Sum('vote__value'), 0)
).order_by(
'-rank_score',
'-votes'
)
PS: 我没有测试代码,因为我自己没有使用 Django 1.8。
您也可以替换行
netvotes = self.votes
到
netvotes = self.votes or 0
您现在可以删除 if 语句。
这在许多其他语言中的作用是 return 非 falsy 值(None, 0, ""),或最后一个值,'0' in这个特殊情况。
正在做我的第一个真正的 Django 项目,需要指导。
背景: 我的项目是一个 reddit 克隆。用户提交链接+文本。访客投票赞成或反对。有一个 social_ranking 算法,作为后台脚本每 ~2 分钟运行一次,根据净票数和内容的新鲜度重新排列所有提交。相当普通的东西。
问题:
按 votes
排序无法正常工作,因为 votes
被初始化为 None
而不是 0
。这会导致获得 None
票的提交排名低于获得反对票的提交。我已经调试这个问题好几天了 - 运气不好。
规格:
我已经超越了模型的模型管理器,为查询集注释了一个 Sum
聚合函数,然后通过 'social rank' 和投票对所述查询集进行排序。
下面是我的models.py。我使用的是 Django 1.5
,因此您在此处看到的某些内容可能不对应 1.8(例如 get_query_set
与 get_queryset
):
class LinkVoteCountManager(models.Manager):
def get_query_set(self):
return super(LinkVoteCountManager, self).get_query_set().annotate(votes=Sum('vote__value')).order_by('-rank_score', '-votes')
class Link(models.Model):
description = models.TextField(_("Write something"))
submitter = models.ForeignKey(User)
submitted_on = models.DateTimeField(auto_now_add=True)
rank_score = models.FloatField(default=0.0)
url = models.URLField(_("Link"), max_length=250, blank=True)
with_votes = LinkVoteCountManager()
objects = models.Manager()
def __unicode__(self):
return self.description
def set_rank(self):
# Based on reddit ranking algo at http://amix.dk/blog/post/19588
epoch = datetime(1970, 1, 1).replace(tzinfo=None)
netvotes = self.votes # 'NONE' votes are messing up netvotes amount.
if netvotes == None:
netvotes = 0
order = log(max(abs(netvotes), 1), 10)
sign = 1 if netvotes > 0 else -1 if netvotes < 0 else 0
unaware_submission = self.submitted_on.replace(tzinfo=None)
td = unaware_submission - epoch
epoch_submission = td.days * 86400 + td.seconds + (float(td.microseconds) / 1000000)
secs = epoch_submission - 1432201843
self.rank_score = round(sign * order + secs / 45000, 8)
self.save()
class Vote(models.Model):
voter = models.ForeignKey(User)
link = models.ForeignKey(Link)
value = models.IntegerField(null=True, blank=True, default=0)
def __unicode__(self):
return "%s gave %s to %s" % (self.voter.username, self.value, self.link.description)
如果需要,以下是我的 views.py 中的相关部分:
class LinkListView(ListView):
model = Link
queryset = Link.with_votes.all()
paginate_by = 10
def get_context_data(self, **kwargs):
context = super(LinkListView, self).get_context_data(**kwargs)
if self.request.user.is_authenticated():
voted = Vote.objects.filter(voter=self.request.user)
links_in_page = [link.id for link in context["object_list"]]
voted = voted.filter(link_id__in=links_in_page)
voted = voted.values_list('link_id', flat=True)
context["voted"] = voted
return context
class LinkCreateView(CreateView):
model = Link
form_class = LinkForm
def form_valid(self, form):
f = form.save(commit=False)
f.rank_score=0
f.with_votes = 0
f.category = '1'
f.save()
return super(CreateView, self).form_valid(form)
任何人都可以阐明我需要做什么来解决“None
”问题吗?提前致谢。
只是碰壁,尽管我选择忽略 None
个条目,将它们排除在结果之外。我猜你不想要那个。
顺便说一句,这个问题有同样的问题Annotating a Sum results in None rather than zero
至于该问题的答案中指出的使用自定义 sql 以外的解决方案,您可以改用 Django 1.8 并寻求在 Django 的错误跟踪器中打开的票证中指出的解决方案6 年 (!) https://code.djangoproject.com/ticket/10929
Coalesce(Sum('field'), 0)
所以你的经理会是:
class LinkVoteCountManager(models.Manager):
def get_query_set(self):
return super(LinkVoteCountManager, self).get_query_set().annotate(
votes=Coalesce(Sum('vote__value'), 0)
).order_by(
'-rank_score',
'-votes'
)
PS: 我没有测试代码,因为我自己没有使用 Django 1.8。
您也可以替换行
netvotes = self.votes
到
netvotes = self.votes or 0
您现在可以删除 if 语句。
这在许多其他语言中的作用是 return 非 falsy 值(None, 0, ""),或最后一个值,'0' in这个特殊情况。