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

Fixes #4211: Include trailing text when naturalizing interface names

Jeremy Stretch 6 лет назад
Родитель
Сommit
322b328584

+ 8 - 0
docs/release-notes/version-2.7.md

@@ -1,5 +1,12 @@
 # v2.7.7 (FUTURE)
 
+**Note:** This release fixes a bug affecting the natural ordering of interfaces. If any interfaces appear unordered in
+NetBox, run the following management command to recalculate their naturalized values after upgrading:
+
+```
+python3 manage.py renaturalize dcim.Interface
+``` 
+
 ## Enhancements
 
 * [#2511](https://github.com/netbox-community/netbox/issues/2511) - Compare object change to the previous change
@@ -19,6 +26,7 @@
 * [#4196](https://github.com/netbox-community/netbox/issues/4196) - Fix exception when viewing LLDP neighbors page
 * [#4202](https://github.com/netbox-community/netbox/issues/4202) - Prevent reassignment to master device when bulk editing VC member interfaces
 * [#4204](https://github.com/netbox-community/netbox/issues/4204) - Fix assignment of mask length when bulk editing prefixes
+* [#4211](https://github.com/netbox-community/netbox/issues/4211) - Include trailing text when naturalizing interface names
 
 ---
 

+ 15 - 10
netbox/utilities/ordering.py

@@ -7,7 +7,8 @@ INTERFACE_NAME_REGEX = r'(^(?P<type>[^\d\.:]+)?)' \
                        r'((?P<subposition>\d+)/)?' \
                        r'((?P<id>\d+))?' \
                        r'(:(?P<channel>\d+))?' \
-                       r'(.(?P<vc>\d+)$)?'
+                       r'(\.(?P<vc>\d+))?' \
+                       r'(?P<remainder>.*)$'
 
 
 def naturalize(value, max_length, integer_places=8):
@@ -50,7 +51,7 @@ def naturalize_interface(value, max_length):
     :param value: The value to be naturalized
     :param max_length: The maximum length of the returned string. Characters beyond this length will be stripped.
     """
-    output = []
+    output = ''
     match = re.search(INTERFACE_NAME_REGEX, value)
     if match is None:
         return value
@@ -60,21 +61,25 @@ def naturalize_interface(value, max_length):
     for part_name in ('slot', 'subslot', 'position', 'subposition'):
         part = match.group(part_name)
         if part is not None:
-            output.append(part.rjust(4, '0'))
+            output += part.rjust(4, '0')
         else:
-            output.append('9999')
+            output += '9999'
 
     # Append the type, if any.
     if match.group('type') is not None:
-        output.append(match.group('type'))
+        output += match.group('type')
 
-    # Finally, append any remaining fields, left-padding to six digits each.
+    # Append any remaining fields, left-padding to six digits each.
     for part_name in ('id', 'channel', 'vc'):
         part = match.group(part_name)
         if part is not None:
-            output.append(part.rjust(6, '0'))
+            output += part.rjust(6, '0')
         else:
-            output.append('000000')
+            output += '000000'
 
-    ret = ''.join(output)
-    return ret[:max_length]
+    # Finally, naturalize any remaining text and append it
+    if match.group('remainder') is not None and len(output) < max_length:
+        remainder = naturalize(match.group('remainder'), max_length - len(output))
+        output += remainder
+
+    return output[:max_length]

+ 11 - 4
netbox/utilities/tests/test_ordering.py

@@ -9,8 +9,8 @@ class NaturalizationTestCase(TestCase):
     """
     def test_naturalize(self):
 
+        # Original, naturalized
         data = (
-            # Original, naturalized
             ('abc', 'abc'),
             ('123', '00000123'),
             ('abc123', 'abc00000123'),
@@ -21,15 +21,16 @@ class NaturalizationTestCase(TestCase):
         )
 
         for origin, naturalized in data:
-            self.assertEqual(naturalize(origin, max_length=50), naturalized)
+            self.assertEqual(naturalize(origin, max_length=100), naturalized)
 
     def test_naturalize_max_length(self):
         self.assertEqual(naturalize('abc123def456', max_length=10), 'abc0000012')
 
     def test_naturalize_interface(self):
 
+        # Original, naturalized
         data = (
-            # Original, naturalized
+            # IOS/JunOS-style
             ('Gi', '9999999999999999Gi000000000000000000'),
             ('Gi1', '9999999999999999Gi000001000000000000'),
             ('Gi1/2', '0001999999999999Gi000002000000000000'),
@@ -40,10 +41,16 @@ class NaturalizationTestCase(TestCase):
             ('Gi1/2/3/4/5:6.7', '0001000200030004Gi000005000006000007'),
             ('Gi1:2', '9999999999999999Gi000001000002000000'),
             ('Gi1:2.3', '9999999999999999Gi000001000002000003'),
+            # Generic
+            ('Interface 1', '9999999999999999Interface 000001000000000000'),
+            ('Interface 1 (other)', '9999999999999999Interface 000001000000000000 (other)'),
+            ('Interface 99', '9999999999999999Interface 000099000000000000'),
+            ('PCIe1-p1', '9999999999999999PCIe000001000000000000-p00000001'),
+            ('PCIe1-p99', '9999999999999999PCIe000001000000000000-p00000099'),
         )
 
         for origin, naturalized in data:
-            self.assertEqual(naturalize_interface(origin, max_length=50), naturalized)
+            self.assertEqual(naturalize_interface(origin, max_length=100), naturalized)
 
     def test_naturalize_interface_max_length(self):
         self.assertEqual(naturalize_interface('Gi1/2/3', max_length=20), '0001000299999999Gi00')