Using Django to calculate "most popular tags", or any query that makes use of an aggregation function, is not the most intuitive process. I decided to roll my own Tags system, having already implemented one in PHP. Let's say I'm working on a simple blog -- here are a couple models:

class Entry(models.Model):
    title = models.CharField(max_length=255)
    body = models.TextField()
    pub_date = models.DateTimeField(editable=False)


class Tag(models.Model):
    name = models.CharField(max_length=50)
    entry = models.ForeignKey(Entry)

    objects = TagManager()

So, the query to pull the most popular tags would look like this:

SELECT name, COUNT(name) AS count
FROM tags
GROUP BY name
ORDER BY count DESC, name ASC

Getting this query into the ORM gave me a bit of a headache, but using Django's aggregation features makes it not too difficult. The TagManager will handle grabbing the most popular tags. Here is what it looks like:

class TagManager(models.Manager):
    def top_tags(self, max=15):
        top = self.model.objects.values('name').annotate(score=Count('entry')).order_by('-score')[:max]
    return [tag['name'] for tag in top]

Note the list comprehension that occurs after the query. This is because performing just the first line (which annotates the queryset) will return a ValuesQuerySet object -- which looks like:

[{'score': 3, 'name': u'python'}, 
 {'score': 1, 'name': u'bbcode'}, 
 {'score': 1, 'name': u'blog'}, 
 {'score': 1, 'name': u'django'}]

Since I just want the most popular tags, I'll run through the result set, grabbing the names and creating a new list. If I want to find entries that match a certain tag:

class EntryManager(models.Manager):
    def matches_tag(self, tag):
        return self.model.objects.filter(tag__name__iexact=tag)

Comments (0)

Commenting has been disabled for this entry