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_byQuerySet 上的方法给出了与图像中看到的相同的排序。有没有办法操纵 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)
| 归档时间: |
|
| 查看次数: |
1662 次 |
| 最近记录: |