Просмотр исходного кода

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

Fixes #2157: Natural ordering breaks when sorting objects by name
Jeremy Stretch 7 лет назад
Родитель
Сommit
81258ea35b
3 измененных файлов с 33 добавлено и 30 удалено
  1. 3 9
      netbox/dcim/models.py
  2. 6 3
      netbox/dcim/tables.py
  3. 24 18
      netbox/utilities/managers.py

+ 3 - 9
netbox/dcim/models.py

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

+ 6 - 3
netbox/dcim/tables.py

@@ -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')])

+ 24 - 18
netbox/utilities/managers.py

@@ -4,29 +4,35 @@ from django.db.models import Manager
 
 
 
 
 class NaturalOrderByManager(Manager):
 class NaturalOrderByManager(Manager):
+    """
+    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
+    the digit 1 is normally ordered before the digit 2.
+    """
+    natural_order_field = None
 
 
-    def natural_order_by(self, *fields):
-        """
-        Attempt to order records naturally by segmenting a field into three parts:
+    def get_queryset(self):
 
 
-        1. Leading integer (if any)
-        2. Middle portion
-        3. Trailing integer (if any)
+        queryset = super(NaturalOrderByManager, self).get_queryset()
 
 
-        :param fields: The fields on which to order the queryset. The last field in the list will be ordered naturally.
-        """
         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)
-        id2 = '_{}_{}2'.format(db_table, primary_field)
-        id3 = '_{}_{}3'.format(db_table, primary_field)
-
-        queryset = super(NaturalOrderByManager, self).get_queryset().extra(select={
-            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),
+        # Append the three subfields derived from the designated natural ordering field
+        queryset = queryset.extra(select={
+            '_nat1': "CAST(SUBSTRING({}.{} FROM '^(\d{{1,9}})') AS integer)".format(db_table, db_field),
+            '_nat2': "SUBSTRING({}.{} FROM '^\d*(.*?)\d*$')".format(db_table, db_field),
+            '_nat3': "CAST(SUBSTRING({}.{} FROM '(\d{{1,9}})$') AS integer)".format(db_table, db_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)