简体   繁体   English

通过ManyToManyField = Value订购django查询集

[英]Order a django queryset by ManyToManyField = Value

If have some models like: 如果有一些模型,如:

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

class Thing(models.Model):
    title = models.CharField()
    tags = models.ManyToManyField(Tag)

I can do a filter: 我可以做一个过滤器:

Thing.objects.filter(tags__name='foo')
Thing.objects.filter(tags__name__in=['foo', 'bar'])

But is it possible to order a queryset on the tags value? 但是可以在标签值上订购查询集吗?

Thing.objects.order_by(tags__name='foo')
Thing.objects.order_by(tags__name__in=['foo','bar'])

What I would expect (or like) back in this example, would be ALL Thing models, but ordered where they have a Tag/Tags that I know. 在这个例子中,我期望(或者喜欢)的是所有Thing模型,但是在他们拥有我知道的标签/标签的地方进行排序。 I don't want to filter them out, but bring them to the top. 我不想过滤它们,但把它们带到顶部。

I gather this is possible using the FIELD operator, but seemingly I can only make it work on columns in that models table, eg title, but not on linked tables. 我收集这可能是使用FIELD运算符,但似乎我只能使它在该模型表中的列上工作,例如标题,但不能在链接表上。

Thanks! 谢谢!

EDIT: After having accepted the below solution, I realised a bug/limitation with it. 编辑:在接受了下面的解决方案之后,我意识到了它的错误/限制。

If a particular Thing has multiple Tags, then (due to the left join done behind the scenes in the SQL) it will produce one entry for that Thing, for each Tag that it has. 如果一个特定的Thing有多个标签,那么(由于在SQL后面的场景中完成左连接),它将为每个标签生成一个条目。 With a True or False for each Tag that matches or not. 每个匹配或不匹配的标记是真还是假。

Adding .distinct() to the queryset helps only slightly, limiting to a max of 2 rows per Thing (ie one tagged=True, and one tagged=False). 将.distinct()添加到查询集只会略有帮助,每个Thing限制为最多2行(即一个tagged = True,一个tagged = False)。

I know what I need to do in the SQL, which is to MAX() the CASE(), and then GROUP BY Thing's primary key, which means I will get one row per Thing, and if there has been any tag matches, tagged will be True (and False otherwise). 我知道我需要在SQL中做什么,即MAX()CASE(),然后是GROUP BY Thing的主键,这意味着每个Thing会得到一行,如果有任何标记匹配,则标记将为True(否则为False)。

I see the way that people typically achieve this kind of thing is to use .values() like this: 我看到,人们通常达到这种事情的方法是使用.values()是这样的:

Thing.objects.values('pk').annotate(tagged=Max(Case(...)))

But the result is only pk and tagged, I need the whole Thing model as the result. 但结果只有pk和标记,我需要整个Thing模型作为结果。 So I've managed to achieve what I want, thusly: 所以我设法实现了我想要的,因此:

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')

This seems to work, but the group by mechanism feels weird/hacky. 这似乎有效,但是按机制分组感觉很奇怪/骇客。 Is it acceptable/reliable to group in this way? 以这种方式分组是否可接受/可靠?

Sorry for the epic updated :( 抱歉史诗已更新:(

I'd try annotate the query with the conditional value that turns true when the tag is in the list you provide 我尝试使用条件值来注释查询,当条件在您提供的列表中时,该条件值变为true

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()
))

Next we use that annotation we called tag_is_known to sort with order_by() : 接下来,我们使用我们称为tag_is_known注释来对order_by()进行排序:

Thing.objects.annotate(tag_is_known=...).order_by('tag_is_known')

Boolean version 布尔版本

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM