n2o*_*n2o 15 python django natural-sort
我正在寻找一种对 Django 的 QuerySet 进行自然排序的方法。我发现了一个类似的问题,但它没有关注 QuerySets。相反,他们直接在 Python 中进行。
所以这是我的问题。假设我有这个模型:
class Item(models.Model):
signature = models.CharField('Signatur', max_length=50)
Run Code Online (Sandbox Code Playgroud)
在 Django 管理界面中,我想使用一个过滤器,将它们按字母数字排序。目前,它们是这样排序的:
我期望的是["BA 1", "BA 2", ...]
. 我admin.SimpleListFilter
在官方文档中找到的,听起来很合适。但是我在queryset()
函数中得到的是一个QuerySet,它不能自然排序,因为它不包含元素,而只包含对数据库的查询。
order_by
QuerySet 上的方法给出了与图像中看到的相同的排序。有没有办法操纵 QuerySet 使其自然排序?
到目前为止我的代码:
class AlphanumericSignatureFilter(admin.SimpleListFilter):
title = 'Signature (alphanumeric)'
parameter_name = 'signature_alphanumeric'
def lookups(self, request, model_admin):
return (
('signature', 'Signature (alphanumeric)'),
)
def queryset(self, request, queryset: QuerySet):
return queryset.order_by('signature')
Run Code Online (Sandbox Code Playgroud)
如何转换 QuerySet 以获得我想要的输出?或者有不同的方法吗?Django Admin Interface 真的很强大,这就是为什么我想尽可能地使用它。但是这个功能真的少了。
我目前正在使用 Django 1.11
任何帮助、评论或提示表示赞赏。谢谢你的帮助。
Ale*_*gin 12
这实际上不是 Django 的错误,这就是数据库在内部工作的方式,例如看起来像 MySql 默认没有自然排序(我用谷歌搜索的并不多,所以也许我错了)。但是我们可以针对这种情况使用一些解决方法。
我把所有的例子和截图放在https://gist.github.com/phpdude/8a45e1bd2943fa806aeffee94877680a
但基本上对于给定的models.py
文件
from django.db import models
class Item(models.Model):
signature = models.CharField('Signatur', max_length=50)
def __str__(self):
return self.signature
Run Code Online (Sandbox Code Playgroud)
admin.py
例如,我使用 了正确的过滤器实现
from django.contrib.admin import ModelAdmin, register, SimpleListFilter
from django.db.models.functions import Length, StrIndex, Substr, NullIf, Coalesce
from django.db.models import Value as V
from .models import Item
class AlphanumericSignatureFilter(SimpleListFilter):
title = 'Signature (alphanumeric)'
parameter_name = 'signature_alphanumeric'
def lookups(self, request, model_admin):
return (
('signature', 'Signature (alphanumeric)'),
)
def queryset(self, request, queryset):
if self.value() == 'signature':
return queryset.order_by(
Coalesce(Substr('signature', V(0), NullIf(StrIndex('signature', V(' ')), V(0))), 'signature'),
Length('signature'),
'signature'
)
@register(Item)
class Item(ModelAdmin):
list_filter = [AlphanumericSignatureFilter]
Run Code Online (Sandbox Code Playgroud)
带有示例的截图
几个参考:
PS:貌似Length(column_name)
django 1.9增加了db函数,应该可以用,不过一般任何django版本都支持自定义db ORM函数调用,可以调用length()
字段的函数。
natsort
它可以工作,但需要在正确排序之前加载所有可能的签名,因为它使用 python 端而不是 DB 端对行列表进行排序。
有用。但是如果桌子很大,它可能会很慢。
从我的角度来看,它应该仅用于小于 50 000 行的数据库表(例如,取决于您的数据库服务器性能等)。
from django.contrib.admin import ModelAdmin, register, SimpleListFilter
from django.db.models.functions import StrIndex, Concat
from django.db.models import Value as V
from natsort import natsorted
from .models import Item
class AlphanumericTruePythonSignatureFilter(SimpleListFilter):
title = 'Signature (alphanumeric true python)'
parameter_name = 'signature_alphanumeric_python'
def lookups(self, request, model_admin):
return (
('signature', 'Signature (alphanumeric)'),
)
def queryset(self, request, queryset):
if self.value() == 'signature':
all_ids = list(queryset.values_list('signature', flat=True))
# let's use "!:!" as a separator for signature values
all_ids_sorted = "!:!" + "!:!".join(natsorted(all_ids))
return queryset.order_by(
StrIndex(V(all_ids_sorted), Concat(V('!:!'), 'signature')),
)
@register(Item)
class Item(ModelAdmin):
list_filter = [AlphanumericTruePythonSignatureFilter]
Run Code Online (Sandbox Code Playgroud)
如果你不介意针对特定的数据库,你可以使用 RawSQL() 注入一个 SQL 表达式来解析你的“签名”字段,然后用结果注释记录集;例如(PostgreSQL):
queryset = (
Item.objects.annotate(
right_part=RawSQL("cast(split_part(signature, ' ', 2) as int)", ())
).order_by('right_part')
)
Run Code Online (Sandbox Code Playgroud)
(如果您需要支持不同的数据库格式,您可以另外检测活动引擎并相应地提供合适的表达式)
RawSQL() 的好处在于,您可以非常明确地说明何时何地应用特定于数据库的功能。
正如@schillingt 所指出的, Func() 也可能是一个选项。另一方面,我会避免 extra() 因为它可能已经过时了(请参阅:https : //docs.djangoproject.com/en/2.2/ref/models/querysets/#extra)。
证明(对于 PostgreSQL):
class Item(models.Model):
signature = models.CharField('Signatur', max_length=50)
def __str__(self):
return self.signature
-----------------------------------------------------
import django
from django.db.models.expressions import RawSQL
from pprint import pprint
from backend.models import Item
class ModelsItemCase(django.test.TransactionTestCase):
def test_item_sorting(self):
signatures = [
'BA 1',
'BA 10',
'BA 100',
'BA 2',
'BA 1002',
'BA 1000',
'BA 1001',
]
for signature in signatures:
Item.objects.create(signature=signature)
pprint(list(Item.objects.all()))
print('')
queryset = (
Item.objects.annotate(
right_part=RawSQL("cast(split_part(signature, ' ', 2) as int)", ())
).order_by('right_part')
)
pprint(list(queryset))
self.assertEqual(queryset[0].signature, 'BA 1')
self.assertEqual(queryset[1].signature, 'BA 2')
self.assertEqual(queryset[2].signature, 'BA 10')
self.assertEqual(queryset[3].signature, 'BA 100')
self.assertEqual(queryset[4].signature, 'BA 1000')
self.assertEqual(queryset[5].signature, 'BA 1001')
self.assertEqual(queryset[6].signature, 'BA 1002')
Run Code Online (Sandbox Code Playgroud)
结果:
test_item_sorting (backend.tests.test_item.ModelsItemCase) ... [<Item: BA 1>,
<Item: BA 10>,
<Item: BA 100>,
<Item: BA 2>,
<Item: BA 1002>,
<Item: BA 1000>,
<Item: BA 1001>]
[<Item: BA 1>,
<Item: BA 2>,
<Item: BA 10>,
<Item: BA 100>,
<Item: BA 1000>,
<Item: BA 1001>,
<Item: BA 1002>]
ok
----------------------------------------------------------------------
Ran 1 test in 0.177s
Run Code Online (Sandbox Code Playgroud)