I'm working on an application using django-voting and have the sort order of the homepage items working using Eric Florenzano's custom VoteAwareManager technique:
models.py
class VoteAwareManager(models.Manager):
""" Get top votes. hot = VoteAwareManager() """
def _get_score_annotation(self):
model_type = ContentType.objects.get_for_model(self.model)
table_name = self.model._meta.db_table
return self.extra(select={
'score': 'SELECT COALESCE(SUM(vote),0) FROM %s WHERE content_type_id=%d AND object_id=%s.id' %
(Vote._meta.db_table, int(model_type.id), table_name)
}
)
def most_loved(self,):
return self._get_score_annotation().order_by('-score')
def most_hated(self):
return self._get_score_annotation().order_by('score')
class Post(models.Model):
"""Post model"""
title = models.CharField(_("title"), max_length=200, blank=False)
slug = models.SlugField(_("slug"), blank=True)
author = models.ForeignKey(User, related_name="added_posts")
kind = models.CharField(max_length=1, choices=KIND, default=1)
url = models.URLField(blank=True, null=True, help_text="The link URL", default='')
content_markdown = models.TextField(_("Entry"), blank=True)
content_html = models.TextField(blank=True, null=True, editable=False)
status = models.IntegerField(_("status"), choices=STATUS_CHOICES, default=IS_PUBLIC)
allow_comments = models.BooleanField(_("Allow Comments?"), blank=False, default=1)
created_at = models.DateTimeField(_("created at"), default=datetime.now)
updated_at = models.DateTimeField(_("updated at"))
objects = models.Manager()
hot = VoteAwareManager()
views.py
def homepage(request):
"""Show top posts"""
return object_list(request,
queryset=Post.hot.most_loved().filter(status=IS_PUBLIC),
template_name='homepage.html',
template_object_name='post',
extra_context= {'profile': get_profiles}
)
I would now like to combine Hacker New's ranking algorithm with the code above so that older items get moved down in rank, but I am having trouble. I am not sure if the relevant code should go into the VoteAwareManager function, or the most_loved method, or elsewhere altogether.
The following is what I have tried:
1. Calculation in most_loved method: returns TypeError at /
unsupported operand type(s) for -: 'QuerySet' and 'int'
(when using a random timestamp just to see if I can get a result, eventually I need to figure out how to get the object timestamp, too — I am a beginning programmer):
def most_loved(self):
totalscore = self._get_score_annotation()
time_stamp = 20120920
gravity = 1.8
return (totalscore - 1) / pow((time_stamp+2), gravity)
2. Calculation in SQL: returns TemplateSyntaxError at / Caught DatabaseError while rendering: column "votes.time_stamp" must appear in the GROUP BY clause or be used in an aggregate function LINE 1: ...(SELECT COALESCE(SUM(vote),0 / (EXTRACT(HOUR FROM TIME_STAMP...
:
class VoteAwareManager(models.Manager):
""" Get top votes. hot = VoteAwareManager() """
def _get_score_annotation(self):
model_type = ContentType.objects.get_for_model(self.model)
table_name = self.model._meta.db_table
return self.extra(select={
'score': 'SELECT COALESCE(SUM(vote),0 / (EXTRACT(HOUR FROM TIME_STAMP)+2 * 1.8)) FROM %s WHERE content_type_id=%d AND object_id=%s.id' %
(Vote._meta.db_table, int(model_type.id), table_name)
}
)
One option is attempt to change the voting system to use django-rangevoting, but I'd like to get this working with django-voting if possible. Any help much appreciated.
Not perfect (omits the -1 subtraction to negate user's own vote), but this seems to work well enough for now:
class VoteAwareManager(models.Manager):
""" Get recent top voted items (hacker news ranking algorythm, without the -1 for now since it breaks the calculation as all scores return 0.0)
(p - 1) / (t + 2)^1.5
where p = points and t = age in hours
"""
def _get_score_annotation(self):
model_type = ContentType.objects.get_for_model(self.model)
table_name = self.model._meta.db_table
return self.extra(select={
'score': 'SELECT COALESCE(SUM(vote / ((EXTRACT(EPOCH FROM current_timestamp - created_at)/3600)+2)^1.5),0) FROM %s WHERE content_type_id=%d AND object_id=%s.id' % (Vote._meta.db_table, int(model_type.id), table_name)
})
def most_loved(self):
return self._get_score_annotation().order_by('-score',)