浏览代码

Fix order_naturally with unbalanced names and use RawSQL instead of extra

Joey Wilhelm 8 年之前
父节点
当前提交
126a5e5e4e
共有 2 个文件被更改,包括 98 次插入16 次删除
  1. 32 16
      netbox/dcim/models.py
  2. 66 0
      netbox/dcim/tests/test_models.py

+ 32 - 16
netbox/dcim/models.py

@@ -13,6 +13,7 @@ from django.core.exceptions import ValidationError
 from django.core.validators import MaxValueValidator, MinValueValidator
 from django.core.validators import MaxValueValidator, MinValueValidator
 from django.db import models
 from django.db import models
 from django.db.models import Count, Q, ObjectDoesNotExist
 from django.db.models import Count, Q, ObjectDoesNotExist
+from django.db.models.expressions import RawSQL
 from django.urls import reverse
 from django.urls import reverse
 from django.utils.encoding import python_2_unicode_compatible
 from django.utils.encoding import python_2_unicode_compatible
 
 
@@ -642,15 +643,16 @@ class InterfaceQuerySet(models.QuerySet):
         To order interfaces naturally, the `name` field is split into six distinct components: leading text (type),
         To order interfaces naturally, the `name` field is split into six distinct components: leading text (type),
         slot, subslot, position, channel, and virtual circuit:
         slot, subslot, position, channel, and virtual circuit:
 
 
-            {type}{slot}/{subslot}/{position}:{channel}.{vc}
+            {type}{slot}/{subslot}/{position}/{subposition}:{channel}.{vc}
 
 
-        Components absent from the interface name are ignored. For example, an interface named GigabitEthernet0/1 would
-        be parsed as follows:
+        Components absent from the interface name are ignored. For example, an interface named GigabitEthernet1/2/3
+        would be parsed as follows:
 
 
             name = 'GigabitEthernet'
             name = 'GigabitEthernet'
-            slot =  None
-            subslot = 0
-            position = 1
+            slot =  1
+            subslot = 2
+            position = 3
+            subposition = 0
             channel = None
             channel = None
             vc = 0
             vc = 0
 
 
@@ -659,17 +661,31 @@ class InterfaceQuerySet(models.QuerySet):
         """
         """
         sql_col = '{}.name'.format(self.model._meta.db_table)
         sql_col = '{}.name'.format(self.model._meta.db_table)
         ordering = {
         ordering = {
-            IFACE_ORDERING_POSITION: ('_slot', '_subslot', '_position', '_channel', '_vc', '_type', 'name'),
-            IFACE_ORDERING_NAME: ('_type', '_slot', '_subslot', '_position', '_channel', '_vc', 'name'),
+            IFACE_ORDERING_POSITION: ('_slot', '_subslot', '_position', '_subposition', '_channel', '_vc', '_type', 'name'),
+            IFACE_ORDERING_NAME: ('_type', '_slot', '_subslot', '_position', '_subposition', '_channel', '_vc', 'name'),
         }[method]
         }[method]
-        return self.extra(select={
-            '_type': "SUBSTRING({} FROM '^([^0-9]+)')".format(sql_col),
-            '_slot': "CAST(SUBSTRING({} FROM '([0-9]+)\/[0-9]+\/[0-9]+(:[0-9]+)?(\.[0-9]+)?$') AS integer)".format(sql_col),
-            '_subslot': "CAST(SUBSTRING({} FROM '([0-9]+)\/[0-9]+(:[0-9]+)?(\.[0-9]+)?$') AS integer)".format(sql_col),
-            '_position': "CAST(SUBSTRING({} FROM '([0-9]+)(:[0-9]+)?(\.[0-9]+)?$') AS integer)".format(sql_col),
-            '_channel': "COALESCE(CAST(SUBSTRING({} FROM ':([0-9]+)(\.[0-9]+)?$') AS integer), 0)".format(sql_col),
-            '_vc': "COALESCE(CAST(SUBSTRING({} FROM '\.([0-9]+)$') AS integer), 0)".format(sql_col),
-        }).order_by(*ordering)
+
+        fields = {
+            '_type': RawSQL(r"SUBSTRING({} FROM '^([^0-9]+)')".format(sql_col), []),
+            '_slot': RawSQL(r"CAST(SUBSTRING({} FROM '^(?:[^0-9]+)([0-9]+)') AS integer)".format(sql_col), []),
+            '_subslot': RawSQL(
+                r"COALESCE(CAST(SUBSTRING({} FROM '^(?:[^0-9]+)(?:[0-9]+)\/([0-9]+)') AS integer), 0)".format(
+                    sql_col), []
+            ),
+            '_position': RawSQL(
+                r"COALESCE(CAST(SUBSTRING({} FROM '^(?:[^0-9]+)(?:[0-9]+\/){{2}}([0-9]+)') AS integer), 0)".format(
+                    sql_col), []
+            ),
+            '_subposition': RawSQL(
+                r"COALESCE(CAST(SUBSTRING({} FROM '^(?:[^0-9]+)(?:[0-9]+\/){{3}}([0-9]+)') AS integer), 0)".format(
+                    sql_col), []
+            ),
+            '_channel': RawSQL(
+                r"COALESCE(CAST(SUBSTRING({} FROM ':([0-9]+)(\.[0-9]+)?$') AS integer), 0)".format(sql_col), []),
+            '_vc': RawSQL(r"COALESCE(CAST(SUBSTRING({} FROM '\.([0-9]+)$') AS integer), 0)".format(sql_col), []),
+        }
+
+        return self.annotate(**fields).order_by(*ordering)
 
 
     def connectable(self):
     def connectable(self):
         """
         """

+ 66 - 0
netbox/dcim/tests/test_models.py

@@ -98,3 +98,69 @@ class RackTestCase(TestCase):
             face=None,
             face=None,
         )
         )
         self.assertTrue(pdu)
         self.assertTrue(pdu)
+
+
+class InterfaceTestCase(TestCase):
+
+    def setUp(self):
+
+        self.site = Site.objects.create(
+            name='TestSite1',
+            slug='my-test-site'
+        )
+        self.rack = Rack.objects.create(
+            name='TestRack1',
+            facility_id='A101',
+            site=self.site,
+            u_height=42
+        )
+        self.manufacturer = Manufacturer.objects.create(
+            name='Acme',
+            slug='acme'
+        )
+
+        self.device_type = DeviceType.objects.create(
+            manufacturer=self.manufacturer,
+            model='FrameForwarder 2048',
+            slug='ff2048'
+        )
+        self.role = DeviceRole.objects.create(
+            name='Switch',
+            slug='switch',
+        )
+
+    def test_device_port_order_natural(self):
+        device1 = Device.objects.create(
+            name='TestSwitch1',
+            device_type=self.device_type,
+            device_role=self.role,
+            site=self.site,
+            rack=self.rack,
+            position=10,
+            face=RACK_FACE_REAR,
+        )
+        interface1 = Interface.objects.create(
+            device=device1,
+            name='Ethernet1/3/1'
+        )
+        interface2 = Interface.objects.create(
+            device=device1,
+            name='Ethernet1/5/1'
+        )
+        interface3 = Interface.objects.create(
+            device=device1,
+            name='Ethernet1/4'
+        )
+        interface4 = Interface.objects.create(
+            device=device1,
+            name='Ethernet1/3/2/4'
+        )
+        interface5 = Interface.objects.create(
+            device=device1,
+            name='Ethernet1/3/2/1'
+        )
+
+        self.assertEqual(
+            list(Interface.objects.all().order_naturally()),
+            [interface1, interface5, interface4, interface3, interface2]
+        )