`length`和`name`排序参数不符合我的要求

时间:2018-06-17 01:01:02

标签: python django django-rest-framework

我有PhysicalServer型号:

class PhysicalServer(models.Model):
    name = models.CharField(max_length=32)
    cabinet = models.ForeignKey(to=Cabinet, on_delete=models.DO_NOTHING, related_name="physical_servers")
    physical_server_model = models.ForeignKey(to=PhysicalServerModel, null=True, on_delete=models.DO_NOTHING)
    ...

    class Meta:
        ordering = ['-cabinet', '-physical_server_model', 'name']

其列表API视图是:

class PhysicalServerListAPIView(ListAPIView):
    serializer_class = PhysicalServerListSerializer
    permission_classes = [AllowAny]
    pagination_class = CommonPagination
    def get_queryset(self):
        qs = PhysicalServer.objects.filter(**filters)
        return qs.annotate(length=Length('name')).order_by('length', 'name') # there if I put the `name` first(order_by('name', 'length')), also inconformity my requirement.

我的物理服务器实例名称如下所示:

我的问题是,当我使用它进行列表排序时:

return qs.annotate(length=Length('name')).order_by('length', 'name')

结果将是:

SE01-A1
SE01-A2
SE01-A3
...
SE01-A9
SE01-C1
SE01-C2
SE01-C3
...
SE01-A10
SE01-A11
SE01-A12
...

如果我使用以下内容进行排序:

return qs.annotate(length=Length('name')).order_by('name', 'length')

结果将是:

SE01-A1
SE01-A11
SE01-A12
SE01-A13
...
SE01-A2
SE01-A21
...
SE01-A3
...

我怎么这样排序:

SE01-A1
SE01-A2
SE01-A3
SE01-A4
...
SE01-A10
...
SE01-C1
SE01-C2
...

3 个答案:

答案 0 :(得分:2)

您需要从字符串中提取数字。 Postgres和mysql提供regexp_replace函数(我不确定其他数据库)。但是Django没有提供实现,因此我们将编写自己的function

from django.db.models import Func, Value

class RegexpReplace(Func):
    function = 'REGEXP_REPLACE'

    def __init__(self, expression, search, replace, **extra):
        search = Value(search)
        replace = Value(replace)
        super(RegexpReplace, self).__init__(expression, search, replace, **extra)

我假设您要分割名称直到末尾的数字,然后再使用前半部分和末尾的数字进行排序。 (这对您来说很好,直到您开始在连字符前得到3位数字,即SE99-A1,SE100-A1)。

from django.db.models import F, IntegerField
from django.db.models.functions import Cast

qs = ... # get your queryset
qs.annotate(
    letters=RegexpReplace(F('name'), '(.*[a-zA-Z])[0-9]+$', r'\1'),
    seq=Cast(
        RegexpReplace(F('name'), '.*[a-zA-Z]([0-9]+)$', r'\1'),
        IntegerField(),
    ),
).order_by('letters', 'seq')

答案 1 :(得分:0)

如果您的服务器名称始终为server-code形式且没有其他连字符出现,您可以拆分连字符并按如此排序:

qs = sorted(qs, key: lambda i: (i.name().split('-')[1], len(i.name())))

注意:这不会返回查询集。

答案 2 :(得分:-1)

使用Substr将名称的第一部分与末尾的数字分开,然后先按此新注释排序。

from django.db.models.functions import Substr, Length
qs = qs.annotate(letters=Substr('name', 1, 6), length=Length('name'))
qs = qs.order_by('letters', 'length', 'name')
return qs