diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 4e9185a89..7f3d0935a 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -6,6 +6,8 @@ be able to accept. Please indicate the relevant feature request or bug report below. + IF YOUR PULL REQUEST DOES NOT REFERENCE AN ACCEPTED BUG REPORT OR + FEATURE REQUEST, IT WILL BE MARKED AS INVALID AND CLOSED. --> ### Fixes: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4820e5a85..546e1de09 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -91,11 +91,13 @@ appropriate labels will be applied for categorization. ## Submitting Pull Requests -* Be sure to open an issue before starting work on a pull request, and discuss -your idea with the NetBox maintainers before beginning work​. This will help -prevent wasting time on something that might we might not be able to implement. -When suggesting a new feature, also make sure it won't conflict with any work -that's already in progress. +* Be sure to open an issue **before** starting work on a pull request, and +discuss your idea with the NetBox maintainers before beginning work. This will +help prevent wasting time on something that might we might not be able to +implement. When suggesting a new feature, also make sure it won't conflict with +any work that's already in progress. + +* Any pull request which does _not_ relate to an accepted issue will be closed. * When submitting a pull request, please be sure to work off of the `develop` branch, rather than `master`. The `develop` branch is used for ongoing diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index e03a0693f..901d9d2a5 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -260,7 +260,7 @@ class DeviceViewSet(CustomFieldModelViewSet): import napalm except ImportError: raise ServiceUnavailable("NAPALM is not installed. Please see the documentation for instructions.") - from napalm.base.exceptions import ConnectAuthError, ModuleImportError + from napalm.base.exceptions import ModuleImportError # Validate the configured driver try: @@ -274,16 +274,8 @@ class DeviceViewSet(CustomFieldModelViewSet): if not request.user.has_perm('dcim.napalm_read'): return HttpResponseForbidden() - # Validate requested NAPALM methods + # Connect to the device napalm_methods = request.GET.getlist('method') - for method in napalm_methods: - if not hasattr(driver, method): - return HttpResponseBadRequest("Unknown NAPALM method: {}".format(method)) - elif not method.startswith('get_'): - return HttpResponseBadRequest("Unsupported NAPALM method: {}".format(method)) - - # Connect to the device and execute the requested methods - # TODO: Improve error handling response = OrderedDict([(m, None) for m in napalm_methods]) ip_address = str(device.primary_ip.address.ip) optional_args = settings.NAPALM_ARGS.copy() @@ -298,12 +290,23 @@ class DeviceViewSet(CustomFieldModelViewSet): ) try: d.open() - for method in napalm_methods: - response[method] = getattr(d, method)() except Exception as e: raise ServiceUnavailable("Error connecting to the device at {}: {}".format(ip_address, e)) + # Validate and execute each specified NAPALM method + for method in napalm_methods: + if not hasattr(driver, method): + response[method] = {'error': 'Unknown NAPALM method'} + continue + if not method.startswith('get_'): + response[method] = {'error': 'Only get_* NAPALM methods are supported'} + continue + try: + response[method] = getattr(d, method)() + except NotImplementedError: + response[method] = {'error': 'Method not implemented for NAPALM driver {}'.format(driver)} d.close() + return Response(response) diff --git a/netbox/dcim/filters.py b/netbox/dcim/filters.py index 1162f66a5..18a0039e6 100644 --- a/netbox/dcim/filters.py +++ b/netbox/dcim/filters.py @@ -521,7 +521,7 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet): Q(name__icontains=value) | Q(serial__icontains=value.strip()) | Q(inventory_items__serial__icontains=value.strip()) | - Q(asset_tag=value.strip()) | + Q(asset_tag__icontains=value.strip()) | Q(comments__icontains=value) ).distinct() diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index 9a1f71847..72d95eec1 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -1085,9 +1085,6 @@ class DeviceRole(ChangeLoggedModel): def __str__(self): return self.name - def get_absolute_url(self): - return "{}?role={}".format(reverse('dcim:device_list'), self.slug) - def to_csv(self): return ( self.name, diff --git a/netbox/dcim/signals.py b/netbox/dcim/signals.py index c29a8a857..80e47391a 100644 --- a/netbox/dcim/signals.py +++ b/netbox/dcim/signals.py @@ -11,13 +11,8 @@ def assign_virtualchassis_master(instance, created, **kwargs): """ When a VirtualChassis is created, automatically assign its master device to the VC. """ - # Default to 1 but don't overwrite an existing position (see #2087) - if instance.master.vc_position is not None: - vc_position = instance.master.vc_position - else: - vc_position = 1 if created: - Device.objects.filter(pk=instance.master.pk).update(virtual_chassis=instance, vc_position=vc_position) + Device.objects.filter(pk=instance.master.pk).update(virtual_chassis=instance, vc_position=None) @receiver(pre_delete, sender=VirtualChassis) diff --git a/netbox/dcim/tables.py b/netbox/dcim/tables.py index 22a781787..fc9105774 100644 --- a/netbox/dcim/tables.py +++ b/netbox/dcim/tables.py @@ -432,7 +432,6 @@ class DeviceBayTemplateTable(BaseTable): class DeviceRoleTable(BaseTable): pk = ToggleColumn() - name = tables.LinkColumn(verbose_name='Name') device_count = tables.TemplateColumn( template_code=DEVICEROLE_DEVICE_COUNT, accessor=Accessor('devices.count'), diff --git a/netbox/ipam/api/views.py b/netbox/ipam/api/views.py index cbd5da341..30a987f74 100644 --- a/netbox/ipam/api/views.py +++ b/netbox/ipam/api/views.py @@ -193,8 +193,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) + prefix_length = prefix.prefix.prefixlen for requested_ip in requested_ips: - requested_ip['address'] = next(available_ips) + requested_ip['address'] = '{}/{}'.format(next(available_ips), prefix_length) 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 diff --git a/netbox/secrets/forms.py b/netbox/secrets/forms.py index 566abc0ee..59e637a18 100644 --- a/netbox/secrets/forms.py +++ b/netbox/secrets/forms.py @@ -157,7 +157,8 @@ class UserKeyForm(BootstrapMixin, forms.ModelForm): model = UserKey fields = ['public_key'] help_texts = { - 'public_key': "Enter your public RSA key. Keep the private one with you; you'll need it for decryption.", + 'public_key': "Enter your public RSA key. Keep the private one with you; you'll need it for decryption. " + "Please note that passphrase-protected keys are not supported.", } def clean_public_key(self): diff --git a/netbox/templates/dcim/device.html b/netbox/templates/dcim/device.html index 348fc9ef2..03b55eb25 100644 --- a/netbox/templates/dcim/device.html +++ b/netbox/templates/dcim/device.html @@ -219,7 +219,7 @@ Role - {{ device.device_role }} + {{ device.device_role }} diff --git a/netbox/templates/ipam/ipaddress.html b/netbox/templates/ipam/ipaddress.html index 27f1e96ef..663a6e55f 100644 --- a/netbox/templates/ipam/ipaddress.html +++ b/netbox/templates/ipam/ipaddress.html @@ -77,10 +77,11 @@ Tenant {% if ipaddress.tenant %} + {% if ipaddress.tenant.group %} + {{ ipaddress.tenant.group }} + + {% endif %} {{ ipaddress.tenant }} - {% elif ipaddress.vrf.tenant %} - {{ ipaddress.vrf.tenant }} - {% else %} None {% endif %} diff --git a/netbox/templates/ipam/prefix.html b/netbox/templates/ipam/prefix.html index 3a35336a0..bd6f61c48 100644 --- a/netbox/templates/ipam/prefix.html +++ b/netbox/templates/ipam/prefix.html @@ -99,13 +99,6 @@ {% endif %} {{ prefix.tenant }} - {% elif prefix.vrf.tenant %} - {% if prefix.vrf.tenant.group %} - {{ prefix.vrf.tenant.group }} - - {% endif %} - {{ prefix.vrf.tenant }} - {% else %} None {% endif %} diff --git a/netbox/tenancy/views.py b/netbox/tenancy/views.py index 95b68d4fa..6a56974a4 100644 --- a/netbox/tenancy/views.py +++ b/netbox/tenancy/views.py @@ -75,14 +75,8 @@ class TenantView(View): 'rackreservation_count': RackReservation.objects.filter(tenant=tenant).count(), 'device_count': Device.objects.filter(tenant=tenant).count(), 'vrf_count': VRF.objects.filter(tenant=tenant).count(), - 'prefix_count': Prefix.objects.filter( - Q(tenant=tenant) | - Q(tenant__isnull=True, vrf__tenant=tenant) - ).count(), - 'ipaddress_count': IPAddress.objects.filter( - Q(tenant=tenant) | - Q(tenant__isnull=True, vrf__tenant=tenant) - ).count(), + 'prefix_count': Prefix.objects.filter(tenant=tenant).count(), + 'ipaddress_count': IPAddress.objects.filter(tenant=tenant).count(), 'vlan_count': VLAN.objects.filter(tenant=tenant).count(), 'circuit_count': Circuit.objects.filter(tenant=tenant).count(), 'virtualmachine_count': VirtualMachine.objects.filter(tenant=tenant).count(),