Most popular entry tags per blog - how best to do it?

536 views
Skip to first unread message

Phil Gyford

unread,
Sep 29, 2010, 10:56:22 AM9/29/10
to django-taggit
I'm using django-taggit for a weblog project - all Entries can have
Tags on them. Each Entry is associated with only one of several Blogs
on the site.

I'm wondering how best to get a list of popular tags within a Blog,
rather than across the entire site.

Would it be best to create a child model for Tags which stores the
blog_id as well as object_id (ie, entry ID)? Or just create a custom
query that only fetches tags on objects (Entries) that are associated
with the Blog in question?

Any pointers appreciated - I'm fairly new to Django so don't quite
have a feel for the best way to do things like this.

Carl Meyer

unread,
Sep 29, 2010, 3:30:50 PM9/29/10
to django...@googlegroups.com
Hi Phil,

Phil Gyford wrote:
> I'm using django-taggit for a weblog project - all Entries can have
> Tags on them. Each Entry is associated with only one of several Blogs
> on the site.
>
> I'm wondering how best to get a list of popular tags within a Blog,
> rather than across the entire site.

Are Entries the only things on your site that receive tags? In that case
you should probably be using the "custom through model" feature and
providing your own TaggedItem model with a direct FK to Entry, instead
of the GenericForeignKey used by the default TaggedItem model (see the
taggit docs for instructions).

If you do this, then the query you want should be a relatively
straightforward query on the TaggedItem table, something like (untested
top-of-head code):

TaggedItem.objects.filter(content_object__blog=myblog).values('tag').annotate(count=Count('id')).order_by('count')

Which will return an iterable of dictionaries, where each dictionary has
a "tag" key containing the PK of the tag, and a "count" key containing
the number of times that tag appears on an entry in the given blog.

I wouldn't try to denormalize this (i.e. by storing the blog id directly
on the through model) unless you have evidence that it's actually a
performance problem in your use cases.

Carl

Julian Moritz

unread,
Sep 29, 2010, 4:45:33 PM9/29/10
to django...@googlegroups.com
Hi Phil,

Am Mittwoch, den 29.09.2010, 07:56 -0700 schrieb Phil Gyford:
> I'm using django-taggit for a weblog project - all Entries can have
> Tags on them. Each Entry is associated with only one of several Blogs
> on the site.
>
> I'm wondering how best to get a list of popular tags within a Blog,
> rather than across the entire site.
>

there's an app I've written: django-taggit-templatetags. You can install
it via pip:

pip install django-taggit-templatetags

As you can read in the README

http://pypi.python.org/pypi/django-taggit-templatetags/

there's a template-tag called 'get_taglist'. Use it like

{% get_taglist as taglist for 'yourapp' %}

'yourapp' is in your case the blogapp you use. in the template-var tags
are now all tags of your blog-app ordered by item-count descending. you
can slice it with the slice template-filter of django's builtin filters.

the app gives you the possibility for a hash cloud as well. If there are
any questions, please ask.

Regards
Julian

Phil Gyford

unread,
Sep 30, 2010, 9:10:29 AM9/30/10
to django...@googlegroups.com
Carl,

Many thanks for this - it very nearly does the trick! I hadn't quite
understood what the 'through model' stuff was for before, and now I
know. For the record I now have models whose relevant parts are like
this:

class TaggedEntry(TaggedItemBase):
content_object = models.ForeignKey('Entry')

class Entry(models.Model):
LIVE_STATUS = 2
blog = models.ForeignKey(Blog)
tags = TaggableManager(through=TaggedEntry)

class Blog(models.Model):
...

In one view -- listing all entries with a specific tag within a Blog
-- I can get all the matching Entries like this:

blog = get_object_or_404(Blog, slug=blog_slug)
tag = get_object_or_404(Tag, slug=tag_slug)

entries = list(Entry.live.filter(
blog = blog,
tags__name__in=[tag.slug]
))

And then in the view where I want a list of most popular Tags within a
Blog I'm doing this:

blog = get_object_or_404(Blog, slug=blog_slug)

popular_tags = TaggedEntry.objects.filter(
content_object__blog = blog,
content_object__status = Entry.LIVE_STATUS,
).values('tag').annotate(num_times=Count('id')).order_by('-num_times').select_related('tag')

That seems to work fine, except... popular_tags doesn't contain data
about each tag. Doing this in the template:

{% for tag in popular_tags %}
{{ tag }}<br />
{% endfor %}

only outputs lines like this:

{'tag': 1L, 'num_times': 4}

The SQL generated by the query does fetch the name and slug of each
tag, thanks to the select_related('tag') clause, but I'm not sure how
to access them. Any ideas?

--
http://www.gyford.com/

Phil Gyford

unread,
Sep 30, 2010, 9:12:24 AM9/30/10
to django...@googlegroups.com
Julian,

Thanks for your suggestion. I don't think django-taggit-templatetags
is the answer here I'm afraid - I don't want to fetch tags just within
an app, but only tags applied to Entries which are within a particular
instance of a Blog object. I hope that makes sense...

Thanks,
Phil

--
http://www.gyford.com/

Carl Meyer

unread,
Sep 30, 2010, 9:48:53 AM9/30/10
to django...@googlegroups.com
Hi Phil,

Phil Gyford wrote:
[snip]


> popular_tags = TaggedEntry.objects.filter(
> content_object__blog = blog,
> content_object__status = Entry.LIVE_STATUS,
> ).values('tag').annotate(num_times=Count('id')).order_by('-num_times').select_related('tag')
>
> That seems to work fine, except... popular_tags doesn't contain data
> about each tag. Doing this in the template:
>
> {% for tag in popular_tags %}
> {{ tag }}<br />
> {% endfor %}
>
> only outputs lines like this:
>
> {'tag': 1L, 'num_times': 4}

Yes, this is a limitation of using .values() with a foreign key field;
it gives you back raw primary keys, not object accessors. I'm not aware
of an elegant way around it (maybe Alex or someone else is?) But there
is a fairly simple, if inelegant, way, which is to do a second query on
the Tag model using __in with the list of PKs:

tags = Tag.objects.filter(pk__in=[v['tag'] for v in popular_tags])

And then use this queryset in your template. If you also need the actual
num_times value in your template, you'd need an additional step in the
view to zip that data onto the tag objects.

Actually, it occurs to me that you might be able to get this all done in
one step by querying directly on the Tag model instead, something like:

Tag.objects.filter(
blog_taggedentry_items__content_object__blog=blog,
blog_taggedentry_items__content_object__status=Entry.LIVE_STATUS,
).annotate(num_times=Count('blog_taggedentry_items')
).order_by('num_times')

This assumes that your TaggedEntry model lives in an app called "blog"
and you're running Django 1.2. If your app is called something else,
you'd replace "blog" with that in "blog_taggedentry_items", and if
you're on Django 1.1 you'd leave out the app name entirely
("taggedentry_items").

Again, untested code, YMMV. Good luck!

Carl

Phil Gyford

unread,
Sep 30, 2010, 10:07:00 AM9/30/10
to django...@googlegroups.com
On Thu, Sep 30, 2010 at 2:48 PM, Carl Meyer <ca...@oddbird.net> wrote:

> Tag.objects.filter(
>    blog_taggedentry_items__content_object__blog=blog,
>    blog_taggedentry_items__content_object__status=Entry.LIVE_STATUS,
>  ).annotate(num_times=Count('blog_taggedentry_items')
>  ).order_by('num_times')
>
> This assumes that your TaggedEntry model lives in an app called "blog"
> and you're running Django 1.2. If your app is called something else,
> you'd replace "blog" with that in "blog_taggedentry_items"

Ooh, that's... interesting! And it works :) Thanks so much.

I didn't know you could do that appname_modelname_items__... kind of
thing. So much to learn.

Thanks again,
Phil

--
http://www.gyford.com/

Karen Wright

unread,
Jan 7, 2014, 7:02:51 AM1/7/14
to django...@googlegroups.com, ph...@gyford.com




Forgive me, but I am still new to Django and have diligently studied the above thread five times, but understand it only enough to know I'm trying to do the same thing Phil is describing, but am unable to follow Carl's approach.  I am on Django 1.6.1.

Specifically, I am building a small polling application that allows members to tag community decisions as they vote on them:


class Vote(models.Model):
    decision                = models.ForeignKey(Decision)
    member                  = models.ForeignKey(CustomUser)
    choices                 = models.CharField(max_length=500)
    tags                    = TaggableManager()



...so, Jim votes "green" on "Decision #46: What color should we paint the Community Room?", and tags this decision with terms like "beautification, maintenance"

Susan votes "blue" on "Decision #46...", which she tags "maintenance, infrastructure"


...when Joe votes on "Decision $46...", I'd like to auto-suggest and present a cloud of popular tags, queried from Jim's and Susan's votes above.

But I don't want to show Joe their tags on any *other* decisions.

I'm also afraid I don't understand Carl's suggestion of using a "custom through model" feature at all, but that sounds like precisely what I need to understand.  Wondering if someone can point me to a blog post or stackoverflow or some other way of explaining it?

Thank you!
Reply all
Reply to author
Forward
0 new messages