From e56fc4b1ee88491328416d5bf68f177dea02fbb8 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 31 Oct 2017 15:25:59 -0400 Subject: [PATCH 01/25] 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 64ea556bc..0283731fe 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -13,7 +13,7 @@ except ImportError: ) -VERSION = '2.2.4' +VERSION = '2.2.5-dev' BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) From b8df05cf88b21f1165b43160329a3a617ffb2a11 Mon Sep 17 00:00:00 2001 From: Ichabond Date: Thu, 2 Nov 2017 08:51:27 -0700 Subject: [PATCH 02/25] Fixes #1655. Removed explicit field references. (#1656) * Fixes #1655 Further field name references were found in `consoleport.html`. These have now been removed, so we rely on proper a proper `__str__` implementation of both `ConsolePort` and `ConsoleServerPort`. * Fixes #1655: Removed explicit field references Cleaned up all (notable) .name references, and removed them so __str__ can do the lifting. Did not remove the references where it was explicitly referenced to .name (eg. in details). Extended the Secret model to also include the name in __str__, since that was weirdly absent. * Adapted PR to comply with comments Re-introduced certain references to make sure explicit references are still used where needed. --- netbox/secrets/models.py | 3 +++ netbox/templates/circuits/circuit.html | 2 +- netbox/templates/dcim/device.html | 8 ++++---- netbox/templates/dcim/devicebay_populate.html | 2 +- netbox/templates/dcim/inc/consoleport.html | 4 ++-- netbox/templates/dcim/inc/consoleserverport.html | 2 +- netbox/templates/dcim/inc/device_header.html | 2 +- netbox/templates/dcim/inc/devicebay.html | 2 +- netbox/templates/dcim/inc/interface.html | 4 ++-- netbox/templates/dcim/inc/inventoryitem.html | 2 +- netbox/templates/dcim/inc/poweroutlet.html | 4 ++-- netbox/templates/dcim/inc/powerport.html | 4 ++-- netbox/templates/dcim/rack.html | 8 ++++---- netbox/templates/dcim/site.html | 4 ++-- netbox/templates/ipam/prefix.html | 6 +++--- netbox/templates/ipam/vlan.html | 8 ++++---- netbox/templates/virtualization/inc/interface.html | 2 +- netbox/templates/virtualization/virtualmachine.html | 4 ++-- 18 files changed, 37 insertions(+), 34 deletions(-) diff --git a/netbox/secrets/models.py b/netbox/secrets/models.py index ded93ed58..317fe0dd5 100644 --- a/netbox/secrets/models.py +++ b/netbox/secrets/models.py @@ -286,6 +286,9 @@ class Secret(CreatedUpdatedModel): super(Secret, self).__init__(*args, **kwargs) def __str__(self): + if self.role and self.device and self.name: + return '{} for {} ({})'.format(self.role, self.device, self.name) + # Return role and device if no name is set if self.role and self.device: return '{} for {}'.format(self.role, self.device) return 'Secret' diff --git a/netbox/templates/circuits/circuit.html b/netbox/templates/circuits/circuit.html index ce375daef..027efa83c 100644 --- a/netbox/templates/circuits/circuit.html +++ b/netbox/templates/circuits/circuit.html @@ -65,7 +65,7 @@ {% if circuit.tenant %} {% if circuit.tenant.group %} - {{ circuit.tenant.group.name }} + {{ circuit.tenant.group }} {% endif %} {{ circuit.tenant }} diff --git a/netbox/templates/dcim/device.html b/netbox/templates/dcim/device.html index 9625a002c..056396991 100644 --- a/netbox/templates/dcim/device.html +++ b/netbox/templates/dcim/device.html @@ -28,10 +28,10 @@ {% if device.rack %} {% if device.rack.group %} - {{ device.rack.group.name }} + {{ device.rack.group }} {% endif %} - {{ device.rack.name }}{% if device.rack.facility_id %} ({{ device.rack.facility_id }}){% endif %} + {{ device.rack }} {% else %} None {% endif %} @@ -42,7 +42,7 @@ {% if device.parent_bay %} {% with device.parent_bay.device as parent %} - {{ parent }} {{ device.parent_bay.name }} + {{ parent }} {{ device.parent_bay }} {% if parent.position %} (U{{ parent.position }} / {{ parent.get_face_display }}) {% endif %} @@ -61,7 +61,7 @@ {% if device.tenant %} {% if device.tenant.group %} - {{ device.tenant.group.name }} + {{ device.tenant.group }} {% endif %} {{ device.tenant }} diff --git a/netbox/templates/dcim/devicebay_populate.html b/netbox/templates/dcim/devicebay_populate.html index d328d557d..38a8d57da 100644 --- a/netbox/templates/dcim/devicebay_populate.html +++ b/netbox/templates/dcim/devicebay_populate.html @@ -26,7 +26,7 @@
-

{{ device_bay.name }}

+

{{ device_bay }}

{% render_form form %} diff --git a/netbox/templates/dcim/inc/consoleport.html b/netbox/templates/dcim/inc/consoleport.html index 34c08af94..62375c7f2 100644 --- a/netbox/templates/dcim/inc/consoleport.html +++ b/netbox/templates/dcim/inc/consoleport.html @@ -1,13 +1,13 @@ - {{ cp.name }} + {{ cp }} {% if cp.cs_port %} {{ cp.cs_port.device }} - {{ cp.cs_port.name }} + {{ cp.cs_port }} {% else %} diff --git a/netbox/templates/dcim/inc/consoleserverport.html b/netbox/templates/dcim/inc/consoleserverport.html index 69dbfc0b8..62da09245 100644 --- a/netbox/templates/dcim/inc/consoleserverport.html +++ b/netbox/templates/dcim/inc/consoleserverport.html @@ -13,7 +13,7 @@ {{ csp.connected_console.device }} - {{ csp.connected_console.name }} + {{ csp.connected_console }} {% else %} diff --git a/netbox/templates/dcim/inc/device_header.html b/netbox/templates/dcim/inc/device_header.html index 37c5e715a..73b5845ef 100644 --- a/netbox/templates/dcim/inc/device_header.html +++ b/netbox/templates/dcim/inc/device_header.html @@ -8,7 +8,7 @@ {% endif %} {% if device.parent_bay %}
  • {{ device.parent_bay.device }}
  • -
  • {{ device.parent_bay.name }}
  • +
  • {{ device.parent_bay }}
  • {% endif %}
  • {{ device }}
  • diff --git a/netbox/templates/dcim/inc/devicebay.html b/netbox/templates/dcim/inc/devicebay.html index f69299bfb..f974f9fcb 100644 --- a/netbox/templates/dcim/inc/devicebay.html +++ b/netbox/templates/dcim/inc/devicebay.html @@ -5,7 +5,7 @@ {% endif %} - {{ devicebay.name }} + {{ devicebay }} {% if devicebay.installed_device %} diff --git a/netbox/templates/dcim/inc/interface.html b/netbox/templates/dcim/inc/interface.html index 7e133da8d..c6d36dbef 100644 --- a/netbox/templates/dcim/inc/interface.html +++ b/netbox/templates/dcim/inc/interface.html @@ -6,9 +6,9 @@ {% endif %} - {{ iface.name }} + {{ iface }} {% if iface.lag %} - {{ iface.lag.name }} + {{ iface.lag }} {% endif %} {% if iface.description %} diff --git a/netbox/templates/dcim/inc/inventoryitem.html b/netbox/templates/dcim/inc/inventoryitem.html index 8bc3149b1..21de1014e 100644 --- a/netbox/templates/dcim/inc/inventoryitem.html +++ b/netbox/templates/dcim/inc/inventoryitem.html @@ -1,5 +1,5 @@ - {{ item.name }} + {{ item }} {% if not item.discovered %}{% endif %} {{ item.manufacturer|default:"" }} {{ item.part_id }} diff --git a/netbox/templates/dcim/inc/poweroutlet.html b/netbox/templates/dcim/inc/poweroutlet.html index 061b3c682..b462e1ca1 100644 --- a/netbox/templates/dcim/inc/poweroutlet.html +++ b/netbox/templates/dcim/inc/poweroutlet.html @@ -5,7 +5,7 @@ {% endif %} - {{ po.name }} + {{ po }} {% if po.connected_port %} @@ -13,7 +13,7 @@ {{ po.connected_port.device }} - {{ po.connected_port.name }} + {{ po.connected_port }} {% else %} diff --git a/netbox/templates/dcim/inc/powerport.html b/netbox/templates/dcim/inc/powerport.html index 06ec9da75..555d6d3ee 100644 --- a/netbox/templates/dcim/inc/powerport.html +++ b/netbox/templates/dcim/inc/powerport.html @@ -1,13 +1,13 @@ - {{ pp.name }} + {{ pp }} {% if pp.power_outlet %} {{ pp.power_outlet.device }} - {{ pp.power_outlet.name }} + {{ pp.power_outlet }} {% else %} diff --git a/netbox/templates/dcim/rack.html b/netbox/templates/dcim/rack.html index 4463ee21b..05585348f 100644 --- a/netbox/templates/dcim/rack.html +++ b/netbox/templates/dcim/rack.html @@ -49,7 +49,7 @@ {% endif %} -

    {% block title %}Rack {{ rack.name }}{% endblock %}

    +

    {% block title %}Rack {{ rack }}{% endblock %}

    {% include 'inc/created_updated.html' with obj=rack %}
    @@ -72,7 +72,7 @@ Group {% if rack.group %} - {{ rack.group.name }} + {{ rack.group }} {% else %} None {% endif %} @@ -93,7 +93,7 @@ {% if rack.tenant %} {% if rack.tenant.group %} - {{ rack.tenant.group.name }} + {{ rack.tenant.group }} {% endif %} {{ rack.tenant }} @@ -173,7 +173,7 @@ {% for device in nonracked_devices %} - {{ device.name }} + {{ device }} {{ device.device_role }} {{ device.device_type.full_name }} diff --git a/netbox/templates/dcim/site.html b/netbox/templates/dcim/site.html index c178fbf64..228eec4c8 100644 --- a/netbox/templates/dcim/site.html +++ b/netbox/templates/dcim/site.html @@ -76,7 +76,7 @@ {% if site.tenant %} {% if site.tenant.group %} - {{ site.tenant.group.name }} + {{ site.tenant.group }} {% endif %} {{ site.tenant }} @@ -221,7 +221,7 @@ {% for rg in rack_groups %} - + {% endfor %} diff --git a/netbox/templates/ipam/prefix.html b/netbox/templates/ipam/prefix.html index 23a996d82..25780ba5e 100644 --- a/netbox/templates/ipam/prefix.html +++ b/netbox/templates/ipam/prefix.html @@ -30,13 +30,13 @@ {% endif %} {# VRF #} From d306e7642080a5239351bee49ead9ed43f0a2144 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 6 Nov 2017 10:07:44 -0500 Subject: [PATCH 12/25] Fixes #1689: Disregard IP address mask when filtering for child IPs of a prefix --- netbox/ipam/models.py | 6 ++---- netbox/ipam/views.py | 8 ++------ 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/netbox/ipam/models.py b/netbox/ipam/models.py index 5a9a16bb1..4d16913cf 100644 --- a/netbox/ipam/models.py +++ b/netbox/ipam/models.py @@ -285,7 +285,7 @@ class Prefix(CreatedUpdatedModel, CustomFieldModel): """ Return all IPAddresses within this Prefix. """ - return IPAddress.objects.filter(address__net_contained_or_equal=str(self.prefix), vrf=self.vrf) + return IPAddress.objects.filter(address__net_host_contained=self.prefix, vrf=self.vrf) def get_available_ips(self): """ @@ -314,9 +314,7 @@ class Prefix(CreatedUpdatedModel, CustomFieldModel): child_prefixes = netaddr.IPSet([p.prefix for p in queryset]) return int(float(child_prefixes.size) / self.prefix.size * 100) else: - child_count = IPAddress.objects.filter( - address__net_contained_or_equal=str(self.prefix), vrf=self.vrf - ).count() + child_count = self.get_child_ips().count() prefix_size = self.prefix.size if self.family == 4 and self.prefix.prefixlen < 31 and not self.is_pool: prefix_size -= 2 diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 684aa4fdc..6f24a675f 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -459,9 +459,7 @@ class PrefixView(View): aggregate = None # Count child IP addresses - ipaddress_count = IPAddress.objects.filter( - vrf=prefix.vrf, address__net_host_contained=str(prefix.prefix) - ).count() + ipaddress_count = prefix.get_child_ips().count() # Parent prefixes table parent_prefixes = Prefix.objects.filter( @@ -530,9 +528,7 @@ class PrefixIPAddressesView(View): prefix = get_object_or_404(Prefix.objects.all(), pk=pk) # Find all IPAddresses belonging to this Prefix - ipaddresses = IPAddress.objects.filter( - vrf=prefix.vrf, address__net_host_contained=str(prefix.prefix) - ).select_related( + ipaddresses = prefix.get_child_ips().select_related( 'vrf', 'interface__device', 'primary_ip4_for', 'primary_ip6_for' ) ipaddresses = add_available_ipaddresses(prefix.prefix, ipaddresses, prefix.is_pool) From 6b0721cc21df90f56eeafae47b366e39e996deae Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 6 Nov 2017 17:24:09 -0500 Subject: [PATCH 13/25] Fixed PermissionError handling for Python 2 --- netbox/utilities/middleware.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/netbox/utilities/middleware.py b/netbox/utilities/middleware.py index b79ecd041..2909acfa7 100644 --- a/netbox/utilities/middleware.py +++ b/netbox/utilities/middleware.py @@ -66,7 +66,11 @@ class ExceptionHandlingMiddleware(object): template_name = 'exceptions/programming_error.html' elif isinstance(exception, ImportError): template_name = 'exceptions/import_error.html' - elif isinstance(exception, PermissionError): + elif ( + sys.version_info[0] >= 3 and isinstance(exception, PermissionError) + ) or ( + isinstance(exception, OSError) and exception.errno == 13 + ): template_name = 'exceptions/permission_error.html' else: template_name = '500.html' From c33775d71ef4fff7a5c6c4817fdaef0d50626086 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 6 Nov 2017 17:44:19 -0500 Subject: [PATCH 14/25] #1689: Fix for Python 2 --- netbox/ipam/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/ipam/models.py b/netbox/ipam/models.py index 4d16913cf..b84ac23c3 100644 --- a/netbox/ipam/models.py +++ b/netbox/ipam/models.py @@ -285,7 +285,7 @@ class Prefix(CreatedUpdatedModel, CustomFieldModel): """ Return all IPAddresses within this Prefix. """ - return IPAddress.objects.filter(address__net_host_contained=self.prefix, vrf=self.vrf) + return IPAddress.objects.filter(address__net_host_contained=str(self.prefix), vrf=self.vrf) def get_available_ips(self): """ From 2519ebff9d3d5904895bca3e0dd14db3eb1e5bde Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 6 Nov 2017 17:48:13 -0500 Subject: [PATCH 15/25] Tweaked exception-handling middleware to preserve tracebacks --- netbox/utilities/middleware.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/netbox/utilities/middleware.py b/netbox/utilities/middleware.py index 2909acfa7..927855e0e 100644 --- a/netbox/utilities/middleware.py +++ b/netbox/utilities/middleware.py @@ -57,9 +57,9 @@ class ExceptionHandlingMiddleware(object): def process_exception(self, request, exception): - # Raise exceptions if in debug mode + # Don't catch exceptions when in debug mode if settings.DEBUG: - raise exception + return # Determine the type of exception if isinstance(exception, ProgrammingError): From 00986fd7bf434321b24de871c39acd80c19c9599 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 7 Nov 2017 11:08:23 -0500 Subject: [PATCH 16/25] Closes #1691: Cleaned up and reorganized import statements --- netbox/circuits/api/views.py | 5 ++--- netbox/circuits/filters.py | 1 - netbox/circuits/forms.py | 1 - netbox/circuits/models.py | 2 +- netbox/circuits/tables.py | 4 +--- netbox/circuits/tests/test_api.py | 11 ++++++----- netbox/circuits/urls.py | 1 - netbox/circuits/views.py | 3 ++- netbox/dcim/api/serializers.py | 16 ++++++++++------ netbox/dcim/api/views.py | 16 +++++++++------- netbox/dcim/filters.py | 15 ++++++++------- netbox/dcim/formfields.py | 3 +-- netbox/dcim/forms.py | 18 ++++++++++-------- netbox/dcim/models.py | 4 ++-- netbox/dcim/tables.py | 1 - netbox/dcim/tests/test_api.py | 12 ++++++------ netbox/dcim/urls.py | 3 +-- netbox/dcim/views.py | 14 ++++++++------ netbox/extras/api/customfields.py | 11 +++++------ netbox/extras/api/serializers.py | 6 ++---- netbox/extras/api/views.py | 9 ++++----- netbox/extras/filters.py | 4 ++-- netbox/extras/forms.py | 7 +++---- netbox/extras/management/commands/nbshell.py | 1 - .../management/commands/run_inventory.py | 4 ++-- netbox/extras/management/commands/runreport.py | 1 - netbox/extras/models.py | 3 ++- netbox/extras/reports.py | 3 ++- netbox/extras/rpc.py | 4 ++-- netbox/extras/tests/test_api.py | 8 ++++---- netbox/extras/tests/test_customfields.py | 12 +++++------- netbox/extras/urls.py | 1 - netbox/extras/views.py | 2 +- netbox/ipam/api/serializers.py | 7 ++++--- netbox/ipam/api/views.py | 9 ++++----- netbox/ipam/fields.py | 3 +-- netbox/ipam/filters.py | 9 +++------ netbox/ipam/formfields.py | 3 +-- netbox/ipam/forms.py | 7 ++----- netbox/ipam/models.py | 2 +- netbox/ipam/tables.py | 1 - netbox/ipam/tests/test_api.py | 10 ++++------ netbox/ipam/tests/test_models.py | 1 - netbox/ipam/views.py | 10 +++------- netbox/netbox/forms.py | 1 - netbox/netbox/urls.py | 4 +--- netbox/netbox/views.py | 10 ++++------ netbox/netbox/wsgi.py | 9 --------- netbox/secrets/api/views.py | 5 ++--- netbox/secrets/filters.py | 3 +-- netbox/secrets/forms.py | 1 - netbox/secrets/models.py | 2 +- netbox/secrets/tables.py | 2 -- netbox/secrets/tests/test_api.py | 7 +++---- netbox/secrets/tests/test_models.py | 3 +-- netbox/secrets/urls.py | 1 - netbox/secrets/views.py | 1 + netbox/tenancy/filters.py | 1 - netbox/tenancy/tables.py | 2 -- netbox/tenancy/tests/test_api.py | 5 ++--- netbox/tenancy/urls.py | 1 - netbox/tenancy/views.py | 2 +- netbox/users/api/serializers.py | 1 - netbox/users/forms.py | 2 +- netbox/users/models.py | 3 ++- netbox/users/urls.py | 1 - netbox/utilities/api.py | 3 +-- netbox/utilities/fields.py | 1 - netbox/utilities/filters.py | 2 +- netbox/utilities/forms.py | 5 ++--- netbox/utilities/middleware.py | 2 +- netbox/utilities/tables.py | 1 - netbox/utilities/templatetags/helpers.py | 4 +--- netbox/utilities/utils.py | 1 + netbox/utilities/validators.py | 1 + netbox/utilities/views.py | 4 ++-- netbox/virtualization/constants.py | 1 - netbox/virtualization/filters.py | 3 +-- netbox/virtualization/forms.py | 4 +--- netbox/virtualization/tables.py | 1 - netbox/virtualization/tests/test_api.py | 5 ++--- netbox/virtualization/urls.py | 1 - netbox/virtualization/views.py | 4 +--- 83 files changed, 159 insertions(+), 219 deletions(-) diff --git a/netbox/circuits/api/views.py b/netbox/circuits/api/views.py index 9defb5060..666f67502 100644 --- a/netbox/circuits/api/views.py +++ b/netbox/circuits/api/views.py @@ -1,16 +1,15 @@ from __future__ import unicode_literals +from django.shortcuts import get_object_or_404 from rest_framework.decorators import detail_route from rest_framework.response import Response from rest_framework.viewsets import ModelViewSet -from django.shortcuts import get_object_or_404 - from circuits import filters from circuits.models import Provider, CircuitTermination, CircuitType, Circuit -from extras.models import Graph, GRAPH_TYPE_PROVIDER from extras.api.serializers import RenderedGraphSerializer from extras.api.views import CustomFieldModelViewSet +from extras.models import Graph, GRAPH_TYPE_PROVIDER from utilities.api import FieldChoicesViewSet, WritableSerializerMixin from . import serializers diff --git a/netbox/circuits/filters.py b/netbox/circuits/filters.py index e2d5321cd..d4b84fc60 100644 --- a/netbox/circuits/filters.py +++ b/netbox/circuits/filters.py @@ -1,7 +1,6 @@ from __future__ import unicode_literals import django_filters - from django.db.models import Q from dcim.models import Site diff --git a/netbox/circuits/forms.py b/netbox/circuits/forms.py index 1e60cad57..26d38d56a 100644 --- a/netbox/circuits/forms.py +++ b/netbox/circuits/forms.py @@ -11,7 +11,6 @@ from utilities.forms import ( APISelect, BootstrapMixin, ChainedFieldsMixin, ChainedModelChoiceField, CommentField, FilterChoiceField, SmallTextarea, SlugField, ) - from .models import Circuit, CircuitTermination, CircuitType, Provider diff --git a/netbox/circuits/models.py b/netbox/circuits/models.py index 17b38a5d8..fd8a2b2f6 100644 --- a/netbox/circuits/models.py +++ b/netbox/circuits/models.py @@ -8,8 +8,8 @@ from django.utils.encoding import python_2_unicode_compatible from dcim.fields import ASNField from extras.models import CustomFieldModel, CustomFieldValue from tenancy.models import Tenant -from utilities.utils import csv_format from utilities.models import CreatedUpdatedModel +from utilities.utils import csv_format from .constants import * diff --git a/netbox/circuits/tables.py b/netbox/circuits/tables.py index bf628e6f8..10f776ea3 100644 --- a/netbox/circuits/tables.py +++ b/netbox/circuits/tables.py @@ -1,14 +1,12 @@ from __future__ import unicode_literals import django_tables2 as tables -from django_tables2.utils import Accessor - from django.utils.safestring import mark_safe +from django_tables2.utils import Accessor from utilities.tables import BaseTable, ToggleColumn from .models import Circuit, CircuitType, Provider - CIRCUITTYPE_ACTIONS = """ {% if perms.circuit.change_circuittype %} diff --git a/netbox/circuits/tests/test_api.py b/netbox/circuits/tests/test_api.py index fc39b72de..0e588fe16 100644 --- a/netbox/circuits/tests/test_api.py +++ b/netbox/circuits/tests/test_api.py @@ -1,14 +1,15 @@ from __future__ import unicode_literals -from rest_framework import status -from rest_framework.test import APITestCase - from django.contrib.auth.models import User from django.urls import reverse +from rest_framework import status +from rest_framework.test import APITestCase +from circuits.constants import TERM_SIDE_A, TERM_SIDE_Z +from circuits.models import Circuit, CircuitTermination, CircuitType, Provider from dcim.models import Site -from extras.models import Graph, GRAPH_TYPE_PROVIDER -from circuits.models import Circuit, CircuitTermination, CircuitType, Provider, TERM_SIDE_A, TERM_SIDE_Z +from extras.constants import GRAPH_TYPE_PROVIDER +from extras.models import Graph from users.models import Token from utilities.tests import HttpStatusMixin diff --git a/netbox/circuits/urls.py b/netbox/circuits/urls.py index 636ce5205..569c1eb9a 100644 --- a/netbox/circuits/urls.py +++ b/netbox/circuits/urls.py @@ -4,7 +4,6 @@ from django.conf.urls import url from . import views - app_name = 'circuits' urlpatterns = [ diff --git a/netbox/circuits/views.py b/netbox/circuits/views.py index d8910ba29..87747f36f 100644 --- a/netbox/circuits/views.py +++ b/netbox/circuits/views.py @@ -15,7 +15,8 @@ from utilities.views import ( BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView, ) from . import filters, forms, tables -from .models import Circuit, CircuitTermination, CircuitType, Provider, TERM_SIDE_A, TERM_SIDE_Z +from .constants import TERM_SIDE_A, TERM_SIDE_Z +from .models import Circuit, CircuitTermination, CircuitType, Provider # diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index f92c1f64e..8f6b3ada8 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -1,19 +1,23 @@ from __future__ import unicode_literals + from collections import OrderedDict from rest_framework import serializers from rest_framework.validators import UniqueTogetherValidator -from ipam.models import IPAddress from circuits.models import Circuit, CircuitTermination +from dcim.constants import ( + CONNECTION_STATUS_CHOICES, IFACE_FF_CHOICES, IFACE_ORDERING_CHOICES, RACK_FACE_CHOICES, RACK_TYPE_CHOICES, + RACK_WIDTH_CHOICES, STATUS_CHOICES, SUBDEVICE_ROLE_CHOICES, +) from dcim.models import ( - CONNECTION_STATUS_CHOICES, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, - DeviceBay, DeviceBayTemplate, DeviceType, DeviceRole, IFACE_FF_CHOICES, IFACE_ORDERING_CHOICES, Interface, - InterfaceConnection, InterfaceTemplate, Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, - PowerPort, PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RACK_FACE_CHOICES, RACK_TYPE_CHOICES, - RACK_WIDTH_CHOICES, Region, Site, STATUS_CHOICES, SUBDEVICE_ROLE_CHOICES, + ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, + DeviceBayTemplate, DeviceType, DeviceRole, Interface, InterfaceConnection, InterfaceTemplate, Manufacturer, + InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, + RackReservation, RackRole, Region, Site, ) from extras.api.customfields import CustomFieldModelSerializer +from ipam.models import IPAddress from tenancy.api.serializers import NestedTenantSerializer from utilities.api import ChoiceFieldSerializer, ValidatedModelSerializer from virtualization.models import Cluster diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index af41a2a83..1df0f9cbc 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -1,28 +1,30 @@ from __future__ import unicode_literals + from collections import OrderedDict +from django.conf import settings +from django.http import HttpResponseBadRequest, HttpResponseForbidden +from django.shortcuts import get_object_or_404 from rest_framework.decorators import detail_route from rest_framework.mixins import ListModelMixin from rest_framework.response import Response from rest_framework.viewsets import GenericViewSet, ModelViewSet, ViewSet -from django.conf import settings -from django.http import HttpResponseBadRequest, HttpResponseForbidden -from django.shortcuts import get_object_or_404 - +from dcim import filters from dcim.models import ( ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceConnection, InterfaceTemplate, Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, Region, Site, ) -from dcim import filters from extras.api.serializers import RenderedGraphSerializer from extras.api.views import CustomFieldModelViewSet from extras.models import Graph, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE -from utilities.api import IsAuthenticatedOrLoginNotRequired, FieldChoicesViewSet, ServiceUnavailable, WritableSerializerMixin -from .exceptions import MissingFilterException +from utilities.api import ( + IsAuthenticatedOrLoginNotRequired, FieldChoicesViewSet, ServiceUnavailable, WritableSerializerMixin, +) from . import serializers +from .exceptions import MissingFilterException # diff --git a/netbox/dcim/filters.py b/netbox/dcim/filters.py index 6316cad4e..3fd4fdb7f 100644 --- a/netbox/dcim/filters.py +++ b/netbox/dcim/filters.py @@ -1,22 +1,23 @@ from __future__ import unicode_literals import django_filters -from netaddr import EUI -from netaddr.core import AddrFormatError - from django.contrib.auth.models import User from django.db.models import Q +from netaddr import EUI +from netaddr.core import AddrFormatError from extras.filters import CustomFieldFilterSet from tenancy.models import Tenant from utilities.filters import NullableCharFieldFilter, NumericInFilter from virtualization.models import Cluster +from .constants import ( + IFACE_FF_LAG, NONCONNECTABLE_IFACE_TYPES, STATUS_CHOICES, VIRTUAL_IFACE_TYPES, WIRELESS_IFACE_TYPES, +) from .models import ( ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, - DeviceBayTemplate, DeviceRole, DeviceType, STATUS_CHOICES, IFACE_FF_LAG, Interface, InterfaceConnection, - InterfaceTemplate, Manufacturer, InventoryItem, NONCONNECTABLE_IFACE_TYPES, Platform, PowerOutlet, - PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, Region, Site, - VIRTUAL_IFACE_TYPES, WIRELESS_IFACE_TYPES, + DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceConnection, InterfaceTemplate, Manufacturer, + InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, + RackReservation, RackRole, Region, Site, ) diff --git a/netbox/dcim/formfields.py b/netbox/dcim/formfields.py index 83054c088..804c2c956 100644 --- a/netbox/dcim/formfields.py +++ b/netbox/dcim/formfields.py @@ -1,9 +1,8 @@ from __future__ import unicode_literals -from netaddr import EUI, AddrFormatError - from django import forms from django.core.exceptions import ValidationError +from netaddr import EUI, AddrFormatError # diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index c4ec531f3..1f5d50c4d 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -1,12 +1,12 @@ from __future__ import unicode_literals -from mptt.forms import TreeNodeChoiceField import re from django import forms from django.contrib.auth.models import User from django.contrib.postgres.forms.array import SimpleArrayField from django.db.models import Count, Q +from mptt.forms import TreeNodeChoiceField from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm from ipam.models import IPAddress @@ -19,17 +19,19 @@ from utilities.forms import ( SlugField, FilterTreeNodeMultipleChoiceField, ) from virtualization.models import Cluster +from .constants import ( + CONNECTION_STATUS_CHOICES, CONNECTION_STATUS_CONNECTED, IFACE_FF_CHOICES, IFACE_FF_LAG, IFACE_ORDERING_CHOICES, + RACK_FACE_CHOICES, RACK_TYPE_CHOICES, RACK_WIDTH_CHOICES, RACK_WIDTH_19IN, RACK_WIDTH_23IN, STATUS_CHOICES, + SUBDEVICE_ROLE_CHILD, SUBDEVICE_ROLE_PARENT, SUBDEVICE_ROLE_CHOICES, +) from .formfields import MACAddressFormField from .models import ( - DeviceBay, DeviceBayTemplate, CONNECTION_STATUS_CHOICES, CONNECTION_STATUS_CONNECTED, ConsolePort, - ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceRole, DeviceType, Interface, - IFACE_FF_CHOICES, IFACE_FF_LAG, IFACE_ORDERING_CHOICES, InterfaceConnection, InterfaceTemplate, Manufacturer, - InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, RACK_FACE_CHOICES, - RACK_TYPE_CHOICES, RACK_WIDTH_CHOICES, Rack, RackGroup, RackReservation, RackRole, RACK_WIDTH_19IN, RACK_WIDTH_23IN, - Region, Site, STATUS_CHOICES, SUBDEVICE_ROLE_CHILD, SUBDEVICE_ROLE_PARENT, SUBDEVICE_ROLE_CHOICES, + DeviceBay, DeviceBayTemplate, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, + Device, DeviceRole, DeviceType, Interface, InterfaceConnection, InterfaceTemplate, Manufacturer, InventoryItem, + Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, RackReservation, + RackRole, Region, Site, ) - DEVICE_BY_PK_RE = '{\d+\}' diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index 0ff1434c0..67550e136 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -1,9 +1,8 @@ from __future__ import unicode_literals + from collections import OrderedDict from itertools import count, groupby -from mptt.models import MPTTModel, TreeForeignKey - from django.conf import settings from django.contrib.auth.models import User from django.contrib.contenttypes.fields import GenericRelation @@ -14,6 +13,7 @@ from django.db import models from django.db.models import Count, Q, ObjectDoesNotExist from django.urls import reverse from django.utils.encoding import python_2_unicode_compatible +from mptt.models import MPTTModel, TreeForeignKey from circuits.models import Circuit from extras.models import CustomFieldModel, CustomFieldValue, ImageAttachment diff --git a/netbox/dcim/tables.py b/netbox/dcim/tables.py index 5f41b9dfa..3d1e79360 100644 --- a/netbox/dcim/tables.py +++ b/netbox/dcim/tables.py @@ -10,7 +10,6 @@ from .models import ( PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, RackReservation, Region, Site, ) - REGION_LINK = """ {% if record.get_children %} diff --git a/netbox/dcim/tests/test_api.py b/netbox/dcim/tests/test_api.py index 9fe191cc7..bec333974 100644 --- a/netbox/dcim/tests/test_api.py +++ b/netbox/dcim/tests/test_api.py @@ -1,16 +1,16 @@ from __future__ import unicode_literals -from rest_framework import status -from rest_framework.test import APITestCase - from django.contrib.auth.models import User from django.urls import reverse +from rest_framework import status +from rest_framework.test import APITestCase +from dcim.constants import IFACE_FF_LAG, SUBDEVICE_ROLE_CHILD, SUBDEVICE_ROLE_PARENT from dcim.models import ( ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, - DeviceBayTemplate, DeviceRole, DeviceType, IFACE_FF_LAG, Interface, InterfaceConnection, InterfaceTemplate, - Manufacturer, InventoryItem, Platform, PowerPort, PowerPortTemplate, PowerOutlet, PowerOutletTemplate, Rack, RackGroup, - RackReservation, RackRole, Region, Site, SUBDEVICE_ROLE_CHILD, SUBDEVICE_ROLE_PARENT, + DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceConnection, InterfaceTemplate, Manufacturer, + InventoryItem, Platform, PowerPort, PowerPortTemplate, PowerOutlet, PowerOutletTemplate, Rack, RackGroup, + RackReservation, RackRole, Region, Site, ) from extras.models import Graph, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE from users.models import Token diff --git a/netbox/dcim/urls.py b/netbox/dcim/urls.py index 2d00f096d..a15774569 100644 --- a/netbox/dcim/urls.py +++ b/netbox/dcim/urls.py @@ -5,9 +5,8 @@ from django.conf.urls import url from extras.views import ImageAttachmentEditView from ipam.views import ServiceCreateView from secrets.views import secret_add -from .models import Device, Rack, Site from . import views - +from .models import Device, Rack, Site app_name = 'dcim' urlpatterns = [ diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index f0f3ffa35..674fa5b8f 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -1,5 +1,5 @@ from __future__ import unicode_literals -from natsort import natsorted + from operator import attrgetter from django.contrib import messages @@ -14,10 +14,11 @@ from django.utils.html import escape from django.utils.http import urlencode from django.utils.safestring import mark_safe from django.views.generic import View +from natsort import natsorted -from ipam.models import Prefix, Service, VLAN from circuits.models import Circuit from extras.models import Graph, TopologyMap, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE, UserAction +from ipam.models import Prefix, Service, VLAN from utilities.forms import ConfirmationForm from utilities.paginator import EnhancedPaginator from utilities.views import ( @@ -25,11 +26,12 @@ from utilities.views import ( ComponentEditView, ObjectDeleteView, ObjectEditView, ObjectListView, ) from . import filters, forms, tables +from .constants import CONNECTION_STATUS_CONNECTED from .models import ( - CONNECTION_STATUS_CONNECTED, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, - DeviceBay, DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceConnection, InterfaceTemplate, - Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, - RackGroup, RackReservation, RackRole, Region, Site, + ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, + DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceConnection, InterfaceTemplate, Manufacturer, + InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, + RackReservation, RackRole, Region, Site, ) diff --git a/netbox/extras/api/customfields.py b/netbox/extras/api/customfields.py index 5554df924..f3a9815fe 100644 --- a/netbox/extras/api/customfields.py +++ b/netbox/extras/api/customfields.py @@ -1,15 +1,14 @@ from __future__ import unicode_literals -from datetime import datetime -from rest_framework import serializers -from rest_framework.exceptions import ValidationError +from datetime import datetime from django.contrib.contenttypes.models import ContentType from django.db import transaction +from rest_framework import serializers +from rest_framework.exceptions import ValidationError -from extras.models import ( - CF_TYPE_BOOLEAN, CF_TYPE_DATE, CF_TYPE_SELECT, CustomField, CustomFieldChoice, CustomFieldValue, -) +from extras.constants import CF_TYPE_BOOLEAN, CF_TYPE_DATE, CF_TYPE_SELECT +from extras.models import CustomField, CustomFieldChoice, CustomFieldValue from utilities.api import ValidatedModelSerializer diff --git a/netbox/extras/api/serializers.py b/netbox/extras/api/serializers.py index 8998b509b..6c3cdd409 100644 --- a/netbox/extras/api/serializers.py +++ b/netbox/extras/api/serializers.py @@ -1,14 +1,12 @@ from __future__ import unicode_literals from django.core.exceptions import ObjectDoesNotExist - from rest_framework import serializers from dcim.api.serializers import NestedDeviceSerializer, NestedRackSerializer, NestedSiteSerializer from dcim.models import Device, Rack, Site -from extras.models import ( - ACTION_CHOICES, ExportTemplate, Graph, GRAPH_TYPE_CHOICES, ImageAttachment, ReportResult, TopologyMap, UserAction, -) +from extras.constants import ACTION_CHOICES, GRAPH_TYPE_CHOICES +from extras.models import ExportTemplate, Graph, ImageAttachment, ReportResult, TopologyMap, UserAction from users.api.serializers import NestedUserSerializer from utilities.api import ChoiceFieldSerializer, ContentTypeFieldSerializer, ValidatedModelSerializer diff --git a/netbox/extras/api/views.py b/netbox/extras/api/views.py index 689780879..c8d1e58c4 100644 --- a/netbox/extras/api/views.py +++ b/netbox/extras/api/views.py @@ -1,13 +1,12 @@ from __future__ import unicode_literals -from rest_framework.decorators import detail_route -from rest_framework.exceptions import PermissionDenied -from rest_framework.response import Response -from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet, ViewSet - from django.contrib.contenttypes.models import ContentType from django.http import Http404, HttpResponse from django.shortcuts import get_object_or_404 +from rest_framework.decorators import detail_route +from rest_framework.exceptions import PermissionDenied +from rest_framework.response import Response +from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet, ViewSet from extras import filters from extras.models import CustomField, ExportTemplate, Graph, ImageAttachment, ReportResult, TopologyMap, UserAction diff --git a/netbox/extras/filters.py b/netbox/extras/filters.py index 474410e65..5713d4af4 100644 --- a/netbox/extras/filters.py +++ b/netbox/extras/filters.py @@ -1,12 +1,12 @@ from __future__ import unicode_literals import django_filters - from django.contrib.auth.models import User from django.contrib.contenttypes.models import ContentType from dcim.models import Site -from .models import CF_TYPE_SELECT, CustomField, Graph, ExportTemplate, TopologyMap, UserAction +from .constants import CF_TYPE_SELECT +from .models import CustomField, Graph, ExportTemplate, TopologyMap, UserAction class CustomFieldFilter(django_filters.Filter): diff --git a/netbox/extras/forms.py b/netbox/extras/forms.py index d7a06fa5f..e39754ae0 100644 --- a/netbox/extras/forms.py +++ b/netbox/extras/forms.py @@ -1,14 +1,13 @@ from __future__ import unicode_literals + from collections import OrderedDict from django import forms from django.contrib.contenttypes.models import ContentType from utilities.forms import BootstrapMixin, BulkEditForm, LaxURLField -from .models import ( - CF_TYPE_BOOLEAN, CF_TYPE_DATE, CF_TYPE_INTEGER, CF_TYPE_SELECT, CF_TYPE_URL, CustomField, CustomFieldValue, - ImageAttachment, -) +from .constants import CF_TYPE_BOOLEAN, CF_TYPE_DATE, CF_TYPE_INTEGER, CF_TYPE_SELECT, CF_TYPE_URL +from .models import CustomField, CustomFieldValue, ImageAttachment def get_custom_fields_for_model(content_type, filterable_only=False, bulk_edit=False): diff --git a/netbox/extras/management/commands/nbshell.py b/netbox/extras/management/commands/nbshell.py index d016c7f57..15b8acac5 100644 --- a/netbox/extras/management/commands/nbshell.py +++ b/netbox/extras/management/commands/nbshell.py @@ -10,7 +10,6 @@ from django.conf import settings from django.core.management.base import BaseCommand from django.db.models import Model - APPS = ['circuits', 'dcim', 'extras', 'ipam', 'secrets', 'tenancy', 'users', 'virtualization'] BANNER_TEXT = """### NetBox interactive shell ({node}) diff --git a/netbox/extras/management/commands/run_inventory.py b/netbox/extras/management/commands/run_inventory.py index 335cdb783..574f47e3b 100644 --- a/netbox/extras/management/commands/run_inventory.py +++ b/netbox/extras/management/commands/run_inventory.py @@ -1,12 +1,12 @@ from __future__ import unicode_literals from getpass import getpass -from ncclient.transport.errors import AuthenticationError -from paramiko import AuthenticationException from django.conf import settings from django.core.management.base import BaseCommand, CommandError from django.db import transaction +from ncclient.transport.errors import AuthenticationError +from paramiko import AuthenticationException from dcim.models import Device, InventoryItem, Site, STATUS_ACTIVE diff --git a/netbox/extras/management/commands/runreport.py b/netbox/extras/management/commands/runreport.py index 05083b7f7..96efc43a0 100644 --- a/netbox/extras/management/commands/runreport.py +++ b/netbox/extras/management/commands/runreport.py @@ -3,7 +3,6 @@ from __future__ import unicode_literals from django.core.management.base import BaseCommand from django.utils import timezone -from extras.models import ReportResult from extras.reports import get_reports diff --git a/netbox/extras/models.py b/netbox/extras/models.py index 1e7a73166..9e6624fc4 100644 --- a/netbox/extras/models.py +++ b/netbox/extras/models.py @@ -1,8 +1,9 @@ from __future__ import unicode_literals + from collections import OrderedDict from datetime import date -import graphviz +import graphviz from django.contrib.auth.models import User from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType diff --git a/netbox/extras/reports.py b/netbox/extras/reports.py index d17b811cb..b84363763 100644 --- a/netbox/extras/reports.py +++ b/netbox/extras/reports.py @@ -1,8 +1,9 @@ from __future__ import unicode_literals -from collections import OrderedDict + import importlib import inspect import pkgutil +from collections import OrderedDict from django.conf import settings from django.utils import timezone diff --git a/netbox/extras/rpc.py b/netbox/extras/rpc.py index d2d4835ea..2a547ca45 100644 --- a/netbox/extras/rpc.py +++ b/netbox/extras/rpc.py @@ -1,11 +1,11 @@ from __future__ import unicode_literals + import re import time -from ncclient import manager import paramiko import xmltodict - +from ncclient import manager CONNECT_TIMEOUT = 5 # seconds diff --git a/netbox/extras/tests/test_api.py b/netbox/extras/tests/test_api.py index eddc6d71f..fde9c3185 100644 --- a/netbox/extras/tests/test_api.py +++ b/netbox/extras/tests/test_api.py @@ -1,14 +1,14 @@ from __future__ import unicode_literals -from rest_framework import status -from rest_framework.test import APITestCase - from django.contrib.auth.models import User from django.contrib.contenttypes.models import ContentType from django.urls import reverse +from rest_framework import status +from rest_framework.test import APITestCase from dcim.models import Device -from extras.models import Graph, GRAPH_TYPE_SITE, ExportTemplate +from extras.constants import GRAPH_TYPE_SITE +from extras.models import Graph, ExportTemplate from users.models import Token from utilities.tests import HttpStatusMixin diff --git a/netbox/extras/tests/test_customfields.py b/netbox/extras/tests/test_customfields.py index 5bbb407ce..84aaa76b2 100644 --- a/netbox/extras/tests/test_customfields.py +++ b/netbox/extras/tests/test_customfields.py @@ -1,19 +1,17 @@ from __future__ import unicode_literals -from datetime import date -from rest_framework import status -from rest_framework.test import APITestCase +from datetime import date from django.contrib.auth.models import User from django.contrib.contenttypes.models import ContentType from django.test import TestCase from django.urls import reverse +from rest_framework import status +from rest_framework.test import APITestCase from dcim.models import Site -from extras.models import ( - CustomField, CustomFieldValue, CustomFieldChoice, CF_TYPE_TEXT, CF_TYPE_INTEGER, CF_TYPE_BOOLEAN, CF_TYPE_DATE, - CF_TYPE_SELECT, CF_TYPE_URL, -) +from extras.constants import CF_TYPE_TEXT, CF_TYPE_INTEGER, CF_TYPE_BOOLEAN, CF_TYPE_DATE, CF_TYPE_SELECT, CF_TYPE_URL +from extras.models import CustomField, CustomFieldValue, CustomFieldChoice from users.models import Token from utilities.tests import HttpStatusMixin diff --git a/netbox/extras/urls.py b/netbox/extras/urls.py index 1ac7fce6c..13e50a229 100644 --- a/netbox/extras/urls.py +++ b/netbox/extras/urls.py @@ -4,7 +4,6 @@ from django.conf.urls import url from extras import views - app_name = 'extras' urlpatterns = [ diff --git a/netbox/extras/views.py b/netbox/extras/views.py index e92b28187..3f7c0435b 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals -from django.contrib.auth.mixins import PermissionRequiredMixin from django.contrib import messages +from django.contrib.auth.mixins import PermissionRequiredMixin from django.http import Http404 from django.shortcuts import get_object_or_404, redirect, render from django.utils.safestring import mark_safe diff --git a/netbox/ipam/api/serializers.py b/netbox/ipam/api/serializers.py index 36394dad2..e4e14e4e4 100644 --- a/netbox/ipam/api/serializers.py +++ b/netbox/ipam/api/serializers.py @@ -1,4 +1,5 @@ from __future__ import unicode_literals + from collections import OrderedDict from rest_framework import serializers @@ -6,10 +7,10 @@ from rest_framework.validators import UniqueTogetherValidator from dcim.api.serializers import NestedDeviceSerializer, InterfaceSerializer, NestedSiteSerializer from extras.api.customfields import CustomFieldModelSerializer -from ipam.models import ( - Aggregate, IPAddress, IPADDRESS_ROLE_CHOICES, IPADDRESS_STATUS_CHOICES, IP_PROTOCOL_CHOICES, Prefix, - PREFIX_STATUS_CHOICES, RIR, Role, Service, VLAN, VLAN_STATUS_CHOICES, VLANGroup, VRF, +from ipam.constants import ( + IPADDRESS_ROLE_CHOICES, IPADDRESS_STATUS_CHOICES, IP_PROTOCOL_CHOICES, PREFIX_STATUS_CHOICES, VLAN_STATUS_CHOICES, ) +from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF from tenancy.api.serializers import NestedTenantSerializer from utilities.api import ChoiceFieldSerializer, ValidatedModelSerializer from virtualization.api.serializers import NestedVirtualMachineSerializer diff --git a/netbox/ipam/api/views.py b/netbox/ipam/api/views.py index b615b470f..649e74069 100644 --- a/netbox/ipam/api/views.py +++ b/netbox/ipam/api/views.py @@ -1,17 +1,16 @@ from __future__ import unicode_literals +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.response import Response from rest_framework.viewsets import ModelViewSet -from django.conf import settings -from django.shortcuts import get_object_or_404 - -from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF -from ipam import filters from extras.api.views import CustomFieldModelViewSet +from ipam import filters +from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF from utilities.api import FieldChoicesViewSet, WritableSerializerMixin from . import serializers diff --git a/netbox/ipam/fields.py b/netbox/ipam/fields.py index a20a5dce2..39895b52a 100644 --- a/netbox/ipam/fields.py +++ b/netbox/ipam/fields.py @@ -1,9 +1,8 @@ from __future__ import unicode_literals -from netaddr import IPNetwork - from django.core.exceptions import ValidationError from django.db import models +from netaddr import IPNetwork from .formfields import IPFormField from .lookups import ( diff --git a/netbox/ipam/filters.py b/netbox/ipam/filters.py index 3c1fe06dd..f80374ca0 100644 --- a/netbox/ipam/filters.py +++ b/netbox/ipam/filters.py @@ -1,20 +1,17 @@ from __future__ import unicode_literals import django_filters +from django.db.models import Q from netaddr import IPNetwork from netaddr.core import AddrFormatError -from django.db.models import Q - from dcim.models import Site, Device, Interface from extras.filters import CustomFieldFilterSet from tenancy.models import Tenant from utilities.filters import NumericInFilter from virtualization.models import VirtualMachine -from .models import ( - Aggregate, IPAddress, IPADDRESS_ROLE_CHOICES, IPADDRESS_STATUS_CHOICES, Prefix, PREFIX_STATUS_CHOICES, RIR, Role, - Service, VLAN, VLAN_STATUS_CHOICES, VLANGroup, VRF, -) +from .constants import IPADDRESS_ROLE_CHOICES, IPADDRESS_STATUS_CHOICES, PREFIX_STATUS_CHOICES, VLAN_STATUS_CHOICES +from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF class VRFFilter(CustomFieldFilterSet, django_filters.FilterSet): diff --git a/netbox/ipam/formfields.py b/netbox/ipam/formfields.py index 8d30e11e5..c67c13414 100644 --- a/netbox/ipam/formfields.py +++ b/netbox/ipam/formfields.py @@ -1,9 +1,8 @@ from __future__ import unicode_literals -from netaddr import IPNetwork, AddrFormatError - from django import forms from django.core.exceptions import ValidationError +from netaddr import IPNetwork, AddrFormatError # diff --git a/netbox/ipam/forms.py b/netbox/ipam/forms.py index 839324702..44316aea2 100644 --- a/netbox/ipam/forms.py +++ b/netbox/ipam/forms.py @@ -14,11 +14,8 @@ from utilities.forms import ( add_blank_choice, ) from virtualization.models import VirtualMachine -from .models import ( - Aggregate, IPAddress, IPADDRESS_ROLE_CHOICES, IPADDRESS_STATUS_CHOICES, Prefix, PREFIX_STATUS_CHOICES, RIR, Role, - Service, VLAN, VLANGroup, VLAN_STATUS_CHOICES, VRF, -) - +from .constants import IPADDRESS_ROLE_CHOICES, IPADDRESS_STATUS_CHOICES, PREFIX_STATUS_CHOICES, VLAN_STATUS_CHOICES +from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF IP_FAMILY_CHOICES = [ ('', 'All'), diff --git a/netbox/ipam/models.py b/netbox/ipam/models.py index b84ac23c3..6e4788840 100644 --- a/netbox/ipam/models.py +++ b/netbox/ipam/models.py @@ -1,6 +1,6 @@ from __future__ import unicode_literals -import netaddr +import netaddr from django.conf import settings from django.contrib.contenttypes.fields import GenericRelation from django.core.exceptions import ValidationError diff --git a/netbox/ipam/tables.py b/netbox/ipam/tables.py index bd81b005d..f7253c6bf 100644 --- a/netbox/ipam/tables.py +++ b/netbox/ipam/tables.py @@ -6,7 +6,6 @@ from django_tables2.utils import Accessor from utilities.tables import BaseTable, ToggleColumn from .models import Aggregate, IPAddress, Prefix, RIR, Role, VLAN, VLANGroup, VRF - RIR_UTILIZATION = """
    {% if record.stats.total %} diff --git a/netbox/ipam/tests/test_api.py b/netbox/ipam/tests/test_api.py index 1a40b95a5..cba624a59 100644 --- a/netbox/ipam/tests/test_api.py +++ b/netbox/ipam/tests/test_api.py @@ -1,16 +1,14 @@ from __future__ import unicode_literals +from django.contrib.auth.models import User +from django.urls import reverse from netaddr import IPNetwork from rest_framework import status from rest_framework.test import APITestCase -from django.contrib.auth.models import User -from django.urls import reverse - from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site -from ipam.models import ( - Aggregate, IPAddress, IP_PROTOCOL_TCP, IP_PROTOCOL_UDP, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF, -) +from ipam.constants import IP_PROTOCOL_TCP, IP_PROTOCOL_UDP +from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF from users.models import Token from utilities.tests import HttpStatusMixin diff --git a/netbox/ipam/tests/test_models.py b/netbox/ipam/tests/test_models.py index 0f75cc795..790b665cd 100644 --- a/netbox/ipam/tests/test_models.py +++ b/netbox/ipam/tests/test_models.py @@ -1,7 +1,6 @@ from __future__ import unicode_literals import netaddr - from django.core.exceptions import ValidationError from django.test import TestCase, override_settings diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 6f24a675f..a298dd3d7 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -1,14 +1,13 @@ from __future__ import unicode_literals -from django_tables2 import RequestConfig import netaddr - from django.conf import settings from django.contrib.auth.mixins import PermissionRequiredMixin from django.db.models import Count, Q from django.shortcuts import get_object_or_404, render from django.urls import reverse from django.views.generic import View +from django_tables2 import RequestConfig from dcim.models import Device, Interface from utilities.paginator import EnhancedPaginator @@ -17,11 +16,8 @@ from utilities.views import ( ) from virtualization.models import VirtualMachine from . import filters, forms, tables -from .constants import IPADDRESS_ROLE_ANYCAST -from .models import ( - Aggregate, IPAddress, PREFIX_STATUS_ACTIVE, PREFIX_STATUS_DEPRECATED, PREFIX_STATUS_RESERVED, Prefix, RIR, Role, - Service, VLAN, VLANGroup, VRF, -) +from .constants import IPADDRESS_ROLE_ANYCAST, PREFIX_STATUS_ACTIVE, PREFIX_STATUS_DEPRECATED, PREFIX_STATUS_RESERVED +from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF def add_available_prefixes(parent, prefix_list): diff --git a/netbox/netbox/forms.py b/netbox/netbox/forms.py index 0521f2d2f..69db4fba5 100644 --- a/netbox/netbox/forms.py +++ b/netbox/netbox/forms.py @@ -4,7 +4,6 @@ from django import forms from utilities.forms import BootstrapMixin - OBJ_TYPE_CHOICES = ( ('', 'All Objects'), ('Circuits', ( diff --git a/netbox/netbox/urls.py b/netbox/netbox/urls.py index 3c0f8de9f..6cd7a9e8d 100644 --- a/netbox/netbox/urls.py +++ b/netbox/netbox/urls.py @@ -1,16 +1,14 @@ from __future__ import unicode_literals -from rest_framework_swagger.views import get_swagger_view - from django.conf import settings from django.conf.urls import include, url from django.contrib import admin from django.views.static import serve +from rest_framework_swagger.views import get_swagger_view from netbox.views import APIRootView, HomeView, SearchView from users.views import LoginView, LogoutView - swagger_view = get_swagger_view(title='NetBox API') _patterns = [ diff --git a/netbox/netbox/views.py b/netbox/netbox/views.py index ce1171235..3be9e9df9 100644 --- a/netbox/netbox/views.py +++ b/netbox/netbox/views.py @@ -1,13 +1,12 @@ from __future__ import unicode_literals -from collections import OrderedDict -import sys -from rest_framework.views import APIView -from rest_framework.response import Response -from rest_framework.reverse import reverse +from collections import OrderedDict from django.shortcuts import render from django.views.generic import View +from rest_framework.response import Response +from rest_framework.reverse import reverse +from rest_framework.views import APIView from circuits.filters import CircuitFilter, ProviderFilter from circuits.models import Circuit, Provider @@ -30,7 +29,6 @@ from virtualization.models import Cluster, VirtualMachine from virtualization.tables import ClusterTable, VirtualMachineDetailTable from .forms import SearchForm - SEARCH_MAX_RESULTS = 15 SEARCH_TYPES = OrderedDict(( # Circuits diff --git a/netbox/netbox/wsgi.py b/netbox/netbox/wsgi.py index 6d13ffe9d..ecfd81d9a 100644 --- a/netbox/netbox/wsgi.py +++ b/netbox/netbox/wsgi.py @@ -1,12 +1,3 @@ -""" -WSGI config for do_ipam project. - -It exposes the WSGI callable as a module-level variable named ``application``. - -For more information on this file, see -https://docs.djangoproject.com/en/1.8/howto/deployment/wsgi/ -""" - import os from django.core.wsgi import get_wsgi_application diff --git a/netbox/secrets/api/views.py b/netbox/secrets/api/views.py index 07897aa19..e2ffa3b28 100644 --- a/netbox/secrets/api/views.py +++ b/netbox/secrets/api/views.py @@ -1,21 +1,20 @@ from __future__ import unicode_literals + import base64 from Crypto.PublicKey import RSA +from django.http import HttpResponseBadRequest from rest_framework.exceptions import ValidationError from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.viewsets import ModelViewSet, ViewSet -from django.http import HttpResponseBadRequest - from secrets import filters from secrets.exceptions import InvalidKey from secrets.models import Secret, SecretRole, SessionKey, UserKey from utilities.api import FieldChoicesViewSet, WritableSerializerMixin from . import serializers - ERR_USERKEY_MISSING = "No UserKey found for the current user." ERR_USERKEY_INACTIVE = "UserKey has not been activated for decryption." ERR_PRIVKEY_MISSING = "Private key was not provided." diff --git a/netbox/secrets/filters.py b/netbox/secrets/filters.py index eb40e8770..6578eb4b8 100644 --- a/netbox/secrets/filters.py +++ b/netbox/secrets/filters.py @@ -1,12 +1,11 @@ from __future__ import unicode_literals import django_filters - from django.db.models import Q -from .models import Secret, SecretRole from dcim.models import Device from utilities.filters import NumericInFilter +from .models import Secret, SecretRole class SecretRoleFilter(django_filters.FilterSet): diff --git a/netbox/secrets/forms.py b/netbox/secrets/forms.py index 4e8d6667c..af0ad92cc 100644 --- a/netbox/secrets/forms.py +++ b/netbox/secrets/forms.py @@ -2,7 +2,6 @@ from __future__ import unicode_literals from Crypto.Cipher import PKCS1_OAEP from Crypto.PublicKey import RSA - from django import forms from django.db.models import Count diff --git a/netbox/secrets/models.py b/netbox/secrets/models.py index 317fe0dd5..f673af2f0 100644 --- a/netbox/secrets/models.py +++ b/netbox/secrets/models.py @@ -1,10 +1,10 @@ from __future__ import unicode_literals + import os from Crypto.Cipher import AES, PKCS1_OAEP from Crypto.PublicKey import RSA from Crypto.Util import strxor - from django.conf import settings from django.contrib.auth.hashers import make_password, check_password from django.contrib.auth.models import Group, User diff --git a/netbox/secrets/tables.py b/netbox/secrets/tables.py index 30424b0bb..d68ac37fe 100644 --- a/netbox/secrets/tables.py +++ b/netbox/secrets/tables.py @@ -3,10 +3,8 @@ from __future__ import unicode_literals import django_tables2 as tables from utilities.tables import BaseTable, ToggleColumn - from .models import SecretRole, Secret - SECRETROLE_ACTIONS = """ {% if perms.secrets.change_secretrole %} diff --git a/netbox/secrets/tests/test_api.py b/netbox/secrets/tests/test_api.py index 4acda3eff..02b4d3e90 100644 --- a/netbox/secrets/tests/test_api.py +++ b/netbox/secrets/tests/test_api.py @@ -1,18 +1,17 @@ from __future__ import unicode_literals -import base64 -from rest_framework import status -from rest_framework.test import APITestCase +import base64 from django.contrib.auth.models import User from django.urls import reverse +from rest_framework import status +from rest_framework.test import APITestCase from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site from secrets.models import Secret, SecretRole, SessionKey, UserKey from users.models import Token from utilities.tests import HttpStatusMixin - # Dummy RSA key pair for testing use only PRIVATE_KEY = """-----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEA97wPWxpq5cClRu8Ssq609ZLfyx6E8ln/v/PdFZ7fxxmA4k+z diff --git a/netbox/secrets/tests/test_models.py b/netbox/secrets/tests/test_models.py index 4be37801d..887c048bf 100644 --- a/netbox/secrets/tests/test_models.py +++ b/netbox/secrets/tests/test_models.py @@ -1,14 +1,13 @@ from __future__ import unicode_literals from Crypto.PublicKey import RSA - from django.conf import settings from django.contrib.auth.models import User from django.core.exceptions import ValidationError from django.test import TestCase -from secrets.models import UserKey, Secret, encrypt_master_key, decrypt_master_key, generate_random_key from secrets.hashers import SecretValidationHasher +from secrets.models import UserKey, Secret, encrypt_master_key, decrypt_master_key, generate_random_key class UserKeyTestCase(TestCase): diff --git a/netbox/secrets/urls.py b/netbox/secrets/urls.py index ecba5741f..cd6415719 100644 --- a/netbox/secrets/urls.py +++ b/netbox/secrets/urls.py @@ -4,7 +4,6 @@ from django.conf.urls import url from . import views - app_name = 'secrets' urlpatterns = [ diff --git a/netbox/secrets/views.py b/netbox/secrets/views.py index e7f6939a9..250559139 100644 --- a/netbox/secrets/views.py +++ b/netbox/secrets/views.py @@ -1,4 +1,5 @@ from __future__ import unicode_literals + import base64 from django.contrib import messages diff --git a/netbox/tenancy/filters.py b/netbox/tenancy/filters.py index 090a0f9ee..330ab7f56 100644 --- a/netbox/tenancy/filters.py +++ b/netbox/tenancy/filters.py @@ -1,7 +1,6 @@ from __future__ import unicode_literals import django_filters - from django.db.models import Q from extras.filters import CustomFieldFilterSet diff --git a/netbox/tenancy/tables.py b/netbox/tenancy/tables.py index 4ef774fb6..2b2989941 100644 --- a/netbox/tenancy/tables.py +++ b/netbox/tenancy/tables.py @@ -3,10 +3,8 @@ from __future__ import unicode_literals import django_tables2 as tables from utilities.tables import BaseTable, ToggleColumn - from .models import Tenant, TenantGroup - TENANTGROUP_ACTIONS = """ {% if perms.tenancy.change_tenantgroup %} diff --git a/netbox/tenancy/tests/test_api.py b/netbox/tenancy/tests/test_api.py index 5414a1b06..1ac05ea89 100644 --- a/netbox/tenancy/tests/test_api.py +++ b/netbox/tenancy/tests/test_api.py @@ -1,10 +1,9 @@ from __future__ import unicode_literals -from rest_framework import status -from rest_framework.test import APITestCase - from django.contrib.auth.models import User from django.urls import reverse +from rest_framework import status +from rest_framework.test import APITestCase from tenancy.models import Tenant, TenantGroup from users.models import Token diff --git a/netbox/tenancy/urls.py b/netbox/tenancy/urls.py index a0337a938..668b194f0 100644 --- a/netbox/tenancy/urls.py +++ b/netbox/tenancy/urls.py @@ -4,7 +4,6 @@ from django.conf.urls import url from . import views - app_name = 'tenancy' urlpatterns = [ diff --git a/netbox/tenancy/views.py b/netbox/tenancy/views.py index 32709dfb7..33df6a5ca 100644 --- a/netbox/tenancy/views.py +++ b/netbox/tenancy/views.py @@ -13,8 +13,8 @@ from utilities.views import ( BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView, ) from virtualization.models import VirtualMachine -from .models import Tenant, TenantGroup from . import filters, forms, tables +from .models import Tenant, TenantGroup # diff --git a/netbox/users/api/serializers.py b/netbox/users/api/serializers.py index a516b2121..80f79516c 100644 --- a/netbox/users/api/serializers.py +++ b/netbox/users/api/serializers.py @@ -1,7 +1,6 @@ from __future__ import unicode_literals from django.contrib.auth.models import User - from rest_framework import serializers diff --git a/netbox/users/forms.py b/netbox/users/forms.py index 811ca6cc5..d25e128e6 100644 --- a/netbox/users/forms.py +++ b/netbox/users/forms.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals -from django.contrib.auth.forms import AuthenticationForm, PasswordChangeForm as DjangoPasswordChangeForm from django import forms +from django.contrib.auth.forms import AuthenticationForm, PasswordChangeForm as DjangoPasswordChangeForm from utilities.forms import BootstrapMixin from .models import Token diff --git a/netbox/users/models.py b/netbox/users/models.py index a2d9d09ba..02f5bc0a0 100644 --- a/netbox/users/models.py +++ b/netbox/users/models.py @@ -1,12 +1,13 @@ from __future__ import unicode_literals + import binascii import os from django.contrib.auth.models import User from django.core.validators import MinLengthValidator from django.db import models -from django.utils.encoding import python_2_unicode_compatible from django.utils import timezone +from django.utils.encoding import python_2_unicode_compatible @python_2_unicode_compatible diff --git a/netbox/users/urls.py b/netbox/users/urls.py index 57a7e43cc..aad89e104 100644 --- a/netbox/users/urls.py +++ b/netbox/users/urls.py @@ -4,7 +4,6 @@ from django.conf.urls import url from . import views - app_name = 'user' urlpatterns = [ diff --git a/netbox/utilities/api.py b/netbox/utilities/api.py index d9ae855ea..91e0fb8af 100644 --- a/netbox/utilities/api.py +++ b/netbox/utilities/api.py @@ -1,17 +1,16 @@ from __future__ import unicode_literals + from collections import OrderedDict from django.conf import settings from django.contrib.contenttypes.models import ContentType from django.http import Http404 - from rest_framework.exceptions import APIException from rest_framework.permissions import BasePermission from rest_framework.response import Response from rest_framework.serializers import Field, ModelSerializer, ValidationError from rest_framework.viewsets import ViewSet - WRITE_OPERATIONS = ['create', 'update', 'partial_update', 'delete'] diff --git a/netbox/utilities/fields.py b/netbox/utilities/fields.py index 2b1a51048..d0a0c2180 100644 --- a/netbox/utilities/fields.py +++ b/netbox/utilities/fields.py @@ -5,7 +5,6 @@ from django.db import models from .forms import ColorSelect - validate_color = RegexValidator('^[0-9a-f]{6}$', 'Enter a valid hexadecimal RGB color code.', 'invalid') diff --git a/netbox/utilities/filters.py b/netbox/utilities/filters.py index de671cd0a..647ecb723 100644 --- a/netbox/utilities/filters.py +++ b/netbox/utilities/filters.py @@ -1,8 +1,8 @@ from __future__ import unicode_literals -import django_filters import itertools +import django_filters from django import forms from django.utils.encoding import force_text diff --git a/netbox/utilities/forms.py b/netbox/utilities/forms.py index f63c0c11d..6ba49d02c 100644 --- a/netbox/utilities/forms.py +++ b/netbox/utilities/forms.py @@ -1,17 +1,16 @@ from __future__ import unicode_literals + import csv import itertools import re -from mptt.forms import TreeNodeMultipleChoiceField - from django import forms from django.conf import settings from django.urls import reverse_lazy +from mptt.forms import TreeNodeMultipleChoiceField from .validators import EnhancedURLValidator - COLOR_CHOICES = ( ('aa1409', 'Dark red'), ('f44336', 'Red'), diff --git a/netbox/utilities/middleware.py b/netbox/utilities/middleware.py index 927855e0e..b9c95fc5e 100644 --- a/netbox/utilities/middleware.py +++ b/netbox/utilities/middleware.py @@ -1,4 +1,5 @@ from __future__ import unicode_literals + import sys from django.conf import settings @@ -7,7 +8,6 @@ from django.http import HttpResponseRedirect from django.shortcuts import render from django.urls import reverse - BASE_PATH = getattr(settings, 'BASE_PATH', False) LOGIN_REQUIRED = getattr(settings, 'LOGIN_REQUIRED', False) diff --git a/netbox/utilities/tables.py b/netbox/utilities/tables.py index d4c68c54f..30a88f72e 100644 --- a/netbox/utilities/tables.py +++ b/netbox/utilities/tables.py @@ -1,7 +1,6 @@ from __future__ import unicode_literals import django_tables2 as tables - from django.utils.safestring import mark_safe diff --git a/netbox/utilities/templatetags/helpers.py b/netbox/utilities/templatetags/helpers.py index 23fcfcca5..ea6cd1173 100644 --- a/netbox/utilities/templatetags/helpers.py +++ b/netbox/utilities/templatetags/helpers.py @@ -1,10 +1,8 @@ from __future__ import unicode_literals -from markdown import markdown - from django import template from django.utils.safestring import mark_safe - +from markdown import markdown register = template.Library() diff --git a/netbox/utilities/utils.py b/netbox/utilities/utils.py index 3e9364419..72da778a5 100644 --- a/netbox/utilities/utils.py +++ b/netbox/utilities/utils.py @@ -1,4 +1,5 @@ from __future__ import unicode_literals + import six diff --git a/netbox/utilities/validators.py b/netbox/utilities/validators.py index b5b058111..8eb990486 100644 --- a/netbox/utilities/validators.py +++ b/netbox/utilities/validators.py @@ -1,4 +1,5 @@ from __future__ import unicode_literals + import re from django.core.validators import _lazy_re_compile, URLValidator diff --git a/netbox/utilities/views.py b/netbox/utilities/views.py index d7b82236c..d8ba3712a 100644 --- a/netbox/utilities/views.py +++ b/netbox/utilities/views.py @@ -1,9 +1,8 @@ from __future__ import unicode_literals + from collections import OrderedDict from copy import deepcopy -from django_tables2 import RequestConfig - from django.conf import settings from django.contrib import messages from django.contrib.contenttypes.models import ContentType @@ -19,6 +18,7 @@ from django.utils.html import escape from django.utils.http import is_safe_url from django.utils.safestring import mark_safe from django.views.generic import View +from django_tables2 import RequestConfig from extras.models import CustomField, CustomFieldValue, ExportTemplate, UserAction from utilities.forms import BootstrapMixin, CSVDataField diff --git a/netbox/virtualization/constants.py b/netbox/virtualization/constants.py index f1d9e6c39..6324fb785 100644 --- a/netbox/virtualization/constants.py +++ b/netbox/virtualization/constants.py @@ -2,7 +2,6 @@ from __future__ import unicode_literals from dcim.constants import STATUS_ACTIVE, STATUS_OFFLINE, STATUS_STAGED - # VirtualMachine statuses (replicated from Device statuses) STATUS_CHOICES = [ [STATUS_ACTIVE, 'Active'], diff --git a/netbox/virtualization/filters.py b/netbox/virtualization/filters.py index c661bc973..1000f2ab4 100644 --- a/netbox/virtualization/filters.py +++ b/netbox/virtualization/filters.py @@ -1,11 +1,10 @@ from __future__ import unicode_literals import django_filters +from django.db.models import Q from netaddr import EUI from netaddr.core import AddrFormatError -from django.db.models import Q - from dcim.models import DeviceRole, Interface, Platform, Site from extras.filters import CustomFieldFilterSet from tenancy.models import Tenant diff --git a/netbox/virtualization/forms.py b/netbox/virtualization/forms.py index 9a7185f94..01d8b5765 100644 --- a/netbox/virtualization/forms.py +++ b/netbox/virtualization/forms.py @@ -1,10 +1,9 @@ from __future__ import unicode_literals -from mptt.forms import TreeNodeChoiceField - from django import forms from django.core.exceptions import ValidationError from django.db.models import Count +from mptt.forms import TreeNodeChoiceField from dcim.constants import IFACE_FF_VIRTUAL from dcim.formfields import MACAddressFormField @@ -20,7 +19,6 @@ from utilities.forms import ( from .constants import STATUS_CHOICES from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine - VIFACE_FF_CHOICES = ( (IFACE_FF_VIRTUAL, 'Virtual'), ) diff --git a/netbox/virtualization/tables.py b/netbox/virtualization/tables.py index a8f9068ac..0498edd46 100644 --- a/netbox/virtualization/tables.py +++ b/netbox/virtualization/tables.py @@ -7,7 +7,6 @@ from dcim.models import Interface from utilities.tables import BaseTable, ToggleColumn from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine - CLUSTERTYPE_ACTIONS = """ {% if perms.virtualization.change_clustertype %} diff --git a/netbox/virtualization/tests/test_api.py b/netbox/virtualization/tests/test_api.py index 9d3e01091..f83e0ea58 100644 --- a/netbox/virtualization/tests/test_api.py +++ b/netbox/virtualization/tests/test_api.py @@ -1,10 +1,9 @@ from __future__ import unicode_literals -from rest_framework import status -from rest_framework.test import APITestCase - from django.contrib.auth.models import User from django.urls import reverse +from rest_framework import status +from rest_framework.test import APITestCase from users.models import Token from utilities.tests import HttpStatusMixin diff --git a/netbox/virtualization/urls.py b/netbox/virtualization/urls.py index ca794cdd8..2ba0daff7 100644 --- a/netbox/virtualization/urls.py +++ b/netbox/virtualization/urls.py @@ -5,7 +5,6 @@ from django.conf.urls import url from ipam.views import ServiceCreateView from . import views - app_name = 'virtualization' urlpatterns = [ diff --git a/netbox/virtualization/views.py b/netbox/virtualization/views.py index 00f59f8d4..4f2981748 100644 --- a/netbox/virtualization/views.py +++ b/netbox/virtualization/views.py @@ -14,10 +14,8 @@ from utilities.views import ( BulkComponentCreateView, BulkDeleteView, BulkEditView, BulkImportView, ComponentCreateView, ComponentDeleteView, ComponentEditView, ObjectDeleteView, ObjectEditView, ObjectListView, ) +from . import filters, forms, tables from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine -from . import filters -from . import forms -from . import tables # From 9d50b78b699bf5de83b5d52c0cd2a7cb88b4345d Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 8 Nov 2017 09:51:37 -0500 Subject: [PATCH 17/25] Fixes #1696: Fix for NAPALM v2.0+ --- netbox/dcim/api/views.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index 1df0f9cbc..a74a34422 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -258,12 +258,19 @@ class DeviceViewSet(WritableSerializerMixin, CustomFieldModelViewSet): device.platform )) - # Check that NAPALM is installed and verify the configured driver + # Check that NAPALM is installed try: import napalm - from napalm_base.exceptions import ConnectAuthError, ModuleImportError except ImportError: raise ServiceUnavailable("NAPALM is not installed. Please see the documentation for instructions.") + + # TODO: Remove support for NAPALM < 2.0 + try: + from napalm.base.exceptions import ConnectAuthError, ModuleImportError + except ImportError: + from napalm_base.exceptions import ConnectAuthError, ModuleImportError + + # Validate the configured driver try: driver = napalm.get_network_driver(device.platform.napalm_driver) except ModuleImportError: From a1b1e261de3105b39ed1dbddc1db988a2ff9ced2 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 9 Nov 2017 09:33:40 -0500 Subject: [PATCH 18/25] Fixes #1699: Correct nested representation in the API of primary IPs for virtual machines and add missing primary_ip property --- netbox/virtualization/api/serializers.py | 17 +++++++++++++++-- netbox/virtualization/models.py | 12 ++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/netbox/virtualization/api/serializers.py b/netbox/virtualization/api/serializers.py index b53734734..078df19b6 100644 --- a/netbox/virtualization/api/serializers.py +++ b/netbox/virtualization/api/serializers.py @@ -6,6 +6,7 @@ from dcim.api.serializers import NestedDeviceRoleSerializer, NestedPlatformSeria from dcim.constants import IFACE_FF_VIRTUAL from dcim.models import Interface from extras.api.customfields import CustomFieldModelSerializer +from ipam.models import IPAddress from tenancy.api.serializers import NestedTenantSerializer from utilities.api import ChoiceFieldSerializer, ValidatedModelSerializer from virtualization.constants import STATUS_CHOICES @@ -83,18 +84,30 @@ class WritableClusterSerializer(CustomFieldModelSerializer): # Virtual machines # +# Cannot import ipam.api.NestedIPAddressSerializer due to circular dependency +class VirtualMachineIPAddressSerializer(serializers.ModelSerializer): + url = serializers.HyperlinkedIdentityField(view_name='ipam-api:ipaddress-detail') + + class Meta: + model = IPAddress + fields = ['id', 'url', 'family', 'address'] + + class VirtualMachineSerializer(CustomFieldModelSerializer): status = ChoiceFieldSerializer(choices=STATUS_CHOICES) cluster = NestedClusterSerializer() role = NestedDeviceRoleSerializer() tenant = NestedTenantSerializer() platform = NestedPlatformSerializer() + primary_ip = VirtualMachineIPAddressSerializer() + primary_ip4 = VirtualMachineIPAddressSerializer() + primary_ip6 = VirtualMachineIPAddressSerializer() class Meta: model = VirtualMachine fields = [ - 'id', 'name', 'status', 'cluster', 'role', 'tenant', 'platform', 'primary_ip4', 'primary_ip6', 'vcpus', - 'memory', 'disk', 'comments', 'custom_fields', + 'id', 'name', 'status', 'cluster', 'role', 'tenant', 'platform', 'primary_ip', 'primary_ip4', 'primary_ip6', + 'vcpus', 'memory', 'disk', 'comments', 'custom_fields', ] diff --git a/netbox/virtualization/models.py b/netbox/virtualization/models.py index 8fb13c7cc..edb35f4cb 100644 --- a/netbox/virtualization/models.py +++ b/netbox/virtualization/models.py @@ -1,5 +1,6 @@ from __future__ import unicode_literals +from django.conf import settings from django.contrib.contenttypes.fields import GenericRelation from django.core.exceptions import ValidationError from django.db import models @@ -255,3 +256,14 @@ class VirtualMachine(CreatedUpdatedModel, CustomFieldModel): def get_status_class(self): return VM_STATUS_CLASSES[self.status] + + @property + def primary_ip(self): + if settings.PREFER_IPV4 and self.primary_ip4: + return self.primary_ip4 + elif self.primary_ip6: + return self.primary_ip6 + elif self.primary_ip4: + return self.primary_ip4 + else: + return None From e1d655cb235b4b55d931c55d9666dd90e6af4816 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 10 Nov 2017 09:34:30 -0500 Subject: [PATCH 19/25] Fixes #1471: Correct bulk selection of IP addresses within a prefix assigned to a VRF --- netbox/ipam/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index a298dd3d7..4184d57b6 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -550,7 +550,7 @@ class PrefixIPAddressesView(View): 'prefix': prefix, 'ip_table': ip_table, 'permissions': permissions, - 'bulk_querystring': 'vrf_id={}&parent={}'.format(prefix.vrf or '0', prefix.prefix), + 'bulk_querystring': 'vrf_id={}&parent={}'.format(prefix.vrf.pk if prefix.vrf else '0', prefix.prefix), }) From a0bb7b08bdf4e4ecf04c4294ef52b0a685f8cd43 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 10 Nov 2017 11:58:59 -0500 Subject: [PATCH 20/25] Closes #1512: Added a view to search for an IP address being assigned to an interface --- netbox/ipam/forms.py | 5 ++ netbox/ipam/tables.py | 18 ++++++- netbox/ipam/urls.py | 1 + netbox/ipam/views.py | 47 +++++++++++++++++- .../ipam/inc/ipadress_edit_header.html | 16 ++++++- netbox/templates/ipam/ipaddress_assign.html | 48 +++++++++++++++++++ netbox/utilities/templatetags/helpers.py | 2 +- 7 files changed, 132 insertions(+), 5 deletions(-) create mode 100644 netbox/templates/ipam/ipaddress_assign.html diff --git a/netbox/ipam/forms.py b/netbox/ipam/forms.py index 44316aea2..d2bac96a6 100644 --- a/netbox/ipam/forms.py +++ b/netbox/ipam/forms.py @@ -688,6 +688,11 @@ class IPAddressBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm): nullable_fields = ['vrf', 'role', 'tenant', 'description'] +class IPAddressAssignForm(BootstrapMixin, forms.Form): + vrf = forms.ModelChoiceField(queryset=VRF.objects.all(), required=False, label='VRF') + address = forms.CharField(label='IP Address') + + def ipaddress_status_choices(): status_counts = {} for status in IPAddress.objects.values('status').annotate(count=Count('status')).order_by('status'): diff --git a/netbox/ipam/tables.py b/netbox/ipam/tables.py index f7253c6bf..ebb86731c 100644 --- a/netbox/ipam/tables.py +++ b/netbox/ipam/tables.py @@ -76,6 +76,10 @@ IPADDRESS_LINK = """ {% endif %} """ +IPADDRESS_ASSIGN_LINK = """ +{{ record }} +""" + IPADDRESS_PARENT = """ {% if record.interface %} {{ record.interface.parent }} @@ -268,8 +272,8 @@ class PrefixDetailTable(PrefixTable): class IPAddressTable(BaseTable): pk = ToggleColumn() address = tables.TemplateColumn(IPADDRESS_LINK, verbose_name='IP Address') - status = tables.TemplateColumn(STATUS_LABEL) vrf = tables.TemplateColumn(VRF_LINK, verbose_name='VRF') + status = tables.TemplateColumn(STATUS_LABEL) tenant = tables.TemplateColumn(TENANT_LINK) parent = tables.TemplateColumn(IPADDRESS_PARENT, orderable=False) interface = tables.Column(orderable=False) @@ -293,6 +297,18 @@ class IPAddressDetailTable(IPAddressTable): ) +class IPAddressAssignTable(BaseTable): + address = tables.TemplateColumn(IPADDRESS_ASSIGN_LINK, verbose_name='IP Address') + status = tables.TemplateColumn(STATUS_LABEL) + parent = tables.TemplateColumn(IPADDRESS_PARENT, orderable=False) + interface = tables.Column(orderable=False) + + class Meta(BaseTable.Meta): + model = IPAddress + fields = ('address', 'vrf', 'status', 'role', 'tenant', 'parent', 'interface') + orderable = False + + # # VLAN groups # diff --git a/netbox/ipam/urls.py b/netbox/ipam/urls.py index c5723ef94..a67e9e865 100644 --- a/netbox/ipam/urls.py +++ b/netbox/ipam/urls.py @@ -60,6 +60,7 @@ urlpatterns = [ url(r'^ip-addresses/import/$', views.IPAddressBulkImportView.as_view(), name='ipaddress_import'), url(r'^ip-addresses/edit/$', views.IPAddressBulkEditView.as_view(), name='ipaddress_bulk_edit'), url(r'^ip-addresses/delete/$', views.IPAddressBulkDeleteView.as_view(), name='ipaddress_bulk_delete'), + url(r'^ip-addresses/assign/$', views.IPAddressAssignView.as_view(), name='ipaddress_assign'), url(r'^ip-addresses/(?P\d+)/$', views.IPAddressView.as_view(), name='ipaddress'), url(r'^ip-addresses/(?P\d+)/edit/$', views.IPAddressEditView.as_view(), name='ipaddress_edit'), url(r'^ip-addresses/(?P\d+)/delete/$', views.IPAddressDeleteView.as_view(), name='ipaddress_delete'), diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 4184d57b6..22335da8f 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -4,7 +4,7 @@ import netaddr from django.conf import settings from django.contrib.auth.mixins import PermissionRequiredMixin from django.db.models import Count, Q -from django.shortcuts import get_object_or_404, render +from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse from django.views.generic import View from django_tables2 import RequestConfig @@ -686,6 +686,51 @@ class IPAddressEditView(IPAddressCreateView): permission_required = 'ipam.change_ipaddress' +class IPAddressAssignView(PermissionRequiredMixin, View): + """ + Search for IPAddresses to be assigned to an Interface. + """ + permission_required = 'ipam.change_ipaddress' + + def dispatch(self, request, *args, **kwargs): + + # Redirect user if an interface has not been provided + if 'interface' not in request.GET: + return redirect('ipam:ipaddress_add') + + return super(IPAddressAssignView, self).dispatch(request, *args, **kwargs) + + def get(self, request): + + form = forms.IPAddressAssignForm() + + return render(request, 'ipam/ipaddress_assign.html', { + 'form': form, + 'return_url': request.GET.get('return_url', ''), + }) + + def post(self, request): + + form = forms.IPAddressAssignForm(request.POST) + table = None + + if form.is_valid(): + + queryset = IPAddress.objects.select_related( + 'vrf', 'tenant', 'interface__device', 'interface__virtual_machine' + ).filter( + vrf=form.cleaned_data['vrf'], + address__net_host=form.cleaned_data['address'], + ) + table = tables.IPAddressAssignTable(queryset) + + return render(request, 'ipam/ipaddress_assign.html', { + 'form': form, + 'table': table, + 'return_url': request.GET.get('return_url', ''), + }) + + class IPAddressDeleteView(PermissionRequiredMixin, ObjectDeleteView): permission_required = 'ipam.delete_ipaddress' model = IPAddress diff --git a/netbox/templates/ipam/inc/ipadress_edit_header.html b/netbox/templates/ipam/inc/ipadress_edit_header.html index 7d590291a..b8ec3878a 100644 --- a/netbox/templates/ipam/inc/ipadress_edit_header.html +++ b/netbox/templates/ipam/inc/ipadress_edit_header.html @@ -1,4 +1,16 @@ +{% load helpers %} + diff --git a/netbox/templates/ipam/ipaddress_assign.html b/netbox/templates/ipam/ipaddress_assign.html new file mode 100644 index 000000000..a623a55f3 --- /dev/null +++ b/netbox/templates/ipam/ipaddress_assign.html @@ -0,0 +1,48 @@ +{% extends 'utilities/obj_edit.html' %} +{% load static from staticfiles %} +{% load form_helpers %} +{% load helpers %} + +{% block content %} + + {% csrf_token %} + {% for field in form.hidden_fields %} + {{ field }} + {% endfor %} +
    +
    +

    Assign an IP Address

    + {% include 'ipam/inc/ipadress_edit_header.html' with active_tab='assign' %} + {% if form.non_field_errors %} +
    +
    Errors
    +
    + {{ form.non_field_errors }} +
    +
    + {% endif %} +
    +
    Select IP Address
    +
    + {% render_field form.vrf %} + {% render_field form.address %} +
    +
    +
    +
    +
    +
    + + Cancel +
    +
    + + {% if table %} +
    +
    +

    Search Results

    + {% include 'utilities/obj_table.html' with table_template='panel_table.html' %} +
    +
    + {% endif %} +{% endblock %} diff --git a/netbox/utilities/templatetags/helpers.py b/netbox/utilities/templatetags/helpers.py index ea6cd1173..2af936885 100644 --- a/netbox/utilities/templatetags/helpers.py +++ b/netbox/utilities/templatetags/helpers.py @@ -132,7 +132,7 @@ def querystring(request, **kwargs): querydict[k] = v elif k in querydict: querydict.pop(k) - querystring = querydict.urlencode() + querystring = querydict.urlencode(safe='/') if querystring: return '?' + querystring else: From 30b544a74313d42f00ee638910530e4a58364aed Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 10 Nov 2017 15:01:46 -0500 Subject: [PATCH 21/25] Fixes #1642: Validate device type classification when creating console server ports and power outlets --- netbox/dcim/models.py | 25 +++++++++++++++++++++++++ netbox/dcim/tests/test_api.py | 6 +++--- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index 67550e136..84d4dc39c 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -1117,6 +1117,15 @@ class ConsoleServerPort(models.Model): def __str__(self): return self.name + def clean(self): + + # Check that the parent device's DeviceType is a console server + device_type = self.device.device_type + if not device_type.is_console_server: + raise ValidationError("The {} {} device type not support assignment of console server ports.".format( + device_type.manufacturer, device_type + )) + # # Power ports @@ -1182,6 +1191,15 @@ class PowerOutlet(models.Model): def __str__(self): return self.name + def clean(self): + + # Check that the parent device's DeviceType is a PDU + device_type = self.device.device_type + if not device_type.is_pdu: + raise ValidationError("The {} {} device type not support assignment of power outlets.".format( + device_type.manufacturer, device_type + )) + # # Interfaces @@ -1238,6 +1256,13 @@ class Interface(models.Model): def clean(self): + # Check that the parent device's DeviceType is a network device + device_type = self.device.device_type + if not device_type.is_network_device: + raise ValidationError("The {} {} device type not support assignment of network interfaces.".format( + device_type.manufacturer, device_type + )) + # An Interface must belong to a Device *or* to a VirtualMachine if self.device and self.virtual_machine: raise ValidationError("An interface cannot belong to both a device and a virtual machine.") diff --git a/netbox/dcim/tests/test_api.py b/netbox/dcim/tests/test_api.py index bec333974..5d6f66d30 100644 --- a/netbox/dcim/tests/test_api.py +++ b/netbox/dcim/tests/test_api.py @@ -1432,7 +1432,7 @@ class ConsoleServerPortTest(HttpStatusMixin, APITestCase): site = Site.objects.create(name='Test Site 1', slug='test-site-1') manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1') devicetype = DeviceType.objects.create( - manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1' + manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1', is_console_server=True ) devicerole = DeviceRole.objects.create( name='Test Device Role 1', slug='test-device-role-1', color='ff0000' @@ -1590,7 +1590,7 @@ class PowerOutletTest(HttpStatusMixin, APITestCase): site = Site.objects.create(name='Test Site 1', slug='test-site-1') manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1') devicetype = DeviceType.objects.create( - manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1' + manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1', is_pdu=True ) devicerole = DeviceRole.objects.create( name='Test Device Role 1', slug='test-device-role-1', color='ff0000' @@ -1667,7 +1667,7 @@ class InterfaceTest(HttpStatusMixin, APITestCase): site = Site.objects.create(name='Test Site 1', slug='test-site-1') manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1') devicetype = DeviceType.objects.create( - manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1' + manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1', is_network_device=True ) devicerole = DeviceRole.objects.create( name='Test Device Role 1', slug='test-device-role-1', color='ff0000' From 9a7dd5ea1977ce19e6566e655b31e2d2285db4b0 Mon Sep 17 00:00:00 2001 From: Karl Date: Mon, 13 Nov 2017 20:11:41 +0000 Subject: [PATCH 22/25] Update 0008_reports.py (#1702) * Update 0008_reports.py PG10 version string appears to, at least on Windows, contain a comma. * Fix missing re import. Fix missing re import. * Update 0008_reports.py --- netbox/extras/migrations/0008_reports.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/netbox/extras/migrations/0008_reports.py b/netbox/extras/migrations/0008_reports.py index 83565cce7..7f3eccc32 100644 --- a/netbox/extras/migrations/0008_reports.py +++ b/netbox/extras/migrations/0008_reports.py @@ -2,6 +2,7 @@ # Generated by Django 1.11.4 on 2017-09-26 21:25 from __future__ import unicode_literals from distutils.version import StrictVersion +import re from django.conf import settings import django.contrib.postgres.fields.jsonb @@ -18,7 +19,7 @@ def verify_postgresql_version(apps, schema_editor): with connection.cursor() as cursor: cursor.execute("SELECT VERSION()") row = cursor.fetchone() - pg_version = row[0].split()[1] + pg_version = re.match('^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)) From 8d6d55d6289ce184f1a6de209dbf4b811689659e Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 14 Nov 2017 12:58:47 -0500 Subject: [PATCH 23/25] Fixes #1705: Fixed filtering of devices with a status of offline --- netbox/dcim/filters.py | 3 ++- netbox/ipam/filters.py | 9 ++++++--- netbox/virtualization/filters.py | 3 ++- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/netbox/dcim/filters.py b/netbox/dcim/filters.py index 3fd4fdb7f..e466723bd 100644 --- a/netbox/dcim/filters.py +++ b/netbox/dcim/filters.py @@ -425,7 +425,8 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet): label='Device model (slug)', ) status = django_filters.MultipleChoiceFilter( - choices=STATUS_CHOICES + choices=STATUS_CHOICES, + null_value=None ) is_full_depth = django_filters.BooleanFilter( name='device_type__is_full_depth', diff --git a/netbox/ipam/filters.py b/netbox/ipam/filters.py index f80374ca0..521a6ae79 100644 --- a/netbox/ipam/filters.py +++ b/netbox/ipam/filters.py @@ -165,7 +165,8 @@ class PrefixFilter(CustomFieldFilterSet, django_filters.FilterSet): label='Role (slug)', ) status = django_filters.MultipleChoiceFilter( - choices=PREFIX_STATUS_CHOICES + choices=PREFIX_STATUS_CHOICES, + null_value=None ) class Meta: @@ -270,7 +271,8 @@ class IPAddressFilter(CustomFieldFilterSet, django_filters.FilterSet): label='Interface (ID)', ) status = django_filters.MultipleChoiceFilter( - choices=IPADDRESS_STATUS_CHOICES + choices=IPADDRESS_STATUS_CHOICES, + null_value=None ) role = django_filters.MultipleChoiceFilter( choices=IPADDRESS_ROLE_CHOICES @@ -369,7 +371,8 @@ class VLANFilter(CustomFieldFilterSet, django_filters.FilterSet): label='Role (slug)', ) status = django_filters.MultipleChoiceFilter( - choices=VLAN_STATUS_CHOICES + choices=VLAN_STATUS_CHOICES, + null_value=None ) class Meta: diff --git a/netbox/virtualization/filters.py b/netbox/virtualization/filters.py index 1000f2ab4..ac5b2436c 100644 --- a/netbox/virtualization/filters.py +++ b/netbox/virtualization/filters.py @@ -70,7 +70,8 @@ class VirtualMachineFilter(CustomFieldFilterSet): label='Search', ) status = django_filters.MultipleChoiceFilter( - choices=STATUS_CHOICES + choices=STATUS_CHOICES, + null_value=None ) cluster_group_id = django_filters.ModelMultipleChoiceFilter( name='cluster__group', From 2047a16a570bd1a82bdaf500e8952033b75cf2fe Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 14 Nov 2017 13:15:09 -0500 Subject: [PATCH 24/25] Fixes #1703: Added API serializer validation for custom integer fields --- netbox/extras/api/customfields.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/netbox/extras/api/customfields.py b/netbox/extras/api/customfields.py index f3a9815fe..0497138c4 100644 --- a/netbox/extras/api/customfields.py +++ b/netbox/extras/api/customfields.py @@ -7,7 +7,7 @@ from django.db import transaction from rest_framework import serializers from rest_framework.exceptions import ValidationError -from extras.constants import CF_TYPE_BOOLEAN, CF_TYPE_DATE, CF_TYPE_SELECT +from extras.constants import CF_TYPE_BOOLEAN, CF_TYPE_DATE, CF_TYPE_INTEGER, CF_TYPE_SELECT from extras.models import CustomField, CustomFieldChoice, CustomFieldValue from utilities.api import ValidatedModelSerializer @@ -38,6 +38,15 @@ class CustomFieldsSerializer(serializers.BaseSerializer): # Data validation if value not in [None, '']: + # Validate integer + if cf.type == CF_TYPE_INTEGER: + try: + int(value) + except ValueError: + raise ValidationError( + "Invalid value for integer field {}: {}".format(field_name, value) + ) + # Validate boolean if cf.type == CF_TYPE_BOOLEAN and value not in [True, False, 1, 0]: raise ValidationError( From 63ac8863f35becfb30792ed1b034ae5bf9623257 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 14 Nov 2017 13:20:15 -0500 Subject: [PATCH 25/25] Release v2.2.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 d4fbc2359..791f7a068 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -13,7 +13,7 @@ except ImportError: ) -VERSION = '2.2.5-dev' +VERSION = '2.2.5' BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    {{ rg.name }} {{ rg }} {{ rg.rack_count }}
    {% if prefix.tenant %} {% if prefix.tenant.group %} - {{ prefix.tenant.group.name }} + {{ prefix.tenant.group }} {% endif %} {{ prefix.tenant }} {% elif prefix.vrf.tenant %} {% if prefix.vrf.tenant.group %} - {{ prefix.vrf.tenant.group.name }} + {{ prefix.vrf.tenant.group }} {% endif %} {{ prefix.vrf.tenant }} @@ -75,7 +75,7 @@ {% if prefix.vlan %} {% if prefix.vlan.group %} - {{ prefix.vlan.group.name }} + {{ prefix.vlan.group }} {% endif %} {{ prefix.vlan.display_name }} diff --git a/netbox/templates/ipam/vlan.html b/netbox/templates/ipam/vlan.html index 4298e9b46..ae353e823 100644 --- a/netbox/templates/ipam/vlan.html +++ b/netbox/templates/ipam/vlan.html @@ -9,9 +9,9 @@
  • {{ vlan.site }}
  • {% endif %} {% if vlan.group %} -
  • {{ vlan.group.name }}
  • +
  • {{ vlan.group }}
  • {% endif %} -
  • {{ vlan.name }} ({{ vlan.vid }})
  • +
  • {{ vlan }}
  • @@ -68,7 +68,7 @@
    Group {% if vlan.group %} - {{ vlan.group.name }} + {{ vlan.group }} {% else %} None {% endif %} @@ -87,7 +87,7 @@ {% if vlan.tenant %} {% if vlan.tenant.group %} - {{ vlan.tenant.group.name }} + {{ vlan.tenant.group }} {% endif %} {{ vlan.tenant }} diff --git a/netbox/templates/virtualization/inc/interface.html b/netbox/templates/virtualization/inc/interface.html index ef37561bb..bea4758d7 100644 --- a/netbox/templates/virtualization/inc/interface.html +++ b/netbox/templates/virtualization/inc/interface.html @@ -5,7 +5,7 @@ - {{ iface.name }} + {{ iface }} {% if iface.description %} {% endif %} diff --git a/netbox/templates/virtualization/virtualmachine.html b/netbox/templates/virtualization/virtualmachine.html index cb83142ea..194461a90 100644 --- a/netbox/templates/virtualization/virtualmachine.html +++ b/netbox/templates/virtualization/virtualmachine.html @@ -49,7 +49,7 @@ - + @@ -82,7 +82,7 @@ - {% if selectable and perms.dcim.change_consoleserverport or perms.dcim.delete_consoleserverport %} + {% if perms.dcim.change_consoleserverport or perms.dcim.delete_consoleserverport %} @@ -7,7 +7,6 @@ - {% if csp.connected_console %} {% endif %} - - {% if selectable and perms.dcim.change_devicebay or perms.dcim.delete_devicebay %} + {% if perms.dcim.change_devicebay or perms.dcim.delete_devicebay %} diff --git a/netbox/templates/dcim/inc/interface.html b/netbox/templates/dcim/inc/interface.html index c6d36dbef..c67bc18ee 100644 --- a/netbox/templates/dcim/inc/interface.html +++ b/netbox/templates/dcim/inc/interface.html @@ -1,21 +1,37 @@ - - {% if selectable and perms.dcim.change_interface or perms.dcim.delete_interface %} + + + {# Checkbox #} + {% if perms.dcim.change_interface or perms.dcim.delete_interface %} {% endif %} + + {# Icon and name #} + + + {# LAG #} + + {# Description #} + + + {# MTU #} + + {# MAC address #} + + {# Connection or type #} {% if iface.is_lag %} {% endif %} - -{% with iface.ip_addresses.all as ipaddresses %} - {% if ipaddresses %} - - {% if selectable and perms.dcim.change_interface or perms.dcim.delete_interface %} - - + + {# Placeholder #} + {% if perms.dcim.change_interface or perms.dcim.delete_interface %} + + {% endif %} + + {# IP address #} + + + {# Primary, status, role #} + - - {% endif %} -{% endwith %} + {{ ip.get_status_display }} + + + {# VRF #} + + + {# Description #} + + + {# Buttons #} + + +{% endfor %} diff --git a/netbox/templates/dcim/inc/poweroutlet.html b/netbox/templates/dcim/inc/poweroutlet.html index b462e1ca1..306977207 100644 --- a/netbox/templates/dcim/inc/poweroutlet.html +++ b/netbox/templates/dcim/inc/poweroutlet.html @@ -1,5 +1,5 @@ - {% if selectable and perms.dcim.change_poweroutlet or perms.dcim.delete_poweroutlet %} + {% if perms.dcim.change_poweroutlet or perms.dcim.delete_poweroutlet %} @@ -7,7 +7,6 @@ - {% if po.connected_port %} {% endif %} - - {% if selectable and perms.dcim.change_interface or perms.dcim.delete_interface %} - - {% endif %} - - - - - -{% for ip in iface.ip_addresses.all %} - - {% if selectable and perms.dcim.change_interface or perms.dcim.delete_interface %} - - {% endif %} - - - - - -{% endfor %} diff --git a/netbox/templates/virtualization/virtualmachine.html b/netbox/templates/virtualization/virtualmachine.html index 194461a90..7f9e948e7 100644 --- a/netbox/templates/virtualization/virtualmachine.html +++ b/netbox/templates/virtualization/virtualmachine.html @@ -41,7 +41,7 @@

    {% block title %}{{ vm }}{% endblock %}

    {% include 'inc/created_updated.html' with obj=vm %}
    -
    +
    Virtual Machine @@ -123,6 +123,21 @@
    Name{{ vm.name }}{{ vm }}
    Status {% if vm.tenant %} {% if vm.tenant.group %} - {{ vm.tenant.group.name }} + {{ vm.tenant.group }} {% endif %} {{ vm.tenant }} From 626fbd1d1045db7c7c45a6fc0d9662838d403cde Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 2 Nov 2017 13:15:25 -0400 Subject: [PATCH 03/25] Closes #1684: Replaced prefix 'parent' filter with 'within' and 'within_include' --- netbox/ipam/filters.py | 25 ++++++++++++++++++++++--- netbox/ipam/forms.py | 2 +- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/netbox/ipam/filters.py b/netbox/ipam/filters.py index 23a7bdfa5..3c1fe06dd 100644 --- a/netbox/ipam/filters.py +++ b/netbox/ipam/filters.py @@ -102,9 +102,18 @@ class PrefixFilter(CustomFieldFilterSet, django_filters.FilterSet): method='search', label='Search', ) + # TODO: Deprecate in v2.3.0 parent = django_filters.CharFilter( - method='search_by_parent', - label='Parent prefix', + method='search_within_include', + label='Parent prefix (deprecated)', + ) + within = django_filters.CharFilter( + method='search_within', + label='Within prefix', + ) + within_include = django_filters.CharFilter( + method='search_within_include', + label='Within and including prefix', ) mask_length = django_filters.NumberFilter( method='filter_mask_length', @@ -177,7 +186,17 @@ class PrefixFilter(CustomFieldFilterSet, django_filters.FilterSet): pass return queryset.filter(qs_filter) - def search_by_parent(self, queryset, name, value): + def search_within(self, queryset, name, value): + value = value.strip() + if not value: + return queryset + try: + query = str(IPNetwork(value).cidr) + return queryset.filter(prefix__net_contained=query) + except (AddrFormatError, ValueError): + return queryset.none() + + def search_within_include(self, queryset, name, value): value = value.strip() if not value: return queryset diff --git a/netbox/ipam/forms.py b/netbox/ipam/forms.py index 5ed405c8d..839324702 100644 --- a/netbox/ipam/forms.py +++ b/netbox/ipam/forms.py @@ -362,7 +362,7 @@ def prefix_status_choices(): class PrefixFilterForm(BootstrapMixin, CustomFieldFilterForm): model = Prefix q = forms.CharField(required=False, label='Search') - parent = forms.CharField(required=False, label='Parent prefix', widget=forms.TextInput(attrs={ + within_include = forms.CharField(required=False, label='Search within', widget=forms.TextInput(attrs={ 'placeholder': 'Prefix', })) family = forms.ChoiceField(required=False, choices=IP_FAMILY_CHOICES, label='Address family') From c6f3b00f0e1d626f63e7a1bbab67a303bf0546e7 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 2 Nov 2017 13:21:19 -0400 Subject: [PATCH 04/25] Fixes #1676: Correct filtering of child prefixes upon bulk edit/delete from the parent prefix view --- netbox/ipam/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 48e7e8478..684aa4fdc 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -517,6 +517,7 @@ class PrefixView(View): 'parent_prefix_table': parent_prefix_table, 'child_prefix_table': child_prefix_table, 'duplicate_prefix_table': duplicate_prefix_table, + 'bulk_querystring': 'vrf_id={}&within={}'.format(prefix.vrf or '0', prefix.prefix), 'permissions': permissions, 'return_url': prefix.get_absolute_url(), }) From 74cc8c022cbdda0a606dab7fc5f0dd8d1e63c66b Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 2 Nov 2017 13:58:30 -0400 Subject: [PATCH 05/25] Fixes #1650: Correct numeric ordering for interfaces with no alphabetic type --- netbox/dcim/querysets.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/netbox/dcim/querysets.py b/netbox/dcim/querysets.py index 69dbf99dc..3e977ddc6 100644 --- a/netbox/dcim/querysets.py +++ b/netbox/dcim/querysets.py @@ -44,10 +44,10 @@ class InterfaceQuerySet(QuerySet): TYPE_RE = r"SUBSTRING({} FROM '^([^0-9]+)')" ID_RE = r"CAST(SUBSTRING({} FROM '^(?:[^0-9]+)([0-9]+)$') AS integer)" - SLOT_RE = r"CAST(SUBSTRING({} FROM '^(?:[^0-9]+)([0-9]+)\/') AS integer)" - SUBSLOT_RE = r"COALESCE(CAST(SUBSTRING({} FROM '^(?:[^0-9]+)(?:[0-9]+\/)([0-9]+)') AS integer), 0)" - POSITION_RE = r"COALESCE(CAST(SUBSTRING({} FROM '^(?:[^0-9]+)(?:[0-9]+\/){{2}}([0-9]+)') AS integer), 0)" - SUBPOSITION_RE = r"COALESCE(CAST(SUBSTRING({} FROM '^(?:[^0-9]+)(?:[0-9]+\/){{3}}([0-9]+)') AS integer), 0)" + SLOT_RE = r"CAST(SUBSTRING({} FROM '^(?:[^0-9]+)?([0-9]+)\/') AS integer)" + SUBSLOT_RE = r"COALESCE(CAST(SUBSTRING({} FROM '^(?:[^0-9]+)?(?:[0-9]+\/)([0-9]+)') AS integer), 0)" + POSITION_RE = r"COALESCE(CAST(SUBSTRING({} FROM '^(?:[^0-9]+)?(?:[0-9]+\/){{2}}([0-9]+)') AS integer), 0)" + SUBPOSITION_RE = r"COALESCE(CAST(SUBSTRING({} FROM '^(?:[^0-9]+)?(?:[0-9]+\/){{3}}([0-9]+)') AS integer), 0)" CHANNEL_RE = r"COALESCE(CAST(SUBSTRING({} FROM ':([0-9]+)(\.[0-9]+)?$') AS integer), 0)" VC_RE = r"COALESCE(CAST(SUBSTRING({} FROM '\.([0-9]+)$') AS integer), 0)" From 480134302fd906f154d605bca324513631ac7ef2 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 3 Nov 2017 10:10:08 -0400 Subject: [PATCH 06/25] Refreshed contributing docs --- .github/ISSUE_TEMPLATE.md | 19 ++++-- CONTRIBUTING.md | 126 ++++++++++++++++++-------------------- 2 files changed, 72 insertions(+), 73 deletions(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 0027599f4..d3b30bacf 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -4,8 +4,9 @@ remove the "is:open" filter from the search bar to include closed issues. Check the appropriate type for your issue below by placing an x between the - brackets. If none of the below apply, please raise your issue for - discussion on our mailing list: + brackets. For assistance with installation issues, or for any other issues + other than those listed below, please raise your topic for discussion on + our mailing list: https://groups.google.com/forum/#!forum/netbox-discuss @@ -20,7 +21,9 @@ ### Environment * Python version: @@ -28,8 +31,8 @@ ### Description diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2be85cf64..7ce2d519a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,8 +1,8 @@ ## Getting Help If you encounter any issues installing or using NetBox, try one of the -following resources to get assistance. Please **do not** open a GitHub -issue except to report bugs or request features. +following resources to get assistance. Please **do not** open a GitHub issue +except to report bugs or request features. ### Mailing List @@ -13,35 +13,32 @@ installation. You can find us [here](https://groups.google.com/forum/#!forum/net ### Freenode IRC For real-time discussion, you can join the #netbox channel on [Freenode](https://freenode.net/). -You can connect to Freenode at irc.freenode.net using an IRC client, or -you can use their [webchat client](https://webchat.freenode.net/). +You can connect to Freenode at irc.freenode.net using an IRC client, or you can +use their [webchat client](https://webchat.freenode.net/). ## Reporting Bugs * First, ensure that you've installed the [latest stable version](https://github.com/digitalocean/netbox/releases) of -NetBox. If you're running an older version, it's possible that the bug -has already been fixed. +NetBox. If you're running an older version, it's possible that the bug has +already been fixed. -* Next, check the GitHub [issues list](https://github.com/digitalocean/netbox/issues) to see if the bug you've found has -already been reported. If you think you may be experiencing a reported -issue that hasn't already been resolved, please click "add a reaction" -in the top right corner of the issue and add a thumbs up (+1). You might -also want to add a comment describing how it's affecting your -installation. This will allow us to prioritize bugs based on how many -users are affected. +* Next, check the GitHub [issues list](https://github.com/digitalocean/netbox/issues) to see if the bug you've found has already +been reported. If you think you may be experiencing a reported issue that +hasn't already been resolved, please click "add a reaction" in the top right +corner of the issue and add a thumbs up (+1). You mightalso want to add a +comment describing how it's affecting your installation. This will allow us to +prioritize bugs based on how many users are affected. -* If you haven't found an existing issue that describes your suspected -bug, please inquire about it on the mailing list. **Do not** file an -issue until you have received confirmation that it is in fact a bug. -Invalid issues are very distracting and slow the pace at which NetBox is -developed. +* If you haven't found an existing issue that describes your suspected bug, +please inquire about it on the mailing list. **Do not** file an issue until you +have received confirmation that it is in fact a bug. Invalid issues are very +distracting and slow the pace at which NetBox is developed. -* When submitting an issue, please be as descriptive as possible. Be -sure to include: +* When submitting an issue, please be as descriptive as possible. Be sure to +include: * The environment in which NetBox is running - * The exact steps that can be taken to reproduce the issue (if - applicable) + * The exact steps that can be taken to reproduce the issue (if applicable) * Any error messages generated * Screenshots (if applicable) @@ -49,71 +46,64 @@ sure to include: The issue will be reviewed by a moderator after submission and the appropriate labels will be applied. -* Keep in mind that we prioritize bugs based on their severity and how -much work is required to resolve them. It may take some time for someone -to address your issue. +* Keep in mind that we prioritize bugs based on their severity and how much +work is required to resolve them. It may take some time for someone to address +your issue. ## Feature Requests -* First, check the GitHub [issues list](https://github.com/digitalocean/netbox/issues) to see if the feature you're -requesting is already listed. (Be sure to search closed issues as well, -since some feature requests are rejected.) If the feature you'd like to -see has already been requested, click "add a reaction" in the top right -corner of the issue and add a thumbs up (+1). This ensures that the -issue has a better chance of making it onto the roadmap. Also feel free -to add a comment with any additional justification for the feature. -(However, note that comments with no substance other than a "+1" will be -deleted. Please use GitHub's reactions feature to indicate your -support.) +* First, check the GitHub [issues list](https://github.com/digitalocean/netbox/issues) to see if the feature you're requesting +is already listed. (Be sure to search closed issues as well, since some +feature requests have been rejected.) If the feature you'd like to see has +already been requested and is open, click "add a reaction" in the top right +corner of the issue and add a thumbs up (+1). This ensures that the issue has +a better chance of receiving attention. Also feel free to add a comment with +any additional justification for the feature. (However, note that comments with +no substance other than a "+1" will be deleted. Please use GitHub's reactions +feature to indicate your support.) -* While suggestions for new features are welcome, it's important to -limit the scope of NetBox's feature set to avoid feature creep. For -example, the following features would be firmly out of scope for NetBox: - - * Ticket management - * Network state monitoring - * Acting as a DNS server - * Acting as an authentication server +* Due to an excessive backlog of feature requests, we are not currently +accepting any proposals which substantially extend NetBox's functionality +beyond its current feature set. This includes the introduction of any new views +or models which have not already been proposed in an existing feature request. * Before filing a new feature request, consider raising your idea on the -mailing list first. Feedback you receive there will help validate and -shape the proposed feature before filing a formal issue. +mailing list first. Feedback you receive there will help validate and shape the +proposed feature before filing a formal issue. -* Good feature requests are very narrowly defined. Be sure to enumerate -specific functionality and data schema. The more effort you put into -writing a feature request, the better its chance is of being +* Good feature requests are very narrowly defined. Be sure to thoroughly +describe the functionality and data model(s) being proposed. The more effort +you put into writing a feature request, the better its chance is of being implemented. Overly broad feature requests will be closed. * When submitting a feature request on GitHub, be sure to include the following: * A detailed description of the proposed functionality - * A use case for the feature; who would use it and what value it - would add to NetBox - * A rough description of changes necessary to the database schema - (if applicable) - * Any third-party libraries or other resources which would be - involved + * A use case for the feature; who would use it and what value it would add + to NetBox + * A rough description of changes necessary to the database schema (if + applicable) + * Any third-party libraries or other resources which would be involved -* Please avoid prepending any sort of tag (e.g. "[Feature]") to the issue title. -The issue will be reviewed by a moderator after submission and the appropriate -labels will be applied. +* Please avoid prepending any sort of tag (e.g. "[Feature]") to the issue +title. The issue will be reviewed by a moderator after submission and the +appropriate labels will be applied. ## 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. -* When submitting a pull request, please be sure to work off of the -`develop` branch, rather than `master`. In NetBox, the `develop` branch -is used for ongoing development, while `master` is used for tagging new -stable releases. +* 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 +development, while `master` is used for tagging new stable releases. -* All code submissions should meet the following criteria (CI will -enforce these checks): +* All code submissions should meet the following criteria (CI will enforce +these checks): * Python syntax is valid * All tests pass when run with `./manage.py test` From f2fbd92f78a5179a97435e2a969b90e36500859b Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 3 Nov 2017 10:50:02 -0400 Subject: [PATCH 07/25] Tweaked the issue template --- .github/ISSUE_TEMPLATE.md | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index d3b30bacf..fb2863947 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -11,12 +11,16 @@ https://groups.google.com/forum/#!forum/netbox-discuss Please note that issues which do not fall under any of the below categories - will be closed. + will be closed. Due to an excessive backlog of feature requests, we are + not currently accepting any proposals which extend NetBox's feature scope. + + Do not prepend any sort of tag to your issue's title. An administrator will + review your issue and assign labels as appropriate. ---> ### Issue type -[ ] Feature request -[ ] Bug report -[ ] Documentation +[ ] Feature request +[ ] Bug report +[ ] Documentation ### Description From f77bf72de824bd86a60feb61ebb9b13264f5f9d5 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 3 Nov 2017 13:24:31 -0400 Subject: [PATCH 08/25] Closes #1683: Replaced default 500 handler with custom middleware to provide preliminary troubleshooting assistance --- netbox/netbox/settings.py | 1 + netbox/netbox/urls.py | 6 +-- netbox/netbox/views.py | 20 ---------- netbox/templates/500.html | 19 ++++++--- netbox/templates/exceptions/import_error.html | 18 +++++++++ .../exceptions/permission_error.html | 12 ++++++ .../exceptions/programming_error.html | 17 ++++++++ netbox/utilities/middleware.py | 40 ++++++++++++++++++- 8 files changed, 101 insertions(+), 32 deletions(-) create mode 100644 netbox/templates/exceptions/import_error.html create mode 100644 netbox/templates/exceptions/permission_error.html create mode 100644 netbox/templates/exceptions/programming_error.html diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 0283731fe..d4fbc2359 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -148,6 +148,7 @@ MIDDLEWARE = ( 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.security.SecurityMiddleware', + 'utilities.middleware.ExceptionHandlingMiddleware', 'utilities.middleware.LoginRequiredMiddleware', 'utilities.middleware.APIVersionMiddleware', ) diff --git a/netbox/netbox/urls.py b/netbox/netbox/urls.py index 1c8051c06..3c0f8de9f 100644 --- a/netbox/netbox/urls.py +++ b/netbox/netbox/urls.py @@ -7,11 +7,10 @@ from django.conf.urls import include, url from django.contrib import admin from django.views.static import serve -from netbox.views import APIRootView, handle_500, HomeView, SearchView, trigger_500 +from netbox.views import APIRootView, HomeView, SearchView from users.views import LoginView, LogoutView -handler500 = handle_500 swagger_view = get_swagger_view(title='NetBox API') _patterns = [ @@ -48,9 +47,6 @@ _patterns = [ # Serving static media in Django to pipe it through LoginRequiredMiddleware url(r'^media/(?P.*)$', serve, {'document_root': settings.MEDIA_ROOT}), - # Error testing - url(r'^500/$', trigger_500), - # Admin url(r'^admin/', admin.site.urls), diff --git a/netbox/netbox/views.py b/netbox/netbox/views.py index f41ff53c2..ce1171235 100644 --- a/netbox/netbox/views.py +++ b/netbox/netbox/views.py @@ -247,23 +247,3 @@ class APIRootView(APIView): ('tenancy', reverse('tenancy-api:api-root', request=request, format=format)), ('virtualization', reverse('virtualization-api:api-root', request=request, format=format)), ))) - - -def handle_500(request): - """ - Custom server error handler - """ - type_, error, traceback = sys.exc_info() - return render(request, '500.html', { - 'exception': str(type_), - 'error': error, - }, status=500) - - -def trigger_500(request): - """ - Hot-wired method of triggering a server error to test reporting - """ - raise Exception( - "Congratulations, you've triggered an exception! Go tell all your friends what an exceptional person you are." - ) diff --git a/netbox/templates/500.html b/netbox/templates/500.html index 2857325b9..1da608a48 100644 --- a/netbox/templates/500.html +++ b/netbox/templates/500.html @@ -12,7 +12,7 @@
    -
    +
    @@ -21,13 +21,20 @@
    -

    There was a problem with your request. This error has been logged and administrative staff have - been notified. Please return to the home page and try again.

    -

    If you are responsible for this installation, please consider - filing a bug report. Additional - information is provided below:

    + {% block message %} +

    + There was a problem with your request. Please contact an administrator. +

    + {% endblock %} +
    +

    + The complete exception is provided below: +

    {{ exception }}
    {{ error }}
    +

    + If further assistance is required, please post to the NetBox mailing list. +

    diff --git a/netbox/templates/exceptions/import_error.html b/netbox/templates/exceptions/import_error.html new file mode 100644 index 000000000..20dc3728b --- /dev/null +++ b/netbox/templates/exceptions/import_error.html @@ -0,0 +1,18 @@ +{% extends '500.html' %} + +{% block message %} +

    + A module import error occurred during this request. Common causes include the following: +

    +

    + Missing required packages - This installation of NetBox might be missing one or more required + Python packages. These packages are listed in requirements.txt and are normally installed as part + of the installation or upgrade process. To verify installed packages, run pip freeze from the + console and compare the output to the list of required packages. +

    +

    + WSGI service not restarted after upgrade - If this installation has recently been upgraded, + check that the WSGI service (e.g. gunicorn or uWSGI) has been restarted. This ensures that the new code is + running. +

    +{% endblock %} diff --git a/netbox/templates/exceptions/permission_error.html b/netbox/templates/exceptions/permission_error.html new file mode 100644 index 000000000..9898f25dc --- /dev/null +++ b/netbox/templates/exceptions/permission_error.html @@ -0,0 +1,12 @@ +{% extends '500.html' %} + +{% block message %} +

    + A file permission error was detected while processing this request. Common causes include the following: +

    +

    + Insufficient write permission to the media root - The configured + media root is {{ settings.MEDIA_ROOT }}. Ensure that the user NetBox runs as has access to write + files to all locations within this path. +

    +{% endblock %} diff --git a/netbox/templates/exceptions/programming_error.html b/netbox/templates/exceptions/programming_error.html new file mode 100644 index 000000000..6f10c2e27 --- /dev/null +++ b/netbox/templates/exceptions/programming_error.html @@ -0,0 +1,17 @@ +{% extends '500.html' %} + +{% block message %} +

    + A database programming error was detected while processing this request. Common causes include the following: +

    +

    + Database migrations missing - When upgrading to a new NetBox release, the upgrade script must + be run to apply any new database migrations. You can run migrations manually by executing + python3 manage.py migrate from the command line. +

    +

    + Unsupported PostgreSQL version - Ensure that PostgreSQL version 9.4 or higher is in use. You + can check this by connecting to the database using NetBox's credentials and issuing a query for + SELECT VERSION(). +

    +{% endblock %} diff --git a/netbox/utilities/middleware.py b/netbox/utilities/middleware.py index d5d151c12..b79ecd041 100644 --- a/netbox/utilities/middleware.py +++ b/netbox/utilities/middleware.py @@ -1,7 +1,10 @@ from __future__ import unicode_literals +import sys -from django.http import HttpResponseRedirect from django.conf import settings +from django.db import ProgrammingError +from django.http import HttpResponseRedirect +from django.shortcuts import render from django.urls import reverse @@ -39,3 +42,38 @@ class APIVersionMiddleware(object): if request.path_info.startswith(api_path): response['API-Version'] = settings.REST_FRAMEWORK_VERSION return response + + +class ExceptionHandlingMiddleware(object): + """ + Intercept certain exceptions which are likely indicative of installation issues and provide helpful instructions + to the user. + """ + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + return self.get_response(request) + + def process_exception(self, request, exception): + + # Raise exceptions if in debug mode + if settings.DEBUG: + raise exception + + # Determine the type of exception + if isinstance(exception, ProgrammingError): + template_name = 'exceptions/programming_error.html' + elif isinstance(exception, ImportError): + template_name = 'exceptions/import_error.html' + elif isinstance(exception, PermissionError): + template_name = 'exceptions/permission_error.html' + else: + template_name = '500.html' + + # Return an error message + type_, error, traceback = sys.exc_info() + return render(request, template_name, { + 'exception': str(type_), + 'error': error, + }, status=500) From 368c30ef9de3257fd2b070f8c510e464d6656f6a Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 3 Nov 2017 14:36:28 -0400 Subject: [PATCH 09/25] Removed unused imports --- netbox/dcim/models.py | 3 +-- netbox/ipam/models.py | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index daa4c80cd..0ff1434c0 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -6,7 +6,6 @@ from mptt.models import MPTTModel, TreeForeignKey from django.conf import settings from django.contrib.auth.models import User -from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.fields import GenericRelation from django.contrib.postgres.fields import ArrayField from django.core.exceptions import ValidationError @@ -17,7 +16,7 @@ from django.urls import reverse from django.utils.encoding import python_2_unicode_compatible from circuits.models import Circuit -from extras.models import CustomFieldModel, CustomField, CustomFieldValue, ImageAttachment +from extras.models import CustomFieldModel, CustomFieldValue, ImageAttachment from extras.rpc import RPC_CLIENTS from tenancy.models import Tenant from utilities.fields import ColorField, NullableCharField diff --git a/netbox/ipam/models.py b/netbox/ipam/models.py index 73a522d63..c6efdf646 100644 --- a/netbox/ipam/models.py +++ b/netbox/ipam/models.py @@ -3,7 +3,6 @@ import netaddr from django.conf import settings from django.contrib.contenttypes.fields import GenericRelation -from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ValidationError from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models From 5d19a9f50f39f66d7ef5582cad433313ac027238 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 3 Nov 2017 16:58:56 -0400 Subject: [PATCH 10/25] Rearranged device/VM view and expanded component lists --- netbox/dcim/views.py | 1 - netbox/ipam/constants.py | 12 +- netbox/ipam/models.py | 3 + netbox/project-static/css/base.css | 3 + netbox/templates/dcim/device.html | 170 +++++++++++------- .../templates/dcim/inc/consoleserverport.html | 5 +- netbox/templates/dcim/inc/devicebay.html | 2 +- netbox/templates/dcim/inc/interface.html | 140 ++++++++------- netbox/templates/dcim/inc/poweroutlet.html | 5 +- .../virtualization/inc/interface.html | 70 -------- .../virtualization/virtualmachine.html | 50 ++++-- 11 files changed, 234 insertions(+), 227 deletions(-) delete mode 100644 netbox/templates/virtualization/inc/interface.html diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 3120197d2..f0f3ffa35 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -1,5 +1,4 @@ from __future__ import unicode_literals -import re from natsort import natsorted from operator import attrgetter diff --git a/netbox/ipam/constants.py b/netbox/ipam/constants.py index 8413d38c3..e2b98a1ef 100644 --- a/netbox/ipam/constants.py +++ b/netbox/ipam/constants.py @@ -61,7 +61,7 @@ VLAN_STATUS_CHOICES = ( (VLAN_STATUS_DEPRECATED, 'Deprecated') ) -# Bootstrap CSS classes for various statuses +# Bootstrap CSS classes STATUS_CHOICE_CLASSES = { 0: 'default', 1: 'primary', @@ -70,6 +70,16 @@ STATUS_CHOICE_CLASSES = { 4: 'warning', 5: 'success', } +ROLE_CHOICE_CLASSES = { + 10: 'default', + 20: 'primary', + 30: 'warning', + 40: 'success', + 41: 'success', + 42: 'success', + 43: 'success', + 44: 'success', +} # IP protocols (for services) IP_PROTOCOL_TCP = 6 diff --git a/netbox/ipam/models.py b/netbox/ipam/models.py index c6efdf646..5a9a16bb1 100644 --- a/netbox/ipam/models.py +++ b/netbox/ipam/models.py @@ -460,6 +460,9 @@ class IPAddress(CreatedUpdatedModel, CustomFieldModel): def get_status_class(self): return STATUS_CHOICE_CLASSES[self.status] + def get_role_class(self): + return ROLE_CHOICE_CLASSES[self.role] + @python_2_unicode_compatible class VLANGroup(models.Model): diff --git a/netbox/project-static/css/base.css b/netbox/project-static/css/base.css index fb9687d3a..dca3cd948 100644 --- a/netbox/project-static/css/base.css +++ b/netbox/project-static/css/base.css @@ -395,6 +395,9 @@ table.reports td.stats label { } /* Misc */ +.text-nowrap { + white-space: nowrap; +} .banner-bottom { margin-bottom: 50px; } diff --git a/netbox/templates/dcim/device.html b/netbox/templates/dcim/device.html index 056396991..549b93465 100644 --- a/netbox/templates/dcim/device.html +++ b/netbox/templates/dcim/device.html @@ -7,7 +7,7 @@ {% block content %} {% include 'dcim/inc/device_header.html' with active_tab='info' %}
    -
    +
    Device @@ -172,6 +172,69 @@ {% with device.get_custom_fields as custom_fields %} {% include 'inc/custom_fields_panel.html' %} {% endwith %} +
    +
    + Comments +
    +
    + {% if device.comments %} + {{ device.comments|gfm }} + {% else %} + None + {% endif %} +
    +
    +
    +
    +
    +
    + Console / Power +
    + + {% for cp in console_ports %} + {% include 'dcim/inc/consoleport.html' %} + {% empty %} + {% if device.device_type.console_port_templates.exists %} + + + + {% endif %} + {% endfor %} + {% for pp in power_ports %} + {% include 'dcim/inc/powerport.html' %} + {% empty %} + {% if device.device_type.power_port_templates.exists %} + + + + {% endif %} + {% endfor %} +
    + No console ports defined + {% if perms.dcim.add_consoleport %} + + {% endif %} +
    + No power ports defined + {% if perms.dcim.add_powerport %} + + {% endif %} +
    + {% if perms.dcim.add_interface or perms.dcim.add_consoleport or perms.dcim.add_powerport %} + + {% endif %} +
    {% if request.user.is_authenticated %}
    @@ -224,67 +287,6 @@
    {% endif %}
    -
    -
    - Console / Power -
    - - {% for cp in console_ports %} - {% include 'dcim/inc/consoleport.html' %} - {% empty %} - {% if device.device_type.console_port_templates.exists %} - - - - {% endif %} - {% endfor %} - {% for pp in power_ports %} - {% include 'dcim/inc/powerport.html' %} - {% empty %} - {% if device.device_type.power_port_templates.exists %} - - - - {% endif %} - {% endfor %} -
    - No console ports defined - {% if perms.dcim.add_consoleport %} - - {% endif %} -
    - No power ports defined - {% if perms.dcim.add_powerport %} - - {% endif %} -
    - {% if perms.dcim.add_interface or perms.dcim.add_consoleport or perms.dcim.add_powerport %} - - {% endif %} -
    -
    -
    - Comments -
    -
    - {% if device.comments %} - {{ device.comments|gfm }} - {% else %} - None - {% endif %} -
    -
    Images @@ -326,7 +328,9 @@ {% endif %}
    -
    +
    +
    +
    {% if device_bays or device.device_type.is_parent_device %} {% if perms.dcim.delete_devicebay %}
    @@ -350,7 +354,7 @@
    {% for devicebay in device_bays %} - {% include 'dcim/inc/devicebay.html' with selectable=True %} + {% include 'dcim/inc/devicebay.html' %} {% empty %} @@ -405,11 +409,23 @@
    No device bays defined
    + + {% if perms.dcim.change_interface or perms.dcim.delete_interface %} + + {% endif %} + + + + + + + + {% for iface in interfaces %} - {% include 'dcim/inc/interface.html' with selectable=True %} + {% include 'dcim/inc/interface.html' %} {% empty %} - + {% endfor %}
    NameLAGDescriptionMTUMAC AddressConnection
    No interfaces definedNo interfaces defined
    @@ -467,8 +483,16 @@
    + + {% if perms.dcim.change_consoleserverport or perms.dcim.delete_consoleserverport %} + + {% endif %} + + + + {% for csp in cs_ports %} - {% include 'dcim/inc/consoleserverport.html' with selectable=True %} + {% include 'dcim/inc/consoleserverport.html' %} {% empty %} @@ -524,12 +548,20 @@
    NameConnection
    No console server ports defined
    + + {% if perms.dcim.change_poweroutlet or perms.dcim.delete_poweroutlet %} + + {% endif %} + + + + {% for po in power_outlets %} - {% include 'dcim/inc/poweroutlet.html' with selectable=True %} + {% include 'dcim/inc/poweroutlet.html' %} {% empty %} - + text-nowrap {% endfor %}
    NameConnection
    No power outlets defined
    {% if perms.dcim.add_poweroutlet or perms.dcim.delete_poweroutlet %} diff --git a/netbox/templates/dcim/inc/consoleserverport.html b/netbox/templates/dcim/inc/consoleserverport.html index 62da09245..aed27d62a 100644 --- a/netbox/templates/dcim/inc/consoleserverport.html +++ b/netbox/templates/dcim/inc/consoleserverport.html @@ -1,5 +1,5 @@
    {{ csp }} {{ csp.connected_console.device }} @@ -20,7 +19,7 @@ Not connected + {% if perms.dcim.change_consoleserverport %} {% if csp.connected_console %} {% if csp.connected_console.connection_status %} diff --git a/netbox/templates/dcim/inc/devicebay.html b/netbox/templates/dcim/inc/devicebay.html index f974f9fcb..2a1a9708d 100644 --- a/netbox/templates/dcim/inc/devicebay.html +++ b/netbox/templates/dcim/inc/devicebay.html @@ -1,5 +1,5 @@
    + + + {{ iface }} + + - - {{ iface }} {% if iface.lag %} - {{ iface.lag }} - {% endif %} - {% if iface.description %} - + {{ iface.lag }} {% endif %} {{ iface.description }}{{ iface.mtu|default:"" }}{{ iface.mac_address|default:"" }} LAG interface
    @@ -55,7 +71,9 @@ Not connected
    + + {# Buttons #} + {% if show_graphs %} {% if iface.circuit_termination or iface.connection %} {% else %} - + {% endif %} {% endif %}
    - {% else %} - +{% for ip in iface.ip_addresses.all %} +
    + {{ ip }} + + {% if device.primary_ip4 == ip or device.primary_ip6 == ip %} + Primary {% endif %} - - {% for ip in ipaddresses %} - - - - - - - - {% endfor %} -
    - {{ ip }} - {% if ip.description %} - - {% endif %} - - {% if device.primary_ip4 == ip or device.primary_ip6 == ip %} - Primary - {% endif %} - - {% if ip.vrf %} - {{ ip.vrf }} - {% else %} - Global table - {% endif %} - - {{ ip.get_status_display }} - - {% if perms.ipam.change_ipaddress %} - - - - {% endif %} - {% if perms.ipam.delete_ipaddress %} - - - - {% endif %} -
    -
    + {% if ip.vrf %} + {{ ip.vrf.name }} + {% else %} + Global + {% endif %} + + {{ ip.description }} + + {% if perms.ipam.change_ipaddress %} + + + + {% endif %} + {% if perms.ipam.delete_ipaddress %} + + + + {% endif %} +
    {{ po }} {{ po.connected_port.device }} @@ -20,7 +19,7 @@ Not connected + {% if perms.dcim.change_poweroutlet %} {% if po.connected_port %} {% if po.connected_port.connection_status %} diff --git a/netbox/templates/virtualization/inc/interface.html b/netbox/templates/virtualization/inc/interface.html deleted file mode 100644 index bea4758d7..000000000 --- a/netbox/templates/virtualization/inc/interface.html +++ /dev/null @@ -1,70 +0,0 @@ -
    - - - {{ iface }} - {% if iface.description %} - - {% endif %} - {{ iface.mtu|default:"" }}{{ iface.mac_address|default:"" }} - {% if perms.ipam.add_ipaddress %} - - - - {% endif %} - {% if perms.dcim.change_interface %} - - - - {% endif %} - {% if perms.dcim.delete_interface %} - - - - {% endif %} -
    - {{ ip }} - {% if ip.description %} - - {% endif %} - {% if vm.primary_ip4 == ip or vm.primary_ip6 == ip %} - Primary - {% endif %} - - {% if ip.vrf %} - {{ ip.vrf }} - {% else %} - Global - {% endif %} - - {{ ip.get_status_display }} - - {% if perms.ipam.change_ipaddress %} - - - - {% endif %} - {% if perms.ipam.delete_ipaddress %} - - - - {% endif %} -
    + {% include 'inc/custom_fields_panel.html' with custom_fields=vm.get_custom_fields %} +
    +
    + Comments +
    +
    + {% if vm.comments %} + {{ vm.comments|gfm }} + {% else %} + None + {% endif %} +
    +
    + +
    Cluster @@ -204,21 +219,10 @@
    {% endif %}
    - {% include 'inc/custom_fields_panel.html' with custom_fields=vm.get_custom_fields %} -
    -
    - Comments -
    -
    - {% if vm.comments %} - {{ vm.comments|gfm }} - {% else %} - None - {% endif %} -
    -
    -
    +
    +
    +
    {% if perms.dcim.change_interface or perms.dcim.delete_interface %} {% csrf_token %} @@ -244,11 +248,23 @@
    + + {% if perms.dcim.change_interface or perms.dcim.delete_interface %} + + {% endif %} + + + + + + + + {% for iface in interfaces %} - {% include 'virtualization/inc/interface.html' with selectable=True %} + {% include 'dcim/inc/interface.html' with device=vm %} {% empty %} - + {% endfor %}
    NameLAGDescriptionMTUMAC AddressConnection
    No interfaces definedNo interfaces defined
    From 73cd76932a08ac1cdbb7d7bac94a85b254514459 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 3 Nov 2017 17:00:08 -0400 Subject: [PATCH 11/25] Closes #1679: Added IP address roles to device/VM interface lists --- netbox/templates/dcim/inc/interface.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/netbox/templates/dcim/inc/interface.html b/netbox/templates/dcim/inc/interface.html index c67bc18ee..b0c35a0e9 100644 --- a/netbox/templates/dcim/inc/interface.html +++ b/netbox/templates/dcim/inc/interface.html @@ -150,6 +150,9 @@ Primary {% endif %} {{ ip.get_status_display }} + {% if ip.role %} + {{ ip.get_role_display }} + {% endif %}