Django поиск в нескольких моделях и удаление дубликатов
Я пытаюсь создать функцию поиска для моего блога, которая ищет по двум моделям Articles
и Spots
. Эти две модели связаны через поворотную таблицу ArticleSpots
.
Мой блог структурирован таким образом, что в каждой статье есть несколько мест.
При поиске запроса я хочу, чтобы запрос искался в обеих моделях, но отображались только кликабельные статьи. У меня есть html-страница для каждой статьи, но не для каждого спота, поэтому все споты, полученные в результате поиска, должны быть показаны как статья, которая содержит спот. Надеюсь, это имеет смысл!
Вот код, который я придумал, но проблема в том, что я получаю много дубликатов в переменной results
. Есть дубликаты внутри каждого articles_from_spots
и articles_from_query
, а также есть дубликаты между ними.
Правильно ли это? Как я могу удалить дубликаты из результатов? Любая помощь будет принята с благодарностью!
views.py
def search(request):
query = request.GET.get("q")
articles_from_query = Articles.objects.filter(
Q(title__icontains=query) |
Q(summary__icontains=query)
)
spots_from_query = Spots.objects.filter(
Q(title__icontains=query) |
Q(summary__icontains=query) |
Q(content__icontains=query)
)
articles_from_spots = []
for x in spots_from_query:
article = Articles.objects.filter(articlespots__spot=x)
articles_from_spots.extend(article)
results = chain(articles_from_spots, articles_from_query)
context = {
'results': results,
}
return render(request, "Search.html", context)
models.py
class Articles(models.Model):
title = models.CharField(max_length=155)
summary = models.TextField(blank=True, null=True)
class ArticleSpots(models.Model):
article = models.ForeignKey('Articles', models.DO_NOTHING)
spot = models.ForeignKey('Spots', models.DO_NOTHING)
class Spots(models.Model):
title = models.CharField(max_length=155)
summary = models.TextField(blank=True, null=True)
content = models.TextField(blank=True, null=True)
Вы должны быть в состоянии сделать это в одном запросе, следуя взаимосвязи от статьи к месту
Articles.objects.filter(
Q(title__icontains=query) |
Q(summary__icontains=query) |
Q(articlespots__spot__title__icontains=query) |
Q(articlespots__spot__summary__icontains=query) |
Q(articlespots__spot__content__icontains=query)
).distinct()
Если бы вы добавили поле ManyToManyField из Article в Spots, это бы немного упростило дело и имело бы смысл с точки зрения дизайна
class Articles(models.Model):
...
spots = models.ManyToManyField('Spots', through='ArticleSpots')
Articles.objects.filter(
Q(title__icontains=query) |
Q(summary__icontains=query) |
Q(spots__title__icontains=query) |
Q(spots__summary__icontains=query) |
Q(spots__content__icontains=query)
).distinct()
Основной проблемой является неэффективный цикл for, но сначала я должен предложить кое-что другое.
Я бы настоятельно рекомендовал изменить дизайн модели:
class Articles(models.Model):
title = models.CharField(max_length=155)
summary = models.TextField(blank=True, null=True)
spots = models.ManyToManyField(Spot, blank=True, related_name='articles')
class Spots(models.Model):
title = models.CharField(max_length=155)
summary = models.TextField(blank=True, null=True)
content = models.TextField(blank=True, null=True)
Функция точно такая же (плюс вы можете вызывать spot.articles.all()
и article.spots.all()
). Вы все еще можете получить доступ к вашей модели ArticleSpots
как Article.spots.through
, если вам нужно. В случае, если вам позже понадобится больше полей на соединение, вы можете сделать следующее (вместе с вашим оригинальным классом ArticleSpots, возможно, с on_delete=models.CASCADE
там вместо этого):
spots = models.ManyToManyField(Spot, blank=True, through= 'ArticleSpots')
Цикл for-loop неэффективен (подумайте о десятках секунд, когда вы дойдете до тысяч объектов или если произойдет объединение), потому что он запускает запрос для каждого элемента в результате. Вместо этого вы должны получить articles_from_spots
с помощью прямого запроса. IE.:
article_ids = spots_from_query.values_list('articles__id', flat=True)
articles_from_spots = Article.objects.filter(id__in=article_ids)
Это гарантирует только 2 db-запроса за прогон. Затем вам нужно будет сделать что-то вроде того, чтобы превратить наборы запросов в списки перед их объединением:
results = chain(map(list, [articles_from_spots, articles_from_query]))
Возможно, все еще есть проблемы со смешиванием двух модельных кверисетов вместе, но все зависит от вашего шаблона. В целом это плохая практика, но острой проблемы, насколько вы знаете, нет.