Django 搜索多个模型并删除重复项

Django searching multiple models and removing duplicates

我正在尝试为我的博客构建一个搜索功能,用于搜索 ArticlesSpots 这两个模型。这两个模型通过枢轴 table ArticleSpots 连接。 我的博客结构合理,每篇文章都有多个位置。

搜索查询时,我希望在两个模型中搜索查询,但只显示可点击的文章。 我的每篇文章都有一个 html 页面,但不是每个广告位,因此搜索产生的所有广告位都必须显示为包含该广告位的文章。希望这是有道理的!

这是我想出的代码,但问题是我在变量 results 中得到了很多重复项。每个 articles_from_spotsarticles_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()

如果您要将 Article 中的 ManyToManyField 添加到 Spots,它会稍微简化这一点,并且从设计 POV 来看是有意义的

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())。如果需要,您仍然可以使用 Article.spots.through 访问您的 ArticleSpots 模型。如果以后每个连接需要更多字段,您可以这样做(连同您原来的 ArticleSpots class,也许 on_delete=models.CASCADE 代替):

    spots = models.ManyToManyField(Spot, blank=True, through= 'ArticleSpots')

for 循环是低效的(当你得到几千个对象或者如果发生连接时想想几十秒),因为它会触发对每个项目的查询结果。相反,您应该通过直接查询获得 articles_from_spots。即.:

article_ids = spots_from_query.values_list('articles__id', flat=True)
articles_from_spots = Article.objects.filter(id__in=article_ids)

这将保证每个 运行 只有 2 个数据库查询。然后你需要做一些事情,比如在组合它们之前将查询集变成列表:

results = chain(map(list, [articles_from_spots, articles_from_query]))

将两个模型查询集混合在一起可能仍然存在问题,但这完全取决于您的模板。这通常是一种不好的做法,但据您所知,这并不是什么严重的问题。