From 3854a9d633af1345a5d2a82256545fa080fb667e Mon Sep 17 00:00:00 2001 From: tradiuz Date: Thu, 14 Jun 2018 16:59:00 -0500 Subject: [PATCH 01/20] Changes for Issue #2168 Adding support for Extreme Networks SummitStack port types. --- netbox/dcim/constants.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/netbox/dcim/constants.py b/netbox/dcim/constants.py index cea56e176..2a477932b 100644 --- a/netbox/dcim/constants.py +++ b/netbox/dcim/constants.py @@ -93,6 +93,10 @@ IFACE_FF_STACKWISE_PLUS = 5050 IFACE_FF_FLEXSTACK = 5100 IFACE_FF_FLEXSTACK_PLUS = 5150 IFACE_FF_JUNIPER_VCP = 5200 +IFACE_FF_EXTREME_SS = 5300 +IFACE_FF_EXTREME_SS128 = 5310 +IFACE_FF_EXTREME_SS512 = 5320 + # Other IFACE_FF_OTHER = 32767 @@ -168,6 +172,9 @@ IFACE_FF_CHOICES = [ [IFACE_FF_FLEXSTACK, 'Cisco FlexStack'], [IFACE_FF_FLEXSTACK_PLUS, 'Cisco FlexStack Plus'], [IFACE_FF_JUNIPER_VCP, 'Juniper VCP'], + [IFACE_FF_EXTREME_SS, 'Extreme SummitStack'], + [IFACE_FF_EXTREME_SS128, 'Extreme SummitStack128'], + [IFACE_FF_EXTREME_SS512, 'Extreme SummitStack512'], ] ], [ From 264bf6c48410b8b98069c5c28dc605354403fee0 Mon Sep 17 00:00:00 2001 From: tradiuz Date: Fri, 15 Jun 2018 13:43:04 -0500 Subject: [PATCH 02/20] Adding SummitStack-256 --- netbox/dcim/constants.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/netbox/dcim/constants.py b/netbox/dcim/constants.py index 2a477932b..b24ba8e4f 100644 --- a/netbox/dcim/constants.py +++ b/netbox/dcim/constants.py @@ -95,7 +95,8 @@ IFACE_FF_FLEXSTACK_PLUS = 5150 IFACE_FF_JUNIPER_VCP = 5200 IFACE_FF_EXTREME_SS = 5300 IFACE_FF_EXTREME_SS128 = 5310 -IFACE_FF_EXTREME_SS512 = 5320 +IFACE_FF_EXTREME_SS256 = 5320 +IFACE_FF_EXTREME_SS512 = 5330 # Other IFACE_FF_OTHER = 32767 @@ -173,8 +174,9 @@ IFACE_FF_CHOICES = [ [IFACE_FF_FLEXSTACK_PLUS, 'Cisco FlexStack Plus'], [IFACE_FF_JUNIPER_VCP, 'Juniper VCP'], [IFACE_FF_EXTREME_SS, 'Extreme SummitStack'], - [IFACE_FF_EXTREME_SS128, 'Extreme SummitStack128'], - [IFACE_FF_EXTREME_SS512, 'Extreme SummitStack512'], + [IFACE_FF_EXTREME_SS128, 'Extreme SummitStack-128'], + [IFACE_FF_EXTREME_SS128, 'Extreme SummitStack-256'], + [IFACE_FF_EXTREME_SS512, 'Extreme SummitStack-512'], ] ], [ From 7c11fa7b506364c1b6c60f210548adaf4e71eea0 Mon Sep 17 00:00:00 2001 From: Chris Howells Date: Mon, 18 Jun 2018 14:35:07 +0100 Subject: [PATCH 03/20] Add a serial number to the popover in rack elevation number --- netbox/templates/dcim/inc/rack_elevation.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/templates/dcim/inc/rack_elevation.html b/netbox/templates/dcim/inc/rack_elevation.html index e6b721db4..46fe01c8d 100644 --- a/netbox/templates/dcim/inc/rack_elevation.html +++ b/netbox/templates/dcim/inc/rack_elevation.html @@ -26,7 +26,7 @@
  • {% ifequal u.device.face face_id %} + data-content="{{ u.device.device_role }}
    {{ u.device.device_type.full_name }} ({{ u.device.device_type.u_height }}U){% if u.device.asset_tag %}
    {{ u.device.asset_tag }}{% endif %}{% if u.device.serial %}
    {{ u.device.serial }}{% endif %}"> {{ u.device.name|default:u.device.device_role }} {% if u.device.devicebay_count %} ({{ u.device.get_children.count }}/{{ u.device.devicebay_count }}) From 6dde0f030a5382a1425ac7820edfa9dcf5162c02 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 19 Jun 2018 13:37:12 -0400 Subject: [PATCH 04/20] Fixes #2182: ValueError raised when viewing the interface connections table --- netbox/dcim/tables.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/netbox/dcim/tables.py b/netbox/dcim/tables.py index e71395ebc..427687ef9 100644 --- a/netbox/dcim/tables.py +++ b/netbox/dcim/tables.py @@ -7,9 +7,9 @@ from tenancy.tables import COL_TENANT from utilities.tables import BaseTable, ToggleColumn from .models import ( ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, - DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceTemplate, InventoryItem, Manufacturer, Platform, - PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, RackReservation, Region, Site, - VirtualChassis, + DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceConnection, InterfaceTemplate, InventoryItem, + Manufacturer, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, + RackReservation, Region, Site, VirtualChassis, ) REGION_LINK = """ @@ -594,7 +594,7 @@ class InterfaceConnectionTable(BaseTable): interface_b = tables.Column(verbose_name='Interface B') class Meta(BaseTable.Meta): - model = Interface + model = InterfaceConnection fields = ('device_a', 'interface_a', 'device_b', 'interface_b') From 4e09b32dd96ab26181dc0a32bef1654154504873 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ana=C3=ABl=20Beutot?= Date: Wed, 27 Jun 2018 17:23:32 +0200 Subject: [PATCH 05/20] Fix pycodestyle errors Mainly two kind of errors: * pokemon exceptions * invalid escape sequences --- netbox/dcim/forms.py | 2 +- netbox/dcim/models.py | 8 ++++---- netbox/extras/api/views.py | 2 +- netbox/extras/migrations/0008_reports.py | 2 +- netbox/extras/rpc.py | 14 +++++++------- netbox/netbox/settings.py | 2 +- netbox/secrets/forms.py | 2 +- netbox/secrets/models.py | 2 +- netbox/utilities/forms.py | 10 +++++----- netbox/utilities/managers.py | 6 +++--- netbox/utilities/validators.py | 2 +- 11 files changed, 26 insertions(+), 26 deletions(-) diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index da8dc0457..593bef113 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -34,7 +34,7 @@ from .models import ( RackRole, Region, Site, VirtualChassis ) -DEVICE_BY_PK_RE = '{\d+\}' +DEVICE_BY_PK_RE = r'{\d+\}' INTERFACE_MODE_HELP_TEXT = """ Access: One untagged VLAN
    diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index 39bd4ad3d..8ee5d5704 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -1205,8 +1205,8 @@ class ConsoleServerPortManager(models.Manager): def get_queryset(self): # Pad any trailing digits to effect natural sorting return super(ConsoleServerPortManager, self).get_queryset().extra(select={ - 'name_padded': "CONCAT(REGEXP_REPLACE(dcim_consoleserverport.name, '\d+$', ''), " - "LPAD(SUBSTRING(dcim_consoleserverport.name FROM '\d+$'), 8, '0'))", + 'name_padded': r"CONCAT(REGEXP_REPLACE(dcim_consoleserverport.name, '\d+$', ''), " + r"LPAD(SUBSTRING(dcim_consoleserverport.name FROM '\d+$'), 8, '0'))", }).order_by('device', 'name_padded') @@ -1287,8 +1287,8 @@ class PowerOutletManager(models.Manager): def get_queryset(self): # Pad any trailing digits to effect natural sorting return super(PowerOutletManager, self).get_queryset().extra(select={ - 'name_padded': "CONCAT(REGEXP_REPLACE(dcim_poweroutlet.name, '\d+$', ''), " - "LPAD(SUBSTRING(dcim_poweroutlet.name FROM '\d+$'), 8, '0'))", + 'name_padded': r"CONCAT(REGEXP_REPLACE(dcim_poweroutlet.name, '\d+$', ''), " + r"LPAD(SUBSTRING(dcim_poweroutlet.name FROM '\d+$'), 8, '0'))", }).order_by('device', 'name_padded') diff --git a/netbox/extras/api/views.py b/netbox/extras/api/views.py index 252c2d12c..f39629fa0 100644 --- a/netbox/extras/api/views.py +++ b/netbox/extras/api/views.py @@ -99,7 +99,7 @@ class TopologyMapViewSet(ModelViewSet): try: data = tmap.render(img_format=img_format) - except: + except Exception: return HttpResponse( "There was an error generating the requested graph. Ensure that the GraphViz executables have been " "installed correctly." diff --git a/netbox/extras/migrations/0008_reports.py b/netbox/extras/migrations/0008_reports.py index 7f3eccc32..fbfde2cba 100644 --- a/netbox/extras/migrations/0008_reports.py +++ b/netbox/extras/migrations/0008_reports.py @@ -19,7 +19,7 @@ def verify_postgresql_version(apps, schema_editor): with connection.cursor() as cursor: cursor.execute("SELECT VERSION()") row = cursor.fetchone() - pg_version = re.match('^PostgreSQL (\d+\.\d+(\.\d+)?)', row[0]).group(1) + pg_version = re.match(r'^PostgreSQL (\d+\.\d+(\.\d+)?)', row[0]).group(1) if StrictVersion(pg_version) < StrictVersion('9.4.0'): raise Exception("PostgreSQL 9.4.0 or higher is required ({} found). Upgrade PostgreSQL and then run migrations again.".format(pg_version)) diff --git a/netbox/extras/rpc.py b/netbox/extras/rpc.py index 2a547ca45..552f592c7 100644 --- a/netbox/extras/rpc.py +++ b/netbox/extras/rpc.py @@ -163,8 +163,8 @@ class IOSSSH(SSHClient): sh_ver = self._send('show version').split('\r\n') return { - 'serial': parse(sh_ver, 'Processor board ID ([^\s]+)'), - 'description': parse(sh_ver, 'cisco ([^\s]+)') + 'serial': parse(sh_ver, r'Processor board ID ([^\s]+)'), + 'description': parse(sh_ver, r'cisco ([^\s]+)') } def items(chassis_serial=None): @@ -172,9 +172,9 @@ class IOSSSH(SSHClient): for i in cmd: i_fmt = i.replace('\r\n', ' ') try: - m_name = re.search('NAME: "([^"]+)"', i_fmt).group(1) - m_pid = re.search('PID: ([^\s]+)', i_fmt).group(1) - m_serial = re.search('SN: ([^\s]+)', i_fmt).group(1) + m_name = re.search(r'NAME: "([^"]+)"', i_fmt).group(1) + m_pid = re.search(r'PID: ([^\s]+)', i_fmt).group(1) + m_serial = re.search(r'SN: ([^\s]+)', i_fmt).group(1) # Omit built-in items and those with no PID if m_serial != chassis_serial and m_pid.lower() != 'unspecified': yield { @@ -208,7 +208,7 @@ class OpengearSSH(SSHClient): try: stdin, stdout, stderr = self.ssh.exec_command("showserial") serial = stdout.readlines()[0].strip() - except: + except Exception: raise RuntimeError("Failed to glean chassis serial from device.") # Older models don't provide serial info if serial == "No serial number information available": @@ -217,7 +217,7 @@ class OpengearSSH(SSHClient): try: stdin, stdout, stderr = self.ssh.exec_command("config -g config.system.model") description = stdout.readlines()[0].split(' ', 1)[1].strip() - except: + except Exception: raise RuntimeError("Failed to glean chassis description from device.") return { diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 8cb98f268..9e3110e3b 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -281,5 +281,5 @@ INTERNAL_IPS = ( try: HOSTNAME = socket.gethostname() -except: +except Exception: HOSTNAME = 'localhost' diff --git a/netbox/secrets/forms.py b/netbox/secrets/forms.py index 8f8107805..8e7fa3b16 100644 --- a/netbox/secrets/forms.py +++ b/netbox/secrets/forms.py @@ -26,7 +26,7 @@ def validate_rsa_key(key, is_secret=True): raise forms.ValidationError("This looks like a private key. Please provide your public RSA key.") try: PKCS1_OAEP.new(key) - except: + except Exception: raise forms.ValidationError("Error validating RSA key. Please ensure that your key supports PKCS#1 OAEP.") diff --git a/netbox/secrets/models.py b/netbox/secrets/models.py index e1f367d03..4bd644564 100644 --- a/netbox/secrets/models.py +++ b/netbox/secrets/models.py @@ -87,7 +87,7 @@ class UserKey(CreatedUpdatedModel): raise ValidationError({ 'public_key': "Invalid RSA key format." }) - except: + except Exception: raise ValidationError("Something went wrong while trying to save your key. Please ensure that you're " "uploading a valid RSA public key in PEM format (no SSH/PGP).") diff --git a/netbox/utilities/forms.py b/netbox/utilities/forms.py index 69b102f5c..1cea0b0da 100644 --- a/netbox/utilities/forms.py +++ b/netbox/utilities/forms.py @@ -38,10 +38,10 @@ COLOR_CHOICES = ( ('607d8b', 'Dark grey'), ('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})\]' +NUMERIC_EXPANSION_PATTERN = r'\[((?:\d+[?:,-])+\d+)\]' +ALPHANUMERIC_EXPANSION_PATTERN = r'\[((?:[a-zA-Z0-9]+[?:,-])+[a-zA-Z0-9]+)\]' +IP4_EXPANSION_PATTERN = r'\[((?:[0-9]{1,3}[?:,-])+[0-9]{1,3})\]' +IP6_EXPANSION_PATTERN = r'\[((?:[0-9a-f]{1,4}[?:,-])+[0-9a-f]{1,4})\]' def parse_numeric_range(string, base=10): @@ -407,7 +407,7 @@ class FlexibleModelChoiceField(forms.ModelChoiceField): try: if not self.to_field_name: key = 'pk' - elif re.match('^\{\d+\}$', value): + elif re.match(r'^\{\d+\}$', value): key = 'pk' value = value.strip('{}') else: diff --git a/netbox/utilities/managers.py b/netbox/utilities/managers.py index dc882d462..33b4356d4 100644 --- a/netbox/utilities/managers.py +++ b/netbox/utilities/managers.py @@ -23,9 +23,9 @@ class NaturalOrderByManager(Manager): id3 = '_{}_{}3'.format(db_table, primary_field) queryset = super(NaturalOrderByManager, self).get_queryset().extra(select={ - id1: "CAST(SUBSTRING({}.{} FROM '^(\d{{1,9}})') AS integer)".format(db_table, primary_field), - id2: "SUBSTRING({}.{} FROM '^\d*(.*?)\d*$')".format(db_table, primary_field), - id3: "CAST(SUBSTRING({}.{} FROM '(\d{{1,9}})$') AS integer)".format(db_table, primary_field), + id1: r"CAST(SUBSTRING({}.{} FROM '^(\d{{1,9}})') AS integer)".format(db_table, primary_field), + id2: r"SUBSTRING({}.{} FROM '^\d*(.*?)\d*$')".format(db_table, primary_field), + id3: r"CAST(SUBSTRING({}.{} FROM '(\d{{1,9}})$') AS integer)".format(db_table, primary_field), }) ordering = fields[0:-1] + (id1, id2, id3) diff --git a/netbox/utilities/validators.py b/netbox/utilities/validators.py index 8eb990486..dcdb9bc6d 100644 --- a/netbox/utilities/validators.py +++ b/netbox/utilities/validators.py @@ -15,7 +15,7 @@ class EnhancedURLValidator(URLValidator): A fake URL list which "contains" all scheme names abiding by the syntax defined in RFC 3986 section 3.1 """ def __contains__(self, item): - if not item or not re.match('^[a-z][0-9a-z+\-.]*$', item.lower()): + if not item or not re.match(r'^[a-z][0-9a-z+\-.]*$', item.lower()): return False return True From 007852a48f8e42173d80f88f8bcd520e8f681a30 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 29 Jun 2018 12:18:49 -0400 Subject: [PATCH 06/20] Revert "Closes #2168: Add Extreme SummitStack interface form factors" --- netbox/dcim/constants.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/netbox/dcim/constants.py b/netbox/dcim/constants.py index b24ba8e4f..cea56e176 100644 --- a/netbox/dcim/constants.py +++ b/netbox/dcim/constants.py @@ -93,11 +93,6 @@ IFACE_FF_STACKWISE_PLUS = 5050 IFACE_FF_FLEXSTACK = 5100 IFACE_FF_FLEXSTACK_PLUS = 5150 IFACE_FF_JUNIPER_VCP = 5200 -IFACE_FF_EXTREME_SS = 5300 -IFACE_FF_EXTREME_SS128 = 5310 -IFACE_FF_EXTREME_SS256 = 5320 -IFACE_FF_EXTREME_SS512 = 5330 - # Other IFACE_FF_OTHER = 32767 @@ -173,10 +168,6 @@ IFACE_FF_CHOICES = [ [IFACE_FF_FLEXSTACK, 'Cisco FlexStack'], [IFACE_FF_FLEXSTACK_PLUS, 'Cisco FlexStack Plus'], [IFACE_FF_JUNIPER_VCP, 'Juniper VCP'], - [IFACE_FF_EXTREME_SS, 'Extreme SummitStack'], - [IFACE_FF_EXTREME_SS128, 'Extreme SummitStack-128'], - [IFACE_FF_EXTREME_SS128, 'Extreme SummitStack-256'], - [IFACE_FF_EXTREME_SS512, 'Extreme SummitStack-512'], ] ], [ From 28a2a37ed25180f971f0a97cb2d60716cef70874 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 29 Jun 2018 13:17:07 -0400 Subject: [PATCH 07/20] Fixes #2191: Added missing static choices to circuits and DCIM API endpoints --- netbox/circuits/api/views.py | 1 + netbox/dcim/api/views.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/netbox/circuits/api/views.py b/netbox/circuits/api/views.py index 9b75bc184..479c21add 100644 --- a/netbox/circuits/api/views.py +++ b/netbox/circuits/api/views.py @@ -19,6 +19,7 @@ from . import serializers class CircuitsFieldChoicesViewSet(FieldChoicesViewSet): fields = ( + (Circuit, ['status']), (CircuitTermination, ['term_side']), ) diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index 13f68639f..ce8b4c349 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -3,7 +3,6 @@ from __future__ import unicode_literals from collections import OrderedDict from django.conf import settings -from django.db import transaction from django.http import HttpResponseBadRequest, HttpResponseForbidden from django.shortcuts import get_object_or_404 from drf_yasg import openapi @@ -37,11 +36,12 @@ class DCIMFieldChoicesViewSet(FieldChoicesViewSet): fields = ( (Device, ['face', 'status']), (ConsolePort, ['connection_status']), - (Interface, ['form_factor']), + (Interface, ['form_factor', 'mode']), (InterfaceConnection, ['connection_status']), (InterfaceTemplate, ['form_factor']), (PowerPort, ['connection_status']), (Rack, ['type', 'width']), + (Site, ['status']), ) From 982b9454f8e26173869caa5c29e3b14734dc2679 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 29 Jun 2018 13:54:21 -0400 Subject: [PATCH 08/20] Closes #2194: Added 'address' filter to IPAddress model --- netbox/ipam/filters.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/netbox/ipam/filters.py b/netbox/ipam/filters.py index 005d44a84..f21cc299d 100644 --- a/netbox/ipam/filters.py +++ b/netbox/ipam/filters.py @@ -1,6 +1,7 @@ from __future__ import unicode_literals import django_filters +from django.core.exceptions import ValidationError from django.db.models import Q import netaddr from netaddr.core import AddrFormatError @@ -233,6 +234,10 @@ class IPAddressFilter(CustomFieldFilterSet, django_filters.FilterSet): method='search_by_parent', label='Parent prefix', ) + address = django_filters.CharFilter( + method='filter_address', + label='Address', + ) mask_length = django_filters.NumberFilter( method='filter_mask_length', label='Mask length', @@ -313,6 +318,17 @@ class IPAddressFilter(CustomFieldFilterSet, django_filters.FilterSet): except (AddrFormatError, ValueError): return queryset.none() + def filter_address(self, queryset, name, value): + if not value.strip(): + return queryset + try: + # Match address and subnet mask + if '/' in value: + return queryset.filter(address=value) + return queryset.filter(address__net_host=value) + except ValidationError: + return queryset.none() + def filter_mask_length(self, queryset, name, value): if not value: return queryset From 8d4c686ae2b5d1c82dd87e4aa645546c65719e2b Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 29 Jun 2018 14:09:20 -0400 Subject: [PATCH 09/20] Fixes #2192: Prevent a 0U device from being assigned to a rack position --- netbox/dcim/models.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index 39bd4ad3d..95e9299b8 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -963,6 +963,12 @@ class Device(CreatedUpdatedModel, CustomFieldModel): 'face': "Must specify rack face when defining rack position.", }) + # Prevent 0U devices from being assigned to a specific position + if self.position and self.device_type.u_height == 0: + raise ValidationError({ + 'position': "A U0 device type ({}) cannot be assigned to a rack position.".format(self.device_type) + }) + if self.rack: try: From d98aa03e9d012715bb4d47d92e7dec92fd30fa6e Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 29 Jun 2018 14:52:37 -0400 Subject: [PATCH 10/20] Fixes #2173: Fixed IndexError when automaticating allocating IP addresses from large IPv6 prefixes --- netbox/ipam/api/views.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/netbox/ipam/api/views.py b/netbox/ipam/api/views.py index f6a55b618..127fe77d9 100644 --- a/netbox/ipam/api/views.py +++ b/netbox/ipam/api/views.py @@ -160,8 +160,8 @@ class PrefixViewSet(CustomFieldModelViewSet): requested_ips = request.data if isinstance(request.data, list) else [request.data] # Determine if the requested number of IPs is available - available_ips = list(prefix.get_available_ips()) - if len(available_ips) < len(requested_ips): + available_ips = prefix.get_available_ips() + if available_ips.size < len(requested_ips): return Response( { "detail": "An insufficient number of IP addresses are available within the prefix {} ({} " @@ -171,8 +171,9 @@ class PrefixViewSet(CustomFieldModelViewSet): ) # Assign addresses from the list of available IPs and copy VRF assignment from the parent prefix + available_ips = iter(available_ips) for requested_ip in requested_ips: - requested_ip['address'] = available_ips.pop(0) + requested_ip['address'] = next(available_ips) requested_ip['vrf'] = prefix.vrf.pk if prefix.vrf else None # Initialize the serializer with a list or a single object depending on what was requested From af54d96d30f8a194d76f8df733ed109a91c1cf4c Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 29 Jun 2018 15:10:30 -0400 Subject: [PATCH 11/20] Fixes #2181: Raise validation error on invalid prefix_length when allocating next-available prefix --- netbox/ipam/api/views.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/netbox/ipam/api/views.py b/netbox/ipam/api/views.py index 127fe77d9..ac5ccd972 100644 --- a/netbox/ipam/api/views.py +++ b/netbox/ipam/api/views.py @@ -4,7 +4,7 @@ from django.conf import settings from django.shortcuts import get_object_or_404 from rest_framework import status from rest_framework.decorators import detail_route -from rest_framework.exceptions import PermissionDenied +from rest_framework.exceptions import PermissionDenied, ValidationError from rest_framework.response import Response from extras.api.views import CustomFieldModelViewSet @@ -98,7 +98,23 @@ class PrefixViewSet(CustomFieldModelViewSet): requested_prefixes = request.data if isinstance(request.data, list) else [request.data] # Allocate prefixes to the requested objects based on availability within the parent - for requested_prefix in requested_prefixes: + for i, requested_prefix in enumerate(requested_prefixes): + + # Validate requested prefix size + if 'prefix_length' not in requested_prefix: + raise ValidationError("Item {}: prefix_length field missing".format(i)) + elif not isinstance(requested_prefix['prefix_length'], int): + raise ValidationError("Item {}: Invalid prefix length ({})".format( + i, requested_prefix['prefix_length'] + )) + elif prefix.family == 4 and requested_prefix['prefix_length'] > 32: + raise ValidationError("Item {}: Invalid prefix length ({}) for IPv4".format( + i, requested_prefix['prefix_length'] + )) + elif prefix.family == 6 and requested_prefix['prefix_length'] > 128: + raise ValidationError("Item {}: Invalid prefix length ({}) for IPv6".format( + i, requested_prefix['prefix_length'] + )) # Find the first available prefix equal to or larger than the requested size for available_prefix in available_prefixes.iter_cidrs(): From 80080150823449cf57e9459400e32a58248a31e3 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 29 Jun 2018 15:18:30 -0400 Subject: [PATCH 12/20] Tweaked API error reporting from #2181 --- netbox/ipam/api/views.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/netbox/ipam/api/views.py b/netbox/ipam/api/views.py index ac5ccd972..31e899afd 100644 --- a/netbox/ipam/api/views.py +++ b/netbox/ipam/api/views.py @@ -101,20 +101,28 @@ class PrefixViewSet(CustomFieldModelViewSet): for i, requested_prefix in enumerate(requested_prefixes): # Validate requested prefix size + error_msg = None if 'prefix_length' not in requested_prefix: - raise ValidationError("Item {}: prefix_length field missing".format(i)) + error_msg = "Item {}: prefix_length field missing".format(i) elif not isinstance(requested_prefix['prefix_length'], int): - raise ValidationError("Item {}: Invalid prefix length ({})".format( + error_msg = "Item {}: Invalid prefix length ({})".format( i, requested_prefix['prefix_length'] - )) + ) elif prefix.family == 4 and requested_prefix['prefix_length'] > 32: - raise ValidationError("Item {}: Invalid prefix length ({}) for IPv4".format( + error_msg = "Item {}: Invalid prefix length ({}) for IPv4".format( i, requested_prefix['prefix_length'] - )) + ) elif prefix.family == 6 and requested_prefix['prefix_length'] > 128: - raise ValidationError("Item {}: Invalid prefix length ({}) for IPv6".format( + error_msg = "Item {}: Invalid prefix length ({}) for IPv6".format( i, requested_prefix['prefix_length'] - )) + ) + if error_msg: + return Response( + { + "detail": error_msg + }, + status=status.HTTP_400_BAD_REQUEST + ) # Find the first available prefix equal to or larger than the requested size for available_prefix in available_prefixes.iter_cidrs(): From 943ec0b64b398f7b8c472842c1fd8e9985795c42 Mon Sep 17 00:00:00 2001 From: Erik Hetland Date: Fri, 29 Jun 2018 22:01:01 +0200 Subject: [PATCH 13/20] Adding Swagger settings to describe API authentication correctly. Fixes #1826 --- netbox/netbox/settings.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 8cb98f268..81ef511f9 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -268,7 +268,14 @@ SWAGGER_SETTINGS = { 'utilities.custom_inspectors.NullablePaginatorInspector', 'drf_yasg.inspectors.DjangoRestResponsePagination', 'drf_yasg.inspectors.CoreAPICompatInspector', - ] + ], + 'SECURITY_DEFINITIONS': { + 'Bearer': { + 'type': 'apiKey', + 'name': 'Authorization', + 'in': 'header', + } + } } From 3e9cec3e8e1a3cbacbe651af6fdb2477c53d9f13 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 29 Jun 2018 16:01:28 -0400 Subject: [PATCH 14/20] Closes #2159: Allow custom choice field to specify a default choice --- netbox/extras/forms.py | 10 +++++++++- netbox/extras/migrations/0007_unicode_literals.py | 2 +- netbox/extras/models.py | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/netbox/extras/forms.py b/netbox/extras/forms.py index a923ae596..55f8435d6 100644 --- a/netbox/extras/forms.py +++ b/netbox/extras/forms.py @@ -4,6 +4,7 @@ from collections import OrderedDict from django import forms from django.contrib.contenttypes.models import ContentType +from django.core.exceptions import ObjectDoesNotExist from utilities.forms import BootstrapMixin, BulkEditForm, LaxURLField from .constants import CF_FILTER_DISABLED, CF_TYPE_BOOLEAN, CF_TYPE_DATE, CF_TYPE_INTEGER, CF_TYPE_SELECT, CF_TYPE_URL @@ -53,7 +54,14 @@ def get_custom_fields_for_model(content_type, filterable_only=False, bulk_edit=F choices = [(cfc.pk, cfc) for cfc in cf.choices.all()] if not cf.required or bulk_edit or filterable_only: choices = [(None, '---------')] + choices - field = forms.TypedChoiceField(choices=choices, coerce=int, required=cf.required) + # Check for a default choice + default_choice = None + if initial: + try: + default_choice = cf.choices.get(value=initial).pk + except ObjectDoesNotExist: + pass + field = forms.TypedChoiceField(choices=choices, coerce=int, required=cf.required, initial=default_choice) # URL elif cf.type == CF_TYPE_URL: diff --git a/netbox/extras/migrations/0007_unicode_literals.py b/netbox/extras/migrations/0007_unicode_literals.py index c9a624510..cda07583f 100644 --- a/netbox/extras/migrations/0007_unicode_literals.py +++ b/netbox/extras/migrations/0007_unicode_literals.py @@ -16,7 +16,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='customfield', name='default', - field=models.CharField(blank=True, help_text='Default value for the field. Use "true" or "false" for booleans. N/A for selection fields.', max_length=100), + field=models.CharField(blank=True, help_text='Default value for the field. Use "true" or "false" for booleans.', max_length=100), ), migrations.AlterField( model_name='customfield', diff --git a/netbox/extras/models.py b/netbox/extras/models.py index 75945adcd..cb68f0e0d 100644 --- a/netbox/extras/models.py +++ b/netbox/extras/models.py @@ -91,7 +91,7 @@ class CustomField(models.Model): default = models.CharField( max_length=100, blank=True, - help_text='Default value for the field. Use "true" or "false" for booleans. N/A for selection fields.' + help_text='Default value for the field. Use "true" or "false" for booleans.' ) weight = models.PositiveSmallIntegerField( default=100, From fa5493a5d8d27ada3c52cb7db6c0890029dabcb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ana=C3=ABl=20Beutot?= Date: Wed, 27 Jun 2018 17:24:48 +0200 Subject: [PATCH 15/20] Update CI to use pycostyle instead of pep8 --- .travis.yml | 2 +- scripts/cibuild.sh | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 94bfa0dab..33abc8425 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ python: - "3.5" install: - pip install -r requirements.txt - - pip install pep8 + - pip install pycodestyle before_script: - psql --version - psql -U postgres -c 'SELECT version();' diff --git a/scripts/cibuild.sh b/scripts/cibuild.sh index 4f4fe1ca3..c0a49b5ef 100755 --- a/scripts/cibuild.sh +++ b/scripts/cibuild.sh @@ -23,8 +23,11 @@ fi # Check all python source files for PEP 8 compliance, but explicitly # ignore: +# - W504: line break after binary operator # - E501: line greater than 80 characters in length -pep8 --ignore=E501 netbox/ +pycodestyle \ + --ignore=W504,E501 \ + netbox/ RC=$? if [[ $RC != 0 ]]; then echo -e "\n$(info) one or more PEP 8 errors detected, failing build." From ef61c70a9dc36969678691afed6b9dbedb33963c Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 2 Jul 2018 14:39:32 -0400 Subject: [PATCH 16/20] Fixes 2064: Disable calls to online swagger validator --- netbox/netbox/settings.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 9e3110e3b..47bb32669 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -268,7 +268,8 @@ SWAGGER_SETTINGS = { 'utilities.custom_inspectors.NullablePaginatorInspector', 'drf_yasg.inspectors.DjangoRestResponsePagination', 'drf_yasg.inspectors.CoreAPICompatInspector', - ] + ], + 'VALIDATOR_URL': None, } From 698c0decb4b9b42ec846ca5afe43885d54cc58a2 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 2 Jul 2018 15:25:49 -0400 Subject: [PATCH 17/20] Fixes #2021: Fix recursion error when viewing API docs under Python 3.4 --- netbox/netbox/urls.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/netbox/netbox/urls.py b/netbox/netbox/urls.py index 5f7b26a71..4907555ed 100644 --- a/netbox/netbox/urls.py +++ b/netbox/netbox/urls.py @@ -52,9 +52,9 @@ _patterns = [ url(r'^api/secrets/', include('secrets.api.urls')), url(r'^api/tenancy/', include('tenancy.api.urls')), url(r'^api/virtualization/', include('virtualization.api.urls')), - url(r'^api/docs/$', schema_view.with_ui('swagger', cache_timeout=None), name='api_docs'), - url(r'^api/redoc/$', schema_view.with_ui('redoc', cache_timeout=None), name='api_redocs'), - url(r'^api/swagger(?P.json|.yaml)$', schema_view.without_ui(cache_timeout=None), name='schema_swagger'), + url(r'^api/docs/$', schema_view.with_ui('swagger'), name='api_docs'), + url(r'^api/redoc/$', schema_view.with_ui('redoc'), name='api_redocs'), + url(r'^api/swagger(?P.json|.yaml)$', schema_view.without_ui(), name='schema_swagger'), # Serving static media in Django to pipe it through LoginRequiredMiddleware url(r'^media/(?P.*)$', serve, {'document_root': settings.MEDIA_ROOT}), From 398041c607daf88c71149a7471360526be840a32 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 2 Jul 2018 15:54:09 -0400 Subject: [PATCH 18/20] Release v2.3.5 --- netbox/netbox/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index f3555b5e7..5e4e8c76f 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -22,7 +22,7 @@ if sys.version_info[0] < 3: DeprecationWarning ) -VERSION = '2.3.5-dev' +VERSION = '2.3.5' BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) From 302c14186a2020fa2a241cb0b0bbe1fd9dc0a367 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 2 Jul 2018 15:55:46 -0400 Subject: [PATCH 19/20] Post-release version bump --- netbox/netbox/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 5e4e8c76f..71c41063c 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -22,7 +22,7 @@ if sys.version_info[0] < 3: DeprecationWarning ) -VERSION = '2.3.5' +VERSION = '2.3.6-dev' BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) From b10635a9b18a77af028128f2272fa2f17f020d6f Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 2 Jul 2018 16:39:38 -0400 Subject: [PATCH 20/20] Added housekeeping as an issue category --- .github/ISSUE_TEMPLATE.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index fb2863947..59746cacd 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -21,6 +21,7 @@ [ ] Feature request [ ] Bug report [ ] Documentation +[ ] Housekeeping ### Environment * Python version: -* NetBox version: +* NetBox version: