sultan
sultan

Reputation: 6058

Django: select hot/most popular articles

I've the following models

class Article(models.Model):
    title = models.CharField(max_length=255, db_index=True)
    slug = UniqueSlugField(prepopulate_from='title', unique=True)
    favorited = models.ManyToManyField(to=User, blank=True)
    tags = TaggableManager()
    ...

class Vote(models.Model):
    article = models.ForeignKey(to=Article)
    voter = models.ForeignKey(to=User, related_name='voter')
    timestamp = models.DateTimeField(auto_now_add=True)
    ...

I use following line of code to get popular articles in descending order for a selected tag

Vote.objects.filter(tags__in=[tag]).annotate(num_articles=Count('article')).order_by('-num_articles')

How to build orm query to get popular articles by the following field of Article.favorited and based on Vote model?

Thanks,

Sultan

Upvotes: 0

Views: 1585

Answers (2)

okm
okm

Reputation: 23871

Use join

Article.objects.filter(favorited__in=[...]).annotate(
        vc=models.Count('vote')).order_by('-vc')

Or normally faster(less joins), but more complex, and limited(no longer a QuerySet but not a big issue normally) sub-query version

qs = Article.objects.filter(favorited__in=[...])
top_votes = Vote.objects.filter(article__in=qs).values('article').annotate(
         vc=models.Count('pk')).values_list('article', 'vc').order_by('-vc')

# normally you may only want Top N
top_n_pks = tuple(pk for pk, vc in top_votes[:N])
tops = sorted(Article.objects.filter(pk__in=top_n_pks).order_by(),
         key=lambda x: top_n_pks.index(x.pk))

Upvotes: 0

Chris Pratt
Chris Pratt

Reputation: 239290

Why would you get "popular articles" through the Vote model? When you run the query, you end up with a queryset of Votes. Then, you must issue additional queries to get the Articles from that.

You should be using:

Article.objects.filter(tags=tag).annotate(vote_count=Count('vote')).order_by('-vote_count')

Then, you have a proper queryset of Articles and you're good to go.

If you want favorites, you would modify the above to use user instead of vote:

Article.objects.filter(tags=tag).annotate(favorite_count=Count('user')).order_by('-favorite_count')

Upvotes: 3

Related Questions