Джанго рассчитывает на многие ко многим - PullRequest
11 голосов
/ 19 октября 2011

У меня есть модели:

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)

Я хочу узнать, сколько статей содержит каждое ключевое слово.По сути, я хочу иметь список ключевых слов, где я могу подсчитать каждое из них, чтобы дать ему относительный вес.

Я пробовал:

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

, но

keyword_list[0].key_count    

просто дает мне количество различных ключевых слов в каждой статье?Это как-то обратный поиск?

Любая помощь будет высоко ценится.

ОБНОВЛЕНИЕ

Так что я получил его на работу:

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 }

но представление приводит к ужасному количеству запросов.Я попытался реализовать предложения, приведенные здесь (спасибо), но это единственный метод, который, кажется, работает на данный момент.Тем не менее, я, должно быть, что-то делаю не так, потому что теперь у меня 400+ запросов!

ОБНОВЛЕНИЕ

Вау!Наконец-то все заработало:

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}

Ответы [ 3 ]

17 голосов
/ 19 октября 2011

Поскольку вам нужно количество статей, в которых есть каждое ключевое слово, вы должны сделать это другим способом:

>>> Keyword.objects.all().annotate(article_count=models.Count('article'))[0].article_count
2
4 голосов
/ 20 октября 2011

Это то же самое, что и ответ от Vebjorn Ljosa , но с небольшим контекстом, где article_set - это related_name обратного объекта отношения многие ко многим.

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

Чтобы проиллюстрировать ваши результаты, было бы проще вернуть .values():

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

Который вернул бы список словарей, которые выглядели бы примерно так:

[{'article_count': 36, 'keyword': u'bacon'}, 
 {'article_count': 4, 'keyword': u'unicorns'}, 
 {'article_count': 8, 'keyword': u'python'}]
1 голос
/ 19 октября 2011

Я не знаю, как бы вы сделали это эффективно, но если вам нужно это сделать.

keywords = Keyword.objects.all()
for keyword in keywords:
  print 'Total Articles: %d' % (Article.objects.filter(keywords=keyword).count())
...