diff --git a/netbox/utilities/forms.py b/netbox/utilities/forms.py
index 6bf84cbe0..15fb69f7f 100644
--- a/netbox/utilities/forms.py
+++ b/netbox/utilities/forms.py
@@ -39,6 +39,7 @@ COLOR_CHOICES = (
('111111', 'Black'),
)
NUMERIC_EXPANSION_PATTERN = '\[((?:\d+[?:,-])+\d+)\]'
+ALPHANUMERIC_EXPANSION_PATTERN = '\[((?:[a-zA-Z0-9]+[?:,-])+[a-zA-Z0-9]+)\]'
IP4_EXPANSION_PATTERN = '\[((?:[0-9]{1,3}[?:,-])+[0-9]{1,3})\]'
IP6_EXPANSION_PATTERN = '\[((?:[0-9a-f]{1,4}[?:,-])+[0-9a-f]{1,4})\]'
@@ -77,6 +78,45 @@ def expand_numeric_pattern(string):
yield "{}{}{}".format(lead, i, remnant)
+def parse_alphanumeric_range(string):
+ """
+ Expand an alphanumeric range (continuous or not) into a list.
+ 'a-d,f' => [a, b, c, d, f]
+ '0-3,a-d' => [0, 1, 2, 3, a, b, c, d]
+ """
+ values = []
+ for dash_range in string.split(','):
+ try:
+ begin, end = dash_range.split('-')
+ vals = begin + end
+ # Break out of loop if there's an invalid pattern to return an error
+ if (not (vals.isdigit() or vals.isalpha())) or (vals.isalpha() and not (vals.isupper() or vals.islower())):
+ return []
+ except ValueError:
+ begin, end = dash_range, dash_range
+ if begin.isdigit() and end.isdigit():
+ 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))
+ return values
+
+
+def expand_alphanumeric_pattern(string):
+ """
+ Expand an alphabetic pattern into a list of strings.
+ """
+ lead, pattern, remnant = re.split(ALPHANUMERIC_EXPANSION_PATTERN, string, maxsplit=1)
+ parsed_range = parse_alphanumeric_range(pattern)
+ for i in parsed_range:
+ if re.search(ALPHANUMERIC_EXPANSION_PATTERN, remnant):
+ for string in expand_alphanumeric_pattern(remnant):
+ yield "{}{}{}".format(lead, i, string)
+ else:
+ yield "{}{}{}".format(lead, i, remnant)
+
+
def expand_ipaddress_pattern(string, family):
"""
Expand an IP address pattern into a list of strings. Examples:
@@ -306,12 +346,15 @@ class ExpandableNameField(forms.CharField):
def __init__(self, *args, **kwargs):
super(ExpandableNameField, self).__init__(*args, **kwargs)
if not self.help_text:
- self.help_text = 'Numeric ranges are supported for bulk creation.
'\
- 'Example: ge-0/0/[0-23,25,30]
'
+ self.help_text = 'Alphanumeric ranges are supported for bulk creation.
' \
+ 'Mixed cases and types within a single range are not supported.
' \
+ 'Examples:
ge-0/0/[0-23,25,30]
e[0-3][a-d,f]
e[0-3,a-d,f]