Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django count on many-to-many

I have the models:

class Article(models.Model):
    title = models.TextField(blank=True)
    keywords = models.ManyToManyField(Keyword, null=True, blank=True)

class Keyword(models.Model):
    keyword = models.CharField(max_length=355, blank=True)

I want to get a count of how many articles have each keyword. In essence I want to have a list of keywords where I can get each ones count to give it a relative weighting.

I have tried:

keyword_list=Article.objects.all().annotate(key_count=Count('keywords__keyword'))

but

keyword_list[0].key_count    

just seems to give me the number of different keywords each article has? Is it somehow a reverse lookup?

Any help would be much appreciated.

UPDATE

So I got it working:

def keyword_list(request):
    MAX_WEIGHT = 5
    keywords = Keyword.objects.order_by('keyword')
    for keyword in keywords:
        keyword.count =  Article.objects.filter(keywords=keyword).count()
    min_count = max_count = keywords[0].count
    for keyword in keywords:
        if keyword.count < min_count:
            min_count = keyword.count
        if max_count > keyword.count:
            max_count = keyword.count 
    range = float(max_count - min_count)
    if range == 0.0:
        range = 1.0 
    for keyword in keywords:
        keyword.weight = (
            MAX_WEIGHT * (keyword.count - min_count) / range
        )
    return { 'keywords': keywords }

but the view results in a hideous number of queries. I have tried implementing the suggestions given here (thanks) but this is the only methid which seems to work at the moment. However, I must be doing something wrong as I now have 400+ queries!

UPDATE

Wooh! Finally got it working:

def keyword_list(request):
    MAX_WEIGHT = 5
    keywords_with_article_counts = Keyword.objects.all().annotate(count=Count('keyword_set'))
    # get keywords and count limit to top 20 by count
    keywords = keywords_with_article_counts.values('keyword', 'count').order_by('-count')[:20]
    min_count = max_count = keywords[0]['count']
    for keyword in keywords:
        if keyword['count'] < min_count:
            min_count = keyword['count']
        if max_count < keyword['count']:
            max_count = keyword['count']             
    range = float(max_count - min_count)
    if range == 0.0:
        range = 1.0
    for keyword in keywords:
        keyword['weight'] = int(
            MAX_WEIGHT * (keyword['count'] - min_count) / range
        )
    return { 'keywords': keywords}
like image 599
Darwin Tech Avatar asked Oct 19 '11 17:10

Darwin Tech


People also ask

How do you count to many fields in Django?

A ManyToManyField in Django is a field that allows multiple objects to be stored. This is useful and applicable for things such as shopping carts, where a user can buy multiple products. To add an item to a ManyToManyField, we can use the add() function.

What is a many-to-many relationship Django?

¶ A many-to-many relationship refers to a relationship between tables in a database when a parent row in one table contains several child rows in the second table, and vice versa.

How do I get rid of ManyToMany field data in Django?

ManyToMany field has some methods that you can call: add(Object) remove(Object) clear() this one is for removing all objects.

What is Related_name in Django?

The related_name attribute specifies the name of the reverse relation from the User model back to your model. If you don't specify a related_name, Django automatically creates one using the name of your model with the suffix _set. Syntax: field_name = models.Field(related_name="name")


1 Answers

This is the same as the answer from Vebjorn Ljosa, but with a little context, where article_set is the related_name of the reverse many-to-many relationship object.

keywords_with_article_counts = Keyword.objects.all().annotate(article_count=Count('article_set'))

To illustrate your results, it would be easier to return the .values():

keywords_with_article_counts.values('keyword', 'article_count')

Which would return a list of dictionaries that would look something like this:

[{'article_count': 36, 'keyword': u'bacon'}, 
 {'article_count': 4, 'keyword': u'unicorns'}, 
 {'article_count': 8, 'keyword': u'python'}]
like image 185
jathanism Avatar answered Oct 26 '22 16:10

jathanism