Django Queryset - 按字母顺序排序然后将特定条目移至顶部

Django Queryset - Sort alphabetical then move specific entries to top

编辑(背景信息)

我创建了一个自定义的 UserModelChoiceField class,它接受用户的 QuerySet 并用他们的全名填充 select 字段:

class UserModelChoiceField(forms.ModelChoiceField):
    def label_from_instance(self, obj):
        return "{} ({})".format(obj.get_full_name(), obj.username)

我现在正尝试使用此 class,例如:

calibrated_by_internal = UserModelChoiceField(
                                              queryset=total_qs,
                                              required=False,
                                              widget=forms.Select(attrs={"class": "form-control"})
                                              )

.

原问题

我正在尝试创建一个自定义排序的 QuerySet,它允许我在按字母顺序排序的表单上放置一个 User ModelChoice select 字段,但是特定权限组的 3 个成员被放置在顶.

例如:

群组成员: 查理、艾伦、布赖恩

不是群组成员: 本、克里斯、艾丽西亚……等等

必需的查询集/Select 字段排序: 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_BYs,它似乎工作正常,但是列表没有按字母顺序排序,并且该组的成员需要位于 select 的顶部(第一个在 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 来填充 select 字段,然后我尝试将其转换回 QS:

total_qs = User.objects.filter(username__in=total_list)

但这只给我一个未排序的列表,所有用户混合在一起,没有任何排序。

也许可以尝试使用 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',))

您不能使用该语法按条件排序。您可以按 groups__name 订购,但这不是您想要的。相反,您需要将它翻译成另一个您可以用来订购的字段——我们称它为 weightgroups__name="AddEditCalibrations" 应该得到 0weight 并且每个其他人应该得到 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")

你能试试这样的吗?

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