From f3db914e9dc6572885b6489b6a08b1d02b045d99 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 25 Jan 2017 14:34:34 -0500 Subject: [PATCH 1/3] Fixes #844: Apply order_naturally() to API interfaces list --- netbox/dcim/api/views.py | 8 ++++---- netbox/dcim/views.py | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index 70ca17bbc..e76ec82ad 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -6,7 +6,6 @@ from rest_framework.views import APIView from django.conf import settings from django.contrib.contenttypes.models import ContentType -from django.db.models import Count from django.http import Http404 from django.shortcuts import get_object_or_404 @@ -332,7 +331,8 @@ class InterfaceListView(generics.ListAPIView): def get_queryset(self): device = get_object_or_404(Device, pk=self.kwargs['pk']) - queryset = Interface.objects.filter(device=device).select_related('connected_as_a', 'connected_as_b') + queryset = Interface.objects.order_naturally(device.device_type.interface_ordering).filter(device=device)\ + .select_related('connected_as_a', 'connected_as_b', 'circuit_termination') # Filter by type (physical or virtual) iface_type = self.request.query_params.get('type') @@ -490,8 +490,8 @@ class RelatedConnectionsView(APIView): response['power-ports'].append(data) # Interface connections - interfaces = Interface.objects.filter(device=device).select_related('connected_as_a', 'connected_as_b', - 'circuit_termination') + interfaces = Interface.objects.order_naturally(device.device_type.interface_ordering).filter(device=device)\ + .select_related('connected_as_a', 'connected_as_b', 'circuit_termination') for iface in interfaces: data = serializers.InterfaceDetailSerializer(instance=iface).data del(data['device']) diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index b98252747..96c8e167e 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -749,7 +749,8 @@ def device_inventory(request, pk): def device_lldp_neighbors(request, pk): device = get_object_or_404(Device, pk=pk) - interfaces = Interface.objects.filter(device=device).select_related('connected_as_a', 'connected_as_b') + interfaces = Interface.objects.order_naturally(device.device_type.interface_ordering).filter(device=device)\ + .select_related('connected_as_a', 'connected_as_b') return render(request, 'dcim/device_lldp_neighbors.html', { 'device': device, From 6be520a8f9ab4253c35c5e98527ff03bd8ac215d Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 25 Jan 2017 14:38:45 -0500 Subject: [PATCH 2/3] Fixed DeviceTypeTest --- netbox/dcim/tests/test_apis.py | 1 + 1 file changed, 1 insertion(+) diff --git a/netbox/dcim/tests/test_apis.py b/netbox/dcim/tests/test_apis.py index 0739b86ce..0f7d1bbe3 100644 --- a/netbox/dcim/tests/test_apis.py +++ b/netbox/dcim/tests/test_apis.py @@ -239,6 +239,7 @@ class DeviceTypeTest(APITestCase): 'subdevice_role', 'comments', 'custom_fields', + 'instance_count', ] nested_fields = [ From b3b96e5e10ba073b10534f0e5dd1bb18e0b151ff Mon Sep 17 00:00:00 2001 From: dav3860 Date: Wed, 25 Jan 2017 20:47:14 +0100 Subject: [PATCH 3/3] Support for comma in interfaces and ip addresses bulk creation (#833) * Added support for comma in interfaces and ip addresses bulk creation * fixed PEP8 style * removed unnecessary assertions --- netbox/utilities/forms.py | 44 +++++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/netbox/utilities/forms.py b/netbox/utilities/forms.py index f957f8e88..5a8814381 100644 --- a/netbox/utilities/forms.py +++ b/netbox/utilities/forms.py @@ -37,20 +37,38 @@ COLOR_CHOICES = ( ('607d8b', 'Dark grey'), ('111111', 'Black'), ) -NUMERIC_EXPANSION_PATTERN = '\[(\d+-\d+)\]' -IP4_EXPANSION_PATTERN = '\[([0-9]{1,3}-[0-9]{1,3})\]' -IP6_EXPANSION_PATTERN = '\[([0-9a-f]{1,4}-[0-9a-f]{1,4})\]' +NUMERIC_EXPANSION_PATTERN = '\[((?:\d+[?:,-])+\d+)\]' +IP4_EXPANSION_PATTERN = '\[((?:[0-9]{1,3}[?:,-])+[0-9]{1,3})\]' +IP6_EXPANSION_PATTERN = '\[((?:[0-9a-f]{1,4}[?:,-])+[0-9a-f]{1,4})\]' + + +def parse_numeric_range(string, base=10): + """ + Expand a numeric range (continuous or not) into a decimal or + hexadecimal list, as specified by the base parameter + '0-3,5' => [0, 1, 2, 3, 5] + '2,8-b,d,f' => [2, 8, 9, a, b, d, f] + """ + values = list() + for dash_range in string.split(','): + try: + begin, end = dash_range.split('-') + except ValueError: + begin, end = dash_range, dash_range + begin, end = int(begin.strip()), int(end.strip(), base=base) + 1 + values.extend(range(begin, end)) + return list(set(values)) def expand_numeric_pattern(string): """ Expand a numeric pattern into a list of strings. Examples: - 'ge-0/0/[0-3]' => ['ge-0/0/0', 'ge-0/0/1', 'ge-0/0/2', 'ge-0/0/3'] - 'xe-0/[0-3]/[0-7]' => ['xe-0/0/0', 'xe-0/0/1', 'xe-0/0/2', ... 'xe-0/3/5', 'xe-0/3/6', 'xe-0/3/7'] + 'ge-0/0/[0-3,5]' => ['ge-0/0/0', 'ge-0/0/1', 'ge-0/0/2', 'ge-0/0/3', 'ge-0/0/5'] + 'xe-0/[0,2-3]/[0-7]' => ['xe-0/0/0', 'xe-0/0/1', 'xe-0/0/2', ... 'xe-0/3/5', 'xe-0/3/6', 'xe-0/3/7'] """ lead, pattern, remnant = re.split(NUMERIC_EXPANSION_PATTERN, string, maxsplit=1) - x, y = pattern.split('-') - for i in range(int(x), int(y) + 1): + parsed_range = parse_numeric_range(pattern) + for i in parsed_range: if re.search(NUMERIC_EXPANSION_PATTERN, remnant): for string in expand_numeric_pattern(remnant): yield "{}{}{}".format(lead, i, string) @@ -61,8 +79,8 @@ def expand_numeric_pattern(string): def expand_ipaddress_pattern(string, family): """ Expand an IP address pattern into a list of strings. Examples: - '192.0.2.[1-254]/24' => ['192.0.2.1/24', '192.0.2.2/24', '192.0.2.3/24' ... '192.0.2.254/24'] - '2001:db8:0:[0-ff]::/64' => ['2001:db8:0:0::/64', '2001:db8:0:1::/64', ... '2001:db8:0:ff::/64'] + '192.0.2.[1,2,100-250,254]/24' => ['192.0.2.1/24', '192.0.2.2/24', '192.0.2.100/24' ... '192.0.2.250/24', '192.0.2.254/24'] + '2001:db8:0:[0,fd-ff]::/64' => ['2001:db8:0:0::/64', '2001:db8:0:fd::/64', ... '2001:db8:0:ff::/64'] """ if family not in [4, 6]: raise Exception("Invalid IP address family: {}".format(family)) @@ -73,8 +91,8 @@ def expand_ipaddress_pattern(string, family): regex = IP6_EXPANSION_PATTERN base = 16 lead, pattern, remnant = re.split(regex, string, maxsplit=1) - x, y = pattern.split('-') - for i in range(int(x, base), int(y, base) + 1): + parsed_range = parse_numeric_range(pattern, base) + for i in parsed_range: if re.search(regex, remnant): for string in expand_ipaddress_pattern(remnant, family): yield ''.join([lead, format(i, 'x' if family == 6 else 'd'), string]) @@ -248,7 +266,7 @@ class ExpandableNameField(forms.CharField): 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-47]' + 'Example: ge-0/0/[0-23,25,30]' def to_python(self, value): if re.search(NUMERIC_EXPANSION_PATTERN, value): @@ -265,7 +283,7 @@ class ExpandableIPAddressField(forms.CharField): super(ExpandableIPAddressField, self).__init__(*args, **kwargs) if not self.help_text: self.help_text = 'Specify a numeric range to create multiple IPs.
'\ - 'Example: 192.0.2.[1-254]/24' + 'Example: 192.0.2.[1,5,100-254]/24' def to_python(self, value): # Hackish address family detection but it's all we have to work with