Самый простой способ сделать это с двумя запросами: один запрос для получения всех Keyword
с, затем один, который фильтрует Article
с этим ключевым словом, например:
from functools import reduce
from operator import or_
kws = Keyword.objects.values_list('title', flat=True)
articles = Article.objects.filter(
reduce(or_, [Q(title__icontains=kw) for kw in kws])
)
.Однако приведенное выше может привести к большому запросу, поскольку мы сначала загружаем все заголовки.
Альтернативой является создание JOIN
с .extra
, а затем выполнение «необработанной» аннотации SQL-запроса:
Article.objects.extra(
tables=[Keyword._meta.db_table]
).annotate(
key_title=RawSQL('{}.{}'.format(Keyword._meta.db_table, Keyword.title.field_name), ())
).filter(title__icontains=F('key_title'))
В результате будет получен запрос, подобный следующему:
SELECT article.id, article.title, (keyword.title) AS key_title
FROM article , keyword
WHERE article.title LIKE CONCAT('%', REPLACE(REPLACE(REPLACE(((keyword.title)), '\\\\', '\\\\\\\\'), '%', '\\%'), '_', '\\_'), '%')
REPLACE(..)
части находятся здесь, чтобы экранировать заголовок ключевого слова, поскольку, если он содержит %
, это может привести к «подстановочному знаку» в выражении LIKE
.Это все введено поиском __icontains
, поэтому нам не нужно беспокоиться об этом.