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

Merge pull request #3865 from hSaria/3623-interface-word-expansion

Fixes #3623: Word expansion for interfaces
Jeremy Stretch 6 лет назад
Родитель
Сommit
cb91c9231d
3 измененных файлов с 295 добавлено и 2 удалено
  1. 1 0
      docs/release-notes/version-2.6.md
  2. 11 2
      netbox/utilities/forms.py
  3. 283 0
      netbox/utilities/tests/test_forms.py

+ 1 - 0
docs/release-notes/version-2.6.md

@@ -10,6 +10,7 @@
 * [#3187](https://github.com/netbox-community/netbox/issues/3187) - Add rack selection field to rack elevations
 * [#3393](https://github.com/netbox-community/netbox/issues/3393) - Paginate the circuits at the provider details view
 * [#3440](https://github.com/netbox-community/netbox/issues/3440) - Add total length to cable trace
+* [#3623](https://github.com/netbox-community/netbox/issues/3623) - Add word expansion during interface creation
 * [#3851](https://github.com/netbox-community/netbox/issues/3851) - Allow passing initial data to custom script forms
 
 ## Bug Fixes

+ 11 - 2
netbox/utilities/forms.py

@@ -60,8 +60,16 @@ def parse_alphanumeric_range(string):
             for n in list(range(int(begin), int(end) + 1)):
                 values.append(n)
         else:
-            for n in list(range(ord(begin), ord(end) + 1)):
-                values.append(chr(n))
+            # Value-based
+            if begin == end:
+                values.append(begin)
+            # Range-based
+            else:
+                # Not a valid range (more than a single character)
+                if not len(begin) == len(end) == 1:
+                    raise forms.ValidationError('Range "{}" is invalid.'.format(dash_range))
+                for n in list(range(ord(begin), ord(end) + 1)):
+                    values.append(chr(n))
     return values
 
 
@@ -481,6 +489,7 @@ class ExpandableNameField(forms.CharField):
                              'Mixed cases and types within a single range are not supported.<br />' \
                              'Examples:<ul><li><code>ge-0/0/[0-23,25,30]</code></li>' \
                              '<li><code>e[0-3][a-d,f]</code></li>' \
+                             '<li><code>[xe,ge]-0/0/0</code></li>' \
                              '<li><code>e[0-3,a-d,f]</code></li></ul>'
 
     def to_python(self, value):

+ 283 - 0
netbox/utilities/tests/test_forms.py

@@ -0,0 +1,283 @@
+from django import forms
+from django.test import TestCase
+
+from utilities.forms import *
+
+
+class ExpandIPAddress(TestCase):
+    """
+    Validate the operation of expand_ipaddress_pattern().
+    """
+    def test_ipv4_range(self):
+        input = '1.2.3.[9-10]/32'
+        output = sorted([
+            '1.2.3.9/32',
+            '1.2.3.10/32',
+        ])
+
+        self.assertEqual(sorted(expand_ipaddress_pattern(input, 4)), output)
+
+    def test_ipv4_set(self):
+        input = '1.2.3.[4,44]/32'
+        output = sorted([
+            '1.2.3.4/32',
+            '1.2.3.44/32',
+        ])
+
+        self.assertEqual(sorted(expand_ipaddress_pattern(input, 4)), output)
+
+    def test_ipv4_multiple_ranges(self):
+        input = '1.[9-10].3.[9-11]/32'
+        output = sorted([
+            '1.9.3.9/32',
+            '1.9.3.10/32',
+            '1.9.3.11/32',
+            '1.10.3.9/32',
+            '1.10.3.10/32',
+            '1.10.3.11/32',
+        ])
+
+        self.assertEqual(sorted(expand_ipaddress_pattern(input, 4)), output)
+
+    def test_ipv4_multiple_sets(self):
+        input = '1.[2,22].3.[4,44]/32'
+        output = sorted([
+            '1.2.3.4/32',
+            '1.2.3.44/32',
+            '1.22.3.4/32',
+            '1.22.3.44/32',
+        ])
+
+        self.assertEqual(sorted(expand_ipaddress_pattern(input, 4)), output)
+
+    def test_ipv4_set_and_range(self):
+        input = '1.[2,22].3.[9-11]/32'
+        output = sorted([
+            '1.2.3.9/32',
+            '1.2.3.10/32',
+            '1.2.3.11/32',
+            '1.22.3.9/32',
+            '1.22.3.10/32',
+            '1.22.3.11/32',
+        ])
+
+        self.assertEqual(sorted(expand_ipaddress_pattern(input, 4)), output)
+
+    def test_ipv6_range(self):
+        input = 'fec::abcd:[9-b]/64'
+        output = sorted([
+            'fec::abcd:9/64',
+            'fec::abcd:a/64',
+            'fec::abcd:b/64',
+        ])
+
+        self.assertEqual(sorted(expand_ipaddress_pattern(input, 6)), output)
+
+    def test_ipv6_range_multichar_field(self):
+        input = 'fec::abcd:[f-11]/64'
+        output = sorted([
+            'fec::abcd:f/64',
+            'fec::abcd:10/64',
+            'fec::abcd:11/64',
+        ])
+
+        self.assertEqual(sorted(expand_ipaddress_pattern(input, 6)), output)
+
+    def test_ipv6_set(self):
+        input = 'fec::abcd:[9,ab]/64'
+        output = sorted([
+            'fec::abcd:9/64',
+            'fec::abcd:ab/64',
+        ])
+
+        self.assertEqual(sorted(expand_ipaddress_pattern(input, 6)), output)
+
+    def test_ipv6_multiple_ranges(self):
+        input = 'fec::[1-2]bcd:[9-b]/64'
+        output = sorted([
+            'fec::1bcd:9/64',
+            'fec::1bcd:a/64',
+            'fec::1bcd:b/64',
+            'fec::2bcd:9/64',
+            'fec::2bcd:a/64',
+            'fec::2bcd:b/64',
+        ])
+
+        self.assertEqual(sorted(expand_ipaddress_pattern(input, 6)), output)
+
+    def test_ipv6_multiple_sets(self):
+        input = 'fec::[a,f]bcd:[9,ab]/64'
+        output = sorted([
+            'fec::abcd:9/64',
+            'fec::abcd:ab/64',
+            'fec::fbcd:9/64',
+            'fec::fbcd:ab/64',
+        ])
+
+        self.assertEqual(sorted(expand_ipaddress_pattern(input, 6)), output)
+
+    def test_ipv6_set_and_range(self):
+        input = 'fec::[dead,beaf]:[9-b]/64'
+        output = sorted([
+            'fec::dead:9/64',
+            'fec::dead:a/64',
+            'fec::dead:b/64',
+            'fec::beaf:9/64',
+            'fec::beaf:a/64',
+            'fec::beaf:b/64',
+        ])
+
+        self.assertEqual(sorted(expand_ipaddress_pattern(input, 6)), output)
+
+    def test_invalid_address_family(self):
+        with self.assertRaisesRegex(Exception, 'Invalid IP address family: 5'):
+            sorted(expand_ipaddress_pattern(None, 5))
+
+    def test_invalid_non_pattern(self):
+        with self.assertRaises(ValueError):
+            sorted(expand_ipaddress_pattern('1.2.3.4/32', 4))
+
+    def test_invalid_range(self):
+        with self.assertRaises(ValueError):
+            sorted(expand_ipaddress_pattern('1.2.3.[4-]/32', 4))
+
+        with self.assertRaises(ValueError):
+            sorted(expand_ipaddress_pattern('1.2.3.[-4]/32', 4))
+
+        with self.assertRaises(ValueError):
+            sorted(expand_ipaddress_pattern('1.2.3.[4--5]/32', 4))
+
+    def test_invalid_range_bounds(self):
+        self.assertEqual(sorted(expand_ipaddress_pattern('1.2.3.[4-3]/32', 6)), [])
+
+    def test_invalid_set(self):
+        with self.assertRaises(ValueError):
+            sorted(expand_ipaddress_pattern('1.2.3.[4]/32', 4))
+
+        with self.assertRaises(ValueError):
+            sorted(expand_ipaddress_pattern('1.2.3.[4,]/32', 4))
+
+        with self.assertRaises(ValueError):
+            sorted(expand_ipaddress_pattern('1.2.3.[,4]/32', 4))
+
+        with self.assertRaises(ValueError):
+            sorted(expand_ipaddress_pattern('1.2.3.[4,,5]/32', 4))
+
+
+class ExpandAlphanumeric(TestCase):
+    """
+    Validate the operation of expand_alphanumeric_pattern().
+    """
+    def test_range_numberic(self):
+        input = 'r[9-11]a'
+        output = sorted([
+            'r9a',
+            'r10a',
+            'r11a',
+        ])
+
+        self.assertEqual(sorted(expand_alphanumeric_pattern(input)), output)
+
+    def test_range_alpha(self):
+        input = '[r-t]1a'
+        output = sorted([
+            'r1a',
+            's1a',
+            't1a',
+        ])
+
+        self.assertEqual(sorted(expand_alphanumeric_pattern(input)), output)
+
+    def test_set(self):
+        input = '[r,t]1a'
+        output = sorted([
+            'r1a',
+            't1a',
+        ])
+
+        self.assertEqual(sorted(expand_alphanumeric_pattern(input)), output)
+
+    def test_set_multichar(self):
+        input = '[ra,tb]1a'
+        output = sorted([
+            'ra1a',
+            'tb1a',
+        ])
+
+        self.assertEqual(sorted(expand_alphanumeric_pattern(input)), output)
+
+    def test_multiple_ranges(self):
+        input = '[r-t]1[a-b]'
+        output = sorted([
+            'r1a',
+            'r1b',
+            's1a',
+            's1b',
+            't1a',
+            't1b',
+        ])
+
+        self.assertEqual(sorted(expand_alphanumeric_pattern(input)), output)
+
+    def test_multiple_sets(self):
+        input = '[ra,tb]1[ax,by]'
+        output = sorted([
+            'ra1ax',
+            'ra1by',
+            'tb1ax',
+            'tb1by',
+        ])
+
+        self.assertEqual(sorted(expand_alphanumeric_pattern(input)), output)
+
+    def test_set_and_range(self):
+        input = '[ra,tb]1[a-c]'
+        output = sorted([
+            'ra1a',
+            'ra1b',
+            'ra1c',
+            'tb1a',
+            'tb1b',
+            'tb1c',
+        ])
+
+        self.assertEqual(sorted(expand_alphanumeric_pattern(input)), output)
+
+    def test_invalid_non_pattern(self):
+        with self.assertRaises(ValueError):
+            sorted(expand_alphanumeric_pattern('r9a'))
+
+    def test_invalid_range(self):
+        with self.assertRaises(ValueError):
+            sorted(expand_alphanumeric_pattern('r[8-]a'))
+
+        with self.assertRaises(ValueError):
+            sorted(expand_alphanumeric_pattern('r[-8]a'))
+
+        with self.assertRaises(ValueError):
+            sorted(expand_alphanumeric_pattern('r[8--9]a'))
+
+    def test_invalid_range_alphanumeric(self):
+        self.assertEqual(sorted(expand_alphanumeric_pattern('r[9-a]a')), [])
+        self.assertEqual(sorted(expand_alphanumeric_pattern('r[a-9]a')), [])
+
+    def test_invalid_range_bounds(self):
+        self.assertEqual(sorted(expand_alphanumeric_pattern('r[9-8]a')), [])
+        self.assertEqual(sorted(expand_alphanumeric_pattern('r[b-a]a')), [])
+
+    def test_invalid_range_len(self):
+        with self.assertRaises(forms.ValidationError):
+            sorted(expand_alphanumeric_pattern('r[a-bb]a'))
+
+    def test_invalid_set(self):
+        with self.assertRaises(ValueError):
+            sorted(expand_alphanumeric_pattern('r[a]a'))
+
+        with self.assertRaises(ValueError):
+            sorted(expand_alphanumeric_pattern('r[a,]a'))
+
+        with self.assertRaises(ValueError):
+            sorted(expand_alphanumeric_pattern('r[,a]a'))
+
+        with self.assertRaises(ValueError):
+            sorted(expand_alphanumeric_pattern('r[a,,b]a'))