Django Queryset - 按字母顺序排序,然后将特定条目移到顶部
Posted
技术标签:
【中文标题】Django Queryset - 按字母顺序排序,然后将特定条目移到顶部【英文标题】:Django Queryset - Sort alphabetical then move specific entries to top 【发布时间】:2018-05-22 19:22:21 【问题描述】:编辑(背景信息)
我创建了一个自定义 UserModelChoiceField 类,该类接受用户的 QuerySet 并使用他们的全名填充选择字段:
class UserModelChoiceField(forms.ModelChoiceField):
def label_from_instance(self, obj):
return " ()".format(obj.get_full_name(), obj.username)
我现在正在尝试像这样使用这个类:
calibrated_by_internal = UserModelChoiceField(
queryset=total_qs,
required=False,
widget=forms.Select(attrs="class": "form-control")
)
.
原始问题
我正在尝试创建一个自定义排序的 QuerySet,它允许我将 User ModelChoice 选择字段放在按字母顺序排序的表单上,但特定权限组的 3 个成员放在顶部。
例如:
小组成员: 查理、艾伦、布赖恩
不是该组的成员: 本、克里斯、艾丽西亚……等等。
必需的查询集/选择字段排序: Alan、Brian、Charlie、Alicia、Ben、Chris...等。
鉴于我使用的是 Django 1.11,我想我会尝试新的 .union ORM 函数:
group_members = User.objects.filter(groups__name="AddEditCalibrations").order_by("first_name", "last_name")
everyone_else = User.objects.exclude(username__in="group_members").order_by("first_name", "last_name")
total_qs = group_members.union(everyone_else)
这只会引发错误:
DATABASE ERROR - ORDER BY not allowed in subqueries of compound statements
如果没有 2 个 ORDER_BY,它似乎可以正常工作,但是列表没有按字母顺序排序,并且需要位于选择顶部的该组的成员(QS 中的第一个)只是与每个人混合在一起否则。
所以我尝试了更简单的:
total_qs = User.objects.order_by(groups__name="AddEditCalibrations", "first_name", "last_name")
这会引发错误:
SyntaxError: positional argument follows keyword argument
编辑
按照 rajkris 的建议,我尝试过使用 itertools:
total_list = list(chain(group_members, everyone_else))
鉴于我需要一个 QS 来填充选择字段,然后我尝试将其转换回 QS:
total_qs = User.objects.filter(username__in=total_list)
但这只是给我一个未排序的列表,所有用户混合在一起,没有任何排序。
【问题讨论】:
您使用的排序(User.objects.sort())是什么?是你定义的管理器方法吗? 抱歉,从内存中复制代码时出现拼写错误(我以前使用的另一种语言挂断)我已经更新了问题,引发的错误消息仍然相同。 order_by 将不接受关键字参数。我认为您不能根据您的情况使用 order_by 【参考方案1】:您不能使用该语法按条件排序。您可以通过groups__name
订购,但这不是您想要的。相反,您需要将其转换为您可以订购的另一个字段——我们称之为weight
; groups__name="AddEditCalibrations"
应该得到 weight
的 0
并且每个其他应该得到 1
的权重。
你可以试试这个:
from django.db.models import IntegerField, Value
group_members = User.objects.filter(groups__name="AddEditCalibrations").annotate(weight=Value(0, IntegerField()))
everyone_else = User.objects.exclude(groups__name="AddEditCalibrations").annotate(weight=Value(1, IntegerField()))
total_qs = group_members.union(everyone_else).order_by("weight", "first_name", "last_name")
如果您的union
仍然无法按预期工作,您可能需要all=True
。
或者,一口气:
from django.db.models import Case, IntegerField, Value, When
total_qs = User.objects.annotate(weight=Case(
When(groups__name="AddEditCalibrations", then=Value(0)),
default=Value(1),
output_field=IntegerField()
)).order_by("weight", "first_name", "last_name")
【讨论】:
@TimB 运气好吗? 抱歉,我花了这么长时间才回到这个问题上,工作中出现了一些紧急的事情,让我在几周内离开了开发。您编辑的第二个解决方案运行良好,我今天下午将其投入生产。非常感谢您的帮助! 实际上废弃了,似乎 AddEditCalibrations 组中的任何人都出现在 qs 中两次,一次在顶部,然后又在更下方。这意味着当其中一个选择其中一个时,我们得到了一个多功能的错误:get()返回多个用户 - 它返回2! span> 【参考方案2】:也许可以尝试使用 itertools 中的链将其转换为列表:
from itertools import chain
result_list = list(chain(group_members, everyone_else))
也许我们可以使用类似的方法来获取查询集。
pk_list = [each.id for each in result_list]
ordering = 'FIELD(`id`, %s)' % ','.join(str(id) for id in pk_list)
queryset = User.objects.filter(pk__in=pk_list).extra(
select='ordering': ordering, order_by=('ordering',))
【讨论】:
不幸的是,这会将整个 QS 按字母顺序排列,包括我需要位于顶部的两个用户(AddEditCalibrations 组的成员)。我想要的是该组的用户按字母顺序排序,然后在他们下面所有其他用户分别按字母顺序排序。对不起,如果我不够清楚! 我认为在这种情况下我们不能使用联合。也许使用 itertool 链来附加两个有序的查询集。更新了答案 我按照您的建议进行了尝试,得到了一个排序列表,但是当我将其转换回 QS 时,它再次变得未排序!我已更新问题以反映这一点。 我认为您不需要 qs 来填充选择字段,列表应该足以迭代以填充选择字段。请确认结果列表是否按预期排序。 我试过了,得到一个 AttributeError - 'list' object has no attribute 'all'【参考方案3】:你能试试这样的吗?
group_members = User.objects.order_by("first_name", "last_name")
sorted_group_members = sorted(group_members.all(), reverse=True, key = lambda a: a.some_function_returning_the_user_group_of_each_user())
【讨论】:
以上是关于Django Queryset - 按字母顺序排序,然后将特定条目移到顶部的主要内容,如果未能解决你的问题,请参考以下文章
按模型的属性(不是字段)对 Django QuerySet 进行排序