2012-04-21 3 views
4

Я строю небольшую поисковую систему для проекта Django (да, я знаю, что есть много продуктов, которые делают это, но я бы хотел попробовать, просто для удовольствия) , я в основном имею следующие модели:Django - Annotate on ManyToMany поля с использованием промежуточной таблицы

class Word(models.Model): 
    """ A searchable word. 
    We only store the slugified value 
    """ 
    slug = models.SlugField(unique = True) 

class Searchable(models.Model): 
    """ Superclass for Searchable objects. 
    """ 
    words = models.ManyToManyField(
     Word, 
     through='WordCount') 

class WordCount(models.Model): 
    """ Occurences of a word in a Searchable object. 
    """ 
    word = models.ForeignKey(Word) 
    item = models.ForeignKey(Searchable) 
    count = models.IntegerField() 

Так, например, я создаю объект страницу (подклассы Searchable) с текстом «Привет StackOverflow, у меня есть вопрос Джанго». Система создаст экземпляр Word для каждого слова в этом предложении и для каждого экземпляра WordCount, говорящего, что каждое слово появляется один раз в тексте.

Создание запроса для получения всех экземпляров, содержащих возможность поиска один более слово работает нормально (searchable_text извлечь слова и делает список из него):

def search(query) 
    tokens = searchable_text(query) 
    words = Word.objects.filter(
         reduce(operator.or_, 
           [models.Q(slug__contains = t) 
           for t in tokens])) 

    return Searchable.objects.filter(words__in = words) 

Теперь то, что я хотел бы сделать, это использовать промежуточное отношение для упорядочения результатов. Я хотел бы сохранить QuerySet поэтому следующий код не будет работать, но дает представление о том, что я хочу сделать (с уродливыми заплатами сделать аннотации):

def search(query) 
    tokens = searchable_text(query) 
    words = Word.objects.filter(
         reduce(operator.or_, 
           [models.Q(slug__contains = t) 
           for t in tokens])) 
    results = [] 
    for obj in Searchable.objects.filter(words__in = words): 
     matching_words = obj.wordcount_set.filter(word__in = words) 
     obj.weight = sum([w.count for w in matching_words]) 
     results.append(obj) 

    return sorted(results, 
        reverse = True, 
        key = lambda x: x.weight) 

Так в основном: - я получаю все Объекты Word, содержащиеся в запросе (или частично соответствующие, если я ищу «Stack», будет учитываться Word «StackOverflow») - Я получаю все объекты, которые имеют отношение к любому из этих слов - для каждого объекта , Я выбираю все связанные объекты WordCount, которые связаны со Словом в списке Word, ранее вычисленным, затем суммируют атрибут count и сохраняют его как аннотацию «вес» - я сортирую свои объекты по «весу» '

Я не знаю, возможно ли это с QuerySet, но я хотел бы сохранить формат для дополнительных действий после (например, отфильтровать некоторые результаты).

Я знаю, что возможны многие улучшения, но это было бы хорошим началом.

Спасибо за ответы, Винсент

ответ

2

Попробуйте

Searchable.objects.filter(words__in=words).annotate(
    weight=models.Sum('wordcount__count')).order_by('-weight') 
+0

Спасибо, это работает как шарм :) Дело в том, что это немного смущает (не ваше решение, сам Джанго: D) , Я думал, что Sum ('wordcount__count') сделает сумму на всех объектах WordCount, связанных с экземпляром, а не с фильтрами ранее. Я предполагаю, что это имеет смысл, если смотреть на сгенерированный запрос SQL. – Vincent

+0

@ Vincent да, вы можете проверить сгенерированный SQL на 'print queryset.query' – okm

+0

Но он показывает несколько строк с одинаковым результатом. Это не чёткое отличие. –

Смежные вопросы