通过 ManyToManyField = Value 订购 django 查询集

Dav*_*nes 4 python django django-models

如果有一些模型,如:

class Tag(models.Model):
    name = models.CharField()

class Thing(models.Model):
    title = models.CharField()
    tags = models.ManyToManyField(Tag)
Run Code Online (Sandbox Code Playgroud)

我可以做一个过滤器:

Thing.objects.filter(tags__name='foo')
Thing.objects.filter(tags__name__in=['foo', 'bar'])
Run Code Online (Sandbox Code Playgroud)

但是是否可以在标签值上订购查询集?

Thing.objects.order_by(tags__name='foo')
Thing.objects.order_by(tags__name__in=['foo','bar'])
Run Code Online (Sandbox Code Playgroud)

在这个例子中,我期望(或喜欢)的是所有事物模型,但在它们有我知道的标签/标签的地方订购。我不想过滤掉它们,而是将它们带到顶部。

我认为使用 FIELD 运算符是可能的,但似乎我只能使它在该模型表中的列上工作,例如标题,但不能在链接表上工作。

谢谢!

编辑:接受以下解决方案后,我意识到它的一个错误/限制。

如果一个特定的事物有多个标签,那么(由于在 SQL 的幕后进行了左连接)它将为该事物生成一个条目,为它拥有的每个标签。每个匹配或不匹配的标签都有一个真或假。

将 .distinct() 添加到查询集仅略有帮助,限制为每个事物最多 2 行(即一个 tagged=True,一个 tagged=False)。

我知道我需要在 SQL 中做什么,即 MAX() 到 CASE(),然后 GROUP BY Thing 的主键,这意味着我将为每个 Thing 获得一行,如果有任何标签匹配,则标记将为 True(否则为 False)。

我看到的方式,人们通常达到这个样的事情是使用.values()是这样的:

Thing.objects.values('pk').annotate(tagged=Max(Case(...)))
Run Code Online (Sandbox Code Playgroud)

但结果只是 pk 和标记,我需要整个 Thing 模型作为结果。所以我设法实现了我想要的,因此:

from django.db.models import Case, When, Max, BooleanField

tags = ['music'] # for example

queryset = Thing.objects.all().annotate(tagged=Max(Case(
    When(tags__name__in=tags, then=True),
    default=False,
    output_field=BooleanField()
)))
queryset.query.group_by = ['pk']
queryset.order_by('-tagged')
Run Code Online (Sandbox Code Playgroud)

这似乎有效,但按机制分组感觉很奇怪/hacky。以这种方式分组是否可以接受/可靠?

对不起,史诗更新:(

bak*_*kal 5

我会尝试使用条件值注释查询,当标签在您提供的列表中时该条件值变为真

from django.db.models import Case, When, IntegerField

Thing.objects.annotate(tag_is_known=Case(
    When(tags__name__in=['foo', 'bar'], then=1),
    default=0,
    output_field=IntegerField()
))
Run Code Online (Sandbox Code Playgroud)

接下来我们使用我们调用的注解tag_is_known来排序order_by()

Thing.objects.annotate(tag_is_known=...).order_by('tag_is_known')
Run Code Online (Sandbox Code Playgroud)

布尔版本

Thing.objects.annotate(tag_is_known=Case(
    When(tags__name__in=['foo', 'bar'], then=True),
    default=False,
    output_field=BooleanField()
))
Run Code Online (Sandbox Code Playgroud)