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
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
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?
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
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
> 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