Я строю небольшую поисковую систему для проекта 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, но я хотел бы сохранить формат для дополнительных действий после (например, отфильтровать некоторые результаты).
Я знаю, что возможны многие улучшения, но это было бы хорошим началом.
Спасибо за ответы, Винсент
Спасибо, это работает как шарм :) Дело в том, что это немного смущает (не ваше решение, сам Джанго: D) , Я думал, что Sum ('wordcount__count') сделает сумму на всех объектах WordCount, связанных с экземпляром, а не с фильтрами ранее. Я предполагаю, что это имеет смысл, если смотреть на сгенерированный запрос SQL. – Vincent
@ Vincent да, вы можете проверить сгенерированный SQL на 'print queryset.query' – okm
Но он показывает несколько строк с одинаковым результатом. Это не чёткое отличие. –