Merge pull request #2158 from digitalocean/2157-natural-ordering

Fixes #2157: Natural ordering breaks when sorting objects by name
This commit is contained in:
Jeremy Stretch 2018-06-11 16:09:25 -04:00 committed by GitHub
commit 81258ea35b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 33 additions and 30 deletions

View File

@ -77,9 +77,7 @@ class Region(MPTTModel):
# #
class SiteManager(NaturalOrderByManager): class SiteManager(NaturalOrderByManager):
natural_order_field = 'name'
def get_queryset(self):
return self.natural_order_by('name')
@python_2_unicode_compatible @python_2_unicode_compatible
@ -308,9 +306,7 @@ class RackRole(models.Model):
class RackManager(NaturalOrderByManager): class RackManager(NaturalOrderByManager):
natural_order_field = 'name'
def get_queryset(self):
return self.natural_order_by('site__name', 'name')
@python_2_unicode_compatible @python_2_unicode_compatible
@ -1098,9 +1094,7 @@ class Platform(models.Model):
class DeviceManager(NaturalOrderByManager): class DeviceManager(NaturalOrderByManager):
natural_order_field = 'name'
def get_queryset(self):
return self.natural_order_by('name')
@python_2_unicode_compatible @python_2_unicode_compatible

View File

@ -175,7 +175,7 @@ class RegionTable(BaseTable):
class SiteTable(BaseTable): class SiteTable(BaseTable):
pk = ToggleColumn() pk = ToggleColumn()
name = tables.LinkColumn() name = tables.LinkColumn(order_by=('_nat1', '_nat2', '_nat3'))
status = tables.TemplateColumn(template_code=STATUS_LABEL, verbose_name='Status') status = tables.TemplateColumn(template_code=STATUS_LABEL, verbose_name='Status')
region = tables.TemplateColumn(template_code=SITE_REGION_LINK) region = tables.TemplateColumn(template_code=SITE_REGION_LINK)
tenant = tables.TemplateColumn(template_code=COL_TENANT) tenant = tables.TemplateColumn(template_code=COL_TENANT)
@ -236,7 +236,7 @@ class RackRoleTable(BaseTable):
class RackTable(BaseTable): class RackTable(BaseTable):
pk = ToggleColumn() pk = ToggleColumn()
name = tables.LinkColumn() name = tables.LinkColumn(order_by=('_nat1', '_nat2', '_nat3'))
site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')]) site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')])
group = tables.Column(accessor=Accessor('group.name'), verbose_name='Group') group = tables.Column(accessor=Accessor('group.name'), verbose_name='Group')
tenant = tables.TemplateColumn(template_code=COL_TENANT) tenant = tables.TemplateColumn(template_code=COL_TENANT)
@ -469,7 +469,10 @@ class PlatformTable(BaseTable):
class DeviceTable(BaseTable): class DeviceTable(BaseTable):
pk = ToggleColumn() pk = ToggleColumn()
name = tables.TemplateColumn(template_code=DEVICE_LINK) name = tables.TemplateColumn(
order_by=('_nat1', '_nat2', '_nat3'),
template_code=DEVICE_LINK
)
status = tables.TemplateColumn(template_code=STATUS_LABEL, verbose_name='Status') status = tables.TemplateColumn(template_code=STATUS_LABEL, verbose_name='Status')
tenant = tables.TemplateColumn(template_code=COL_TENANT) tenant = tables.TemplateColumn(template_code=COL_TENANT)
site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')]) site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')])

View File

@ -4,29 +4,35 @@ from django.db.models import Manager
class NaturalOrderByManager(Manager): class NaturalOrderByManager(Manager):
def natural_order_by(self, *fields):
""" """
Attempt to order records naturally by segmenting a field into three parts: Order objects naturally by a designated field. Leading and/or trailing digits of values within this field will be
cast as independent integers and sorted accordingly. For example, "Foo2" will be ordered before "Foo10", even though
1. Leading integer (if any) the digit 1 is normally ordered before the digit 2.
2. Middle portion
3. Trailing integer (if any)
:param fields: The fields on which to order the queryset. The last field in the list will be ordered naturally.
""" """
natural_order_field = None
def get_queryset(self):
queryset = super(NaturalOrderByManager, self).get_queryset()
db_table = self.model._meta.db_table db_table = self.model._meta.db_table
primary_field = fields[-1] db_field = self.natural_order_field
id1 = '_{}_{}1'.format(db_table, primary_field) # Append the three subfields derived from the designated natural ordering field
id2 = '_{}_{}2'.format(db_table, primary_field) queryset = queryset.extra(select={
id3 = '_{}_{}3'.format(db_table, primary_field) '_nat1': "CAST(SUBSTRING({}.{} FROM '^(\d{{1,9}})') AS integer)".format(db_table, db_field),
'_nat2': "SUBSTRING({}.{} FROM '^\d*(.*?)\d*$')".format(db_table, db_field),
queryset = super(NaturalOrderByManager, self).get_queryset().extra(select={ '_nat3': "CAST(SUBSTRING({}.{} FROM '(\d{{1,9}})$') AS integer)".format(db_table, db_field),
id1: "CAST(SUBSTRING({}.{} FROM '^(\d{{1,9}})') AS integer)".format(db_table, primary_field),
id2: "SUBSTRING({}.{} FROM '^\d*(.*?)\d*$')".format(db_table, primary_field),
id3: "CAST(SUBSTRING({}.{} FROM '(\d{{1,9}})$') AS integer)".format(db_table, primary_field),
}) })
ordering = fields[0:-1] + (id1, id2, id3)
# Replace any instance of the designated natural ordering field with its three subfields
ordering = []
for field in self.model._meta.ordering:
if field == self.natural_order_field:
ordering.append('_nat1')
ordering.append('_nat2')
ordering.append('_nat3')
else:
ordering.append(field)
return queryset.order_by(*ordering) return queryset.order_by(*ordering)