mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-20 10:16:42 -06:00
Merge pull request #3865 from hSaria/3623-interface-word-expansion
Fixes #3623: Word expansion for interfaces
This commit is contained in:
commit
cb91c9231d
@ -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
|
||||
|
@ -60,6 +60,14 @@ def parse_alphanumeric_range(string):
|
||||
for n in list(range(int(begin), int(end) + 1)):
|
||||
values.append(n)
|
||||
else:
|
||||
# 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
netbox/utilities/tests/test_forms.py
Normal file
283
netbox/utilities/tests/test_forms.py
Normal file
@ -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'))
|
Loading…
Reference in New Issue
Block a user