From ac546a97110362367b0b9a78c98aacc3f587f5ea Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 14 Aug 2018 11:47:54 -0400 Subject: [PATCH 001/191] Closes #2000: Remove support for Python 2 --- .travis.yml | 1 - README.md | 2 -- base_requirements.txt | 2 +- docs/additional-features/netbox-shell.md | 2 +- docs/installation/upgrading.md | 7 ------- netbox/extras/reports.py | 23 ++++++----------------- netbox/netbox/settings.py | 15 +++++++-------- netbox/utilities/middleware.py | 6 +----- requirements.txt | 2 +- upgrade.sh | 16 ---------------- 10 files changed, 17 insertions(+), 59 deletions(-) diff --git a/.travis.yml b/.travis.yml index 33abc8425..13c6d406b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,6 @@ addons: postgresql: "9.4" language: python python: - - "2.7" - "3.5" install: - pip install -r requirements.txt diff --git a/README.md b/README.md index 28673bf36..9955316b9 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,6 @@ or join us in the #netbox Slack channel on [NetworkToCode](https://networktocode ### Build Status -NetBox is built against both Python 2.7 and 3.5. Python 3.5 or higher is strongly recommended. - | | status | |-------------|------------| | **master** | [![Build Status](https://travis-ci.org/digitalocean/netbox.svg?branch=master)](https://travis-ci.org/digitalocean/netbox) | diff --git a/base_requirements.txt b/base_requirements.txt index 6012ffa6c..5f3e0eb80 100644 --- a/base_requirements.txt +++ b/base_requirements.txt @@ -1,5 +1,5 @@ # django-filter-1.1.0 breaks with Django-2.1 -Django>=1.11,<2.1 +Django>=2.0,<2.1 django-cors-headers django-debug-toolbar # django-filter-2.0.0 drops Python 2 support (blocked by #2000) diff --git a/docs/additional-features/netbox-shell.md b/docs/additional-features/netbox-shell.md index 5afd7876d..2ebea5ce5 100644 --- a/docs/additional-features/netbox-shell.md +++ b/docs/additional-features/netbox-shell.md @@ -9,7 +9,7 @@ This will launch a customized version of [the built-in Django shell](https://doc ``` $ ./manage.py nbshell ### NetBox interactive shell (jstretch-laptop) -### Python 2.7.6 | Django 1.11.3 | NetBox 2.1.0-dev +### Python 3.5.2 | Django 2.0.8 | NetBox 2.4.3 ### lsmodels() will show available models. Use help() for more info. ``` diff --git a/docs/installation/upgrading.md b/docs/installation/upgrading.md index bca60ca89..6dc8a3c7a 100644 --- a/docs/installation/upgrading.md +++ b/docs/installation/upgrading.md @@ -64,13 +64,6 @@ Once the new code is in place, run the upgrade script (which may need to be run # ./upgrade.sh ``` -!!! warning - The upgrade script will prefer Python3 and pip3 if both executables are available. To force it to use Python2 and pip, use the `-2` argument as below. Note that Python 2 will no longer be supported in NetBox v2.5. - -```no-highlight -# ./upgrade.sh -2 -``` - This script: * Installs or upgrades any new required Python packages diff --git a/netbox/extras/reports.py b/netbox/extras/reports.py index 52883063c..f6b5d7570 100644 --- a/netbox/extras/reports.py +++ b/netbox/extras/reports.py @@ -4,7 +4,6 @@ from collections import OrderedDict import importlib import inspect import pkgutil -import sys from django.conf import settings from django.utils import timezone @@ -26,22 +25,12 @@ def get_report(module_name, report_name): """ file_path = '{}/{}.py'.format(settings.REPORTS_ROOT, module_name) - # Python 3.5+ - if sys.version_info >= (3, 5): - spec = importlib.util.spec_from_file_location(module_name, file_path) - module = importlib.util.module_from_spec(spec) - try: - spec.loader.exec_module(module) - except FileNotFoundError: - return None - - # Python 2.7 - else: - import imp - try: - module = imp.load_source(module_name, file_path) - except IOError: - return None + spec = importlib.util.spec_from_file_location(module_name, file_path) + module = importlib.util.module_from_spec(spec) + try: + spec.loader.exec_module(module) + except FileNotFoundError: + return None report = getattr(module, report_name, None) if report is None: diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index f6d1b26fd..116192a02 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -7,6 +7,13 @@ import warnings from django.contrib.messages import constants as messages from django.core.exceptions import ImproperlyConfigured +# Check for Python 3.5+ +if sys.version_info < (3, 5): + raise RuntimeError( + "NetBox requires Python 3.5 or higher (current: Python {})".format(sys.version.split()[0]) + ) + +# Check for configuration file try: from netbox import configuration except ImportError: @@ -14,14 +21,6 @@ except ImportError: "Configuration file is not present. Please define netbox/netbox/configuration.py per the documentation." ) -# Raise a deprecation warning for Python 2.x -if sys.version_info[0] < 3: - warnings.warn( - "Support for Python 2 will be removed in NetBox v2.5. Please consider migration to Python 3 at your earliest " - "opportunity. Guidance is available in the documentation at http://netbox.readthedocs.io/.", - DeprecationWarning - ) - VERSION = '2.4.4-dev' BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) diff --git a/netbox/utilities/middleware.py b/netbox/utilities/middleware.py index dafafde24..207968690 100644 --- a/netbox/utilities/middleware.py +++ b/netbox/utilities/middleware.py @@ -72,11 +72,7 @@ class ExceptionHandlingMiddleware(object): custom_template = 'exceptions/programming_error.html' elif isinstance(exception, ImportError): custom_template = 'exceptions/import_error.html' - elif ( - sys.version_info[0] >= 3 and isinstance(exception, PermissionError) - ) or ( - isinstance(exception, OSError) and exception.errno == 13 - ): + elif isinstance(exception, PermissionError): custom_template = 'exceptions/permission_error.html' # Return a custom error message, or fall back to Django's default 500 error handling diff --git a/requirements.txt b/requirements.txt index b3bee6b6d..8fc83c4d7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -Django>=1.11,<2.1 +Django>=2.0,<2.1 django-cors-headers==2.4.0 django-debug-toolbar==1.9.1 django-filter==1.1.0 diff --git a/upgrade.sh b/upgrade.sh index a1930eb3d..3a9f4a864 100755 --- a/upgrade.sh +++ b/upgrade.sh @@ -5,24 +5,8 @@ # Once the script completes, remember to restart the WSGI service (e.g. # gunicorn or uWSGI). -# Determine which version of Python/pip to use. Default to v3 (if available) -# but allow the user to force v2. PYTHON="python3" PIP="pip3" -type $PYTHON >/dev/null 2>&1 && type $PIP >/dev/null 2>&1 || PYTHON="python" PIP="pip" -while getopts ":2" opt; do - case $opt in - 2) - PYTHON="python" - PIP="pip" - echo "Forcing Python/pip v2" - ;; - \?) - echo "Invalid option: -$OPTARG" >&2 - exit - ;; - esac -done # Optionally use sudo if not already root, and always prompt for password # before running the command From 980d62d579ecd369911d705f59a39b56689cd962 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 14 Aug 2018 11:58:42 -0400 Subject: [PATCH 002/191] Removed Python 2 string compatibility --- netbox/circuits/api/serializers.py | 2 -- netbox/circuits/api/urls.py | 2 -- netbox/circuits/api/views.py | 2 -- netbox/circuits/apps.py | 2 -- netbox/circuits/constants.py | 2 -- netbox/circuits/filters.py | 2 -- netbox/circuits/forms.py | 2 -- netbox/circuits/migrations/0001_initial.py | 2 -- ...01_initial_squashed_0010_circuit_status.py | 2 -- .../migrations/0002_auto_20160622_1821.py | 2 -- .../0003_provider_32bit_asn_support.py | 2 -- .../migrations/0004_circuit_add_tenant.py | 2 -- .../0005_circuit_add_upstream_speed.py | 2 -- .../circuits/migrations/0006_terminations.py | 2 -- .../0007_circuit_add_description.py | 2 -- ...termination_interface_protect_on_delete.py | 2 -- .../migrations/0009_unicode_literals.py | 2 -- .../migrations/0010_circuit_status.py | 2 -- netbox/circuits/migrations/0011_tags.py | 2 -- .../migrations/0012_change_logging.py | 2 -- netbox/circuits/models.py | 7 ----- netbox/circuits/signals.py | 2 -- netbox/circuits/tables.py | 2 -- netbox/circuits/tests/test_api.py | 2 -- netbox/circuits/urls.py | 2 -- netbox/circuits/views.py | 2 -- netbox/dcim/api/exceptions.py | 2 -- netbox/dcim/api/serializers.py | 2 -- netbox/dcim/api/urls.py | 2 -- netbox/dcim/api/views.py | 2 -- netbox/dcim/apps.py | 2 -- netbox/dcim/constants.py | 2 -- netbox/dcim/fields.py | 2 -- netbox/dcim/filters.py | 2 -- netbox/dcim/formfields.py | 2 -- netbox/dcim/forms.py | 2 -- netbox/dcim/migrations/0001_initial.py | 2 -- .../migrations/0002_auto_20160622_1821.py | 2 -- ...2_1821_squashed_0022_color_names_to_rgb.py | 2 -- .../migrations/0003_auto_20160628_1721.py | 2 -- .../migrations/0004_auto_20160701_2049.py | 2 -- .../migrations/0005_auto_20160706_1722.py | 2 -- .../0006_add_device_primary_ip4_ip6.py | 2 -- .../migrations/0007_device_copy_primary_ip.py | 2 -- .../0008_device_remove_primary_ip.py | 2 -- .../migrations/0009_site_32bit_asn_support.py | 2 -- ...010_devicebay_installed_device_set_null.py | 2 -- .../migrations/0011_devicetype_part_number.py | 2 -- .../0012_site_rack_device_add_tenant.py | 2 -- .../0013_add_interface_form_factors.py | 2 -- .../migrations/0014_rack_add_type_width.py | 2 -- .../0015_rack_add_u_height_validator.py | 2 -- .../0016_module_add_manufacturer.py | 2 -- netbox/dcim/migrations/0017_rack_add_role.py | 2 -- .../migrations/0018_device_add_asset_tag.py | 2 -- .../migrations/0019_new_iface_form_factors.py | 2 -- .../dcim/migrations/0020_rack_desc_units.py | 2 -- .../dcim/migrations/0021_add_ff_flexstack.py | 2 -- .../migrations/0022_color_names_to_rgb.py | 2 -- .../migrations/0023_devicetype_comments.py | 2 -- ...shed_0043_device_component_name_lengths.py | 2 -- .../0024_site_add_contact_fields.py | 2 -- .../0025_devicetype_add_interface_ordering.py | 2 -- .../migrations/0026_add_rack_reservations.py | 2 -- .../dcim/migrations/0027_device_add_site.py | 2 -- .../0028_device_copy_rack_to_site.py | 2 -- .../migrations/0029_allow_rackless_devices.py | 2 -- .../dcim/migrations/0030_interface_add_lag.py | 2 -- netbox/dcim/migrations/0031_regions.py | 2 -- .../0032_device_increase_name_length.py | 2 -- .../0033_rackreservation_rack_editable.py | 2 -- .../0034_rename_module_to_inventoryitem.py | 2 -- .../0035_device_expand_status_choices.py | 2 -- .../migrations/0036_add_ff_juniper_vcp.py | 2 -- .../dcim/migrations/0037_unicode_literals.py | 2 -- .../migrations/0038_wireless_interfaces.py | 2 -- .../0039_interface_add_enabled_mtu.py | 2 -- ...inventoryitem_add_asset_tag_description.py | 2 -- .../migrations/0041_napalm_integration.py | 2 -- .../migrations/0042_interface_ff_10ge_cx4.py | 2 -- .../0043_device_component_name_lengths.py | 2 -- netbox/dcim/migrations/0044_virtualization.py | 2 -- ...n_squashed_0055_virtualchassis_ordering.py | 2 -- .../migrations/0045_devicerole_vm_role.py | 2 -- .../0046_rack_lengthen_facility_id.py | 2 -- .../0047_more_100ge_form_factors.py | 2 -- netbox/dcim/migrations/0048_rack_serial.py | 2 -- .../0049_rackreservation_change_user.py | 2 -- .../migrations/0050_interface_vlan_tagging.py | 2 -- .../migrations/0051_rackreservation_tenant.py | 2 -- .../dcim/migrations/0052_virtual_chassis.py | 2 -- .../migrations/0053_platform_manufacturer.py | 2 -- .../0054_site_status_timezone_description.py | 2 -- .../0055_virtualchassis_ordering.py | 2 -- netbox/dcim/migrations/0057_tags.py | 2 -- .../0058_relax_rack_naming_constraints.py | 2 -- .../0059_site_latitude_longitude.py | 2 -- netbox/dcim/migrations/0060_change_logging.py | 2 -- netbox/dcim/models.py | 28 ------------------- netbox/dcim/querysets.py | 2 -- netbox/dcim/signals.py | 2 -- netbox/dcim/tables.py | 2 -- netbox/dcim/tests/test_api.py | 2 -- netbox/dcim/tests/test_forms.py | 2 -- netbox/dcim/tests/test_models.py | 2 -- netbox/dcim/urls.py | 2 -- netbox/dcim/views.py | 2 -- netbox/extras/admin.py | 2 -- netbox/extras/api/customfields.py | 2 -- netbox/extras/api/serializers.py | 2 -- netbox/extras/api/urls.py | 2 -- netbox/extras/api/views.py | 2 -- netbox/extras/apps.py | 2 -- netbox/extras/constants.py | 2 -- netbox/extras/filters.py | 2 -- netbox/extras/forms.py | 2 -- netbox/extras/management/commands/nbshell.py | 2 -- .../management/commands/run_inventory.py | 2 -- .../extras/management/commands/runreport.py | 2 -- netbox/extras/middleware.py | 2 -- netbox/extras/migrations/0001_initial.py | 2 -- ..._squashed_0010_customfield_filter_logic.py | 2 -- .../extras/migrations/0002_custom_fields.py | 2 -- .../0003_exporttemplate_add_description.py | 2 -- ...4_topologymap_change_comma_to_semicolon.py | 2 -- .../0005_useraction_add_bulk_create.py | 2 -- .../migrations/0006_add_imageattachments.py | 2 -- .../migrations/0007_unicode_literals.py | 2 -- netbox/extras/migrations/0008_reports.py | 1 - .../migrations/0009_topologymap_type.py | 2 -- .../0010_customfield_filter_logic.py | 2 -- netbox/extras/migrations/0012_webhooks.py | 2 -- netbox/extras/migrations/0013_objectchange.py | 2 -- netbox/extras/models.py | 13 --------- netbox/extras/querysets.py | 2 -- netbox/extras/reports.py | 2 -- netbox/extras/rpc.py | 2 -- netbox/extras/tables.py | 2 -- netbox/extras/tests/test_api.py | 2 -- netbox/extras/tests/test_customfields.py | 2 -- netbox/extras/tests/test_tags.py | 2 -- netbox/extras/urls.py | 2 -- netbox/extras/views.py | 2 -- netbox/ipam/api/serializers.py | 2 -- netbox/ipam/api/urls.py | 2 -- netbox/ipam/api/views.py | 2 -- netbox/ipam/apps.py | 2 -- netbox/ipam/constants.py | 2 -- netbox/ipam/fields.py | 2 -- netbox/ipam/filters.py | 2 -- netbox/ipam/formfields.py | 2 -- netbox/ipam/forms.py | 2 -- netbox/ipam/lookups.py | 2 -- netbox/ipam/migrations/0001_initial.py | 2 -- .../migrations/0002_vrf_add_enforce_unique.py | 2 -- ...18_remove_service_uniqueness_constraint.py | 2 -- .../migrations/0003_ipam_add_vlangroups.py | 2 -- .../0004_ipam_vlangroup_uniqueness.py | 2 -- .../migrations/0005_auto_20160725_1842.py | 2 -- .../migrations/0006_vrf_vlan_add_tenant.py | 2 -- .../0007_prefix_ipaddress_add_tenant.py | 2 -- .../migrations/0008_prefix_change_order.py | 2 -- .../migrations/0009_ipaddress_add_status.py | 2 -- .../migrations/0010_ipaddress_help_texts.py | 2 -- .../migrations/0011_rir_add_is_private.py | 2 -- netbox/ipam/migrations/0012_services.py | 2 -- .../migrations/0013_prefix_add_is_pool.py | 2 -- .../0014_ipaddress_status_add_deprecated.py | 2 -- netbox/ipam/migrations/0015_global_vlans.py | 2 -- .../ipam/migrations/0016_unicode_literals.py | 2 -- .../ipam/migrations/0017_ipaddress_roles.py | 2 -- ...18_remove_service_uniqueness_constraint.py | 2 -- netbox/ipam/migrations/0019_virtualization.py | 2 -- ...n_squashed_0020_ipaddress_add_role_carp.py | 2 -- .../0020_ipaddress_add_role_carp.py | 2 -- netbox/ipam/migrations/0021_vrf_ordering.py | 2 -- netbox/ipam/migrations/0022_tags.py | 2 -- netbox/ipam/migrations/0023_change_logging.py | 2 -- netbox/ipam/models.py | 12 -------- netbox/ipam/querysets.py | 2 -- netbox/ipam/tables.py | 2 -- netbox/ipam/tests/test_api.py | 2 -- netbox/ipam/tests/test_models.py | 2 -- netbox/ipam/urls.py | 2 -- netbox/ipam/views.py | 2 -- netbox/netbox/api.py | 2 -- netbox/netbox/forms.py | 2 -- netbox/netbox/urls.py | 2 -- netbox/netbox/views.py | 2 -- netbox/secrets/admin.py | 2 -- netbox/secrets/api/serializers.py | 2 -- netbox/secrets/api/urls.py | 2 -- netbox/secrets/api/views.py | 2 -- netbox/secrets/apps.py | 2 -- netbox/secrets/decorators.py | 2 -- netbox/secrets/exceptions.py | 2 -- netbox/secrets/filters.py | 2 -- netbox/secrets/forms.py | 2 -- netbox/secrets/hashers.py | 2 -- netbox/secrets/migrations/0001_initial.py | 2 -- ..._initial_squashed_0003_unicode_literals.py | 2 -- .../0002_userkey_add_session_key.py | 2 -- .../migrations/0003_unicode_literals.py | 2 -- netbox/secrets/migrations/0004_tags.py | 2 -- .../secrets/migrations/0005_change_logging.py | 2 -- netbox/secrets/models.py | 8 +----- netbox/secrets/querysets.py | 2 -- netbox/secrets/tables.py | 2 -- netbox/secrets/templatetags/secret_helpers.py | 2 -- netbox/secrets/tests/test_api.py | 2 -- netbox/secrets/tests/test_models.py | 2 -- netbox/secrets/urls.py | 2 -- netbox/secrets/views.py | 2 -- netbox/tenancy/api/serializers.py | 2 -- netbox/tenancy/api/urls.py | 2 -- netbox/tenancy/api/views.py | 2 -- netbox/tenancy/apps.py | 2 -- netbox/tenancy/filters.py | 2 -- netbox/tenancy/forms.py | 2 -- netbox/tenancy/migrations/0001_initial.py | 2 -- .../migrations/0002_tenant_group_optional.py | 2 -- ...optional_squashed_0003_unicode_literals.py | 2 -- .../migrations/0003_unicode_literals.py | 2 -- netbox/tenancy/migrations/0004_tags.py | 2 -- .../tenancy/migrations/0005_change_logging.py | 2 -- netbox/tenancy/models.py | 5 ---- netbox/tenancy/tables.py | 2 -- netbox/tenancy/tests/test_api.py | 2 -- netbox/tenancy/urls.py | 2 -- netbox/tenancy/views.py | 2 -- netbox/users/admin.py | 2 -- netbox/users/api/serializers.py | 2 -- netbox/users/forms.py | 2 -- netbox/users/migrations/0001_api_tokens.py | 2 -- ...i_tokens_squashed_0002_unicode_literals.py | 2 -- .../users/migrations/0002_unicode_literals.py | 2 -- netbox/users/models.py | 4 --- netbox/users/urls.py | 2 -- netbox/users/views.py | 2 -- netbox/utilities/api.py | 2 -- netbox/utilities/context_processors.py | 2 -- netbox/utilities/error_handlers.py | 2 -- netbox/utilities/fields.py | 2 -- netbox/utilities/filters.py | 2 -- netbox/utilities/forms.py | 2 -- netbox/utilities/managers.py | 2 -- netbox/utilities/middleware.py | 2 -- netbox/utilities/models.py | 2 -- netbox/utilities/paginator.py | 2 -- netbox/utilities/sql.py | 2 -- netbox/utilities/tables.py | 2 -- netbox/utilities/templatetags/buttons.py | 2 -- netbox/utilities/templatetags/helpers.py | 2 -- netbox/utilities/testing.py | 2 -- netbox/utilities/tests/test_managers.py | 2 -- netbox/utilities/utils.py | 2 -- netbox/utilities/validators.py | 2 -- netbox/utilities/views.py | 2 -- netbox/virtualization/api/serializers.py | 2 -- netbox/virtualization/api/urls.py | 2 -- netbox/virtualization/api/views.py | 2 -- netbox/virtualization/apps.py | 2 -- netbox/virtualization/constants.py | 2 -- netbox/virtualization/filters.py | 2 -- netbox/virtualization/forms.py | 2 -- .../migrations/0001_virtualization.py | 2 -- .../0002_virtualmachine_add_status.py | 2 -- ...s_squashed_0004_virtualmachine_add_role.py | 2 -- .../migrations/0003_cluster_add_site.py | 2 -- .../0004_virtualmachine_add_role.py | 2 -- netbox/virtualization/migrations/0006_tags.py | 2 -- .../migrations/0007_change_logging.py | 2 -- netbox/virtualization/models.py | 7 ----- netbox/virtualization/tables.py | 2 -- netbox/virtualization/tests/test_api.py | 2 -- netbox/virtualization/urls.py | 2 -- netbox/virtualization/views.py | 2 -- 277 files changed, 1 insertion(+), 620 deletions(-) diff --git a/netbox/circuits/api/serializers.py b/netbox/circuits/api/serializers.py index 739fbf8ff..2ec7921fc 100644 --- a/netbox/circuits/api/serializers.py +++ b/netbox/circuits/api/serializers.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from rest_framework import serializers from taggit_serializer.serializers import TaggitSerializer, TagListSerializerField diff --git a/netbox/circuits/api/urls.py b/netbox/circuits/api/urls.py index 3fb4eda0a..a5e19907f 100644 --- a/netbox/circuits/api/urls.py +++ b/netbox/circuits/api/urls.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from rest_framework import routers from . import views diff --git a/netbox/circuits/api/views.py b/netbox/circuits/api/views.py index 3b1623da4..7ca0165b0 100644 --- a/netbox/circuits/api/views.py +++ b/netbox/circuits/api/views.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.shortcuts import get_object_or_404 from rest_framework.decorators import action from rest_framework.response import Response diff --git a/netbox/circuits/apps.py b/netbox/circuits/apps.py index 613c347f2..bc0b7d87d 100644 --- a/netbox/circuits/apps.py +++ b/netbox/circuits/apps.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.apps import AppConfig diff --git a/netbox/circuits/constants.py b/netbox/circuits/constants.py index c13975b06..03a981ea1 100644 --- a/netbox/circuits/constants.py +++ b/netbox/circuits/constants.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - # Circuit statuses CIRCUIT_STATUS_DEPROVISIONING = 0 diff --git a/netbox/circuits/filters.py b/netbox/circuits/filters.py index 79efdc950..59c510808 100644 --- a/netbox/circuits/filters.py +++ b/netbox/circuits/filters.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import django_filters from django.db.models import Q diff --git a/netbox/circuits/forms.py b/netbox/circuits/forms.py index aae8bb5f6..b6a8f0efc 100644 --- a/netbox/circuits/forms.py +++ b/netbox/circuits/forms.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django import forms from django.db.models import Count from taggit.forms import TagField diff --git a/netbox/circuits/migrations/0001_initial.py b/netbox/circuits/migrations/0001_initial.py index 470fbee46..dd4dc612b 100644 --- a/netbox/circuits/migrations/0001_initial.py +++ b/netbox/circuits/migrations/0001_initial.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.7 on 2016-06-22 18:21 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/netbox/circuits/migrations/0001_initial_squashed_0010_circuit_status.py b/netbox/circuits/migrations/0001_initial_squashed_0010_circuit_status.py index 1ae1c5d45..3fcec7933 100644 --- a/netbox/circuits/migrations/0001_initial_squashed_0010_circuit_status.py +++ b/netbox/circuits/migrations/0001_initial_squashed_0010_circuit_status.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.14 on 2018-07-31 02:25 -from __future__ import unicode_literals - import dcim.fields from django.db import migrations, models import django.db.models.deletion diff --git a/netbox/circuits/migrations/0002_auto_20160622_1821.py b/netbox/circuits/migrations/0002_auto_20160622_1821.py index 32f31b376..2d350b5f3 100644 --- a/netbox/circuits/migrations/0002_auto_20160622_1821.py +++ b/netbox/circuits/migrations/0002_auto_20160622_1821.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.7 on 2016-06-22 18:21 -from __future__ import unicode_literals - from django.db import migrations, models import django.db.models.deletion diff --git a/netbox/circuits/migrations/0003_provider_32bit_asn_support.py b/netbox/circuits/migrations/0003_provider_32bit_asn_support.py index f1010064e..e1e9adab9 100644 --- a/netbox/circuits/migrations/0003_provider_32bit_asn_support.py +++ b/netbox/circuits/migrations/0003_provider_32bit_asn_support.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.7 on 2016-07-13 19:24 -from __future__ import unicode_literals - import dcim.fields from django.db import migrations diff --git a/netbox/circuits/migrations/0004_circuit_add_tenant.py b/netbox/circuits/migrations/0004_circuit_add_tenant.py index 641b13afd..de81f21eb 100644 --- a/netbox/circuits/migrations/0004_circuit_add_tenant.py +++ b/netbox/circuits/migrations/0004_circuit_add_tenant.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.8 on 2016-07-26 21:59 -from __future__ import unicode_literals - from django.db import migrations, models import django.db.models.deletion diff --git a/netbox/circuits/migrations/0005_circuit_add_upstream_speed.py b/netbox/circuits/migrations/0005_circuit_add_upstream_speed.py index f309cb2d8..51b09ad4c 100644 --- a/netbox/circuits/migrations/0005_circuit_add_upstream_speed.py +++ b/netbox/circuits/migrations/0005_circuit_add_upstream_speed.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.8 on 2016-08-08 20:24 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/netbox/circuits/migrations/0006_terminations.py b/netbox/circuits/migrations/0006_terminations.py index e5451498a..1a083c3da 100644 --- a/netbox/circuits/migrations/0006_terminations.py +++ b/netbox/circuits/migrations/0006_terminations.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.10 on 2016-12-13 16:30 -from __future__ import unicode_literals - from django.db import migrations, models import django.db.models.deletion diff --git a/netbox/circuits/migrations/0007_circuit_add_description.py b/netbox/circuits/migrations/0007_circuit_add_description.py index 023e5890a..238cb07dd 100644 --- a/netbox/circuits/migrations/0007_circuit_add_description.py +++ b/netbox/circuits/migrations/0007_circuit_add_description.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.10.4 on 2017-01-17 20:08 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/netbox/circuits/migrations/0008_circuittermination_interface_protect_on_delete.py b/netbox/circuits/migrations/0008_circuittermination_interface_protect_on_delete.py index 14ee6686d..b7ccafd26 100644 --- a/netbox/circuits/migrations/0008_circuittermination_interface_protect_on_delete.py +++ b/netbox/circuits/migrations/0008_circuittermination_interface_protect_on_delete.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11 on 2017-04-19 17:17 -from __future__ import unicode_literals - from django.db import migrations, models import django.db.models.deletion diff --git a/netbox/circuits/migrations/0009_unicode_literals.py b/netbox/circuits/migrations/0009_unicode_literals.py index 0f22a2268..0cc58fea9 100644 --- a/netbox/circuits/migrations/0009_unicode_literals.py +++ b/netbox/circuits/migrations/0009_unicode_literals.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11 on 2017-05-24 15:34 -from __future__ import unicode_literals - import dcim.fields from django.db import migrations, models diff --git a/netbox/circuits/migrations/0010_circuit_status.py b/netbox/circuits/migrations/0010_circuit_status.py index 3abe5d319..675a0c1fb 100644 --- a/netbox/circuits/migrations/0010_circuit_status.py +++ b/netbox/circuits/migrations/0010_circuit_status.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.9 on 2018-02-06 18:48 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/netbox/circuits/migrations/0011_tags.py b/netbox/circuits/migrations/0011_tags.py index b3510f8f4..112436223 100644 --- a/netbox/circuits/migrations/0011_tags.py +++ b/netbox/circuits/migrations/0011_tags.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.12 on 2018-05-22 19:04 -from __future__ import unicode_literals - from django.db import migrations import taggit.managers diff --git a/netbox/circuits/migrations/0012_change_logging.py b/netbox/circuits/migrations/0012_change_logging.py index db5057858..c9a3ee41d 100644 --- a/netbox/circuits/migrations/0012_change_logging.py +++ b/netbox/circuits/migrations/0012_change_logging.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.12 on 2018-06-13 17:14 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/netbox/circuits/models.py b/netbox/circuits/models.py index 6a2e55afc..fea669d72 100644 --- a/netbox/circuits/models.py +++ b/netbox/circuits/models.py @@ -1,9 +1,6 @@ -from __future__ import unicode_literals - from django.contrib.contenttypes.fields import GenericRelation from django.db import models from django.urls import reverse -from django.utils.encoding import python_2_unicode_compatible from taggit.managers import TaggableManager from dcim.constants import STATUS_CLASSES @@ -14,7 +11,6 @@ from utilities.utils import serialize_object from .constants import CIRCUIT_STATUS_ACTIVE, CIRCUIT_STATUS_CHOICES, TERM_SIDE_CHOICES -@python_2_unicode_compatible class Provider(ChangeLoggedModel, CustomFieldModel): """ Each Circuit belongs to a Provider. This is usually a telecommunications company or similar organization. This model @@ -84,7 +80,6 @@ class Provider(ChangeLoggedModel, CustomFieldModel): ) -@python_2_unicode_compatible class CircuitType(ChangeLoggedModel): """ Circuits can be organized by their functional role. For example, a user might wish to define CircuitTypes named @@ -116,7 +111,6 @@ class CircuitType(ChangeLoggedModel): ) -@python_2_unicode_compatible class Circuit(ChangeLoggedModel, CustomFieldModel): """ A communications circuit connects two points. Each Circuit belongs to a Provider; Providers may have multiple @@ -217,7 +211,6 @@ class Circuit(ChangeLoggedModel, CustomFieldModel): return self._get_termination('Z') -@python_2_unicode_compatible class CircuitTermination(models.Model): circuit = models.ForeignKey( to='circuits.Circuit', diff --git a/netbox/circuits/signals.py b/netbox/circuits/signals.py index 40a1e1031..bdfe8c0b6 100644 --- a/netbox/circuits/signals.py +++ b/netbox/circuits/signals.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.db.models.signals import post_delete, post_save from django.dispatch import receiver from django.utils import timezone diff --git a/netbox/circuits/tables.py b/netbox/circuits/tables.py index 6bf3114d9..d70f36bf2 100644 --- a/netbox/circuits/tables.py +++ b/netbox/circuits/tables.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import django_tables2 as tables from django.utils.safestring import mark_safe from django_tables2.utils import Accessor diff --git a/netbox/circuits/tests/test_api.py b/netbox/circuits/tests/test_api.py index a67dbc4ab..d19071b30 100644 --- a/netbox/circuits/tests/test_api.py +++ b/netbox/circuits/tests/test_api.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.urls import reverse from rest_framework import status diff --git a/netbox/circuits/urls.py b/netbox/circuits/urls.py index 449da3964..e40ff9f94 100644 --- a/netbox/circuits/urls.py +++ b/netbox/circuits/urls.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.conf.urls import url from extras.views import ObjectChangeLogView diff --git a/netbox/circuits/views.py b/netbox/circuits/views.py index e116e4556..a38635f90 100644 --- a/netbox/circuits/views.py +++ b/netbox/circuits/views.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.contrib import messages from django.contrib.auth.decorators import permission_required from django.contrib.auth.mixins import PermissionRequiredMixin diff --git a/netbox/dcim/api/exceptions.py b/netbox/dcim/api/exceptions.py index 8804da436..05ad86b5b 100644 --- a/netbox/dcim/api/exceptions.py +++ b/netbox/dcim/api/exceptions.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from rest_framework.exceptions import APIException diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index 0478932f7..26100c3a7 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from rest_framework import serializers from rest_framework.validators import UniqueTogetherValidator from taggit_serializer.serializers import TaggitSerializer, TagListSerializerField diff --git a/netbox/dcim/api/urls.py b/netbox/dcim/api/urls.py index 145cb7f09..6456d53a4 100644 --- a/netbox/dcim/api/urls.py +++ b/netbox/dcim/api/urls.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from rest_framework import routers from . import views diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index 901d9d2a5..90db34676 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from collections import OrderedDict from django.conf import settings diff --git a/netbox/dcim/apps.py b/netbox/dcim/apps.py index d61a46d98..78a243f84 100644 --- a/netbox/dcim/apps.py +++ b/netbox/dcim/apps.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.apps import AppConfig diff --git a/netbox/dcim/constants.py b/netbox/dcim/constants.py index cea56e176..2249f0e0d 100644 --- a/netbox/dcim/constants.py +++ b/netbox/dcim/constants.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - # Rack types RACK_TYPE_2POST = 100 diff --git a/netbox/dcim/fields.py b/netbox/dcim/fields.py index 22e0be581..6b45f6e65 100644 --- a/netbox/dcim/fields.py +++ b/netbox/dcim/fields.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from netaddr import EUI, mac_unix_expanded from django.core.exceptions import ValidationError diff --git a/netbox/dcim/filters.py b/netbox/dcim/filters.py index 18a0039e6..ea8f068fa 100644 --- a/netbox/dcim/filters.py +++ b/netbox/dcim/filters.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import django_filters from django.contrib.auth.models import User from django.db.models import Q diff --git a/netbox/dcim/formfields.py b/netbox/dcim/formfields.py index 804c2c956..5bc2379e5 100644 --- a/netbox/dcim/formfields.py +++ b/netbox/dcim/formfields.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - 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 4e201639c..66ed2447f 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import re from django import forms diff --git a/netbox/dcim/migrations/0001_initial.py b/netbox/dcim/migrations/0001_initial.py index da18bdbfe..db5f3faf2 100644 --- a/netbox/dcim/migrations/0001_initial.py +++ b/netbox/dcim/migrations/0001_initial.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.7 on 2016-06-22 18:21 -from __future__ import unicode_literals - import django.core.validators from django.db import migrations, models import django.db.models.deletion diff --git a/netbox/dcim/migrations/0002_auto_20160622_1821.py b/netbox/dcim/migrations/0002_auto_20160622_1821.py index e269d43f4..1e3aa4d2a 100644 --- a/netbox/dcim/migrations/0002_auto_20160622_1821.py +++ b/netbox/dcim/migrations/0002_auto_20160622_1821.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.7 on 2016-06-22 18:21 -from __future__ import unicode_literals - from django.db import migrations, models import django.db.models.deletion diff --git a/netbox/dcim/migrations/0002_auto_20160622_1821_squashed_0022_color_names_to_rgb.py b/netbox/dcim/migrations/0002_auto_20160622_1821_squashed_0022_color_names_to_rgb.py index a641c3a2f..c3412cf10 100644 --- a/netbox/dcim/migrations/0002_auto_20160622_1821_squashed_0022_color_names_to_rgb.py +++ b/netbox/dcim/migrations/0002_auto_20160622_1821_squashed_0022_color_names_to_rgb.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.14 on 2018-07-31 02:06 -from __future__ import unicode_literals - import dcim.fields import django.core.validators from django.db import migrations, models diff --git a/netbox/dcim/migrations/0003_auto_20160628_1721.py b/netbox/dcim/migrations/0003_auto_20160628_1721.py index deebc8518..312d0456c 100644 --- a/netbox/dcim/migrations/0003_auto_20160628_1721.py +++ b/netbox/dcim/migrations/0003_auto_20160628_1721.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.7 on 2016-06-28 17:21 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/netbox/dcim/migrations/0004_auto_20160701_2049.py b/netbox/dcim/migrations/0004_auto_20160701_2049.py index e051daded..0806acb82 100644 --- a/netbox/dcim/migrations/0004_auto_20160701_2049.py +++ b/netbox/dcim/migrations/0004_auto_20160701_2049.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.7 on 2016-07-01 20:49 -from __future__ import unicode_literals - from django.db import migrations, models import django.db.models.deletion diff --git a/netbox/dcim/migrations/0005_auto_20160706_1722.py b/netbox/dcim/migrations/0005_auto_20160706_1722.py index 83a5cf7cb..a286d6ff3 100644 --- a/netbox/dcim/migrations/0005_auto_20160706_1722.py +++ b/netbox/dcim/migrations/0005_auto_20160706_1722.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.7 on 2016-07-06 17:22 -from __future__ import unicode_literals - import dcim.fields from django.db import migrations, models diff --git a/netbox/dcim/migrations/0006_add_device_primary_ip4_ip6.py b/netbox/dcim/migrations/0006_add_device_primary_ip4_ip6.py index 670a174f9..6038cc027 100644 --- a/netbox/dcim/migrations/0006_add_device_primary_ip4_ip6.py +++ b/netbox/dcim/migrations/0006_add_device_primary_ip4_ip6.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.7 on 2016-07-11 18:40 -from __future__ import unicode_literals - from django.db import migrations, models import django.db.models.deletion diff --git a/netbox/dcim/migrations/0007_device_copy_primary_ip.py b/netbox/dcim/migrations/0007_device_copy_primary_ip.py index 055eac7d0..0d53337f7 100644 --- a/netbox/dcim/migrations/0007_device_copy_primary_ip.py +++ b/netbox/dcim/migrations/0007_device_copy_primary_ip.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.7 on 2016-07-11 18:40 -from __future__ import unicode_literals - from django.db import migrations diff --git a/netbox/dcim/migrations/0008_device_remove_primary_ip.py b/netbox/dcim/migrations/0008_device_remove_primary_ip.py index 91465e878..f43452de2 100644 --- a/netbox/dcim/migrations/0008_device_remove_primary_ip.py +++ b/netbox/dcim/migrations/0008_device_remove_primary_ip.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.7 on 2016-07-11 19:01 -from __future__ import unicode_literals - from django.db import migrations diff --git a/netbox/dcim/migrations/0009_site_32bit_asn_support.py b/netbox/dcim/migrations/0009_site_32bit_asn_support.py index c93340cea..0a72a6cf4 100644 --- a/netbox/dcim/migrations/0009_site_32bit_asn_support.py +++ b/netbox/dcim/migrations/0009_site_32bit_asn_support.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.7 on 2016-07-13 19:24 -from __future__ import unicode_literals - import dcim.fields from django.db import migrations diff --git a/netbox/dcim/migrations/0010_devicebay_installed_device_set_null.py b/netbox/dcim/migrations/0010_devicebay_installed_device_set_null.py index bf2f31c57..769a6f678 100644 --- a/netbox/dcim/migrations/0010_devicebay_installed_device_set_null.py +++ b/netbox/dcim/migrations/0010_devicebay_installed_device_set_null.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.7 on 2016-07-14 21:38 -from __future__ import unicode_literals - from django.db import migrations, models import django.db.models.deletion diff --git a/netbox/dcim/migrations/0011_devicetype_part_number.py b/netbox/dcim/migrations/0011_devicetype_part_number.py index 62c97abc6..eb77ea500 100644 --- a/netbox/dcim/migrations/0011_devicetype_part_number.py +++ b/netbox/dcim/migrations/0011_devicetype_part_number.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.8 on 2016-07-26 15:05 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/netbox/dcim/migrations/0012_site_rack_device_add_tenant.py b/netbox/dcim/migrations/0012_site_rack_device_add_tenant.py index 8dcf8f81a..b01f507c3 100644 --- a/netbox/dcim/migrations/0012_site_rack_device_add_tenant.py +++ b/netbox/dcim/migrations/0012_site_rack_device_add_tenant.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.8 on 2016-07-26 21:59 -from __future__ import unicode_literals - from django.db import migrations, models import django.db.models.deletion diff --git a/netbox/dcim/migrations/0013_add_interface_form_factors.py b/netbox/dcim/migrations/0013_add_interface_form_factors.py index 310eb1eb6..478cb59ff 100644 --- a/netbox/dcim/migrations/0013_add_interface_form_factors.py +++ b/netbox/dcim/migrations/0013_add_interface_form_factors.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.8 on 2016-08-06 20:24 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/netbox/dcim/migrations/0014_rack_add_type_width.py b/netbox/dcim/migrations/0014_rack_add_type_width.py index c14768c0f..a3922c8cd 100644 --- a/netbox/dcim/migrations/0014_rack_add_type_width.py +++ b/netbox/dcim/migrations/0014_rack_add_type_width.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.8 on 2016-08-08 21:11 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/netbox/dcim/migrations/0015_rack_add_u_height_validator.py b/netbox/dcim/migrations/0015_rack_add_u_height_validator.py index 8e555204b..167dd8f54 100644 --- a/netbox/dcim/migrations/0015_rack_add_u_height_validator.py +++ b/netbox/dcim/migrations/0015_rack_add_u_height_validator.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.8 on 2016-08-09 21:18 -from __future__ import unicode_literals - import django.core.validators from django.db import migrations, models diff --git a/netbox/dcim/migrations/0016_module_add_manufacturer.py b/netbox/dcim/migrations/0016_module_add_manufacturer.py index 6a2264a83..7204e6626 100644 --- a/netbox/dcim/migrations/0016_module_add_manufacturer.py +++ b/netbox/dcim/migrations/0016_module_add_manufacturer.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.8 on 2016-08-10 13:45 -from __future__ import unicode_literals - from django.db import migrations, models import django.db.models.deletion diff --git a/netbox/dcim/migrations/0017_rack_add_role.py b/netbox/dcim/migrations/0017_rack_add_role.py index eb3560b37..48500f4b4 100644 --- a/netbox/dcim/migrations/0017_rack_add_role.py +++ b/netbox/dcim/migrations/0017_rack_add_role.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.8 on 2016-08-10 14:58 -from __future__ import unicode_literals - from django.db import migrations, models import django.db.models.deletion diff --git a/netbox/dcim/migrations/0018_device_add_asset_tag.py b/netbox/dcim/migrations/0018_device_add_asset_tag.py index 706b42ac4..84d1cef35 100644 --- a/netbox/dcim/migrations/0018_device_add_asset_tag.py +++ b/netbox/dcim/migrations/0018_device_add_asset_tag.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.10 on 2016-08-11 15:42 -from __future__ import unicode_literals - from django.db import migrations import utilities.fields diff --git a/netbox/dcim/migrations/0019_new_iface_form_factors.py b/netbox/dcim/migrations/0019_new_iface_form_factors.py index b2358ba5e..b2d8be533 100644 --- a/netbox/dcim/migrations/0019_new_iface_form_factors.py +++ b/netbox/dcim/migrations/0019_new_iface_form_factors.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.10 on 2016-09-13 15:20 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/netbox/dcim/migrations/0020_rack_desc_units.py b/netbox/dcim/migrations/0020_rack_desc_units.py index d5a74706d..7408c82ef 100644 --- a/netbox/dcim/migrations/0020_rack_desc_units.py +++ b/netbox/dcim/migrations/0020_rack_desc_units.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.10 on 2016-10-28 15:01 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/netbox/dcim/migrations/0021_add_ff_flexstack.py b/netbox/dcim/migrations/0021_add_ff_flexstack.py index 9e85ac909..bb4c4f4be 100644 --- a/netbox/dcim/migrations/0021_add_ff_flexstack.py +++ b/netbox/dcim/migrations/0021_add_ff_flexstack.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.10 on 2016-10-31 18:47 -from __future__ import unicode_literals - import django.core.validators from django.db import migrations, models diff --git a/netbox/dcim/migrations/0022_color_names_to_rgb.py b/netbox/dcim/migrations/0022_color_names_to_rgb.py index 97e5de9ca..87fba4787 100644 --- a/netbox/dcim/migrations/0022_color_names_to_rgb.py +++ b/netbox/dcim/migrations/0022_color_names_to_rgb.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.10 on 2016-12-06 16:35 -from __future__ import unicode_literals - from django.db import migrations import utilities.fields diff --git a/netbox/dcim/migrations/0023_devicetype_comments.py b/netbox/dcim/migrations/0023_devicetype_comments.py index 677a8af9d..5f70e8076 100644 --- a/netbox/dcim/migrations/0023_devicetype_comments.py +++ b/netbox/dcim/migrations/0023_devicetype_comments.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.10 on 2016-12-16 16:08 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/netbox/dcim/migrations/0023_devicetype_comments_squashed_0043_device_component_name_lengths.py b/netbox/dcim/migrations/0023_devicetype_comments_squashed_0043_device_component_name_lengths.py index a613552ad..4d4cfb603 100644 --- a/netbox/dcim/migrations/0023_devicetype_comments_squashed_0043_device_component_name_lengths.py +++ b/netbox/dcim/migrations/0023_devicetype_comments_squashed_0043_device_component_name_lengths.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.14 on 2018-07-31 02:13 -from __future__ import unicode_literals - import dcim.fields from django.conf import settings import django.contrib.postgres.fields diff --git a/netbox/dcim/migrations/0024_site_add_contact_fields.py b/netbox/dcim/migrations/0024_site_add_contact_fields.py index 34e17561f..218107ba2 100644 --- a/netbox/dcim/migrations/0024_site_add_contact_fields.py +++ b/netbox/dcim/migrations/0024_site_add_contact_fields.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.10.4 on 2016-12-29 16:23 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/netbox/dcim/migrations/0025_devicetype_add_interface_ordering.py b/netbox/dcim/migrations/0025_devicetype_add_interface_ordering.py index d1263cb89..56db88f1c 100644 --- a/netbox/dcim/migrations/0025_devicetype_add_interface_ordering.py +++ b/netbox/dcim/migrations/0025_devicetype_add_interface_ordering.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.10.4 on 2017-01-06 16:56 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/netbox/dcim/migrations/0026_add_rack_reservations.py b/netbox/dcim/migrations/0026_add_rack_reservations.py index b9d4f8214..ba66feea5 100644 --- a/netbox/dcim/migrations/0026_add_rack_reservations.py +++ b/netbox/dcim/migrations/0026_add_rack_reservations.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.10.4 on 2017-02-16 18:43 -from __future__ import unicode_literals - from django.conf import settings import django.contrib.postgres.fields from django.db import migrations, models diff --git a/netbox/dcim/migrations/0027_device_add_site.py b/netbox/dcim/migrations/0027_device_add_site.py index 12d85f53e..bef85a822 100644 --- a/netbox/dcim/migrations/0027_device_add_site.py +++ b/netbox/dcim/migrations/0027_device_add_site.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.10.4 on 2017-02-16 21:21 -from __future__ import unicode_literals - from django.db import migrations, models import django.db.models.deletion diff --git a/netbox/dcim/migrations/0028_device_copy_rack_to_site.py b/netbox/dcim/migrations/0028_device_copy_rack_to_site.py index 6e7c52114..a67f34b38 100644 --- a/netbox/dcim/migrations/0028_device_copy_rack_to_site.py +++ b/netbox/dcim/migrations/0028_device_copy_rack_to_site.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.10.4 on 2017-02-16 21:23 -from __future__ import unicode_literals - from django.db import migrations diff --git a/netbox/dcim/migrations/0029_allow_rackless_devices.py b/netbox/dcim/migrations/0029_allow_rackless_devices.py index 83906fc76..dd9f30bf2 100644 --- a/netbox/dcim/migrations/0029_allow_rackless_devices.py +++ b/netbox/dcim/migrations/0029_allow_rackless_devices.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.10.4 on 2017-02-16 21:25 -from __future__ import unicode_literals - from django.db import migrations, models import django.db.models.deletion diff --git a/netbox/dcim/migrations/0030_interface_add_lag.py b/netbox/dcim/migrations/0030_interface_add_lag.py index 6f5be67a4..1ffd74f04 100644 --- a/netbox/dcim/migrations/0030_interface_add_lag.py +++ b/netbox/dcim/migrations/0030_interface_add_lag.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.10.4 on 2017-02-27 19:55 -from __future__ import unicode_literals - from django.db import migrations, models import django.db.models.deletion diff --git a/netbox/dcim/migrations/0031_regions.py b/netbox/dcim/migrations/0031_regions.py index d4fd4db5e..73bb77b3f 100644 --- a/netbox/dcim/migrations/0031_regions.py +++ b/netbox/dcim/migrations/0031_regions.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.10.4 on 2017-02-28 17:14 -from __future__ import unicode_literals - from django.db import migrations, models import django.db.models.deletion import mptt.fields diff --git a/netbox/dcim/migrations/0032_device_increase_name_length.py b/netbox/dcim/migrations/0032_device_increase_name_length.py index e11e75bab..ff0cd137f 100644 --- a/netbox/dcim/migrations/0032_device_increase_name_length.py +++ b/netbox/dcim/migrations/0032_device_increase_name_length.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.10.4 on 2017-03-02 15:09 -from __future__ import unicode_literals - from django.db import migrations import utilities.fields diff --git a/netbox/dcim/migrations/0033_rackreservation_rack_editable.py b/netbox/dcim/migrations/0033_rackreservation_rack_editable.py index b327bad12..567de4345 100644 --- a/netbox/dcim/migrations/0033_rackreservation_rack_editable.py +++ b/netbox/dcim/migrations/0033_rackreservation_rack_editable.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.10.6 on 2017-03-17 18:39 -from __future__ import unicode_literals - from django.db import migrations, models import django.db.models.deletion diff --git a/netbox/dcim/migrations/0034_rename_module_to_inventoryitem.py b/netbox/dcim/migrations/0034_rename_module_to_inventoryitem.py index ff430c067..db2f0577a 100644 --- a/netbox/dcim/migrations/0034_rename_module_to_inventoryitem.py +++ b/netbox/dcim/migrations/0034_rename_module_to_inventoryitem.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.10.6 on 2017-03-21 14:55 -from __future__ import unicode_literals - from django.db import migrations, models import django.db.models.deletion diff --git a/netbox/dcim/migrations/0035_device_expand_status_choices.py b/netbox/dcim/migrations/0035_device_expand_status_choices.py index 16ea807c9..a6f7aa563 100644 --- a/netbox/dcim/migrations/0035_device_expand_status_choices.py +++ b/netbox/dcim/migrations/0035_device_expand_status_choices.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.10.7 on 2017-05-08 15:57 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/netbox/dcim/migrations/0036_add_ff_juniper_vcp.py b/netbox/dcim/migrations/0036_add_ff_juniper_vcp.py index ac0f89f41..ceed22638 100644 --- a/netbox/dcim/migrations/0036_add_ff_juniper_vcp.py +++ b/netbox/dcim/migrations/0036_add_ff_juniper_vcp.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.1 on 2017-05-09 16:00 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/netbox/dcim/migrations/0037_unicode_literals.py b/netbox/dcim/migrations/0037_unicode_literals.py index cba05becc..57ad7a744 100644 --- a/netbox/dcim/migrations/0037_unicode_literals.py +++ b/netbox/dcim/migrations/0037_unicode_literals.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11 on 2017-05-24 15:34 -from __future__ import unicode_literals - import dcim.fields import django.core.validators from django.db import migrations, models diff --git a/netbox/dcim/migrations/0038_wireless_interfaces.py b/netbox/dcim/migrations/0038_wireless_interfaces.py index 61cdb3996..78ea103e5 100644 --- a/netbox/dcim/migrations/0038_wireless_interfaces.py +++ b/netbox/dcim/migrations/0038_wireless_interfaces.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.1 on 2017-06-16 21:38 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/netbox/dcim/migrations/0039_interface_add_enabled_mtu.py b/netbox/dcim/migrations/0039_interface_add_enabled_mtu.py index 4cc7e9616..c5f8dc83d 100644 --- a/netbox/dcim/migrations/0039_interface_add_enabled_mtu.py +++ b/netbox/dcim/migrations/0039_interface_add_enabled_mtu.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.1 on 2017-06-23 17:05 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/netbox/dcim/migrations/0040_inventoryitem_add_asset_tag_description.py b/netbox/dcim/migrations/0040_inventoryitem_add_asset_tag_description.py index c7d49fe2c..aaca23ea8 100644 --- a/netbox/dcim/migrations/0040_inventoryitem_add_asset_tag_description.py +++ b/netbox/dcim/migrations/0040_inventoryitem_add_asset_tag_description.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11 on 2017-06-23 20:44 -from __future__ import unicode_literals - from django.db import migrations, models import utilities.fields diff --git a/netbox/dcim/migrations/0041_napalm_integration.py b/netbox/dcim/migrations/0041_napalm_integration.py index 73ca8f3ee..50c2fbd99 100644 --- a/netbox/dcim/migrations/0041_napalm_integration.py +++ b/netbox/dcim/migrations/0041_napalm_integration.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.3 on 2017-07-14 17:26 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/netbox/dcim/migrations/0042_interface_ff_10ge_cx4.py b/netbox/dcim/migrations/0042_interface_ff_10ge_cx4.py index 77bea6bc6..e667d9451 100644 --- a/netbox/dcim/migrations/0042_interface_ff_10ge_cx4.py +++ b/netbox/dcim/migrations/0042_interface_ff_10ge_cx4.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.4 on 2017-08-29 21:00 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/netbox/dcim/migrations/0043_device_component_name_lengths.py b/netbox/dcim/migrations/0043_device_component_name_lengths.py index a52f50859..9f0ba2243 100644 --- a/netbox/dcim/migrations/0043_device_component_name_lengths.py +++ b/netbox/dcim/migrations/0043_device_component_name_lengths.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.4 on 2017-08-29 21:26 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/netbox/dcim/migrations/0044_virtualization.py b/netbox/dcim/migrations/0044_virtualization.py index b1e250bc2..362979aef 100644 --- a/netbox/dcim/migrations/0044_virtualization.py +++ b/netbox/dcim/migrations/0044_virtualization.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.4 on 2017-08-31 14:15 -from __future__ import unicode_literals - from django.db import migrations, models import django.db.models.deletion diff --git a/netbox/dcim/migrations/0044_virtualization_squashed_0055_virtualchassis_ordering.py b/netbox/dcim/migrations/0044_virtualization_squashed_0055_virtualchassis_ordering.py index 42fc5f317..78b4e3a41 100644 --- a/netbox/dcim/migrations/0044_virtualization_squashed_0055_virtualchassis_ordering.py +++ b/netbox/dcim/migrations/0044_virtualization_squashed_0055_virtualchassis_ordering.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.14 on 2018-07-31 02:17 -from __future__ import unicode_literals - from django.conf import settings import django.core.validators from django.db import migrations, models diff --git a/netbox/dcim/migrations/0045_devicerole_vm_role.py b/netbox/dcim/migrations/0045_devicerole_vm_role.py index 775effaf2..306a5a806 100644 --- a/netbox/dcim/migrations/0045_devicerole_vm_role.py +++ b/netbox/dcim/migrations/0045_devicerole_vm_role.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.4 on 2017-09-29 16:09 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/netbox/dcim/migrations/0046_rack_lengthen_facility_id.py b/netbox/dcim/migrations/0046_rack_lengthen_facility_id.py index d04006524..f6e93a43d 100644 --- a/netbox/dcim/migrations/0046_rack_lengthen_facility_id.py +++ b/netbox/dcim/migrations/0046_rack_lengthen_facility_id.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.4 on 2017-10-09 17:43 -from __future__ import unicode_literals - from django.db import migrations import utilities.fields diff --git a/netbox/dcim/migrations/0047_more_100ge_form_factors.py b/netbox/dcim/migrations/0047_more_100ge_form_factors.py index dafa81a54..a76ef6c8d 100644 --- a/netbox/dcim/migrations/0047_more_100ge_form_factors.py +++ b/netbox/dcim/migrations/0047_more_100ge_form_factors.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.4 on 2017-10-09 18:43 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/netbox/dcim/migrations/0048_rack_serial.py b/netbox/dcim/migrations/0048_rack_serial.py index 8e060c865..3fb7c0d2e 100644 --- a/netbox/dcim/migrations/0048_rack_serial.py +++ b/netbox/dcim/migrations/0048_rack_serial.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.4 on 2017-10-09 18:50 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/netbox/dcim/migrations/0049_rackreservation_change_user.py b/netbox/dcim/migrations/0049_rackreservation_change_user.py index ae9f95246..2d03db587 100644 --- a/netbox/dcim/migrations/0049_rackreservation_change_user.py +++ b/netbox/dcim/migrations/0049_rackreservation_change_user.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.6 on 2017-10-31 17:32 -from __future__ import unicode_literals - from django.conf import settings from django.db import migrations, models import django.db.models.deletion diff --git a/netbox/dcim/migrations/0050_interface_vlan_tagging.py b/netbox/dcim/migrations/0050_interface_vlan_tagging.py index 1906b9179..8acaf4eec 100644 --- a/netbox/dcim/migrations/0050_interface_vlan_tagging.py +++ b/netbox/dcim/migrations/0050_interface_vlan_tagging.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.6 on 2017-11-10 20:10 -from __future__ import unicode_literals - from django.db import migrations, models import django.db.models.deletion diff --git a/netbox/dcim/migrations/0051_rackreservation_tenant.py b/netbox/dcim/migrations/0051_rackreservation_tenant.py index 90a551eb8..ca0513ab0 100644 --- a/netbox/dcim/migrations/0051_rackreservation_tenant.py +++ b/netbox/dcim/migrations/0051_rackreservation_tenant.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.6 on 2017-11-15 18:56 -from __future__ import unicode_literals - from django.db import migrations, models import django.db.models.deletion diff --git a/netbox/dcim/migrations/0052_virtual_chassis.py b/netbox/dcim/migrations/0052_virtual_chassis.py index 334f60ca7..56777744c 100644 --- a/netbox/dcim/migrations/0052_virtual_chassis.py +++ b/netbox/dcim/migrations/0052_virtual_chassis.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.6 on 2017-11-27 17:27 -from __future__ import unicode_literals - import django.core.validators from django.db import migrations, models import django.db.models.deletion diff --git a/netbox/dcim/migrations/0053_platform_manufacturer.py b/netbox/dcim/migrations/0053_platform_manufacturer.py index 62797716e..bb5f24c91 100644 --- a/netbox/dcim/migrations/0053_platform_manufacturer.py +++ b/netbox/dcim/migrations/0053_platform_manufacturer.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.6 on 2017-12-19 20:56 -from __future__ import unicode_literals - from django.db import migrations, models import django.db.models.deletion diff --git a/netbox/dcim/migrations/0054_site_status_timezone_description.py b/netbox/dcim/migrations/0054_site_status_timezone_description.py index 723f61fc8..554bf554c 100644 --- a/netbox/dcim/migrations/0054_site_status_timezone_description.py +++ b/netbox/dcim/migrations/0054_site_status_timezone_description.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.6 on 2018-01-25 18:21 -from __future__ import unicode_literals - from django.db import migrations, models import timezone_field.fields diff --git a/netbox/dcim/migrations/0055_virtualchassis_ordering.py b/netbox/dcim/migrations/0055_virtualchassis_ordering.py index 51cda0ff6..ab23f403f 100644 --- a/netbox/dcim/migrations/0055_virtualchassis_ordering.py +++ b/netbox/dcim/migrations/0055_virtualchassis_ordering.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.9 on 2018-02-21 14:41 -from __future__ import unicode_literals - from django.db import migrations, models import django.db.models.deletion diff --git a/netbox/dcim/migrations/0057_tags.py b/netbox/dcim/migrations/0057_tags.py index b0cccfdf3..44ed09497 100644 --- a/netbox/dcim/migrations/0057_tags.py +++ b/netbox/dcim/migrations/0057_tags.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.12 on 2018-05-22 19:04 -from __future__ import unicode_literals - from django.db import migrations import taggit.managers diff --git a/netbox/dcim/migrations/0058_relax_rack_naming_constraints.py b/netbox/dcim/migrations/0058_relax_rack_naming_constraints.py index e4974be2f..9676e973d 100644 --- a/netbox/dcim/migrations/0058_relax_rack_naming_constraints.py +++ b/netbox/dcim/migrations/0058_relax_rack_naming_constraints.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.12 on 2018-05-22 19:27 -from __future__ import unicode_literals - from django.db import migrations diff --git a/netbox/dcim/migrations/0059_site_latitude_longitude.py b/netbox/dcim/migrations/0059_site_latitude_longitude.py index 15e666f35..7c019ed5d 100644 --- a/netbox/dcim/migrations/0059_site_latitude_longitude.py +++ b/netbox/dcim/migrations/0059_site_latitude_longitude.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.12 on 2018-06-21 18:45 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/netbox/dcim/migrations/0060_change_logging.py b/netbox/dcim/migrations/0060_change_logging.py index 8a40f4e4e..12a9f95ad 100644 --- a/netbox/dcim/migrations/0060_change_logging.py +++ b/netbox/dcim/migrations/0060_change_logging.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.12 on 2018-06-13 17:14 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index 564e6fa74..0c74600bb 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from collections import OrderedDict from itertools import count, groupby @@ -12,7 +10,6 @@ from django.core.validators import MaxValueValidator, MinValueValidator 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 taggit.managers import TaggableManager from timezone_field import TimeZoneField @@ -58,7 +55,6 @@ class ComponentModel(models.Model): # Regions # -@python_2_unicode_compatible class Region(MPTTModel, ChangeLoggedModel): """ Sites can be grouped within geographic Regions. @@ -106,7 +102,6 @@ class SiteManager(NaturalOrderByManager): natural_order_field = 'name' -@python_2_unicode_compatible class Site(ChangeLoggedModel, CustomFieldModel): """ A Site represents a geographic location within a network; typically a building or campus. The optional facility @@ -268,7 +263,6 @@ class Site(ChangeLoggedModel, CustomFieldModel): # Racks # -@python_2_unicode_compatible class RackGroup(ChangeLoggedModel): """ Racks can be grouped as subsets within a Site. The scope of a group will depend on how Sites are defined. For @@ -308,7 +302,6 @@ class RackGroup(ChangeLoggedModel): ) -@python_2_unicode_compatible class RackRole(ChangeLoggedModel): """ Racks can be organized by functional role, similar to Devices. @@ -345,7 +338,6 @@ class RackManager(NaturalOrderByManager): natural_order_field = 'name' -@python_2_unicode_compatible class Rack(ChangeLoggedModel, CustomFieldModel): """ Devices are housed within Racks. Each rack has a defined height measured in rack units, and a front and rear face. @@ -603,7 +595,6 @@ class Rack(ChangeLoggedModel, CustomFieldModel): return int(float(self.u_height - u_available) / self.u_height * 100) -@python_2_unicode_compatible class RackReservation(ChangeLoggedModel): """ One or more reserved units within a Rack. @@ -677,7 +668,6 @@ class RackReservation(ChangeLoggedModel): # Device Types # -@python_2_unicode_compatible class Manufacturer(ChangeLoggedModel): """ A Manufacturer represents a company which produces hardware devices; for example, Juniper or Dell. @@ -708,7 +698,6 @@ class Manufacturer(ChangeLoggedModel): ) -@python_2_unicode_compatible class DeviceType(ChangeLoggedModel, CustomFieldModel): """ A DeviceType represents a particular make (Manufacturer) and model of device. It specifies rack height and depth, as @@ -882,7 +871,6 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel): return bool(self.subdevice_role is False) -@python_2_unicode_compatible class ConsolePortTemplate(ComponentModel): """ A template for a ConsolePort to be created for a new Device. @@ -907,7 +895,6 @@ class ConsolePortTemplate(ComponentModel): return self.device_type -@python_2_unicode_compatible class ConsoleServerPortTemplate(ComponentModel): """ A template for a ConsoleServerPort to be created for a new Device. @@ -932,7 +919,6 @@ class ConsoleServerPortTemplate(ComponentModel): return self.device_type -@python_2_unicode_compatible class PowerPortTemplate(ComponentModel): """ A template for a PowerPort to be created for a new Device. @@ -957,7 +943,6 @@ class PowerPortTemplate(ComponentModel): return self.device_type -@python_2_unicode_compatible class PowerOutletTemplate(ComponentModel): """ A template for a PowerOutlet to be created for a new Device. @@ -982,7 +967,6 @@ class PowerOutletTemplate(ComponentModel): return self.device_type -@python_2_unicode_compatible class InterfaceTemplate(ComponentModel): """ A template for a physical data interface on a new Device. @@ -1017,7 +1001,6 @@ class InterfaceTemplate(ComponentModel): return self.device_type -@python_2_unicode_compatible class DeviceBayTemplate(ComponentModel): """ A template for a DeviceBay to be created for a new parent Device. @@ -1046,7 +1029,6 @@ class DeviceBayTemplate(ComponentModel): # Devices # -@python_2_unicode_compatible class DeviceRole(ChangeLoggedModel): """ Devices are organized by functional role; for example, "Core Switch" or "File Server". Each DeviceRole is assigned a @@ -1084,7 +1066,6 @@ class DeviceRole(ChangeLoggedModel): ) -@python_2_unicode_compatible class Platform(ChangeLoggedModel): """ Platform refers to the software or firmware running on a Device. For example, "Cisco IOS-XR" or "Juniper Junos". @@ -1150,7 +1131,6 @@ class DeviceManager(NaturalOrderByManager): natural_order_field = 'name' -@python_2_unicode_compatible class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel): """ A Device represents a piece of physical hardware mounted within a Rack. Each Device is assigned a DeviceType, @@ -1543,7 +1523,6 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel): # Console ports # -@python_2_unicode_compatible class ConsolePort(ComponentModel): """ A physical console port within a Device. ConsolePorts connect to ConsoleServerPorts. @@ -1610,7 +1589,6 @@ class ConsoleServerPortManager(models.Manager): }).order_by('device', 'name_padded') -@python_2_unicode_compatible class ConsoleServerPort(ComponentModel): """ A physical port within a Device (typically a designated console server) which provides access to ConsolePorts. @@ -1655,7 +1633,6 @@ class ConsoleServerPort(ComponentModel): # Power ports # -@python_2_unicode_compatible class PowerPort(ComponentModel): """ A physical power supply (intake) port within a Device. PowerPorts connect to PowerOutlets. @@ -1721,7 +1698,6 @@ class PowerOutletManager(models.Manager): }).order_by('device', 'name_padded') -@python_2_unicode_compatible class PowerOutlet(ComponentModel): """ A physical power outlet (output) within a Device which provides power to a PowerPort. @@ -1766,7 +1742,6 @@ class PowerOutlet(ComponentModel): # Interfaces # -@python_2_unicode_compatible class Interface(ComponentModel): """ A network interface within a Device or VirtualMachine. A physical Interface can connect to exactly one other @@ -2095,7 +2070,6 @@ class InterfaceConnection(models.Model): # Device bays # -@python_2_unicode_compatible class DeviceBay(ComponentModel): """ An empty space within a Device which can house a child device @@ -2149,7 +2123,6 @@ class DeviceBay(ComponentModel): # Inventory items # -@python_2_unicode_compatible class InventoryItem(ComponentModel): """ An InventoryItem represents a serialized piece of hardware within a Device, such as a line card or power supply. @@ -2241,7 +2214,6 @@ class InventoryItem(ComponentModel): # Virtual chassis # -@python_2_unicode_compatible class VirtualChassis(ChangeLoggedModel): """ A collection of Devices which operate with a shared control plane (e.g. a switch stack). diff --git a/netbox/dcim/querysets.py b/netbox/dcim/querysets.py index 32275ce01..9b735dfa6 100644 --- a/netbox/dcim/querysets.py +++ b/netbox/dcim/querysets.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.db.models import QuerySet from django.db.models.expressions import RawSQL diff --git a/netbox/dcim/signals.py b/netbox/dcim/signals.py index 80e47391a..2aefdc229 100644 --- a/netbox/dcim/signals.py +++ b/netbox/dcim/signals.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.db.models.signals import post_save, pre_delete from django.dispatch import receiver diff --git a/netbox/dcim/tables.py b/netbox/dcim/tables.py index fc9105774..c51154d21 100644 --- a/netbox/dcim/tables.py +++ b/netbox/dcim/tables.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import django_tables2 as tables from django_tables2.utils import Accessor diff --git a/netbox/dcim/tests/test_api.py b/netbox/dcim/tests/test_api.py index d4a42c196..00034c37d 100644 --- a/netbox/dcim/tests/test_api.py +++ b/netbox/dcim/tests/test_api.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.urls import reverse from netaddr import IPNetwork from rest_framework import status diff --git a/netbox/dcim/tests/test_forms.py b/netbox/dcim/tests/test_forms.py index c8d438728..2f333ea69 100644 --- a/netbox/dcim/tests/test_forms.py +++ b/netbox/dcim/tests/test_forms.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.test import TestCase from dcim.forms import * diff --git a/netbox/dcim/tests/test_models.py b/netbox/dcim/tests/test_models.py index 5b2cdbd51..ec59b59b0 100644 --- a/netbox/dcim/tests/test_models.py +++ b/netbox/dcim/tests/test_models.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.test import TestCase from dcim.models import * diff --git a/netbox/dcim/urls.py b/netbox/dcim/urls.py index 7345cdacd..4ba91f215 100644 --- a/netbox/dcim/urls.py +++ b/netbox/dcim/urls.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.conf.urls import url from extras.views import ObjectChangeLogView, ImageAttachmentEditView diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index eb7f71a25..62f610ce5 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from operator import attrgetter from django.contrib import messages diff --git a/netbox/extras/admin.py b/netbox/extras/admin.py index 2b140b444..9320e7081 100644 --- a/netbox/extras/admin.py +++ b/netbox/extras/admin.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django import forms from django.contrib import admin from django.utils.safestring import mark_safe diff --git a/netbox/extras/api/customfields.py b/netbox/extras/api/customfields.py index 0497138c4..c45f3029d 100644 --- a/netbox/extras/api/customfields.py +++ b/netbox/extras/api/customfields.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from datetime import datetime from django.contrib.contenttypes.models import ContentType diff --git a/netbox/extras/api/serializers.py b/netbox/extras/api/serializers.py index d0d2c67b0..0c5e79d80 100644 --- a/netbox/extras/api/serializers.py +++ b/netbox/extras/api/serializers.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.core.exceptions import ObjectDoesNotExist from rest_framework import serializers from taggit.models import Tag diff --git a/netbox/extras/api/urls.py b/netbox/extras/api/urls.py index cf61841dd..c18ccf657 100644 --- a/netbox/extras/api/urls.py +++ b/netbox/extras/api/urls.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from rest_framework import routers from . import views diff --git a/netbox/extras/api/views.py b/netbox/extras/api/views.py index 0fefa7ae6..32ac0baec 100644 --- a/netbox/extras/api/views.py +++ b/netbox/extras/api/views.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.contrib.contenttypes.models import ContentType from django.db.models import Count from django.http import Http404, HttpResponse diff --git a/netbox/extras/apps.py b/netbox/extras/apps.py index 4520b1923..6ada525a2 100644 --- a/netbox/extras/apps.py +++ b/netbox/extras/apps.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.apps import AppConfig from django.core.exceptions import ImproperlyConfigured from django.conf import settings diff --git a/netbox/extras/constants.py b/netbox/extras/constants.py index 9707d9121..2397ece7b 100644 --- a/netbox/extras/constants.py +++ b/netbox/extras/constants.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - # Models which support custom fields CUSTOMFIELD_MODELS = ( diff --git a/netbox/extras/filters.py b/netbox/extras/filters.py index 3abd5b4cf..4239d1639 100644 --- a/netbox/extras/filters.py +++ b/netbox/extras/filters.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import django_filters from django.contrib.auth.models import User from django.contrib.contenttypes.models import ContentType diff --git a/netbox/extras/forms.py b/netbox/extras/forms.py index 7dfceb390..3b900e8f5 100644 --- a/netbox/extras/forms.py +++ b/netbox/extras/forms.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from collections import OrderedDict from django import forms diff --git a/netbox/extras/management/commands/nbshell.py b/netbox/extras/management/commands/nbshell.py index 15b8acac5..c5a2fa1ec 100644 --- a/netbox/extras/management/commands/nbshell.py +++ b/netbox/extras/management/commands/nbshell.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import code import platform import sys diff --git a/netbox/extras/management/commands/run_inventory.py b/netbox/extras/management/commands/run_inventory.py index c42bdf50a..e5ce1bbca 100644 --- a/netbox/extras/management/commands/run_inventory.py +++ b/netbox/extras/management/commands/run_inventory.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from getpass import getpass from django.conf import settings diff --git a/netbox/extras/management/commands/runreport.py b/netbox/extras/management/commands/runreport.py index 96efc43a0..efc789021 100644 --- a/netbox/extras/management/commands/runreport.py +++ b/netbox/extras/management/commands/runreport.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.core.management.base import BaseCommand from django.utils import timezone diff --git a/netbox/extras/middleware.py b/netbox/extras/middleware.py index 7dfddbad6..5cbe392d4 100644 --- a/netbox/extras/middleware.py +++ b/netbox/extras/middleware.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from datetime import timedelta import random import threading diff --git a/netbox/extras/migrations/0001_initial.py b/netbox/extras/migrations/0001_initial.py index 949b3a2d8..be9b95264 100644 --- a/netbox/extras/migrations/0001_initial.py +++ b/netbox/extras/migrations/0001_initial.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.7 on 2016-06-22 18:21 -from __future__ import unicode_literals - from django.conf import settings from django.db import migrations, models import django.db.models.deletion diff --git a/netbox/extras/migrations/0001_initial_squashed_0010_customfield_filter_logic.py b/netbox/extras/migrations/0001_initial_squashed_0010_customfield_filter_logic.py index 0ac826ba4..dd2b3df27 100644 --- a/netbox/extras/migrations/0001_initial_squashed_0010_customfield_filter_logic.py +++ b/netbox/extras/migrations/0001_initial_squashed_0010_customfield_filter_logic.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.14 on 2018-07-31 02:19 -from __future__ import unicode_literals - import re from distutils.version import StrictVersion diff --git a/netbox/extras/migrations/0002_custom_fields.py b/netbox/extras/migrations/0002_custom_fields.py index 1d33ca281..300ae758a 100644 --- a/netbox/extras/migrations/0002_custom_fields.py +++ b/netbox/extras/migrations/0002_custom_fields.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.10 on 2016-08-23 20:33 -from __future__ import unicode_literals - from django.db import migrations, models import django.db.models.deletion diff --git a/netbox/extras/migrations/0003_exporttemplate_add_description.py b/netbox/extras/migrations/0003_exporttemplate_add_description.py index 6355955b5..fc45f5255 100644 --- a/netbox/extras/migrations/0003_exporttemplate_add_description.py +++ b/netbox/extras/migrations/0003_exporttemplate_add_description.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.10 on 2016-09-27 20:20 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/netbox/extras/migrations/0004_topologymap_change_comma_to_semicolon.py b/netbox/extras/migrations/0004_topologymap_change_comma_to_semicolon.py index ee838046d..b35c641da 100644 --- a/netbox/extras/migrations/0004_topologymap_change_comma_to_semicolon.py +++ b/netbox/extras/migrations/0004_topologymap_change_comma_to_semicolon.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.10 on 2016-11-03 18:33 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/netbox/extras/migrations/0005_useraction_add_bulk_create.py b/netbox/extras/migrations/0005_useraction_add_bulk_create.py index 0f20e5214..58b66fe1a 100644 --- a/netbox/extras/migrations/0005_useraction_add_bulk_create.py +++ b/netbox/extras/migrations/0005_useraction_add_bulk_create.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11 on 2017-04-04 19:45 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/netbox/extras/migrations/0006_add_imageattachments.py b/netbox/extras/migrations/0006_add_imageattachments.py index c4c589a9e..6842cced0 100644 --- a/netbox/extras/migrations/0006_add_imageattachments.py +++ b/netbox/extras/migrations/0006_add_imageattachments.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11 on 2017-04-04 19:58 -from __future__ import unicode_literals - from django.db import migrations, models import django.db.models.deletion import extras.models diff --git a/netbox/extras/migrations/0007_unicode_literals.py b/netbox/extras/migrations/0007_unicode_literals.py index cda07583f..fecb33b7b 100644 --- a/netbox/extras/migrations/0007_unicode_literals.py +++ b/netbox/extras/migrations/0007_unicode_literals.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11 on 2017-05-24 15:34 -from __future__ import unicode_literals - from django.db import migrations, models import extras.models diff --git a/netbox/extras/migrations/0008_reports.py b/netbox/extras/migrations/0008_reports.py index fbfde2cba..5cf84bbb1 100644 --- a/netbox/extras/migrations/0008_reports.py +++ b/netbox/extras/migrations/0008_reports.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.4 on 2017-09-26 21:25 -from __future__ import unicode_literals from distutils.version import StrictVersion import re diff --git a/netbox/extras/migrations/0009_topologymap_type.py b/netbox/extras/migrations/0009_topologymap_type.py index b062c58af..bc9ec07d5 100644 --- a/netbox/extras/migrations/0009_topologymap_type.py +++ b/netbox/extras/migrations/0009_topologymap_type.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.9 on 2018-02-15 16:28 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/netbox/extras/migrations/0010_customfield_filter_logic.py b/netbox/extras/migrations/0010_customfield_filter_logic.py index e35a2f835..dbff03e2d 100644 --- a/netbox/extras/migrations/0010_customfield_filter_logic.py +++ b/netbox/extras/migrations/0010_customfield_filter_logic.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.9 on 2018-02-21 19:48 -from __future__ import unicode_literals - from django.db import migrations, models from extras.constants import CF_FILTER_DISABLED, CF_FILTER_EXACT, CF_FILTER_LOOSE, CF_TYPE_SELECT diff --git a/netbox/extras/migrations/0012_webhooks.py b/netbox/extras/migrations/0012_webhooks.py index 70c8e9c14..8f7fcf36f 100644 --- a/netbox/extras/migrations/0012_webhooks.py +++ b/netbox/extras/migrations/0012_webhooks.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.12 on 2018-05-30 17:55 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/netbox/extras/migrations/0013_objectchange.py b/netbox/extras/migrations/0013_objectchange.py index de4762a46..01d73a841 100644 --- a/netbox/extras/migrations/0013_objectchange.py +++ b/netbox/extras/migrations/0013_objectchange.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.12 on 2018-06-22 18:13 -from __future__ import unicode_literals - from django.conf import settings import django.contrib.postgres.fields.jsonb from django.db import migrations, models diff --git a/netbox/extras/models.py b/netbox/extras/models.py index ad4fcdb18..f2000cdd2 100644 --- a/netbox/extras/models.py +++ b/netbox/extras/models.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from collections import OrderedDict from datetime import date @@ -14,7 +12,6 @@ from django.db.models import Q from django.http import HttpResponse from django.template import Template, Context from django.urls import reverse -from django.utils.encoding import python_2_unicode_compatible from django.utils.safestring import mark_safe from dcim.constants import CONNECTION_STATUS_CONNECTED @@ -27,7 +24,6 @@ from .querysets import ConfigContextQuerySet # Webhooks # -@python_2_unicode_compatible class Webhook(models.Model): """ A Webhook defines a request that will be sent to a remote application when an object is created, updated, and/or @@ -136,7 +132,6 @@ class CustomFieldModel(models.Model): return OrderedDict([(field, None) for field in fields]) -@python_2_unicode_compatible class CustomField(models.Model): obj_type = models.ManyToManyField( to=ContentType, @@ -227,7 +222,6 @@ class CustomField(models.Model): return serialized_value -@python_2_unicode_compatible class CustomFieldValue(models.Model): field = models.ForeignKey( to='extras.CustomField', @@ -271,7 +265,6 @@ class CustomFieldValue(models.Model): super(CustomFieldValue, self).save(*args, **kwargs) -@python_2_unicode_compatible class CustomFieldChoice(models.Model): field = models.ForeignKey( to='extras.CustomField', @@ -309,7 +302,6 @@ class CustomFieldChoice(models.Model): # Graphs # -@python_2_unicode_compatible class Graph(models.Model): type = models.PositiveSmallIntegerField( choices=GRAPH_TYPE_CHOICES @@ -351,7 +343,6 @@ class Graph(models.Model): # Export templates # -@python_2_unicode_compatible class ExportTemplate(models.Model): content_type = models.ForeignKey( to=ContentType, @@ -410,7 +401,6 @@ class ExportTemplate(models.Model): # Topology maps # -@python_2_unicode_compatible class TopologyMap(models.Model): name = models.CharField( max_length=50, @@ -571,7 +561,6 @@ def image_upload(instance, filename): return '{}{}_{}_{}'.format(path, instance.content_type.name, instance.object_id, filename) -@python_2_unicode_compatible class ImageAttachment(models.Model): """ An uploaded image which is associated with an object. @@ -752,7 +741,6 @@ class ReportResult(models.Model): # Change logging # -@python_2_unicode_compatible class ObjectChange(models.Model): """ Record a change to an object and the user account associated with that change. A change record may optionally @@ -904,7 +892,6 @@ class UserActionManager(models.Manager): # TODO: Remove UserAction, which has been replaced by ObjectChange. -@python_2_unicode_compatible class UserAction(models.Model): """ DEPRECATED: A record of an action (add, edit, or delete) performed on an object by a User. diff --git a/netbox/extras/querysets.py b/netbox/extras/querysets.py index bcc6f1e54..439323c94 100644 --- a/netbox/extras/querysets.py +++ b/netbox/extras/querysets.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.db.models import Q, QuerySet diff --git a/netbox/extras/reports.py b/netbox/extras/reports.py index f6b5d7570..b1e051301 100644 --- a/netbox/extras/reports.py +++ b/netbox/extras/reports.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from collections import OrderedDict import importlib import inspect diff --git a/netbox/extras/rpc.py b/netbox/extras/rpc.py index 552f592c7..60fc9208f 100644 --- a/netbox/extras/rpc.py +++ b/netbox/extras/rpc.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import re import time diff --git a/netbox/extras/tables.py b/netbox/extras/tables.py index 22bf26cce..688f159db 100644 --- a/netbox/extras/tables.py +++ b/netbox/extras/tables.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import django_tables2 as tables from taggit.models import Tag diff --git a/netbox/extras/tests/test_api.py b/netbox/extras/tests/test_api.py index 3d0e5d1f7..40258b100 100644 --- a/netbox/extras/tests/test_api.py +++ b/netbox/extras/tests/test_api.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.contrib.contenttypes.models import ContentType from django.urls import reverse from rest_framework import status diff --git a/netbox/extras/tests/test_customfields.py b/netbox/extras/tests/test_customfields.py index 97eb69cd9..8959528d3 100644 --- a/netbox/extras/tests/test_customfields.py +++ b/netbox/extras/tests/test_customfields.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from datetime import date from django.contrib.contenttypes.models import ContentType diff --git a/netbox/extras/tests/test_tags.py b/netbox/extras/tests/test_tags.py index d4c0a79c6..02ea3adef 100644 --- a/netbox/extras/tests/test_tags.py +++ b/netbox/extras/tests/test_tags.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.urls import reverse from rest_framework import status diff --git a/netbox/extras/urls.py b/netbox/extras/urls.py index e56652280..2a8e09090 100644 --- a/netbox/extras/urls.py +++ b/netbox/extras/urls.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.conf.urls import url from extras import views diff --git a/netbox/extras/views.py b/netbox/extras/views.py index 90d0d698d..a7800f2e2 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django import template from django.contrib import messages from django.contrib.auth.mixins import PermissionRequiredMixin diff --git a/netbox/ipam/api/serializers.py b/netbox/ipam/api/serializers.py index 4ba62e8da..0581d1e6d 100644 --- a/netbox/ipam/api/serializers.py +++ b/netbox/ipam/api/serializers.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from collections import OrderedDict from rest_framework import serializers diff --git a/netbox/ipam/api/urls.py b/netbox/ipam/api/urls.py index ca046cd93..c1ea1e200 100644 --- a/netbox/ipam/api/urls.py +++ b/netbox/ipam/api/urls.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from rest_framework import routers from . import views diff --git a/netbox/ipam/api/views.py b/netbox/ipam/api/views.py index e32688343..e7a249e07 100644 --- a/netbox/ipam/api/views.py +++ b/netbox/ipam/api/views.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.conf import settings from django.shortcuts import get_object_or_404 from rest_framework import status diff --git a/netbox/ipam/apps.py b/netbox/ipam/apps.py index c944d1b2c..fd4af74b0 100644 --- a/netbox/ipam/apps.py +++ b/netbox/ipam/apps.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.apps import AppConfig diff --git a/netbox/ipam/constants.py b/netbox/ipam/constants.py index e2b98a1ef..4ee51a3a2 100644 --- a/netbox/ipam/constants.py +++ b/netbox/ipam/constants.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - # IP address families AF_CHOICES = ( diff --git a/netbox/ipam/fields.py b/netbox/ipam/fields.py index 8c7dbb690..6db3a00f2 100644 --- a/netbox/ipam/fields.py +++ b/netbox/ipam/fields.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.core.exceptions import ValidationError from django.db import models from netaddr import AddrFormatError, IPNetwork diff --git a/netbox/ipam/filters.py b/netbox/ipam/filters.py index 0a8606e52..5351d0861 100644 --- a/netbox/ipam/filters.py +++ b/netbox/ipam/filters.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import django_filters from django.core.exceptions import ValidationError from django.db.models import Q diff --git a/netbox/ipam/formfields.py b/netbox/ipam/formfields.py index c67c13414..2909a54b1 100644 --- a/netbox/ipam/formfields.py +++ b/netbox/ipam/formfields.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - 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 8209b2ffa..bad2d8a1c 100644 --- a/netbox/ipam/forms.py +++ b/netbox/ipam/forms.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django import forms from django.core.exceptions import MultipleObjectsReturned from django.core.validators import MaxValueValidator, MinValueValidator diff --git a/netbox/ipam/lookups.py b/netbox/ipam/lookups.py index 9aca3c03b..e1de38a51 100644 --- a/netbox/ipam/lookups.py +++ b/netbox/ipam/lookups.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.db.models import Lookup, Transform, IntegerField from django.db.models import lookups diff --git a/netbox/ipam/migrations/0001_initial.py b/netbox/ipam/migrations/0001_initial.py index f98d04952..567f991ec 100644 --- a/netbox/ipam/migrations/0001_initial.py +++ b/netbox/ipam/migrations/0001_initial.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.7 on 2016-06-22 18:21 -from __future__ import unicode_literals - import django.core.validators from django.db import migrations, models import django.db.models.deletion diff --git a/netbox/ipam/migrations/0002_vrf_add_enforce_unique.py b/netbox/ipam/migrations/0002_vrf_add_enforce_unique.py index 373e93d80..993020a12 100644 --- a/netbox/ipam/migrations/0002_vrf_add_enforce_unique.py +++ b/netbox/ipam/migrations/0002_vrf_add_enforce_unique.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.7 on 2016-07-14 19:34 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/netbox/ipam/migrations/0002_vrf_add_enforce_unique_squashed_0018_remove_service_uniqueness_constraint.py b/netbox/ipam/migrations/0002_vrf_add_enforce_unique_squashed_0018_remove_service_uniqueness_constraint.py index c4271ea51..61d38a69b 100644 --- a/netbox/ipam/migrations/0002_vrf_add_enforce_unique_squashed_0018_remove_service_uniqueness_constraint.py +++ b/netbox/ipam/migrations/0002_vrf_add_enforce_unique_squashed_0018_remove_service_uniqueness_constraint.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.14 on 2018-07-31 02:12 -from __future__ import unicode_literals - import django.core.validators from django.db import migrations, models import django.db.models.deletion diff --git a/netbox/ipam/migrations/0003_ipam_add_vlangroups.py b/netbox/ipam/migrations/0003_ipam_add_vlangroups.py index 2e7157fe1..c9092f0f2 100644 --- a/netbox/ipam/migrations/0003_ipam_add_vlangroups.py +++ b/netbox/ipam/migrations/0003_ipam_add_vlangroups.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.7 on 2016-07-15 16:22 -from __future__ import unicode_literals - from django.db import migrations, models import django.db.models.deletion diff --git a/netbox/ipam/migrations/0004_ipam_vlangroup_uniqueness.py b/netbox/ipam/migrations/0004_ipam_vlangroup_uniqueness.py index fef5ec0b3..d8f628c57 100644 --- a/netbox/ipam/migrations/0004_ipam_vlangroup_uniqueness.py +++ b/netbox/ipam/migrations/0004_ipam_vlangroup_uniqueness.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.7 on 2016-07-15 17:14 -from __future__ import unicode_literals - from django.db import migrations diff --git a/netbox/ipam/migrations/0005_auto_20160725_1842.py b/netbox/ipam/migrations/0005_auto_20160725_1842.py index 17eee6e8c..726b89259 100644 --- a/netbox/ipam/migrations/0005_auto_20160725_1842.py +++ b/netbox/ipam/migrations/0005_auto_20160725_1842.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.8 on 2016-07-25 18:42 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/netbox/ipam/migrations/0006_vrf_vlan_add_tenant.py b/netbox/ipam/migrations/0006_vrf_vlan_add_tenant.py index 8d519261d..9352e4872 100644 --- a/netbox/ipam/migrations/0006_vrf_vlan_add_tenant.py +++ b/netbox/ipam/migrations/0006_vrf_vlan_add_tenant.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.8 on 2016-07-27 14:39 -from __future__ import unicode_literals - from django.db import migrations, models import django.db.models.deletion diff --git a/netbox/ipam/migrations/0007_prefix_ipaddress_add_tenant.py b/netbox/ipam/migrations/0007_prefix_ipaddress_add_tenant.py index eab3b9a47..dfe8fbb52 100644 --- a/netbox/ipam/migrations/0007_prefix_ipaddress_add_tenant.py +++ b/netbox/ipam/migrations/0007_prefix_ipaddress_add_tenant.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.8 on 2016-07-28 15:32 -from __future__ import unicode_literals - from django.db import migrations, models import django.db.models.deletion diff --git a/netbox/ipam/migrations/0008_prefix_change_order.py b/netbox/ipam/migrations/0008_prefix_change_order.py index 3ad3eb9e3..ea219da19 100644 --- a/netbox/ipam/migrations/0008_prefix_change_order.py +++ b/netbox/ipam/migrations/0008_prefix_change_order.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.10 on 2016-09-15 16:08 -from __future__ import unicode_literals - from django.db import migrations diff --git a/netbox/ipam/migrations/0009_ipaddress_add_status.py b/netbox/ipam/migrations/0009_ipaddress_add_status.py index ad876c3b6..b28590730 100644 --- a/netbox/ipam/migrations/0009_ipaddress_add_status.py +++ b/netbox/ipam/migrations/0009_ipaddress_add_status.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.10 on 2016-10-21 15:44 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/netbox/ipam/migrations/0010_ipaddress_help_texts.py b/netbox/ipam/migrations/0010_ipaddress_help_texts.py index a1e05171d..2a7e06335 100644 --- a/netbox/ipam/migrations/0010_ipaddress_help_texts.py +++ b/netbox/ipam/migrations/0010_ipaddress_help_texts.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.10 on 2016-11-01 17:46 -from __future__ import unicode_literals - from django.db import migrations, models import django.db.models.deletion import ipam.fields diff --git a/netbox/ipam/migrations/0011_rir_add_is_private.py b/netbox/ipam/migrations/0011_rir_add_is_private.py index ad7732653..d8b81d484 100644 --- a/netbox/ipam/migrations/0011_rir_add_is_private.py +++ b/netbox/ipam/migrations/0011_rir_add_is_private.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.10 on 2016-12-06 18:27 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/netbox/ipam/migrations/0012_services.py b/netbox/ipam/migrations/0012_services.py index bb6274408..12b2cf673 100644 --- a/netbox/ipam/migrations/0012_services.py +++ b/netbox/ipam/migrations/0012_services.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.10 on 2016-12-15 20:22 -from __future__ import unicode_literals - import django.core.validators from django.db import migrations, models import django.db.models.deletion diff --git a/netbox/ipam/migrations/0013_prefix_add_is_pool.py b/netbox/ipam/migrations/0013_prefix_add_is_pool.py index fd1493610..194bcb651 100644 --- a/netbox/ipam/migrations/0013_prefix_add_is_pool.py +++ b/netbox/ipam/migrations/0013_prefix_add_is_pool.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.10.4 on 2016-12-27 19:34 -from __future__ import unicode_literals - from django.db import migrations, models import django.db.models.deletion import ipam.fields diff --git a/netbox/ipam/migrations/0014_ipaddress_status_add_deprecated.py b/netbox/ipam/migrations/0014_ipaddress_status_add_deprecated.py index adc8e606c..3f5f48437 100644 --- a/netbox/ipam/migrations/0014_ipaddress_status_add_deprecated.py +++ b/netbox/ipam/migrations/0014_ipaddress_status_add_deprecated.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.10.4 on 2017-01-23 19:10 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/netbox/ipam/migrations/0015_global_vlans.py b/netbox/ipam/migrations/0015_global_vlans.py index 18d82cbaf..5471e33e2 100644 --- a/netbox/ipam/migrations/0015_global_vlans.py +++ b/netbox/ipam/migrations/0015_global_vlans.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.10.4 on 2017-02-21 18:45 -from __future__ import unicode_literals - from django.db import migrations, models import django.db.models.deletion diff --git a/netbox/ipam/migrations/0016_unicode_literals.py b/netbox/ipam/migrations/0016_unicode_literals.py index bb29542ad..6807bc555 100644 --- a/netbox/ipam/migrations/0016_unicode_literals.py +++ b/netbox/ipam/migrations/0016_unicode_literals.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11 on 2017-05-24 15:34 -from __future__ import unicode_literals - import django.core.validators from django.db import migrations, models import django.db.models.deletion diff --git a/netbox/ipam/migrations/0017_ipaddress_roles.py b/netbox/ipam/migrations/0017_ipaddress_roles.py index d91c3daa9..11bf37294 100644 --- a/netbox/ipam/migrations/0017_ipaddress_roles.py +++ b/netbox/ipam/migrations/0017_ipaddress_roles.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.1 on 2017-06-16 19:37 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/netbox/ipam/migrations/0018_remove_service_uniqueness_constraint.py b/netbox/ipam/migrations/0018_remove_service_uniqueness_constraint.py index 77e083ef3..3d3184354 100644 --- a/netbox/ipam/migrations/0018_remove_service_uniqueness_constraint.py +++ b/netbox/ipam/migrations/0018_remove_service_uniqueness_constraint.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.3 on 2017-08-03 19:37 -from __future__ import unicode_literals - from django.db import migrations diff --git a/netbox/ipam/migrations/0019_virtualization.py b/netbox/ipam/migrations/0019_virtualization.py index 955ff8b4a..f8ffbca11 100644 --- a/netbox/ipam/migrations/0019_virtualization.py +++ b/netbox/ipam/migrations/0019_virtualization.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.4 on 2017-08-31 15:44 -from __future__ import unicode_literals - from django.db import migrations, models import django.db.models.deletion diff --git a/netbox/ipam/migrations/0019_virtualization_squashed_0020_ipaddress_add_role_carp.py b/netbox/ipam/migrations/0019_virtualization_squashed_0020_ipaddress_add_role_carp.py index c8292bbc0..e271685a0 100644 --- a/netbox/ipam/migrations/0019_virtualization_squashed_0020_ipaddress_add_role_carp.py +++ b/netbox/ipam/migrations/0019_virtualization_squashed_0020_ipaddress_add_role_carp.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.14 on 2018-07-31 02:14 -from __future__ import unicode_literals - from django.db import migrations, models import django.db.models.deletion diff --git a/netbox/ipam/migrations/0020_ipaddress_add_role_carp.py b/netbox/ipam/migrations/0020_ipaddress_add_role_carp.py index 9d16be049..e15c12a32 100644 --- a/netbox/ipam/migrations/0020_ipaddress_add_role_carp.py +++ b/netbox/ipam/migrations/0020_ipaddress_add_role_carp.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.4 on 2017-10-09 20:02 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/netbox/ipam/migrations/0021_vrf_ordering.py b/netbox/ipam/migrations/0021_vrf_ordering.py index 878c02d8c..7f74115b6 100644 --- a/netbox/ipam/migrations/0021_vrf_ordering.py +++ b/netbox/ipam/migrations/0021_vrf_ordering.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.9 on 2018-02-07 18:37 -from __future__ import unicode_literals - from django.db import migrations diff --git a/netbox/ipam/migrations/0022_tags.py b/netbox/ipam/migrations/0022_tags.py index 14a508317..642bccc05 100644 --- a/netbox/ipam/migrations/0022_tags.py +++ b/netbox/ipam/migrations/0022_tags.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.12 on 2018-05-22 19:04 -from __future__ import unicode_literals - from django.db import migrations import taggit.managers diff --git a/netbox/ipam/migrations/0023_change_logging.py b/netbox/ipam/migrations/0023_change_logging.py index d548fdf15..afb732d64 100644 --- a/netbox/ipam/migrations/0023_change_logging.py +++ b/netbox/ipam/migrations/0023_change_logging.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.12 on 2018-06-13 17:14 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/netbox/ipam/models.py b/netbox/ipam/models.py index 1b109f939..4ecda9ccb 100644 --- a/netbox/ipam/models.py +++ b/netbox/ipam/models.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import netaddr from django.conf import settings from django.contrib.contenttypes.fields import GenericRelation @@ -9,7 +7,6 @@ from django.db import models from django.db.models import Q from django.db.models.expressions import RawSQL from django.urls import reverse -from django.utils.encoding import python_2_unicode_compatible from taggit.managers import TaggableManager from dcim.models import Interface @@ -20,7 +17,6 @@ from .fields import IPNetworkField, IPAddressField from .querysets import PrefixQuerySet -@python_2_unicode_compatible class VRF(ChangeLoggedModel, CustomFieldModel): """ A virtual routing and forwarding (VRF) table represents a discrete layer three forwarding domain (e.g. a routing @@ -88,7 +84,6 @@ class VRF(ChangeLoggedModel, CustomFieldModel): return None -@python_2_unicode_compatible class RIR(ChangeLoggedModel): """ A Regional Internet Registry (RIR) is responsible for the allocation of a large portion of the global IP address @@ -128,7 +123,6 @@ class RIR(ChangeLoggedModel): ) -@python_2_unicode_compatible class Aggregate(ChangeLoggedModel, CustomFieldModel): """ An aggregate exists at the root level of the IP address space hierarchy in NetBox. Aggregates are used to organize @@ -223,7 +217,6 @@ class Aggregate(ChangeLoggedModel, CustomFieldModel): return int(float(child_prefixes.size) / self.prefix.size * 100) -@python_2_unicode_compatible class Role(ChangeLoggedModel): """ A Role represents the functional role of a Prefix or VLAN; for example, "Customer," "Infrastructure," or @@ -256,7 +249,6 @@ class Role(ChangeLoggedModel): ) -@python_2_unicode_compatible class Prefix(ChangeLoggedModel, CustomFieldModel): """ A Prefix represents an IPv4 or IPv6 network, including mask length. Prefixes can optionally be assigned to Sites and @@ -496,7 +488,6 @@ class IPAddressManager(models.Manager): return qs.annotate(host=RawSQL('INET(HOST(ipam_ipaddress.address))', [])).order_by('family', 'host') -@python_2_unicode_compatible class IPAddress(ChangeLoggedModel, CustomFieldModel): """ An IPAddress represents an individual IPv4 or IPv6 address and its mask. The mask length should match what is @@ -654,7 +645,6 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel): return ROLE_CHOICE_CLASSES[self.role] -@python_2_unicode_compatible class VLANGroup(ChangeLoggedModel): """ A VLAN group is an arbitrary collection of VLANs within which VLAN IDs and names must be unique. @@ -706,7 +696,6 @@ class VLANGroup(ChangeLoggedModel): return None -@python_2_unicode_compatible class VLAN(ChangeLoggedModel, CustomFieldModel): """ A VLAN is a distinct layer two forwarding domain identified by a 12-bit integer (1-4094). Each VLAN must be assigned @@ -822,7 +811,6 @@ class VLAN(ChangeLoggedModel, CustomFieldModel): ) -@python_2_unicode_compatible class Service(ChangeLoggedModel, CustomFieldModel): """ A Service represents a layer-four service (e.g. HTTP or SSH) running on a Device or VirtualMachine. A Service may diff --git a/netbox/ipam/querysets.py b/netbox/ipam/querysets.py index f606ab1b4..bfb2525f2 100644 --- a/netbox/ipam/querysets.py +++ b/netbox/ipam/querysets.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from utilities.sql import NullsFirstQuerySet diff --git a/netbox/ipam/tables.py b/netbox/ipam/tables.py index 261c047df..73450d1a1 100644 --- a/netbox/ipam/tables.py +++ b/netbox/ipam/tables.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import django_tables2 as tables from django_tables2.utils import Accessor diff --git a/netbox/ipam/tests/test_api.py b/netbox/ipam/tests/test_api.py index 0ff87d5cf..4ba2b8c00 100644 --- a/netbox/ipam/tests/test_api.py +++ b/netbox/ipam/tests/test_api.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.urls import reverse from netaddr import IPNetwork from rest_framework import status diff --git a/netbox/ipam/tests/test_models.py b/netbox/ipam/tests/test_models.py index 790b665cd..e24fdc349 100644 --- a/netbox/ipam/tests/test_models.py +++ b/netbox/ipam/tests/test_models.py @@ -1,5 +1,3 @@ -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/urls.py b/netbox/ipam/urls.py index 700d78ae4..238ab7fb5 100644 --- a/netbox/ipam/urls.py +++ b/netbox/ipam/urls.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.conf.urls import url from extras.views import ObjectChangeLogView diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 91c741789..caa618998 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import netaddr from django.conf import settings from django.contrib.auth.mixins import PermissionRequiredMixin diff --git a/netbox/netbox/api.py b/netbox/netbox/api.py index 28a0d7685..682726ef1 100644 --- a/netbox/netbox/api.py +++ b/netbox/netbox/api.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from rest_framework import authentication, exceptions from rest_framework.pagination import LimitOffsetPagination from rest_framework.permissions import DjangoModelPermissions, SAFE_METHODS diff --git a/netbox/netbox/forms.py b/netbox/netbox/forms.py index 5611f49f9..b87e6ddd5 100644 --- a/netbox/netbox/forms.py +++ b/netbox/netbox/forms.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django import forms from utilities.forms import BootstrapMixin diff --git a/netbox/netbox/urls.py b/netbox/netbox/urls.py index d23e2d64e..e05a192fb 100644 --- a/netbox/netbox/urls.py +++ b/netbox/netbox/urls.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.conf import settings from django.conf.urls import include, url from django.contrib import admin diff --git a/netbox/netbox/views.py b/netbox/netbox/views.py index 1e3433016..f6e59221c 100644 --- a/netbox/netbox/views.py +++ b/netbox/netbox/views.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from collections import OrderedDict from django.db.models import Count diff --git a/netbox/secrets/admin.py b/netbox/secrets/admin.py index ac7a91fd6..9add5892c 100644 --- a/netbox/secrets/admin.py +++ b/netbox/secrets/admin.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.contrib import admin, messages from django.shortcuts import redirect, render diff --git a/netbox/secrets/api/serializers.py b/netbox/secrets/api/serializers.py index ee7217b63..fc1a60b03 100644 --- a/netbox/secrets/api/serializers.py +++ b/netbox/secrets/api/serializers.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from rest_framework import serializers from rest_framework.validators import UniqueTogetherValidator from taggit_serializer.serializers import TaggitSerializer, TagListSerializerField diff --git a/netbox/secrets/api/urls.py b/netbox/secrets/api/urls.py index 2a24c445a..cc4afd496 100644 --- a/netbox/secrets/api/urls.py +++ b/netbox/secrets/api/urls.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from rest_framework import routers from . import views diff --git a/netbox/secrets/api/views.py b/netbox/secrets/api/views.py index 9bc52f9f0..6d8a7f7e3 100644 --- a/netbox/secrets/api/views.py +++ b/netbox/secrets/api/views.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import base64 from Crypto.PublicKey import RSA diff --git a/netbox/secrets/apps.py b/netbox/secrets/apps.py index bc3714966..eec54bd7f 100644 --- a/netbox/secrets/apps.py +++ b/netbox/secrets/apps.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.apps import AppConfig diff --git a/netbox/secrets/decorators.py b/netbox/secrets/decorators.py index 0b9ebc16e..e2f44ac90 100644 --- a/netbox/secrets/decorators.py +++ b/netbox/secrets/decorators.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.contrib import messages from django.shortcuts import redirect diff --git a/netbox/secrets/exceptions.py b/netbox/secrets/exceptions.py index f014d8a14..11433d41e 100644 --- a/netbox/secrets/exceptions.py +++ b/netbox/secrets/exceptions.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - class InvalidKey(Exception): """ diff --git a/netbox/secrets/filters.py b/netbox/secrets/filters.py index f43a82b22..1a0a28f06 100644 --- a/netbox/secrets/filters.py +++ b/netbox/secrets/filters.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import django_filters from django.db.models import Q diff --git a/netbox/secrets/forms.py b/netbox/secrets/forms.py index 59e637a18..7f8844835 100644 --- a/netbox/secrets/forms.py +++ b/netbox/secrets/forms.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from Crypto.Cipher import PKCS1_OAEP from Crypto.PublicKey import RSA from django import forms diff --git a/netbox/secrets/hashers.py b/netbox/secrets/hashers.py index 49da1605d..fc5066fc6 100644 --- a/netbox/secrets/hashers.py +++ b/netbox/secrets/hashers.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.contrib.auth.hashers import PBKDF2PasswordHasher diff --git a/netbox/secrets/migrations/0001_initial.py b/netbox/secrets/migrations/0001_initial.py index 8dc0d54c6..1281a266a 100644 --- a/netbox/secrets/migrations/0001_initial.py +++ b/netbox/secrets/migrations/0001_initial.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.7 on 2016-06-22 18:21 -from __future__ import unicode_literals - from django.conf import settings from django.db import migrations, models import django.db.models.deletion diff --git a/netbox/secrets/migrations/0001_initial_squashed_0003_unicode_literals.py b/netbox/secrets/migrations/0001_initial_squashed_0003_unicode_literals.py index fb7d37431..04db89e7c 100644 --- a/netbox/secrets/migrations/0001_initial_squashed_0003_unicode_literals.py +++ b/netbox/secrets/migrations/0001_initial_squashed_0003_unicode_literals.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.14 on 2018-08-01 17:45 -from __future__ import unicode_literals - from django.conf import settings from django.db import migrations, models import django.db.models.deletion diff --git a/netbox/secrets/migrations/0002_userkey_add_session_key.py b/netbox/secrets/migrations/0002_userkey_add_session_key.py index 4cd885cfb..03abfb70e 100644 --- a/netbox/secrets/migrations/0002_userkey_add_session_key.py +++ b/netbox/secrets/migrations/0002_userkey_add_session_key.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11 on 2017-04-27 15:26 -from __future__ import unicode_literals - from django.conf import settings from django.db import migrations, models import django.db.models.deletion diff --git a/netbox/secrets/migrations/0003_unicode_literals.py b/netbox/secrets/migrations/0003_unicode_literals.py index b8b7956d8..48be221c5 100644 --- a/netbox/secrets/migrations/0003_unicode_literals.py +++ b/netbox/secrets/migrations/0003_unicode_literals.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11 on 2017-05-24 15:34 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/netbox/secrets/migrations/0004_tags.py b/netbox/secrets/migrations/0004_tags.py index ac952dc92..bdba79804 100644 --- a/netbox/secrets/migrations/0004_tags.py +++ b/netbox/secrets/migrations/0004_tags.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.12 on 2018-05-22 19:04 -from __future__ import unicode_literals - from django.db import migrations import taggit.managers diff --git a/netbox/secrets/migrations/0005_change_logging.py b/netbox/secrets/migrations/0005_change_logging.py index 947087934..d920e6fb2 100644 --- a/netbox/secrets/migrations/0005_change_logging.py +++ b/netbox/secrets/migrations/0005_change_logging.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.12 on 2018-06-13 17:29 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/netbox/secrets/models.py b/netbox/secrets/models.py index 8bbf3d14d..dcdfcac73 100644 --- a/netbox/secrets/models.py +++ b/netbox/secrets/models.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import os from Crypto.Cipher import AES, PKCS1_OAEP @@ -12,7 +10,7 @@ from django.contrib.contenttypes.fields import GenericRelation from django.core.exceptions import ValidationError from django.db import models from django.urls import reverse -from django.utils.encoding import force_bytes, python_2_unicode_compatible +from django.utils.encoding import force_bytes from taggit.managers import TaggableManager from extras.models import CustomFieldModel @@ -49,7 +47,6 @@ def decrypt_master_key(master_key_cipher, private_key): return cipher.decrypt(master_key_cipher) -@python_2_unicode_compatible class UserKey(models.Model): """ A UserKey stores a user's personal RSA (public) encryption key, which is used to generate their unique encrypted @@ -187,7 +184,6 @@ class UserKey(models.Model): self.save() -@python_2_unicode_compatible class SessionKey(models.Model): """ A SessionKey stores a User's temporary key to be used for the encryption and decryption of secrets. @@ -258,7 +254,6 @@ class SessionKey(models.Model): return session_key -@python_2_unicode_compatible class SecretRole(ChangeLoggedModel): """ A SecretRole represents an arbitrary functional classification of Secrets. For example, a user might define roles @@ -311,7 +306,6 @@ class SecretRole(ChangeLoggedModel): return user in self.users.all() or user.groups.filter(pk__in=self.groups.all()).exists() -@python_2_unicode_compatible class Secret(ChangeLoggedModel, CustomFieldModel): """ A Secret stores an AES256-encrypted copy of sensitive data, such as passwords or secret keys. An irreversible diff --git a/netbox/secrets/querysets.py b/netbox/secrets/querysets.py index c5595e1d3..c9732c5fe 100644 --- a/netbox/secrets/querysets.py +++ b/netbox/secrets/querysets.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.db.models import QuerySet diff --git a/netbox/secrets/tables.py b/netbox/secrets/tables.py index 4cfb1a6ea..39d260a6d 100644 --- a/netbox/secrets/tables.py +++ b/netbox/secrets/tables.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import django_tables2 as tables from utilities.tables import BaseTable, ToggleColumn diff --git a/netbox/secrets/templatetags/secret_helpers.py b/netbox/secrets/templatetags/secret_helpers.py index 0e1ff554c..142c0d2cb 100644 --- a/netbox/secrets/templatetags/secret_helpers.py +++ b/netbox/secrets/templatetags/secret_helpers.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django import template diff --git a/netbox/secrets/tests/test_api.py b/netbox/secrets/tests/test_api.py index 985e0ea7f..fa8c241e7 100644 --- a/netbox/secrets/tests/test_api.py +++ b/netbox/secrets/tests/test_api.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import base64 from django.urls import reverse diff --git a/netbox/secrets/tests/test_models.py b/netbox/secrets/tests/test_models.py index 887c048bf..0e5ad55ca 100644 --- a/netbox/secrets/tests/test_models.py +++ b/netbox/secrets/tests/test_models.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from Crypto.PublicKey import RSA from django.conf import settings from django.contrib.auth.models import User diff --git a/netbox/secrets/urls.py b/netbox/secrets/urls.py index 952725b54..e1ce2b8f2 100644 --- a/netbox/secrets/urls.py +++ b/netbox/secrets/urls.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.conf.urls import url from extras.views import ObjectChangeLogView diff --git a/netbox/secrets/views.py b/netbox/secrets/views.py index d15c9cbc2..428c3a905 100644 --- a/netbox/secrets/views.py +++ b/netbox/secrets/views.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import base64 from django.contrib import messages diff --git a/netbox/tenancy/api/serializers.py b/netbox/tenancy/api/serializers.py index 592e35a6e..08492c55d 100644 --- a/netbox/tenancy/api/serializers.py +++ b/netbox/tenancy/api/serializers.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from rest_framework import serializers from taggit_serializer.serializers import TaggitSerializer, TagListSerializerField diff --git a/netbox/tenancy/api/urls.py b/netbox/tenancy/api/urls.py index a36a1ec3d..a501b4302 100644 --- a/netbox/tenancy/api/urls.py +++ b/netbox/tenancy/api/urls.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from rest_framework import routers from . import views diff --git a/netbox/tenancy/api/views.py b/netbox/tenancy/api/views.py index 1ebd95500..febf86a52 100644 --- a/netbox/tenancy/api/views.py +++ b/netbox/tenancy/api/views.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from extras.api.views import CustomFieldModelViewSet from tenancy import filters from tenancy.models import Tenant, TenantGroup diff --git a/netbox/tenancy/apps.py b/netbox/tenancy/apps.py index df2cd2fbb..53cb9a056 100644 --- a/netbox/tenancy/apps.py +++ b/netbox/tenancy/apps.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.apps import AppConfig diff --git a/netbox/tenancy/filters.py b/netbox/tenancy/filters.py index 7eccff5d3..4b92cd6f5 100644 --- a/netbox/tenancy/filters.py +++ b/netbox/tenancy/filters.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import django_filters from django.db.models import Q diff --git a/netbox/tenancy/forms.py b/netbox/tenancy/forms.py index b90934923..1cc13483f 100644 --- a/netbox/tenancy/forms.py +++ b/netbox/tenancy/forms.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django import forms from django.db.models import Count from taggit.forms import TagField diff --git a/netbox/tenancy/migrations/0001_initial.py b/netbox/tenancy/migrations/0001_initial.py index ed2f800ef..fcad19413 100644 --- a/netbox/tenancy/migrations/0001_initial.py +++ b/netbox/tenancy/migrations/0001_initial.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.8 on 2016-07-26 21:58 -from __future__ import unicode_literals - from django.db import migrations, models import django.db.models.deletion diff --git a/netbox/tenancy/migrations/0002_tenant_group_optional.py b/netbox/tenancy/migrations/0002_tenant_group_optional.py index 95b1138ac..3d91b76ec 100644 --- a/netbox/tenancy/migrations/0002_tenant_group_optional.py +++ b/netbox/tenancy/migrations/0002_tenant_group_optional.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.8 on 2016-08-02 19:54 -from __future__ import unicode_literals - from django.db import migrations, models import django.db.models.deletion diff --git a/netbox/tenancy/migrations/0002_tenant_group_optional_squashed_0003_unicode_literals.py b/netbox/tenancy/migrations/0002_tenant_group_optional_squashed_0003_unicode_literals.py index d4258f4dc..77dc55975 100644 --- a/netbox/tenancy/migrations/0002_tenant_group_optional_squashed_0003_unicode_literals.py +++ b/netbox/tenancy/migrations/0002_tenant_group_optional_squashed_0003_unicode_literals.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.14 on 2018-07-31 02:12 -from __future__ import unicode_literals - from django.db import migrations, models import django.db.models.deletion diff --git a/netbox/tenancy/migrations/0003_unicode_literals.py b/netbox/tenancy/migrations/0003_unicode_literals.py index ed547c510..24cc7f969 100644 --- a/netbox/tenancy/migrations/0003_unicode_literals.py +++ b/netbox/tenancy/migrations/0003_unicode_literals.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11 on 2017-05-24 15:34 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/netbox/tenancy/migrations/0004_tags.py b/netbox/tenancy/migrations/0004_tags.py index 5cb9398b5..dbea49cd0 100644 --- a/netbox/tenancy/migrations/0004_tags.py +++ b/netbox/tenancy/migrations/0004_tags.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.12 on 2018-05-22 19:04 -from __future__ import unicode_literals - from django.db import migrations import taggit.managers diff --git a/netbox/tenancy/migrations/0005_change_logging.py b/netbox/tenancy/migrations/0005_change_logging.py index 7712e9d02..eb0979366 100644 --- a/netbox/tenancy/migrations/0005_change_logging.py +++ b/netbox/tenancy/migrations/0005_change_logging.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.12 on 2018-06-13 17:14 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/netbox/tenancy/models.py b/netbox/tenancy/models.py index 5a22143d3..045679b90 100644 --- a/netbox/tenancy/models.py +++ b/netbox/tenancy/models.py @@ -1,16 +1,12 @@ -from __future__ import unicode_literals - from django.contrib.contenttypes.fields import GenericRelation from django.db import models from django.urls import reverse -from django.utils.encoding import python_2_unicode_compatible from taggit.managers import TaggableManager from extras.models import CustomFieldModel from utilities.models import ChangeLoggedModel -@python_2_unicode_compatible class TenantGroup(ChangeLoggedModel): """ An arbitrary collection of Tenants. @@ -41,7 +37,6 @@ class TenantGroup(ChangeLoggedModel): ) -@python_2_unicode_compatible class Tenant(ChangeLoggedModel, CustomFieldModel): """ A Tenant represents an organization served by the NetBox owner. This is typically a customer or an internal diff --git a/netbox/tenancy/tables.py b/netbox/tenancy/tables.py index 2e763591a..91122df7a 100644 --- a/netbox/tenancy/tables.py +++ b/netbox/tenancy/tables.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import django_tables2 as tables from utilities.tables import BaseTable, ToggleColumn diff --git a/netbox/tenancy/tests/test_api.py b/netbox/tenancy/tests/test_api.py index 95e1a6de3..b44b153ee 100644 --- a/netbox/tenancy/tests/test_api.py +++ b/netbox/tenancy/tests/test_api.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.urls import reverse from rest_framework import status diff --git a/netbox/tenancy/urls.py b/netbox/tenancy/urls.py index 2da03b7f5..19522e6c7 100644 --- a/netbox/tenancy/urls.py +++ b/netbox/tenancy/urls.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.conf.urls import url from extras.views import ObjectChangeLogView diff --git a/netbox/tenancy/views.py b/netbox/tenancy/views.py index fdb453665..ed17695b2 100644 --- a/netbox/tenancy/views.py +++ b/netbox/tenancy/views.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.contrib.auth.mixins import PermissionRequiredMixin from django.db.models import Count, Q from django.shortcuts import get_object_or_404, render diff --git a/netbox/users/admin.py b/netbox/users/admin.py index ccf640edd..adfa151d5 100644 --- a/netbox/users/admin.py +++ b/netbox/users/admin.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django import forms from django.contrib import admin diff --git a/netbox/users/api/serializers.py b/netbox/users/api/serializers.py index 861bdade9..d97ba7ed1 100644 --- a/netbox/users/api/serializers.py +++ b/netbox/users/api/serializers.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.contrib.auth.models import User from utilities.api import WritableNestedSerializer diff --git a/netbox/users/forms.py b/netbox/users/forms.py index d25e128e6..e4b39fa92 100644 --- a/netbox/users/forms.py +++ b/netbox/users/forms.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django import forms from django.contrib.auth.forms import AuthenticationForm, PasswordChangeForm as DjangoPasswordChangeForm diff --git a/netbox/users/migrations/0001_api_tokens.py b/netbox/users/migrations/0001_api_tokens.py index d766b2ef0..3e2ea274e 100644 --- a/netbox/users/migrations/0001_api_tokens.py +++ b/netbox/users/migrations/0001_api_tokens.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.10.6 on 2017-03-08 15:32 -from __future__ import unicode_literals - from django.conf import settings import django.core.validators from django.db import migrations, models diff --git a/netbox/users/migrations/0001_api_tokens_squashed_0002_unicode_literals.py b/netbox/users/migrations/0001_api_tokens_squashed_0002_unicode_literals.py index 54a6078a0..1c82a092d 100644 --- a/netbox/users/migrations/0001_api_tokens_squashed_0002_unicode_literals.py +++ b/netbox/users/migrations/0001_api_tokens_squashed_0002_unicode_literals.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.14 on 2018-08-01 17:43 -from __future__ import unicode_literals - from django.conf import settings import django.core.validators from django.db import migrations, models diff --git a/netbox/users/migrations/0002_unicode_literals.py b/netbox/users/migrations/0002_unicode_literals.py index 8a7f96bbd..d0cf75fd8 100644 --- a/netbox/users/migrations/0002_unicode_literals.py +++ b/netbox/users/migrations/0002_unicode_literals.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11 on 2017-05-24 15:34 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/netbox/users/models.py b/netbox/users/models.py index b3698d925..dc16e12a2 100644 --- a/netbox/users/models.py +++ b/netbox/users/models.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import binascii import os @@ -7,10 +5,8 @@ from django.contrib.auth.models import User from django.core.validators import MinLengthValidator from django.db import models from django.utils import timezone -from django.utils.encoding import python_2_unicode_compatible -@python_2_unicode_compatible class Token(models.Model): """ An API token used for user authentication. This extends the stock model to allow each user to have multiple tokens. diff --git a/netbox/users/urls.py b/netbox/users/urls.py index aad89e104..d288866ec 100644 --- a/netbox/users/urls.py +++ b/netbox/users/urls.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.conf.urls import url from . import views diff --git a/netbox/users/views.py b/netbox/users/views.py index c87fa5c7a..de78ad1fd 100644 --- a/netbox/users/views.py +++ b/netbox/users/views.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.contrib import messages from django.contrib.auth import login as auth_login, logout as auth_logout, update_session_auth_hash from django.contrib.auth.decorators import login_required diff --git a/netbox/utilities/api.py b/netbox/utilities/api.py index f7d4293a7..7854bd8ae 100644 --- a/netbox/utilities/api.py +++ b/netbox/utilities/api.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from collections import OrderedDict import pytz diff --git a/netbox/utilities/context_processors.py b/netbox/utilities/context_processors.py index dab35e982..06c5c8784 100644 --- a/netbox/utilities/context_processors.py +++ b/netbox/utilities/context_processors.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.conf import settings as django_settings diff --git a/netbox/utilities/error_handlers.py b/netbox/utilities/error_handlers.py index 3b7eb7a5b..da8510950 100644 --- a/netbox/utilities/error_handlers.py +++ b/netbox/utilities/error_handlers.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.contrib import messages from django.utils.html import escape from django.utils.safestring import mark_safe diff --git a/netbox/utilities/fields.py b/netbox/utilities/fields.py index 34f59fe16..dfbe00ef3 100644 --- a/netbox/utilities/fields.py +++ b/netbox/utilities/fields.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.core.validators import RegexValidator from django.db import models diff --git a/netbox/utilities/filters.py b/netbox/utilities/filters.py index 90cdcd9fc..1fb048db4 100644 --- a/netbox/utilities/filters.py +++ b/netbox/utilities/filters.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import itertools import django_filters diff --git a/netbox/utilities/forms.py b/netbox/utilities/forms.py index 1e6e3c0c4..09b313ab6 100644 --- a/netbox/utilities/forms.py +++ b/netbox/utilities/forms.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import csv from io import StringIO import re diff --git a/netbox/utilities/managers.py b/netbox/utilities/managers.py index b112f4fae..da4117594 100644 --- a/netbox/utilities/managers.py +++ b/netbox/utilities/managers.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.db.models import Manager diff --git a/netbox/utilities/middleware.py b/netbox/utilities/middleware.py index 207968690..f2d17ee3e 100644 --- a/netbox/utilities/middleware.py +++ b/netbox/utilities/middleware.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import sys from django.conf import settings diff --git a/netbox/utilities/models.py b/netbox/utilities/models.py index 4b04c03e1..3008fc39a 100644 --- a/netbox/utilities/models.py +++ b/netbox/utilities/models.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.db import models from extras.models import ObjectChange diff --git a/netbox/utilities/paginator.py b/netbox/utilities/paginator.py index 9ebbbab57..ae915f773 100644 --- a/netbox/utilities/paginator.py +++ b/netbox/utilities/paginator.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.conf import settings from django.core.paginator import Paginator, Page diff --git a/netbox/utilities/sql.py b/netbox/utilities/sql.py index ac2c70624..617586ab8 100644 --- a/netbox/utilities/sql.py +++ b/netbox/utilities/sql.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.db import connections, models from django.db.models.sql.compiler import SQLCompiler diff --git a/netbox/utilities/tables.py b/netbox/utilities/tables.py index e531b5e32..cc67bbeb4 100644 --- a/netbox/utilities/tables.py +++ b/netbox/utilities/tables.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import django_tables2 as tables from django.utils.safestring import mark_safe diff --git a/netbox/utilities/templatetags/buttons.py b/netbox/utilities/templatetags/buttons.py index 3090f4538..b9a8bf6ec 100644 --- a/netbox/utilities/templatetags/buttons.py +++ b/netbox/utilities/templatetags/buttons.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django import template from extras.models import ExportTemplate diff --git a/netbox/utilities/templatetags/helpers.py b/netbox/utilities/templatetags/helpers.py index 39959a668..555cbe03f 100644 --- a/netbox/utilities/templatetags/helpers.py +++ b/netbox/utilities/templatetags/helpers.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import datetime import json diff --git a/netbox/utilities/testing.py b/netbox/utilities/testing.py index dcc564dfa..86fa8c836 100644 --- a/netbox/utilities/testing.py +++ b/netbox/utilities/testing.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.contrib.auth.models import User from rest_framework.test import APITestCase as _APITestCase diff --git a/netbox/utilities/tests/test_managers.py b/netbox/utilities/tests/test_managers.py index 0bafaefde..7ff23b69d 100644 --- a/netbox/utilities/tests/test_managers.py +++ b/netbox/utilities/tests/test_managers.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.test import TestCase from dcim.models import Site diff --git a/netbox/utilities/utils.py b/netbox/utilities/utils.py index 14c29d211..be756275e 100644 --- a/netbox/utilities/utils.py +++ b/netbox/utilities/utils.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import datetime import json import six diff --git a/netbox/utilities/validators.py b/netbox/utilities/validators.py index 102e368a5..cfa733208 100644 --- a/netbox/utilities/validators.py +++ b/netbox/utilities/validators.py @@ -1,5 +1,3 @@ -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 e11d681ef..10e54717c 100644 --- a/netbox/utilities/views.py +++ b/netbox/utilities/views.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from collections import OrderedDict from copy import deepcopy import sys diff --git a/netbox/virtualization/api/serializers.py b/netbox/virtualization/api/serializers.py index f4e0d5e5c..4114c7260 100644 --- a/netbox/virtualization/api/serializers.py +++ b/netbox/virtualization/api/serializers.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from rest_framework import serializers from taggit_serializer.serializers import TaggitSerializer, TagListSerializerField diff --git a/netbox/virtualization/api/urls.py b/netbox/virtualization/api/urls.py index 45db6aa6a..335ac7d0e 100644 --- a/netbox/virtualization/api/urls.py +++ b/netbox/virtualization/api/urls.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from rest_framework import routers from . import views diff --git a/netbox/virtualization/api/views.py b/netbox/virtualization/api/views.py index 01b8792c8..a4d10d7e3 100644 --- a/netbox/virtualization/api/views.py +++ b/netbox/virtualization/api/views.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from dcim.models import Interface from extras.api.views import CustomFieldModelViewSet from utilities.api import FieldChoicesViewSet, ModelViewSet diff --git a/netbox/virtualization/apps.py b/netbox/virtualization/apps.py index 768508cfb..35d6e8266 100644 --- a/netbox/virtualization/apps.py +++ b/netbox/virtualization/apps.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.apps import AppConfig diff --git a/netbox/virtualization/constants.py b/netbox/virtualization/constants.py index 307921e0e..37e9efea2 100644 --- a/netbox/virtualization/constants.py +++ b/netbox/virtualization/constants.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from dcim.constants import DEVICE_STATUS_ACTIVE, DEVICE_STATUS_OFFLINE, DEVICE_STATUS_STAGED # VirtualMachine statuses (replicated from Device statuses) diff --git a/netbox/virtualization/filters.py b/netbox/virtualization/filters.py index 6af4e4a22..268094be7 100644 --- a/netbox/virtualization/filters.py +++ b/netbox/virtualization/filters.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import django_filters from django.db.models import Q from netaddr import EUI diff --git a/netbox/virtualization/forms.py b/netbox/virtualization/forms.py index 10833234b..6e87be547 100644 --- a/netbox/virtualization/forms.py +++ b/netbox/virtualization/forms.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django import forms from django.core.exceptions import ValidationError from django.db.models import Count diff --git a/netbox/virtualization/migrations/0001_virtualization.py b/netbox/virtualization/migrations/0001_virtualization.py index a5c7535cf..f34bee36c 100644 --- a/netbox/virtualization/migrations/0001_virtualization.py +++ b/netbox/virtualization/migrations/0001_virtualization.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.4 on 2017-08-31 14:15 -from __future__ import unicode_literals - from django.db import migrations, models import django.db.models.deletion diff --git a/netbox/virtualization/migrations/0002_virtualmachine_add_status.py b/netbox/virtualization/migrations/0002_virtualmachine_add_status.py index 5b03b6e33..f9f5c72bd 100644 --- a/netbox/virtualization/migrations/0002_virtualmachine_add_status.py +++ b/netbox/virtualization/migrations/0002_virtualmachine_add_status.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.4 on 2017-09-14 17:49 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/netbox/virtualization/migrations/0002_virtualmachine_add_status_squashed_0004_virtualmachine_add_role.py b/netbox/virtualization/migrations/0002_virtualmachine_add_status_squashed_0004_virtualmachine_add_role.py index 295ec7d17..6ee06f912 100644 --- a/netbox/virtualization/migrations/0002_virtualmachine_add_status_squashed_0004_virtualmachine_add_role.py +++ b/netbox/virtualization/migrations/0002_virtualmachine_add_status_squashed_0004_virtualmachine_add_role.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.14 on 2018-07-31 02:23 -from __future__ import unicode_literals - from django.db import migrations, models import django.db.models.deletion diff --git a/netbox/virtualization/migrations/0003_cluster_add_site.py b/netbox/virtualization/migrations/0003_cluster_add_site.py index 5ac3c578b..bdcce88bc 100644 --- a/netbox/virtualization/migrations/0003_cluster_add_site.py +++ b/netbox/virtualization/migrations/0003_cluster_add_site.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.4 on 2017-09-22 16:30 -from __future__ import unicode_literals - from django.db import migrations, models import django.db.models.deletion diff --git a/netbox/virtualization/migrations/0004_virtualmachine_add_role.py b/netbox/virtualization/migrations/0004_virtualmachine_add_role.py index 10dec60fa..db416fc5d 100644 --- a/netbox/virtualization/migrations/0004_virtualmachine_add_role.py +++ b/netbox/virtualization/migrations/0004_virtualmachine_add_role.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.4 on 2017-09-29 14:32 -from __future__ import unicode_literals - from django.db import migrations, models import django.db.models.deletion diff --git a/netbox/virtualization/migrations/0006_tags.py b/netbox/virtualization/migrations/0006_tags.py index eed800852..5152086de 100644 --- a/netbox/virtualization/migrations/0006_tags.py +++ b/netbox/virtualization/migrations/0006_tags.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.12 on 2018-05-22 19:04 -from __future__ import unicode_literals - from django.db import migrations import taggit.managers diff --git a/netbox/virtualization/migrations/0007_change_logging.py b/netbox/virtualization/migrations/0007_change_logging.py index 954f9f2a9..4c2d342e5 100644 --- a/netbox/virtualization/migrations/0007_change_logging.py +++ b/netbox/virtualization/migrations/0007_change_logging.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.12 on 2018-06-13 17:14 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/netbox/virtualization/models.py b/netbox/virtualization/models.py index 119c9ee4f..ff9f39ee9 100644 --- a/netbox/virtualization/models.py +++ b/netbox/virtualization/models.py @@ -1,11 +1,8 @@ -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 from django.urls import reverse -from django.utils.encoding import python_2_unicode_compatible from taggit.managers import TaggableManager from dcim.models import Device @@ -18,7 +15,6 @@ from .constants import DEVICE_STATUS_ACTIVE, VM_STATUS_CHOICES, VM_STATUS_CLASSE # Cluster types # -@python_2_unicode_compatible class ClusterType(ChangeLoggedModel): """ A type of Cluster. @@ -53,7 +49,6 @@ class ClusterType(ChangeLoggedModel): # Cluster groups # -@python_2_unicode_compatible class ClusterGroup(ChangeLoggedModel): """ An organizational group of Clusters. @@ -88,7 +83,6 @@ class ClusterGroup(ChangeLoggedModel): # Clusters # -@python_2_unicode_compatible class Cluster(ChangeLoggedModel, CustomFieldModel): """ A cluster of VirtualMachines. Each Cluster may optionally be associated with one or more Devices. @@ -164,7 +158,6 @@ class Cluster(ChangeLoggedModel, CustomFieldModel): # Virtual machines # -@python_2_unicode_compatible class VirtualMachine(ChangeLoggedModel, ConfigContextModel, CustomFieldModel): """ A virtual machine which runs inside a Cluster. diff --git a/netbox/virtualization/tables.py b/netbox/virtualization/tables.py index 84579af49..b825ba59f 100644 --- a/netbox/virtualization/tables.py +++ b/netbox/virtualization/tables.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import django_tables2 as tables from django_tables2.utils import Accessor diff --git a/netbox/virtualization/tests/test_api.py b/netbox/virtualization/tests/test_api.py index c3eebf5ed..19845e508 100644 --- a/netbox/virtualization/tests/test_api.py +++ b/netbox/virtualization/tests/test_api.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.urls import reverse from netaddr import IPNetwork from rest_framework import status diff --git a/netbox/virtualization/urls.py b/netbox/virtualization/urls.py index b03b3bc0a..5fc5997a8 100644 --- a/netbox/virtualization/urls.py +++ b/netbox/virtualization/urls.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.conf.urls import url from extras.views import ObjectChangeLogView diff --git a/netbox/virtualization/views.py b/netbox/virtualization/views.py index 4ddacce40..7ded5e4a3 100644 --- a/netbox/virtualization/views.py +++ b/netbox/virtualization/views.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.contrib import messages from django.contrib.auth.mixins import PermissionRequiredMixin from django.db.models import Count From 5f66710fcda919ae232e39da5a6ca21d62283f83 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 14 Aug 2018 12:13:43 -0400 Subject: [PATCH 003/191] Closes #2292: Remove the deprecated UserAction model --- netbox/extras/admin.py | 20 +--- netbox/extras/api/serializers.py | 15 +-- netbox/extras/api/urls.py | 3 - netbox/extras/api/views.py | 14 --- netbox/extras/filters.py | 15 +-- .../migrations/0015_remove_useraction.py | 24 +++++ netbox/extras/models.py | 98 ------------------- netbox/templates/users/_user.html | 3 - netbox/templates/users/recent_activity.html | 22 ----- netbox/users/urls.py | 1 - netbox/users/views.py | 12 --- 11 files changed, 27 insertions(+), 200 deletions(-) create mode 100644 netbox/extras/migrations/0015_remove_useraction.py delete mode 100644 netbox/templates/users/recent_activity.html diff --git a/netbox/extras/admin.py b/netbox/extras/admin.py index 9320e7081..9f3678ab0 100644 --- a/netbox/extras/admin.py +++ b/netbox/extras/admin.py @@ -1,13 +1,8 @@ from django import forms from django.contrib import admin -from django.utils.safestring import mark_safe from utilities.forms import LaxURLField -from .constants import OBJECTCHANGE_ACTION_CREATE, OBJECTCHANGE_ACTION_DELETE, OBJECTCHANGE_ACTION_UPDATE -from .models import ( - ConfigContext, CustomField, CustomFieldChoice, Graph, ExportTemplate, ObjectChange, TopologyMap, UserAction, - Webhook, -) +from .models import CustomField, CustomFieldChoice, Graph, ExportTemplate, TopologyMap, Webhook def order_content_types(field): @@ -123,16 +118,3 @@ class TopologyMapAdmin(admin.ModelAdmin): prepopulated_fields = { 'slug': ['name'], } - - -# -# User actions -# - -@admin.register(UserAction) -class UserActionAdmin(admin.ModelAdmin): - actions = None - list_display = ['user', 'action', 'content_type', 'object_id', '_message'] - - def _message(self, obj): - return mark_safe(obj.message) diff --git a/netbox/extras/api/serializers.py b/netbox/extras/api/serializers.py index 0c5e79d80..ba438fd15 100644 --- a/netbox/extras/api/serializers.py +++ b/netbox/extras/api/serializers.py @@ -8,7 +8,7 @@ from dcim.api.serializers import ( ) from dcim.models import Device, DeviceRole, Platform, Rack, Region, Site from extras.models import ( - ConfigContext, ExportTemplate, Graph, ImageAttachment, ObjectChange, ReportResult, TopologyMap, UserAction, + ConfigContext, ExportTemplate, Graph, ImageAttachment, ObjectChange, ReportResult, TopologyMap, ) from extras.constants import * from tenancy.api.serializers import NestedTenantSerializer, NestedTenantGroupSerializer @@ -238,16 +238,3 @@ class ObjectChangeSerializer(serializers.ModelSerializer): context = {'request': self.context['request']} data = serializer(obj.changed_object, context=context).data return data - - -# -# User actions -# - -class UserActionSerializer(serializers.ModelSerializer): - user = NestedUserSerializer() - action = ChoiceField(choices=ACTION_CHOICES) - - class Meta: - model = UserAction - fields = ['id', 'time', 'user', 'action', 'message'] diff --git a/netbox/extras/api/urls.py b/netbox/extras/api/urls.py index c18ccf657..7a82cd359 100644 --- a/netbox/extras/api/urls.py +++ b/netbox/extras/api/urls.py @@ -41,8 +41,5 @@ router.register(r'reports', views.ReportViewSet, base_name='report') # Change logging router.register(r'object-changes', views.ObjectChangeViewSet) -# Recent activity -router.register(r'recent-activity', views.RecentActivityViewSet) - app_name = 'extras-api' urlpatterns = router.urls diff --git a/netbox/extras/api/views.py b/netbox/extras/api/views.py index 32ac0baec..1a91d069b 100644 --- a/netbox/extras/api/views.py +++ b/netbox/extras/api/views.py @@ -11,7 +11,6 @@ from taggit.models import Tag from extras import filters from extras.models import ( ConfigContext, CustomField, ExportTemplate, Graph, ImageAttachment, ObjectChange, ReportResult, TopologyMap, - UserAction, ) from extras.reports import get_report, get_reports from utilities.api import FieldChoicesViewSet, IsAuthenticatedOrLoginNotRequired, ModelViewSet @@ -230,16 +229,3 @@ class ObjectChangeViewSet(ReadOnlyModelViewSet): queryset = ObjectChange.objects.select_related('user') serializer_class = serializers.ObjectChangeSerializer filter_class = filters.ObjectChangeFilter - - -# -# User activity -# - -class RecentActivityViewSet(ReadOnlyModelViewSet): - """ - DEPRECATED: List all UserActions to provide a log of recent activity. - """ - queryset = UserAction.objects.all() - serializer_class = serializers.UserActionSerializer - filter_class = filters.UserActionFilter diff --git a/netbox/extras/filters.py b/netbox/extras/filters.py index 4239d1639..b73eb8981 100644 --- a/netbox/extras/filters.py +++ b/netbox/extras/filters.py @@ -1,5 +1,4 @@ import django_filters -from django.contrib.auth.models import User from django.contrib.contenttypes.models import ContentType from django.db.models import Q from taggit.models import Tag @@ -7,7 +6,7 @@ from taggit.models import Tag from dcim.models import DeviceRole, Platform, Region, Site from tenancy.models import Tenant, TenantGroup from .constants import CF_FILTER_DISABLED, CF_FILTER_EXACT, CF_TYPE_BOOLEAN, CF_TYPE_SELECT -from .models import ConfigContext, CustomField, Graph, ExportTemplate, ObjectChange, TopologyMap, UserAction +from .models import ConfigContext, CustomField, Graph, ExportTemplate, ObjectChange, TopologyMap class CustomFieldFilter(django_filters.Filter): @@ -227,15 +226,3 @@ class ObjectChangeFilter(django_filters.FilterSet): Q(user_name__icontains=value) | Q(object_repr__icontains=value) ) - - -class UserActionFilter(django_filters.FilterSet): - username = django_filters.ModelMultipleChoiceFilter( - name='user__username', - queryset=User.objects.all(), - to_field_name='username', - ) - - class Meta: - model = UserAction - fields = ['user'] diff --git a/netbox/extras/migrations/0015_remove_useraction.py b/netbox/extras/migrations/0015_remove_useraction.py new file mode 100644 index 000000000..eb750bc36 --- /dev/null +++ b/netbox/extras/migrations/0015_remove_useraction.py @@ -0,0 +1,24 @@ +# Generated by Django 2.0.8 on 2018-08-14 16:10 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('extras', '0014_configcontexts'), + ] + + operations = [ + migrations.RemoveField( + model_name='useraction', + name='content_type', + ), + migrations.RemoveField( + model_name='useraction', + name='user', + ), + migrations.DeleteModel( + name='UserAction', + ), + ] diff --git a/netbox/extras/models.py b/netbox/extras/models.py index f2000cdd2..fbe39a99e 100644 --- a/netbox/extras/models.py +++ b/netbox/extras/models.py @@ -12,7 +12,6 @@ from django.db.models import Q from django.http import HttpResponse from django.template import Template, Context from django.urls import reverse -from django.utils.safestring import mark_safe from dcim.constants import CONNECTION_STATUS_CONNECTED from utilities.utils import foreground_color @@ -842,100 +841,3 @@ class ObjectChange(models.Model): self.object_repr, self.object_data, ) - - -# -# User actions -# - -class UserActionManager(models.Manager): - - # Actions affecting a single object - def log_action(self, user, obj, action, message): - self.model.objects.create( - content_type=ContentType.objects.get_for_model(obj), - object_id=obj.pk, - user=user, - action=action, - message=message, - ) - - def log_create(self, user, obj, message=''): - self.log_action(user, obj, ACTION_CREATE, message) - - def log_edit(self, user, obj, message=''): - self.log_action(user, obj, ACTION_EDIT, message) - - def log_delete(self, user, obj, message=''): - self.log_action(user, obj, ACTION_DELETE, message) - - # Actions affecting multiple objects - def log_bulk_action(self, user, content_type, action, message): - self.model.objects.create( - content_type=content_type, - user=user, - action=action, - message=message, - ) - - def log_import(self, user, content_type, message=''): - self.log_bulk_action(user, content_type, ACTION_IMPORT, message) - - def log_bulk_create(self, user, content_type, message=''): - self.log_bulk_action(user, content_type, ACTION_BULK_CREATE, message) - - def log_bulk_edit(self, user, content_type, message=''): - self.log_bulk_action(user, content_type, ACTION_BULK_EDIT, message) - - def log_bulk_delete(self, user, content_type, message=''): - self.log_bulk_action(user, content_type, ACTION_BULK_DELETE, message) - - -# TODO: Remove UserAction, which has been replaced by ObjectChange. -class UserAction(models.Model): - """ - DEPRECATED: A record of an action (add, edit, or delete) performed on an object by a User. - """ - time = models.DateTimeField( - auto_now_add=True, - editable=False - ) - user = models.ForeignKey( - to=User, - on_delete=models.CASCADE, - related_name='actions' - ) - content_type = models.ForeignKey( - to=ContentType, - on_delete=models.CASCADE - ) - object_id = models.PositiveIntegerField( - blank=True, - null=True - ) - action = models.PositiveSmallIntegerField( - choices=ACTION_CHOICES - ) - message = models.TextField( - blank=True - ) - - objects = UserActionManager() - - class Meta: - ordering = ['-time'] - - def __str__(self): - if self.message: - return '{} {}'.format(self.user, self.message) - return '{} {} {}'.format(self.user, self.get_action_display(), self.content_type) - - def icon(self): - if self.action in [ACTION_CREATE, ACTION_BULK_CREATE, ACTION_IMPORT]: - return mark_safe('') - elif self.action in [ACTION_EDIT, ACTION_BULK_EDIT]: - return mark_safe('') - elif self.action in [ACTION_DELETE, ACTION_BULK_DELETE]: - return mark_safe('') - else: - return '' diff --git a/netbox/templates/users/_user.html b/netbox/templates/users/_user.html index 1a4b5c6c5..9f71b9633 100644 --- a/netbox/templates/users/_user.html +++ b/netbox/templates/users/_user.html @@ -21,9 +21,6 @@ User Key - - Recent Activity -
diff --git a/netbox/templates/users/recent_activity.html b/netbox/templates/users/recent_activity.html deleted file mode 100644 index 92933d78b..000000000 --- a/netbox/templates/users/recent_activity.html +++ /dev/null @@ -1,22 +0,0 @@ -{% extends 'users/_user.html' %} - -{% block title %}Recent Activity{% endblock %} - -{% block usercontent %} - - - - - - - - - {% for action in recent_activity %} - - - - - {% endfor %} - -
TimeAction
{{ action.time|date:'SHORT_DATETIME_FORMAT' }}{{ action.icon }} {{ action.message|safe }}
-{% endblock %} diff --git a/netbox/users/urls.py b/netbox/users/urls.py index d288866ec..a45f859e7 100644 --- a/netbox/users/urls.py +++ b/netbox/users/urls.py @@ -14,6 +14,5 @@ urlpatterns = [ url(r'^user-key/$', views.UserKeyView.as_view(), name='userkey'), url(r'^user-key/edit/$', views.UserKeyEditView.as_view(), name='userkey_edit'), url(r'^session-key/delete/$', views.SessionKeyDeleteView.as_view(), name='sessionkey_delete'), - url(r'^recent-activity/$', views.RecentActivityView.as_view(), name='recent_activity'), ] diff --git a/netbox/users/views.py b/netbox/users/views.py index de78ad1fd..45a162d7b 100644 --- a/netbox/users/views.py +++ b/netbox/users/views.py @@ -196,18 +196,6 @@ class SessionKeyDeleteView(LoginRequiredMixin, View): }) -@method_decorator(login_required, name='dispatch') -class RecentActivityView(View): - template_name = 'users/recent_activity.html' - - def get(self, request): - - return render(request, self.template_name, { - 'recent_activity': request.user.actions.all()[:50], - 'active_tab': 'recent_activity', - }) - - # # API tokens # From 6dd62dc891bf8173b8a8aba598410436db6f2685 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 14 Aug 2018 14:06:16 -0400 Subject: [PATCH 004/191] Refactored log_change() methods for device components --- netbox/dcim/models.py | 85 +++++++++++++++---------------------------- 1 file changed, 30 insertions(+), 55 deletions(-) diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index 0c74600bb..8fdc5e998 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -27,25 +27,40 @@ from .fields import ASNField, MACAddressField from .querysets import InterfaceQuerySet -class ComponentModel(models.Model): +class ComponentTemplateModel(models.Model): class Meta: abstract = True - def get_component_parent(self): - raise NotImplementedError( - "ComponentModel must implement get_component_parent()" - ) - def log_change(self, user, request_id, action): """ - Log an ObjectChange including the parent Device/VM. + Log an ObjectChange including the parent DeviceType. """ ObjectChange( user=user, request_id=request_id, changed_object=self, - related_object=self.get_component_parent(), + related_object=self.device_type, + action=action, + object_data=serialize_object(self) + ).save() + + +class ComponentModel(models.Model): + + class Meta: + abstract = True + + def log_change(self, user, request_id, action): + """ + Log an ObjectChange including the parent Device/VM. + """ + parent = self.device if self.device is not None else getattr(self, 'virtual_machine', None) + ObjectChange( + user=user, + request_id=request_id, + changed_object=self, + related_object=parent, action=action, object_data=serialize_object(self) ).save() @@ -871,7 +886,7 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel): return bool(self.subdevice_role is False) -class ConsolePortTemplate(ComponentModel): +class ConsolePortTemplate(ComponentTemplateModel): """ A template for a ConsolePort to be created for a new Device. """ @@ -891,11 +906,8 @@ class ConsolePortTemplate(ComponentModel): def __str__(self): return self.name - def get_component_parent(self): - return self.device_type - -class ConsoleServerPortTemplate(ComponentModel): +class ConsoleServerPortTemplate(ComponentTemplateModel): """ A template for a ConsoleServerPort to be created for a new Device. """ @@ -915,11 +927,8 @@ class ConsoleServerPortTemplate(ComponentModel): def __str__(self): return self.name - def get_component_parent(self): - return self.device_type - -class PowerPortTemplate(ComponentModel): +class PowerPortTemplate(ComponentTemplateModel): """ A template for a PowerPort to be created for a new Device. """ @@ -939,11 +948,8 @@ class PowerPortTemplate(ComponentModel): def __str__(self): return self.name - def get_component_parent(self): - return self.device_type - -class PowerOutletTemplate(ComponentModel): +class PowerOutletTemplate(ComponentTemplateModel): """ A template for a PowerOutlet to be created for a new Device. """ @@ -963,11 +969,8 @@ class PowerOutletTemplate(ComponentModel): def __str__(self): return self.name - def get_component_parent(self): - return self.device_type - -class InterfaceTemplate(ComponentModel): +class InterfaceTemplate(ComponentTemplateModel): """ A template for a physical data interface on a new Device. """ @@ -997,11 +1000,8 @@ class InterfaceTemplate(ComponentModel): def __str__(self): return self.name - def get_component_parent(self): - return self.device_type - -class DeviceBayTemplate(ComponentModel): +class DeviceBayTemplate(ComponentTemplateModel): """ A template for a DeviceBay to be created for a new parent Device. """ @@ -1021,9 +1021,6 @@ class DeviceBayTemplate(ComponentModel): def __str__(self): return self.name - def get_component_parent(self): - return self.device_type - # # Devices @@ -1562,9 +1559,6 @@ class ConsolePort(ComponentModel): def get_absolute_url(self): return self.device.get_absolute_url() - def get_component_parent(self): - return self.device - def to_csv(self): return ( self.cs_port.device.identifier if self.cs_port else None, @@ -1614,9 +1608,6 @@ class ConsoleServerPort(ComponentModel): def get_absolute_url(self): return self.device.get_absolute_url() - def get_component_parent(self): - return self.device - def clean(self): # Check that the parent device's DeviceType is a console server @@ -1671,9 +1662,6 @@ class PowerPort(ComponentModel): def get_absolute_url(self): return self.device.get_absolute_url() - def get_component_parent(self): - return self.device - def to_csv(self): return ( self.power_outlet.device.identifier if self.power_outlet else None, @@ -1723,9 +1711,6 @@ class PowerOutlet(ComponentModel): def get_absolute_url(self): return self.device.get_absolute_url() - def get_component_parent(self): - return self.device - def clean(self): # Check that the parent device's DeviceType is a PDU @@ -1831,9 +1816,6 @@ class Interface(ComponentModel): def get_absolute_url(self): return reverse('dcim:interface', kwargs={'pk': self.pk}) - def get_component_parent(self): - return self.device or self.virtual_machine - def clean(self): # Check that the parent device's DeviceType is a network device @@ -1913,7 +1895,7 @@ class Interface(ComponentModel): # the component parent will raise DoesNotExist. For more discussion, see # https://github.com/digitalocean/netbox/issues/2323 try: - parent_obj = self.get_component_parent() + parent_obj = self.device or self.virtual_machine except ObjectDoesNotExist: parent_obj = None @@ -1929,7 +1911,6 @@ class Interface(ComponentModel): }) ).save() - # TODO: Replace `parent` with get_component_parent() (from ComponentModel) @property def parent(self): return self.device or self.virtual_machine @@ -2103,9 +2084,6 @@ class DeviceBay(ComponentModel): def get_absolute_url(self): return self.device.get_absolute_url() - def get_component_parent(self): - return self.device - def clean(self): # Validate that the parent Device can have DeviceBays @@ -2194,9 +2172,6 @@ class InventoryItem(ComponentModel): def get_absolute_url(self): return self.device.get_absolute_url() - def get_component_parent(self): - return self.device - def to_csv(self): return ( self.device.name or '{' + self.device.pk + '}', From 7ec415584575acb5968c1e3e038c67bbc8eec029 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 15 Aug 2018 14:18:52 -0400 Subject: [PATCH 005/191] Closes #2359: Implement custom makemigrations command to ignore extraneous field attributes --- netbox/utilities/management/__init__.py | 0 .../utilities/management/commands/__init__.py | 0 .../management/commands/makemigrations.py | 32 +++++++++++++++++++ 3 files changed, 32 insertions(+) create mode 100644 netbox/utilities/management/__init__.py create mode 100644 netbox/utilities/management/commands/__init__.py create mode 100644 netbox/utilities/management/commands/makemigrations.py diff --git a/netbox/utilities/management/__init__.py b/netbox/utilities/management/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/netbox/utilities/management/commands/__init__.py b/netbox/utilities/management/commands/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/netbox/utilities/management/commands/makemigrations.py b/netbox/utilities/management/commands/makemigrations.py new file mode 100644 index 000000000..7ebf19748 --- /dev/null +++ b/netbox/utilities/management/commands/makemigrations.py @@ -0,0 +1,32 @@ +from django.core.management.commands.makemigrations import Command +from django.db import models + + +EXEMPT_ATTRS = [ + 'choices', + 'help_text', + 'verbose_name', +] + +_deconstruct = models.Field.deconstruct + + +def custom_deconstruct(field): + """ + Imitate the behavior of the stock deconstruct() method, but ignore the field attributes listed above. + """ + name, path, args, kwargs = _deconstruct(field) + + # Remove any ignored attributes + for attr in EXEMPT_ATTRS: + kwargs.pop(attr, None) + + # A hack to accommodate TimeZoneField, which employs a custom deconstructor to check whether the default choices + # have changed + if hasattr(field, 'CHOICES'): + kwargs['choices'] = field.CHOICES + + return name, path, args, kwargs + + +models.Field.deconstruct = custom_deconstruct From 7145f86a6ed298e59f9bc2236db2737e9d1a2042 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 15 Aug 2018 15:02:58 -0400 Subject: [PATCH 006/191] #2359: Extended exempt attributes to 'migrate' command --- .../utilities/management/commands/__init__.py | 28 +++++++++++++++++ .../management/commands/makemigrations.py | 30 ++----------------- .../utilities/management/commands/migrate.py | 6 ++++ 3 files changed, 36 insertions(+), 28 deletions(-) create mode 100644 netbox/utilities/management/commands/migrate.py diff --git a/netbox/utilities/management/commands/__init__.py b/netbox/utilities/management/commands/__init__.py index e69de29bb..697a3ed9a 100644 --- a/netbox/utilities/management/commands/__init__.py +++ b/netbox/utilities/management/commands/__init__.py @@ -0,0 +1,28 @@ +from django.db import models + + +EXEMPT_ATTRS = [ + 'choices', + 'help_text', + 'verbose_name', +] + +_deconstruct = models.Field.deconstruct + + +def custom_deconstruct(field): + """ + Imitate the behavior of the stock deconstruct() method, but ignore the field attributes listed above. + """ + name, path, args, kwargs = _deconstruct(field) + + # Remove any ignored attributes + for attr in EXEMPT_ATTRS: + kwargs.pop(attr, None) + + # A hack to accommodate TimeZoneField, which employs a custom deconstructor to check whether the default choices + # have changed + if hasattr(field, 'CHOICES'): + kwargs['choices'] = field.CHOICES + + return name, path, args, kwargs diff --git a/netbox/utilities/management/commands/makemigrations.py b/netbox/utilities/management/commands/makemigrations.py index 7ebf19748..f906feb5a 100644 --- a/netbox/utilities/management/commands/makemigrations.py +++ b/netbox/utilities/management/commands/makemigrations.py @@ -1,32 +1,6 @@ +# noinspection PyUnresolvedReferences from django.core.management.commands.makemigrations import Command from django.db import models - - -EXEMPT_ATTRS = [ - 'choices', - 'help_text', - 'verbose_name', -] - -_deconstruct = models.Field.deconstruct - - -def custom_deconstruct(field): - """ - Imitate the behavior of the stock deconstruct() method, but ignore the field attributes listed above. - """ - name, path, args, kwargs = _deconstruct(field) - - # Remove any ignored attributes - for attr in EXEMPT_ATTRS: - kwargs.pop(attr, None) - - # A hack to accommodate TimeZoneField, which employs a custom deconstructor to check whether the default choices - # have changed - if hasattr(field, 'CHOICES'): - kwargs['choices'] = field.CHOICES - - return name, path, args, kwargs - +from . import custom_deconstruct models.Field.deconstruct = custom_deconstruct diff --git a/netbox/utilities/management/commands/migrate.py b/netbox/utilities/management/commands/migrate.py new file mode 100644 index 000000000..9c2a04284 --- /dev/null +++ b/netbox/utilities/management/commands/migrate.py @@ -0,0 +1,6 @@ +# noinspection PyUnresolvedReferences +from django.core.management.commands.migrate import Command +from django.db import models +from . import custom_deconstruct + +models.Field.deconstruct = custom_deconstruct From c4be440cd1b6bf7829fa68981507070e482e50de Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 16 Aug 2018 12:21:24 -0400 Subject: [PATCH 007/191] Closes #2367: Remove deprecated RPCClient functionality --- netbox/dcim/api/serializers.py | 2 +- netbox/dcim/constants.py | 10 - netbox/dcim/fixtures/dcim.json | 6 +- netbox/dcim/fixtures/initial_data.json | 18 +- netbox/dcim/forms.py | 2 +- .../0062_remove_platform_rpc_client.py | 17 ++ netbox/dcim/models.py | 15 -- .../management/commands/run_inventory.py | 125 ---------- netbox/extras/rpc.py | 235 ------------------ 9 files changed, 27 insertions(+), 403 deletions(-) create mode 100644 netbox/dcim/migrations/0062_remove_platform_rpc_client.py delete mode 100644 netbox/extras/management/commands/run_inventory.py delete mode 100644 netbox/extras/rpc.py diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index 26100c3a7..c357891b6 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -344,7 +344,7 @@ class PlatformSerializer(ValidatedModelSerializer): class Meta: model = Platform - fields = ['id', 'name', 'slug', 'manufacturer', 'napalm_driver', 'napalm_args', 'rpc_client'] + fields = ['id', 'name', 'slug', 'manufacturer', 'napalm_driver', 'napalm_args'] class NestedPlatformSerializer(WritableNestedSerializer): diff --git a/netbox/dcim/constants.py b/netbox/dcim/constants.py index 2249f0e0d..0bec76bf3 100644 --- a/netbox/dcim/constants.py +++ b/netbox/dcim/constants.py @@ -243,13 +243,3 @@ CONNECTION_STATUS_CHOICES = [ [CONNECTION_STATUS_PLANNED, 'Planned'], [CONNECTION_STATUS_CONNECTED, 'Connected'], ] - -# Platform -> RPC client mappings -RPC_CLIENT_JUNIPER_JUNOS = 'juniper-junos' -RPC_CLIENT_CISCO_IOS = 'cisco-ios' -RPC_CLIENT_OPENGEAR = 'opengear' -RPC_CLIENT_CHOICES = [ - [RPC_CLIENT_JUNIPER_JUNOS, 'Juniper Junos (NETCONF)'], - [RPC_CLIENT_CISCO_IOS, 'Cisco IOS (SSH)'], - [RPC_CLIENT_OPENGEAR, 'Opengear (SSH)'], -] diff --git a/netbox/dcim/fixtures/dcim.json b/netbox/dcim/fixtures/dcim.json index 761f1ba69..709995f95 100644 --- a/netbox/dcim/fixtures/dcim.json +++ b/netbox/dcim/fixtures/dcim.json @@ -1903,8 +1903,7 @@ "pk": 1, "fields": { "name": "Juniper Junos", - "slug": "juniper-junos", - "rpc_client": "juniper-junos" + "slug": "juniper-junos" } }, { @@ -1912,8 +1911,7 @@ "pk": 2, "fields": { "name": "Opengear", - "slug": "opengear", - "rpc_client": "opengear" + "slug": "opengear" } }, { diff --git a/netbox/dcim/fixtures/initial_data.json b/netbox/dcim/fixtures/initial_data.json index e765de227..83f79e3a3 100644 --- a/netbox/dcim/fixtures/initial_data.json +++ b/netbox/dcim/fixtures/initial_data.json @@ -149,8 +149,7 @@ "pk": 1, "fields": { "name": "Cisco IOS", - "slug": "cisco-ios", - "rpc_client": "cisco-ios" + "slug": "cisco-ios" } }, { @@ -158,8 +157,7 @@ "pk": 2, "fields": { "name": "Cisco NX-OS", - "slug": "cisco-nx-os", - "rpc_client": "" + "slug": "cisco-nx-os" } }, { @@ -167,8 +165,7 @@ "pk": 3, "fields": { "name": "Juniper Junos", - "slug": "juniper-junos", - "rpc_client": "juniper-junos" + "slug": "juniper-junos" } }, { @@ -176,8 +173,7 @@ "pk": 4, "fields": { "name": "Arista EOS", - "slug": "arista-eos", - "rpc_client": "" + "slug": "arista-eos" } }, { @@ -185,8 +181,7 @@ "pk": 5, "fields": { "name": "Linux", - "slug": "linux", - "rpc_client": "" + "slug": "linux" } }, { @@ -194,8 +189,7 @@ "pk": 6, "fields": { "name": "Opengear", - "slug": "opengear", - "rpc_client": "opengear" + "slug": "opengear" } } ] diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 66ed2447f..30c798e9e 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -744,7 +744,7 @@ class PlatformForm(BootstrapMixin, forms.ModelForm): class Meta: model = Platform - fields = ['name', 'slug', 'manufacturer', 'napalm_driver', 'napalm_args', 'rpc_client'] + fields = ['name', 'slug', 'manufacturer', 'napalm_driver', 'napalm_args'] widgets = { 'napalm_args': SmallTextarea(), } diff --git a/netbox/dcim/migrations/0062_remove_platform_rpc_client.py b/netbox/dcim/migrations/0062_remove_platform_rpc_client.py new file mode 100644 index 000000000..22a4900d1 --- /dev/null +++ b/netbox/dcim/migrations/0062_remove_platform_rpc_client.py @@ -0,0 +1,17 @@ +# Generated by Django 2.0.8 on 2018-08-16 16:17 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('dcim', '0061_platform_napalm_args'), + ] + + operations = [ + migrations.RemoveField( + model_name='platform', + name='rpc_client', + ), + ] diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index 8fdc5e998..432d78977 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -17,7 +17,6 @@ from timezone_field import TimeZoneField from circuits.models import Circuit from extras.constants import OBJECTCHANGE_ACTION_DELETE, OBJECTCHANGE_ACTION_UPDATE from extras.models import ConfigContextModel, CustomFieldModel, ObjectChange -from extras.rpc import RPC_CLIENTS from utilities.fields import ColorField, NullableCharField from utilities.managers import NaturalOrderByManager from utilities.models import ChangeLoggedModel @@ -1096,12 +1095,6 @@ class Platform(ChangeLoggedModel): verbose_name='NAPALM arguments', help_text='Additional arguments to pass when initiating the NAPALM driver (JSON format)' ) - rpc_client = models.CharField( - max_length=30, - choices=RPC_CLIENT_CHOICES, - blank=True, - verbose_name='Legacy RPC client' - ) csv_headers = ['name', 'slug', 'manufacturer', 'napalm_driver', 'napalm_args'] @@ -1507,14 +1500,6 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel): def get_status_class(self): return STATUS_CLASSES[self.status] - def get_rpc_client(self): - """ - Return the appropriate RPC (e.g. NETCONF, ssh, etc.) client for this device's platform, if one is defined. - """ - if not self.platform: - return None - return RPC_CLIENTS.get(self.platform.rpc_client) - # # Console ports diff --git a/netbox/extras/management/commands/run_inventory.py b/netbox/extras/management/commands/run_inventory.py deleted file mode 100644 index e5ce1bbca..000000000 --- a/netbox/extras/management/commands/run_inventory.py +++ /dev/null @@ -1,125 +0,0 @@ -from getpass import getpass - -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_STATUS_ACTIVE, Device, InventoryItem, Site - - -class Command(BaseCommand): - help = "Update inventory information for specified devices" - username = settings.NAPALM_USERNAME - password = settings.NAPALM_PASSWORD - - def add_arguments(self, parser): - parser.add_argument('-u', '--username', dest='username', help="Specify the username to use") - parser.add_argument('-p', '--password', action='store_true', default=False, help="Prompt for password to use") - parser.add_argument('-s', '--site', dest='site', action='append', - help="Filter devices by site (include argument once per site)") - parser.add_argument('-n', '--name', dest='name', help="Filter devices by name (regular expression)") - parser.add_argument('--full', action='store_true', default=False, help="For inventory update for all devices") - parser.add_argument('--fake', action='store_true', default=False, help="Do not actually update database") - - def handle(self, *args, **options): - - def create_inventory_items(inventory_items, parent=None): - for item in inventory_items: - i = InventoryItem(device=device, parent=parent, name=item['name'], part_id=item['part_id'], - serial=item['serial'], discovered=True) - i.save() - create_inventory_items(item.get('items', []), parent=i) - - # Credentials - if options['username']: - self.username = options['username'] - if options['password']: - self.password = getpass("Password: ") - - # Attempt to inventory only active devices - device_list = Device.objects.filter(status=DEVICE_STATUS_ACTIVE) - - # --site: Include only devices belonging to specified site(s) - if options['site']: - sites = Site.objects.filter(slug__in=options['site']) - if sites: - site_names = [s.name for s in sites] - self.stdout.write("Running inventory for these sites: {}".format(', '.join(site_names))) - else: - raise CommandError("One or more sites specified but none found.") - device_list = device_list.filter(site__in=sites) - - # --name: Filter devices by name matching a regex - if options['name']: - device_list = device_list.filter(name__iregex=options['name']) - - # --full: Gather inventory data for *all* devices - if options['full']: - self.stdout.write("WARNING: Running inventory for all devices! Prior data will be overwritten. (--full)") - - # --fake: Gathering data but not updating the database - if options['fake']: - self.stdout.write("WARNING: Inventory data will not be saved! (--fake)") - - device_count = device_list.count() - self.stdout.write("** Found {} devices...".format(device_count)) - - for i, device in enumerate(device_list, start=1): - - self.stdout.write("[{}/{}] {}: ".format(i, device_count, device.name), ending='') - - # Skip inactive devices - if not device.status: - self.stdout.write("Skipped (not active)") - continue - - # Skip devices without primary_ip set - if not device.primary_ip: - self.stdout.write("Skipped (no primary IP set)") - continue - - # Skip devices which have already been inventoried if not doing a full update - if device.serial and not options['full']: - self.stdout.write("Skipped (Serial: {})".format(device.serial)) - continue - - RPC = device.get_rpc_client() - if not RPC: - self.stdout.write("Skipped (no RPC client available for platform {})".format(device.platform)) - continue - - # Connect to device and retrieve inventory info - try: - with RPC(device, self.username, self.password) as rpc_client: - inventory = rpc_client.get_inventory() - except KeyboardInterrupt: - raise - except (AuthenticationError, AuthenticationException): - self.stdout.write("Authentication error!") - continue - except Exception as e: - self.stdout.write("Error: {}".format(e)) - continue - - if options['verbosity'] > 1: - self.stdout.write("") - self.stdout.write("\tSerial: {}".format(inventory['chassis']['serial'])) - self.stdout.write("\tDescription: {}".format(inventory['chassis']['description'])) - for item in inventory['items']: - self.stdout.write("\tItem: {} / {} ({})".format(item['name'], item['part_id'], - item['serial'])) - else: - self.stdout.write("{} ({})".format(inventory['chassis']['description'], inventory['chassis']['serial'])) - - if not options['fake']: - with transaction.atomic(): - # Update device serial - if device.serial != inventory['chassis']['serial']: - device.serial = inventory['chassis']['serial'] - device.save() - InventoryItem.objects.filter(device=device, discovered=True).delete() - create_inventory_items(inventory.get('items', [])) - - self.stdout.write("Finished!") diff --git a/netbox/extras/rpc.py b/netbox/extras/rpc.py deleted file mode 100644 index 60fc9208f..000000000 --- a/netbox/extras/rpc.py +++ /dev/null @@ -1,235 +0,0 @@ -import re -import time - -import paramiko -import xmltodict -from ncclient import manager - -CONNECT_TIMEOUT = 5 # seconds - - -class RPCClient(object): - - def __init__(self, device, username='', password=''): - self.username = username - self.password = password - try: - self.host = str(device.primary_ip.address.ip) - except AttributeError: - raise Exception("Specified device ({}) does not have a primary IP defined.".format(device)) - - def get_inventory(self): - """ - Returns a dictionary representing the device chassis and installed inventory items. - - { - 'chassis': { - 'serial': , - 'description': , - } - 'items': [ - { - 'name': , - 'part_id': , - 'serial': , - }, - ... - ] - } - """ - raise NotImplementedError("Feature not implemented for this platform.") - - -class SSHClient(RPCClient): - def __enter__(self): - - self.ssh = paramiko.SSHClient() - self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - try: - self.ssh.connect( - self.host, - username=self.username, - password=self.password, - timeout=CONNECT_TIMEOUT, - allow_agent=False, - look_for_keys=False, - ) - except paramiko.AuthenticationException: - # Try default credentials if the configured creds don't work - try: - default_creds = self.default_credentials - if default_creds.get('username') and default_creds.get('password'): - self.ssh.connect( - self.host, - username=default_creds['username'], - password=default_creds['password'], - timeout=CONNECT_TIMEOUT, - allow_agent=False, - look_for_keys=False, - ) - else: - raise ValueError('default_credentials are incomplete.') - except AttributeError: - raise paramiko.AuthenticationException - - self.session = self.ssh.invoke_shell() - self.session.recv(1000) - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - self.ssh.close() - - def _send(self, cmd, pause=1): - self.session.send('{}\n'.format(cmd)) - data = '' - time.sleep(pause) - while self.session.recv_ready(): - data += self.session.recv(4096).decode() - if not data: - break - return data - - -class JunosNC(RPCClient): - """ - NETCONF client for Juniper Junos devices - """ - - def __enter__(self): - - # Initiate a connection to the device - self.manager = manager.connect(host=self.host, username=self.username, password=self.password, - hostkey_verify=False, timeout=CONNECT_TIMEOUT) - - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - - # Close the connection to the device - self.manager.close_session() - - def get_inventory(self): - - def glean_items(node, depth=0): - items = [] - items_list = node.get('chassis{}-module'.format('-sub' * depth), []) - # Junos like to return single children directly instead of as a single-item list - if hasattr(items_list, 'items'): - items_list = [items_list] - for item in items_list: - m = { - 'name': item['name'], - 'part_id': item.get('model-number') or item.get('part-number', ''), - 'serial': item.get('serial-number', ''), - } - child_items = glean_items(item, depth + 1) - if child_items: - m['items'] = child_items - items.append(m) - return items - - rpc_reply = self.manager.dispatch('get-chassis-inventory') - inventory_raw = xmltodict.parse(rpc_reply.xml)['rpc-reply']['chassis-inventory']['chassis'] - - result = dict() - - # Gather chassis data - result['chassis'] = { - 'serial': inventory_raw['serial-number'], - 'description': inventory_raw['description'], - } - - # Gather inventory items - result['items'] = glean_items(inventory_raw) - - return result - - -class IOSSSH(SSHClient): - """ - SSH client for Cisco IOS devices - """ - - def get_inventory(self): - def version(): - - def parse(cmd_out, rex): - for i in cmd_out: - match = re.search(rex, i) - if match: - return match.groups()[0] - - sh_ver = self._send('show version').split('\r\n') - return { - 'serial': parse(sh_ver, r'Processor board ID ([^\s]+)'), - 'description': parse(sh_ver, r'cisco ([^\s]+)') - } - - def items(chassis_serial=None): - cmd = self._send('show inventory').split('\r\n\r\n') - for i in cmd: - i_fmt = i.replace('\r\n', ' ') - try: - m_name = re.search(r'NAME: "([^"]+)"', i_fmt).group(1) - m_pid = re.search(r'PID: ([^\s]+)', i_fmt).group(1) - m_serial = re.search(r'SN: ([^\s]+)', i_fmt).group(1) - # Omit built-in items and those with no PID - if m_serial != chassis_serial and m_pid.lower() != 'unspecified': - yield { - 'name': m_name, - 'part_id': m_pid, - 'serial': m_serial, - } - except AttributeError: - continue - - self._send('term length 0') - sh_version = version() - - return { - 'chassis': sh_version, - 'items': list(items(chassis_serial=sh_version.get('serial'))) - } - - -class OpengearSSH(SSHClient): - """ - SSH client for Opengear devices - """ - default_credentials = { - 'username': 'root', - 'password': 'default', - } - - def get_inventory(self): - - try: - stdin, stdout, stderr = self.ssh.exec_command("showserial") - serial = stdout.readlines()[0].strip() - except Exception: - raise RuntimeError("Failed to glean chassis serial from device.") - # Older models don't provide serial info - if serial == "No serial number information available": - serial = '' - - try: - stdin, stdout, stderr = self.ssh.exec_command("config -g config.system.model") - description = stdout.readlines()[0].split(' ', 1)[1].strip() - except Exception: - raise RuntimeError("Failed to glean chassis description from device.") - - return { - 'chassis': { - 'serial': serial, - 'description': description, - }, - 'items': [], - } - - -# For mapping platform -> NC client -RPC_CLIENTS = { - 'juniper-junos': JunosNC, - 'cisco-ios': IOSSSH, - 'opengear': OpengearSSH, -} From 19b737a534e38a2c1344dc5be0f0c1d56f384ba1 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 16 Aug 2018 16:33:13 -0400 Subject: [PATCH 008/191] Added v2.5 notes (so far) to changelog --- CHANGELOG.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f164f4c1..f1bc7f499 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,24 @@ +v2.5.0 (FUTURE) + +--- + +## Notes + +* As promised, Python 2 support has been completed removed. Python 3.5 or higher is now required to run NetBox. +* The UserAction model, which was deprecated by the new change logging feature in NetBox v2.4, has been removed. If you need to archive user activity, do so prior to upgrading to NetBox v2.5, as the database migration will remove all data associated with this model. + +## Enhancements + +* [#2000](https://github.com/digitalocean/netbox/issues/2000) - Dropped support for Python 2 +* [#2292](https://github.com/digitalocean/netbox/issues/2292) - Removed the deprecated UserAction model +* [#2367](https://github.com/digitalocean/netbox/issues/2367) - Removed deprecated RPCClient functionality + +## API Changes + +* The `rpc_client` field has been removed from dcim.Platform (see #2367) + +--- + v2.4.4 (FUTURE) ## Enhancements From 3eddeeadc56a24e4b7574ea918159c0660943e05 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 3 Oct 2018 14:04:16 -0400 Subject: [PATCH 009/191] Initial work on #20 - patch panels --- netbox/dcim/api/serializers.py | 104 +++++++- netbox/dcim/api/urls.py | 4 + netbox/dcim/api/views.py | 31 ++- netbox/dcim/constants.py | 30 +++ netbox/dcim/filters.py | 35 ++- netbox/dcim/forms.py | 231 +++++++++++++++++- .../dcim/migrations/0065_patch_panel_ports.py | 114 +++++++++ netbox/dcim/models.py | 201 ++++++++++++++- netbox/dcim/tables.py | 44 +++- netbox/dcim/urls.py | 24 ++ netbox/dcim/views.py | 137 ++++++++++- netbox/templates/dcim/device.html | 105 ++++++++ netbox/templates/dcim/devicetype.html | 19 ++ netbox/templates/dcim/devicetype_edit.html | 1 + netbox/templates/dcim/inc/frontpanelport.html | 25 ++ netbox/templates/dcim/inc/rearpanelport.html | 24 ++ netbox/utilities/forms.py | 3 + netbox/utilities/views.py | 3 +- 18 files changed, 1101 insertions(+), 34 deletions(-) create mode 100644 netbox/dcim/migrations/0065_patch_panel_ports.py create mode 100644 netbox/templates/dcim/inc/frontpanelport.html create mode 100644 netbox/templates/dcim/inc/rearpanelport.html diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index 696464662..ed9ee29ea 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -3,15 +3,13 @@ from rest_framework.validators import UniqueTogetherValidator from taggit_serializer.serializers import TaggitSerializer, TagListSerializerField from circuits.models import Circuit, CircuitTermination -from dcim.constants import ( - CONNECTION_STATUS_CHOICES, DEVICE_STATUS_CHOICES, IFACE_FF_CHOICES, IFACE_MODE_CHOICES, IFACE_ORDERING_CHOICES, - RACK_FACE_CHOICES, RACK_TYPE_CHOICES, RACK_WIDTH_CHOICES, SITE_STATUS_CHOICES, SUBDEVICE_ROLE_CHOICES, -) +from dcim.constants import * from dcim.models import ( 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, VirtualChassis, + DeviceBayTemplate, DeviceType, DeviceRole, FrontPanelPort, FrontPanelPortTemplate, Interface, InterfaceConnection, + InterfaceTemplate, Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, + PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPanelPort, RearPanelPortTemplate, Region, Site, + VirtualChassis, ) from extras.api.customfields import CustomFieldModelSerializer from ipam.models import IPAddress, VLAN @@ -229,8 +227,8 @@ class DeviceTypeSerializer(TaggitSerializer, CustomFieldModelSerializer): model = DeviceType fields = [ 'id', 'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'interface_ordering', - 'is_console_server', 'is_pdu', 'is_network_device', 'subdevice_role', 'comments', 'tags', 'custom_fields', - 'created', 'last_updated', 'instance_count', + 'is_console_server', 'is_pdu', 'is_network_device', 'is_patch_panel', 'subdevice_role', 'comments', 'tags', + 'custom_fields', 'created', 'last_updated', 'instance_count', ] @@ -304,6 +302,49 @@ class InterfaceTemplateSerializer(ValidatedModelSerializer): fields = ['id', 'device_type', 'name', 'form_factor', 'mgmt_only'] +# +# Rear panel port templates +# + +class RearPanelPortTemplateSerializer(ValidatedModelSerializer): + device_type = NestedDeviceTypeSerializer() + type = ChoiceField(choices=PANELPORT_TYPE_CHOICES) + + class Meta: + model = RearPanelPortTemplate + fields = ['id', 'device_type', 'name', 'type', 'positions'] + + +class NestedRearPanelPortTemplateSerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rearpanelporttemplate-detail') + + class Meta: + model = RearPanelPortTemplate + fields = ['id', 'url', 'name'] + + +# +# Front panel port templates +# + +class FrontPanelPortTemplateSerializer(ValidatedModelSerializer): + device_type = NestedDeviceTypeSerializer() + type = ChoiceField(choices=PANELPORT_TYPE_CHOICES) + rear_port = NestedRearPanelPortTemplateSerializer() + + class Meta: + model = FrontPanelPortTemplate + fields = ['id', 'device_type', 'name', 'type', 'rear_port', 'rear_port_position'] + + +class NestedFrontPanelPortTemplateSerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='dcim-api:frontpanelporttemplate-detail') + + class Meta: + model = FrontPanelPortTemplate + fields = ['id', 'url', 'name'] + + # # Device bay templates # @@ -634,6 +675,51 @@ class InterfaceSerializer(TaggitSerializer, ValidatedModelSerializer): return None +# +# Rear panel ports +# + +class RearPanelPortSerializer(ValidatedModelSerializer): + device = NestedDeviceSerializer() + type = ChoiceField(choices=PANELPORT_TYPE_CHOICES) + tags = TagListSerializerField(required=False) + + class Meta: + model = RearPanelPort + fields = ['id', 'device', 'name', 'type', 'positions', 'tags'] + + +class NestedRearPanelPortSerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rearpanelport-detail') + + class Meta: + model = RearPanelPort + fields = ['id', 'url', 'name'] + + +# +# Front panel ports +# + +class FrontPanelPortSerializer(ValidatedModelSerializer): + device = NestedDeviceSerializer() + type = ChoiceField(choices=PANELPORT_TYPE_CHOICES) + rear_port = NestedRearPanelPortSerializer() + tags = TagListSerializerField(required=False) + + class Meta: + model = FrontPanelPort + fields = ['id', 'device', 'name', 'type', 'rear_port', 'rear_port_position', 'tags'] + + +class NestedFrontPanelPortSerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='dcim-api:frontpanelport-detail') + + class Meta: + model = FrontPanelPort + fields = ['id', 'url', 'name'] + + # # Device bays # diff --git a/netbox/dcim/api/urls.py b/netbox/dcim/api/urls.py index 6456d53a4..0e6a5d344 100644 --- a/netbox/dcim/api/urls.py +++ b/netbox/dcim/api/urls.py @@ -37,6 +37,8 @@ router.register(r'console-server-port-templates', views.ConsoleServerPortTemplat router.register(r'power-port-templates', views.PowerPortTemplateViewSet) router.register(r'power-outlet-templates', views.PowerOutletTemplateViewSet) router.register(r'interface-templates', views.InterfaceTemplateViewSet) +router.register(r'front-panel-port-templates', views.FrontPanelPortTemplateViewSet) +router.register(r'rear-panel-port-templates', views.RearPanelPortTemplateViewSet) router.register(r'device-bay-templates', views.DeviceBayTemplateViewSet) # Devices @@ -50,6 +52,8 @@ router.register(r'console-server-ports', views.ConsoleServerPortViewSet) router.register(r'power-ports', views.PowerPortViewSet) router.register(r'power-outlets', views.PowerOutletViewSet) router.register(r'interfaces', views.InterfaceViewSet) +router.register(r'front-panel-ports', views.FrontPanelPortViewSet) +router.register(r'rear-panel-ports', views.RearPanelPortViewSet) router.register(r'device-bays', views.DeviceBayViewSet) router.register(r'inventory-items', views.InventoryItemViewSet) diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index 4fb9c3f20..a9f4c6f9f 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -14,9 +14,10 @@ from rest_framework.viewsets import GenericViewSet, ViewSet 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, VirtualChassis, + DeviceBayTemplate, DeviceRole, DeviceType, FrontPanelPort, FrontPanelPortTemplate, Interface, InterfaceConnection, + InterfaceTemplate, Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, + PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPanelPort, RearPanelPortTemplate, Region, Site, + VirtualChassis, ) from extras.api.serializers import RenderedGraphSerializer from extras.api.views import CustomFieldModelViewSet @@ -191,6 +192,18 @@ class InterfaceTemplateViewSet(ModelViewSet): filter_class = filters.InterfaceTemplateFilter +class FrontPanelPortTemplateViewSet(ModelViewSet): + queryset = FrontPanelPortTemplate.objects.select_related('device_type__manufacturer') + serializer_class = serializers.FrontPanelPortTemplateSerializer + filter_class = filters.FrontPanelPortTemplateFilter + + +class RearPanelPortTemplateViewSet(ModelViewSet): + queryset = RearPanelPortTemplate.objects.select_related('device_type__manufacturer') + serializer_class = serializers.RearPanelPortTemplateSerializer + filter_class = filters.RearPanelPortTemplateFilter + + class DeviceBayTemplateViewSet(ModelViewSet): queryset = DeviceBayTemplate.objects.select_related('device_type__manufacturer') serializer_class = serializers.DeviceBayTemplateSerializer @@ -352,6 +365,18 @@ class InterfaceViewSet(ModelViewSet): return Response(serializer.data) +class FrontPanelPortViewSet(ModelViewSet): + queryset = FrontPanelPort.objects.select_related('device__device_type__manufacturer', 'rear_port') + serializer_class = serializers.FrontPanelPortSerializer + filter_class = filters.FrontPanelPortFilter + + +class RearPanelPortViewSet(ModelViewSet): + queryset = RearPanelPort.objects.select_related('device__device_type__manufacturer') + serializer_class = serializers.RearPanelPortSerializer + filter_class = filters.RearPanelPortFilter + + class DeviceBayViewSet(ModelViewSet): queryset = DeviceBay.objects.select_related('installed_device').prefetch_related('tags') serializer_class = serializers.DeviceBaySerializer diff --git a/netbox/dcim/constants.py b/netbox/dcim/constants.py index a3226a6b2..d51ec97f3 100644 --- a/netbox/dcim/constants.py +++ b/netbox/dcim/constants.py @@ -209,6 +209,36 @@ IFACE_MODE_CHOICES = [ [IFACE_MODE_TAGGED_ALL, 'Tagged All'], ] +# Patch panel port types +PANELPORT_TYPE_8P8C = 1000 +PANELPORT_TYPE_ST = 2000 +PANELPORT_TYPE_SC_SIMPLEX = 2100 +PANELPORT_TYPE_SC_DUPLEX = 2110 +PANELPORT_TYPE_FC = 2200 +PANELPORT_TYPE_LC = 2300 +PANELPORT_TYPE_MTRJ = 2400 +PANELPORT_TYPE_MPO = 2500 +PANELPORT_TYPE_CHOICES = [ + [ + 'Copper', + [ + [PANELPORT_TYPE_8P8C, '8P8C'], + ], + ], + [ + 'Fiber Optic', + [ + [PANELPORT_TYPE_ST, 'ST'], + [PANELPORT_TYPE_SC_SIMPLEX, 'SC (Simplex)'], + [PANELPORT_TYPE_SC_DUPLEX, 'SC (Duplex)'], + [PANELPORT_TYPE_FC, 'FC'], + [PANELPORT_TYPE_LC, 'LC'], + [PANELPORT_TYPE_MTRJ, 'MTRJ'], + [PANELPORT_TYPE_MPO, 'MPO'], + ] + ] +] + # Device statuses DEVICE_STATUS_OFFLINE = 0 DEVICE_STATUS_ACTIVE = 1 diff --git a/netbox/dcim/filters.py b/netbox/dcim/filters.py index 60cbbfcc1..3b370654e 100644 --- a/netbox/dcim/filters.py +++ b/netbox/dcim/filters.py @@ -14,9 +14,10 @@ from .constants import ( ) from .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, VirtualChassis, + DeviceBayTemplate, DeviceRole, DeviceType, FrontPanelPort, FrontPanelPortTemplate, Interface, InterfaceConnection, + InterfaceTemplate, InventoryItem, Manufacturer, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, + PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPanelPort, RearPanelPortTemplate, Region, Site, + VirtualChassis, ) @@ -368,6 +369,20 @@ class InterfaceTemplateFilter(DeviceTypeComponentFilterSet): fields = ['name', 'form_factor', 'mgmt_only'] +class FrontPanelPortTemplateFilter(DeviceTypeComponentFilterSet): + + class Meta: + model = FrontPanelPortTemplate + fields = ['name', 'type'] + + +class RearPanelPortTemplateFilter(DeviceTypeComponentFilterSet): + + class Meta: + model = RearPanelPortTemplate + fields = ['name', 'type'] + + class DeviceBayTemplateFilter(DeviceTypeComponentFilterSet): class Meta: @@ -667,6 +682,20 @@ class InterfaceFilter(django_filters.FilterSet): return queryset.none() +class FrontPanelPortFilter(DeviceComponentFilterSet): + + class Meta: + model = FrontPanelPort + fields = ['name', 'type'] + + +class RearPanelPortFilter(DeviceComponentFilterSet): + + class Meta: + model = RearPanelPort + fields = ['name', 'type'] + + class DeviceBayFilter(DeviceComponentFilterSet): class Meta: diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 923079634..58f17c0a6 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -5,6 +5,8 @@ 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 natsort import natsorted +from operator import attrgetter from taggit.forms import TagField from timezone_field import TimeZoneFormField @@ -19,17 +21,13 @@ from utilities.forms import ( FlexibleModelChoiceField, JSONField, Livesearch, SelectWithDisabled, SelectWithPK, SmallTextarea, SlugField, ) from virtualization.models import Cluster -from .constants import ( - CONNECTION_STATUS_CHOICES, CONNECTION_STATUS_CONNECTED, DEVICE_STATUS_CHOICES, IFACE_FF_CHOICES, IFACE_FF_LAG, - IFACE_MODE_ACCESS, IFACE_MODE_CHOICES, IFACE_MODE_TAGGED_ALL, IFACE_ORDERING_CHOICES, RACK_FACE_CHOICES, - RACK_TYPE_CHOICES, RACK_WIDTH_CHOICES, RACK_WIDTH_19IN, RACK_WIDTH_23IN, SITE_STATUS_CHOICES, SUBDEVICE_ROLE_CHILD, - SUBDEVICE_ROLE_PARENT, SUBDEVICE_ROLE_CHOICES, -) +from .constants import * from .models import ( 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, VirtualChassis + Device, DeviceRole, DeviceType, FrontPanelPort, FrontPanelPortTemplate, Interface, InterfaceConnection, + InterfaceTemplate, Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, + PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPanelPort, RearPanelPortTemplate, Region, Site, + VirtualChassis, ) DEVICE_BY_PK_RE = r'{\d+\}' @@ -532,7 +530,7 @@ class DeviceTypeForm(BootstrapMixin, CustomFieldForm): model = DeviceType fields = [ 'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'is_console_server', 'is_pdu', - 'is_network_device', 'subdevice_role', 'interface_ordering', 'comments', 'tags', + 'is_network_device', 'is_patch_panel', 'subdevice_role', 'interface_ordering', 'comments', 'tags', ] labels = { 'interface_ordering': 'Order interfaces by', @@ -582,6 +580,9 @@ class DeviceTypeBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkE is_network_device = forms.NullBooleanField( required=False, widget=BulkEditNullBooleanSelect, label='Is a network device' ) + is_patch_panel = forms.NullBooleanField( + required=False, widget=BulkEditNullBooleanSelect, label='Is a patch panel' + ) class Meta: nullable_fields = [] @@ -602,6 +603,9 @@ class DeviceTypeFilterForm(BootstrapMixin, CustomFieldFilterForm): is_network_device = forms.BooleanField( required=False, label='Is a network device', widget=forms.CheckboxInput(attrs={'value': 'True'}) ) + is_patch_panel = forms.BooleanField( + required=False, label='Is a patch panel', widget=forms.CheckboxInput(attrs={'value': 'True'}) + ) subdevice_role = forms.NullBooleanField( required=False, label='Subdevice role', widget=forms.Select(choices=( ('', '---------'), @@ -696,6 +700,97 @@ class InterfaceTemplateBulkEditForm(BootstrapMixin, BulkEditForm): nullable_fields = [] +class FrontPanelPortTemplateForm(BootstrapMixin, forms.ModelForm): + + class Meta: + model = FrontPanelPortTemplate + fields = ['device_type', 'name', 'type', 'rear_port', 'rear_port_position'] + widgets = { + 'device_type': forms.HiddenInput(), + } + + +class FrontPanelPortTemplateCreateForm(ComponentForm): + name_pattern = ExpandableNameField( + label='Name' + ) + type = forms.ChoiceField( + choices=PANELPORT_TYPE_CHOICES + ) + rear_port_set = forms.MultipleChoiceField( + choices=[], + label='Rear ports', + help_text='Select one rear port assignment for each front port being created.' + ) + + def __init__(self, *args, **kwargs): + + super(FrontPanelPortTemplateCreateForm, self).__init__(*args, **kwargs) + + # Determine which rear port positions are occupied. These will be excluded from the list of available mappings. + occupied_port_positions = [ + (front_port.rear_port_id, front_port.rear_port_position) + for front_port in self.parent.front_panel_port_templates.all() + ] + + # Populate rear port choices + choices = [] + rear_ports = natsorted(RearPanelPortTemplate.objects.filter(device_type=self.parent), key=attrgetter('name')) + for rear_port in rear_ports: + for i in range(1, rear_port.positions + 1): + if (rear_port.pk, i) not in occupied_port_positions: + choices.append( + ('{}:{}'.format(rear_port.pk, i), '{}:{}'.format(rear_port.name, i)) + ) + self.fields['rear_port_set'].choices = choices + + def clean(self): + + # Validate that the number of ports being created equals the number of selected (rear port, position) tuples + front_port_count = len(self.cleaned_data['name_pattern']) + rear_port_count = len(self.cleaned_data['rear_port_set']) + if front_port_count != rear_port_count: + raise forms.ValidationError({ + 'rear_port_set': 'The provided name pattern will create {} ports, however {} rear port assignments ' + 'were selected. These counts must match.'.format(front_port_count, rear_port_count) + }) + + def get_iterative_data(self, iteration): + + # Assign rear port and position from selected set + rear_port, position = self.cleaned_data['rear_port_set'][iteration].split(':') + + return { + 'rear_port': int(rear_port), + 'rear_port_position': int(position), + } + + +class RearPanelPortTemplateForm(BootstrapMixin, forms.ModelForm): + + class Meta: + model = RearPanelPortTemplate + fields = ['device_type', 'name', 'type', 'positions'] + widgets = { + 'device_type': forms.HiddenInput(), + } + + +class RearPanelPortTemplateCreateForm(ComponentForm): + name_pattern = ExpandableNameField( + label='Name' + ) + type = forms.ChoiceField( + choices=PANELPORT_TYPE_CHOICES + ) + positions = forms.IntegerField( + min_value=1, + max_value=64, + initial=1, + help_text='The number of front ports which may be mapped to each rear port' + ) + + class DeviceBayTemplateForm(BootstrapMixin, forms.ModelForm): class Meta: @@ -2087,6 +2182,122 @@ class InterfaceConnectionCSVForm(forms.ModelForm): return interface +# +# Front panel ports +# + +class FrontPanelPortForm(BootstrapMixin, forms.ModelForm): + tags = TagField(required=False) + + class Meta: + model = FrontPanelPort + fields = ['device', 'name', 'type', 'rear_port', 'rear_port_position', 'tags'] + widgets = { + 'device': forms.HiddenInput(), + } + + +# TODO: Merge with FrontPanelPortTemplateCreateForm to remove duplicate logic +class FrontPanelPortCreateForm(ComponentForm): + name_pattern = ExpandableNameField( + label='Name' + ) + type = forms.ChoiceField( + choices=PANELPORT_TYPE_CHOICES + ) + rear_port_set = forms.MultipleChoiceField( + choices=[], + label='Rear ports', + help_text='Select one rear port assignment for each front port being created.' + ) + + def __init__(self, *args, **kwargs): + + super(FrontPanelPortCreateForm, self).__init__(*args, **kwargs) + + # Determine which rear port positions are occupied. These will be excluded from the list of available mappings. + occupied_port_positions = [ + (front_port.rear_port_id, front_port.rear_port_position) + for front_port in self.parent.front_panel_port_templates.all() + ] + + # Populate rear port choices + choices = [] + rear_ports = natsorted(RearPanelPort.objects.filter(device=self.parent), key=attrgetter('name')) + for rear_port in rear_ports: + for i in range(1, rear_port.positions + 1): + if (rear_port.pk, i) not in occupied_port_positions: + choices.append( + ('{}:{}'.format(rear_port.pk, i), '{}:{}'.format(rear_port.name, i)) + ) + self.fields['rear_port_set'].choices = choices + + def clean(self): + + # Validate that the number of ports being created equals the number of selected (rear port, position) tuples + front_port_count = len(self.cleaned_data['name_pattern']) + rear_port_count = len(self.cleaned_data['rear_port_set']) + if front_port_count != rear_port_count: + raise forms.ValidationError({ + 'rear_port_set': 'The provided name pattern will create {} ports, however {} rear port assignments ' + 'were selected. These counts must match.'.format(front_port_count, rear_port_count) + }) + + def get_iterative_data(self, iteration): + + # Assign rear port and position from selected set + rear_port, position = self.cleaned_data['rear_port_set'][iteration].split(':') + + return { + 'rear_port': int(rear_port), + 'rear_port_position': int(position), + } + + +class FrontPanelPortBulkRenameForm(BulkRenameForm): + pk = forms.ModelMultipleChoiceField( + queryset=FrontPanelPort.objects.all(), + widget=forms.MultipleHiddenInput + ) + + +# +# Rear panel ports +# + +class RearPanelPortForm(BootstrapMixin, forms.ModelForm): + tags = TagField(required=False) + + class Meta: + model = RearPanelPort + fields = ['device', 'name', 'type', 'positions', 'tags'] + widgets = { + 'device': forms.HiddenInput(), + } + + +class RearPanelPortCreateForm(ComponentForm): + name_pattern = ExpandableNameField( + label='Name' + ) + type = forms.ChoiceField( + choices=PANELPORT_TYPE_CHOICES + ) + positions = forms.IntegerField( + min_value=1, + max_value=64, + initial=1, + help_text='The number of front ports which may be mapped to each rear port' + ) + + +class RearPanelPortBulkRenameForm(BulkRenameForm): + pk = forms.ModelMultipleChoiceField( + queryset=RearPanelPort.objects.all(), + widget=forms.MultipleHiddenInput + ) + + # # Device bays # diff --git a/netbox/dcim/migrations/0065_patch_panel_ports.py b/netbox/dcim/migrations/0065_patch_panel_ports.py new file mode 100644 index 000000000..48b77e561 --- /dev/null +++ b/netbox/dcim/migrations/0065_patch_panel_ports.py @@ -0,0 +1,114 @@ +# Generated by Django 2.0.8 on 2018-10-03 17:26 + +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion +import taggit.managers + + +class Migration(migrations.Migration): + + dependencies = [ + ('taggit', '0002_auto_20150616_2121'), + ('dcim', '0064_remove_platform_rpc_client'), + ] + + operations = [ + migrations.CreateModel( + name='FrontPanelPort', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=64)), + ('type', models.PositiveSmallIntegerField()), + ('rear_port_position', models.PositiveSmallIntegerField(default=1, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(64)])), + ('device', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='front_panel_ports', to='dcim.Device')), + ], + options={ + 'ordering': ['device', 'name'], + }, + ), + migrations.CreateModel( + name='FrontPanelPortTemplate', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=64)), + ('type', models.PositiveSmallIntegerField()), + ('rear_port_position', models.PositiveSmallIntegerField(default=1, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(64)])), + ], + options={ + 'ordering': ['device_type', 'name'], + }, + ), + migrations.CreateModel( + name='RearPanelPort', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=64)), + ('type', models.PositiveSmallIntegerField()), + ('positions', models.PositiveSmallIntegerField(default=1, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(64)])), + ('device', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='rear_panel_ports', to='dcim.Device')), + ('tags', taggit.managers.TaggableManager(through='taggit.TaggedItem', to='taggit.Tag')), + ], + options={ + 'ordering': ['device', 'name'], + }, + ), + migrations.CreateModel( + name='RearPanelPortTemplate', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=64)), + ('type', models.PositiveSmallIntegerField()), + ('positions', models.PositiveSmallIntegerField(default=1, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(64)])), + ], + options={ + 'ordering': ['device_type', 'name'], + }, + ), + migrations.AddField( + model_name='devicetype', + name='is_patch_panel', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='rearpanelporttemplate', + name='device_type', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='rear_panel_port_templates', to='dcim.DeviceType'), + ), + migrations.AddField( + model_name='frontpanelporttemplate', + name='device_type', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='front_panel_port_templates', to='dcim.DeviceType'), + ), + migrations.AddField( + model_name='frontpanelporttemplate', + name='rear_port', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='front_panel_port_templates', to='dcim.RearPanelPortTemplate'), + ), + migrations.AddField( + model_name='frontpanelport', + name='rear_port', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='front_panel_ports', to='dcim.RearPanelPort'), + ), + migrations.AddField( + model_name='frontpanelport', + name='tags', + field=taggit.managers.TaggableManager(through='taggit.TaggedItem', to='taggit.Tag'), + ), + migrations.AlterUniqueTogether( + name='rearpanelporttemplate', + unique_together={('device_type', 'name')}, + ), + migrations.AlterUniqueTogether( + name='rearpanelport', + unique_together={('device', 'name')}, + ), + migrations.AlterUniqueTogether( + name='frontpanelporttemplate', + unique_together={('rear_port', 'rear_port_position'), ('device_type', 'name')}, + ), + migrations.AlterUniqueTogether( + name='frontpanelport', + unique_together={('device', 'name'), ('rear_port', 'rear_port_position')}, + ), + ] diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index cd471a834..cdbf78525 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -769,6 +769,11 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel): verbose_name='Is a network device', help_text='This type of device has network interfaces' ) + is_patch_panel = models.BooleanField( + default=False, + verbose_name='Is a patch panel', + help_text='This type of device has patch panel ports' + ) subdevice_role = models.NullBooleanField( default=None, verbose_name='Parent/child status', @@ -789,7 +794,7 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel): csv_headers = [ 'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'is_console_server', - 'is_pdu', 'is_network_device', 'subdevice_role', 'interface_ordering', 'comments', + 'is_pdu', 'is_network_device', 'is_patch_panel', 'subdevice_role', 'interface_ordering', 'comments', ] class Meta: @@ -822,6 +827,7 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel): self.is_console_server, self.is_pdu, self.is_network_device, + self.is_patch_panel, self.get_subdevice_role_display() if self.subdevice_role else None, self.get_interface_ordering_display(), self.comments, @@ -861,6 +867,14 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel): "device before declassifying it as a network device." }) + if not self.is_patch_panel and ( + self.front_panel_port_templates.exists() or self.rear_panel_port_templates.exists() + ): + raise ValidationError({ + 'is_patch_panel': "Must delete all patch panel port templates associated with this device before " + "declassifying it as a network device." + }) + if self.subdevice_role != SUBDEVICE_ROLE_PARENT and self.device_bay_templates.count(): raise ValidationError({ 'subdevice_role': "Must delete all device bay templates associated with this device before " @@ -1000,6 +1014,86 @@ class InterfaceTemplate(ComponentTemplateModel): return self.name +class FrontPanelPortTemplate(ComponentTemplateModel): + """ + A template for a front patch panel port on a new Device. + """ + device_type = models.ForeignKey( + to='dcim.DeviceType', + on_delete=models.CASCADE, + related_name='front_panel_port_templates' + ) + name = models.CharField( + max_length=64 + ) + type = models.PositiveSmallIntegerField( + choices=PANELPORT_TYPE_CHOICES + ) + rear_port = models.ForeignKey( + to='dcim.RearPanelPortTemplate', + on_delete=models.CASCADE, + related_name='front_panel_port_templates' + ) + rear_port_position = models.PositiveSmallIntegerField( + default=1, + validators=[MinValueValidator(1), MaxValueValidator(64)] + ) + + class Meta: + ordering = ['device_type', 'name'] + unique_together = [ + ['device_type', 'name'], + ['rear_port', 'rear_port_position'], + ] + + def __str__(self): + return self.name + + def clean(self): + + # Validate rear port assignment + if self.rear_port.device_type != self.device_type: + raise ValidationError( + "Rear port ({}) must belong to the same device type".format(self.rear_port) + ) + + # Validate rear port position assignment + if self.rear_port_position > self.rear_port.positions: + raise ValidationError( + "Invalid rear port position ({}); rear port {} has only {} positions".format( + self.rear_port_position, self.rear_port.name, self.rear_port.positions + ) + ) + + +class RearPanelPortTemplate(ComponentTemplateModel): + """ + A template for a rear patch panel port on a new Device. + """ + device_type = models.ForeignKey( + to='dcim.DeviceType', + on_delete=models.CASCADE, + related_name='rear_panel_port_templates' + ) + name = models.CharField( + max_length=64 + ) + type = models.PositiveSmallIntegerField( + choices=PANELPORT_TYPE_CHOICES + ) + positions = models.PositiveSmallIntegerField( + default=1, + validators=[MinValueValidator(1), MaxValueValidator(64)] + ) + + class Meta: + ordering = ['device_type', 'name'] + unique_together = ['device_type', 'name'] + + def __str__(self): + return self.name + + class DeviceBayTemplate(ComponentTemplateModel): """ A template for a DeviceBay to be created for a new parent Device. @@ -1417,6 +1511,23 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel): [Interface(device=self, name=template.name, form_factor=template.form_factor, mgmt_only=template.mgmt_only) for template in self.device_type.interface_templates.all()] ) + RearPanelPort.objects.bulk_create([ + RearPanelPort( + device=self, + name=template.name, + type=template.type, + positions=template.positions + ) for template in self.device_type.rear_panel_port_templates.all() + ]) + FrontPanelPort.objects.bulk_create([ + FrontPanelPort( + device=self, + name=template.name, + type=template.type, + rear_port=RearPanelPort.objects.get(device=self, name=template.rear_port.name), + rear_port_position=template.rear_port_position, + ) for template in self.device_type.front_panel_port_templates.all() + ]) DeviceBay.objects.bulk_create( [DeviceBay(device=self, name=template.name) for template in self.device_type.device_bay_templates.all()] @@ -2040,6 +2151,94 @@ class InterfaceConnection(models.Model): ).save() +# +# Patch panel ports +# + +class FrontPanelPort(ComponentModel): + """ + A port on the front of a patch panel. + """ + device = models.ForeignKey( + to='dcim.Device', + on_delete=models.CASCADE, + related_name='front_panel_ports' + ) + name = models.CharField( + max_length=64 + ) + type = models.PositiveSmallIntegerField( + choices=PANELPORT_TYPE_CHOICES + ) + rear_port = models.ForeignKey( + to='dcim.RearPanelPort', + on_delete=models.CASCADE, + related_name='front_panel_ports' + ) + rear_port_position = models.PositiveSmallIntegerField( + default=1, + validators=[MinValueValidator(1), MaxValueValidator(64)] + ) + + tags = TaggableManager() + + class Meta: + ordering = ['device', 'name'] + unique_together = [ + ['device', 'name'], + ['rear_port', 'rear_port_position'], + ] + + def __str__(self): + return self.name + + def clean(self): + + # Validate rear port assignment + if self.rear_port.device != self.device: + raise ValidationError( + "Rear port ({}) must belong to the same device".format(self.rear_port) + ) + + # Validate rear port position assignment + if self.rear_port_position > self.rear_port.positions: + raise ValidationError( + "Invalid rear port position ({}); rear port {} has only {} positions".format( + self.rear_port_position, self.rear_port.name, self.rear_port.positions + ) + ) + + +class RearPanelPort(ComponentModel): + """ + A port on the rear of a patch panel. + """ + device = models.ForeignKey( + to='dcim.Device', + on_delete=models.CASCADE, + related_name='rear_panel_ports' + ) + name = models.CharField( + max_length=64 + ) + type = models.PositiveSmallIntegerField( + choices=PANELPORT_TYPE_CHOICES + ) + positions = models.PositiveSmallIntegerField( + default=1, + validators=[MinValueValidator(1), MaxValueValidator(64)] + ) + + tags = TaggableManager() + + class Meta: + ordering = ['device', 'name'] + unique_together = ['device', 'name'] + + def __str__(self): + return self.name + + # # Device bays # diff --git a/netbox/dcim/tables.py b/netbox/dcim/tables.py index 8c3606713..a1a2c7a4d 100644 --- a/netbox/dcim/tables.py +++ b/netbox/dcim/tables.py @@ -5,9 +5,10 @@ from tenancy.tables import COL_TENANT from utilities.tables import BaseTable, BooleanColumn, ToggleColumn from .models import ( ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, - DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceConnection, InterfaceTemplate, InventoryItem, - Manufacturer, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, - RackReservation, Region, Site, VirtualChassis, + DeviceBayTemplate, DeviceRole, DeviceType, FrontPanelPort, FrontPanelPortTemplate, Interface, InterfaceConnection, + InterfaceTemplate, InventoryItem, Manufacturer, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, + PowerPortTemplate, Rack, RackGroup, RackReservation, RearPanelPort, RearPanelPortTemplate, Region, Site, + VirtualChassis, ) REGION_LINK = """ @@ -348,6 +349,7 @@ class DeviceTypeTable(BaseTable): is_console_server = BooleanColumn(verbose_name='CS') is_pdu = BooleanColumn(verbose_name='PDU') is_network_device = BooleanColumn(verbose_name='Net') + is_patch_panel = BooleanColumn(verbose_name='PP') subdevice_role = tables.TemplateColumn( template_code=SUBDEVICE_ROLE_TEMPLATE, verbose_name='Subdevice Role' @@ -361,7 +363,7 @@ class DeviceTypeTable(BaseTable): model = DeviceType fields = ( 'pk', 'model', 'manufacturer', 'part_number', 'u_height', 'is_full_depth', 'is_console_server', 'is_pdu', - 'is_network_device', 'subdevice_role', 'instance_count', + 'is_network_device', 'is_patch_panel', 'subdevice_role', 'instance_count', ) @@ -415,6 +417,24 @@ class InterfaceTemplateTable(BaseTable): empty_text = "None" +class FrontPanelPortTemplateTable(BaseTable): + pk = ToggleColumn() + + class Meta(BaseTable.Meta): + model = FrontPanelPortTemplate + fields = ('pk', 'name', 'type', 'rear_port', 'rear_port_position') + empty_text = "None" + + +class RearPanelPortTemplateTable(BaseTable): + pk = ToggleColumn() + + class Meta(BaseTable.Meta): + model = RearPanelPortTemplate + fields = ('pk', 'name', 'type', 'positions') + empty_text = "None" + + class DeviceBayTemplateTable(BaseTable): pk = ToggleColumn() @@ -574,6 +594,22 @@ class InterfaceTable(BaseTable): fields = ('name', 'form_factor', 'lag', 'enabled', 'mgmt_only', 'description') +class FrontPanelPortTable(BaseTable): + + class Meta(BaseTable.Meta): + model = FrontPanelPort + fields = ('name', 'type', 'rear_port', 'rear_port_position') + empty_text = "None" + + +class RearPanelPortTable(BaseTable): + + class Meta(BaseTable.Meta): + model = RearPanelPort + fields = ('name', 'type', 'positions') + empty_text = "None" + + class DeviceBayTable(BaseTable): class Meta(BaseTable.Meta): diff --git a/netbox/dcim/urls.py b/netbox/dcim/urls.py index 4ba91f215..2d9c8d009 100644 --- a/netbox/dcim/urls.py +++ b/netbox/dcim/urls.py @@ -109,6 +109,14 @@ urlpatterns = [ url(r'^device-types/(?P\d+)/interfaces/edit/$', views.InterfaceTemplateBulkEditView.as_view(), name='devicetype_bulkedit_interface'), url(r'^device-types/(?P\d+)/interfaces/delete/$', views.InterfaceTemplateBulkDeleteView.as_view(), name='devicetype_delete_interface'), + # Front panel port templates + url(r'^device-types/(?P\d+)/front-panel-ports/add/$', views.FrontPanelPortTemplateCreateView.as_view(), name='devicetype_add_frontpanelport'), + url(r'^device-types/(?P\d+)/front-panel-ports/delete/$', views.FrontPanelPortTemplateBulkDeleteView.as_view(), name='devicetype_delete_frontpanelport'), + + # Front panel port templates + url(r'^device-types/(?P\d+)/rear-panel-ports/add/$', views.RearPanelPortTemplateCreateView.as_view(), name='devicetype_add_rearpanelport'), + url(r'^device-types/(?P\d+)/rear-panel-ports/delete/$', views.RearPanelPortTemplateBulkDeleteView.as_view(), name='devicetype_delete_rearpanelport'), + # Device bay templates url(r'^device-types/(?P\d+)/device-bays/add/$', views.DeviceBayTemplateCreateView.as_view(), name='devicetype_add_devicebay'), url(r'^device-types/(?P\d+)/device-bays/delete/$', views.DeviceBayTemplateBulkDeleteView.as_view(), name='devicetype_delete_devicebay'), @@ -204,6 +212,22 @@ urlpatterns = [ url(r'^interfaces/(?P\d+)/changelog/$', ObjectChangeLogView.as_view(), name='interface_changelog', kwargs={'model': Interface}), url(r'^interfaces/rename/$', views.InterfaceBulkRenameView.as_view(), name='interface_bulk_rename'), + # Front panel ports + # url(r'^devices/front-panel-ports/add/$', views.DeviceBulkAddFrontPanelPortView.as_view(), name='device_bulk_add_frontpanelport'), + url(r'^devices/(?P\d+)/front-panel-ports/add/$', views.FrontPanelPortCreateView.as_view(), name='frontpanelport_add'), + url(r'^devices/(?P\d+)/front-panel-ports/delete/$', views.FrontPanelPortBulkDeleteView.as_view(), name='frontpanelport_bulk_delete'), + url(r'^front-panel-ports/(?P\d+)/edit/$', views.FrontPanelPortEditView.as_view(), name='frontpanelport_edit'), + url(r'^front-panel-ports/(?P\d+)/delete/$', views.FrontPanelPortDeleteView.as_view(), name='frontpanelport_delete'), + url(r'^front-panel-ports/rename/$', views.FrontPanelPortBulkRenameView.as_view(), name='frontpanelport_bulk_rename'), + + # Rear panel ports + # url(r'^devices/rear-panel-ports/add/$', views.DeviceBulkAddRearPanelPortView.as_view(), name='device_bulk_add_rearpanelport'), + url(r'^devices/(?P\d+)/rear-panel-ports/add/$', views.RearPanelPortCreateView.as_view(), name='rearpanelport_add'), + url(r'^devices/(?P\d+)/rear-panel-ports/delete/$', views.RearPanelPortBulkDeleteView.as_view(), name='rearpanelport_bulk_delete'), + url(r'^rear-panel-ports/(?P\d+)/edit/$', views.RearPanelPortEditView.as_view(), name='rearpanelport_edit'), + url(r'^rear-panel-ports/(?P\d+)/delete/$', views.RearPanelPortDeleteView.as_view(), name='rearpanelport_delete'), + url(r'^rear-panel-ports/rename/$', views.RearPanelPortBulkRenameView.as_view(), name='rearpanelport_bulk_rename'), + # Device bays url(r'^devices/device-bays/add/$', views.DeviceBulkAddDeviceBayView.as_view(), name='device_bulk_add_devicebay'), url(r'^devices/(?P\d+)/bays/add/$', views.DeviceBayCreateView.as_view(), name='devicebay_add'), diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 62f610ce5..c7e57b89f 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -31,9 +31,10 @@ from . import filters, forms, tables from .constants import CONNECTION_STATUS_CONNECTED from .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, VirtualChassis, + DeviceBayTemplate, DeviceRole, DeviceType, FrontPanelPort, FrontPanelPortTemplate, Interface, InterfaceConnection, + InterfaceTemplate, Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, + PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPanelPort, RearPanelPortTemplate, Region, Site, + VirtualChassis, ) @@ -559,6 +560,14 @@ class DeviceTypeView(View): ).filter(device_type=devicetype)), orderable=False ) + front_panel_port_table = tables.FrontPanelPortTemplateTable( + natsorted(FrontPanelPortTemplate.objects.filter(device_type=devicetype), key=attrgetter('name')), + orderable=False + ) + rear_panel_port_table = tables.RearPanelPortTemplateTable( + natsorted(RearPanelPortTemplate.objects.filter(device_type=devicetype), key=attrgetter('name')), + orderable=False + ) devicebay_table = tables.DeviceBayTemplateTable( natsorted(DeviceBayTemplate.objects.filter(device_type=devicetype), key=attrgetter('name')), orderable=False @@ -569,6 +578,8 @@ class DeviceTypeView(View): powerport_table.columns.show('pk') poweroutlet_table.columns.show('pk') interface_table.columns.show('pk') + front_panel_port_table.columns.show('pk') + rear_panel_port_table.columns.show('pk') devicebay_table.columns.show('pk') return render(request, 'dcim/devicetype.html', { @@ -578,6 +589,8 @@ class DeviceTypeView(View): 'powerport_table': powerport_table, 'poweroutlet_table': poweroutlet_table, 'interface_table': interface_table, + 'front_panel_port_table': front_panel_port_table, + 'rear_panel_port_table': rear_panel_port_table, 'devicebay_table': devicebay_table, }) @@ -721,6 +734,40 @@ class InterfaceTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): table = tables.InterfaceTemplateTable +class FrontPanelPortTemplateCreateView(PermissionRequiredMixin, ComponentCreateView): + permission_required = 'dcim.add_frontpanelporttemplate' + parent_model = DeviceType + parent_field = 'device_type' + model = FrontPanelPortTemplate + form = forms.FrontPanelPortTemplateCreateForm + model_form = forms.FrontPanelPortTemplateForm + template_name = 'dcim/device_component_add.html' + + +class FrontPanelPortTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): + permission_required = 'dcim.delete_frontpanelporttemplate' + queryset = FrontPanelPortTemplate.objects.all() + parent_model = DeviceType + table = tables.FrontPanelPortTemplateTable + + +class RearPanelPortTemplateCreateView(PermissionRequiredMixin, ComponentCreateView): + permission_required = 'dcim.add_rearpanelporttemplate' + parent_model = DeviceType + parent_field = 'device_type' + model = RearPanelPortTemplate + form = forms.RearPanelPortTemplateCreateForm + model_form = forms.RearPanelPortTemplateForm + template_name = 'dcim/device_component_add.html' + + +class RearPanelPortTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): + permission_required = 'dcim.delete_rearpanelporttemplate' + queryset = RearPanelPortTemplate.objects.all() + parent_model = DeviceType + table = tables.RearPanelPortTemplateTable + + class DeviceBayTemplateCreateView(PermissionRequiredMixin, ComponentCreateView): permission_required = 'dcim.add_devicebaytemplate' parent_model = DeviceType @@ -859,6 +906,12 @@ class DeviceView(View): 'circuit_termination__circuit' ).prefetch_related('ip_addresses') + # Front panel ports + front_panel_ports = device.front_panel_ports.select_related('rear_port') + + # Rear panel ports + rear_panel_ports = device.rear_panel_ports.all() + # Device bays device_bays = natsorted( DeviceBay.objects.filter(device=device).select_related('installed_device__device_type__manufacturer'), @@ -891,6 +944,8 @@ class DeviceView(View): 'power_outlets': power_outlets, 'interfaces': interfaces, 'device_bays': device_bays, + 'front_panel_ports': front_panel_ports, + 'rear_panel_ports': rear_panel_ports, 'services': services, 'secrets': secrets, 'vc_members': vc_members, @@ -1701,6 +1756,82 @@ class InterfaceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): table = tables.InterfaceTable +# +# Front panel ports +# + +class FrontPanelPortCreateView(PermissionRequiredMixin, ComponentCreateView): + permission_required = 'dcim.add_frontpanelport' + parent_model = Device + parent_field = 'device' + model = FrontPanelPort + form = forms.FrontPanelPortCreateForm + model_form = forms.FrontPanelPortForm + template_name = 'dcim/device_component_add.html' + + +class FrontPanelPortEditView(PermissionRequiredMixin, ObjectEditView): + permission_required = 'dcim.change_frontpanelport' + model = FrontPanelPort + model_form = forms.FrontPanelPortForm + + +class FrontPanelPortDeleteView(PermissionRequiredMixin, ObjectDeleteView): + permission_required = 'dcim.delete_frontpanelport' + model = FrontPanelPort + + +class FrontPanelPortBulkRenameView(PermissionRequiredMixin, BulkRenameView): + permission_required = 'dcim.change_frontpanelport' + queryset = FrontPanelPort.objects.all() + form = forms.FrontPanelPortBulkRenameForm + + +class FrontPanelPortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): + permission_required = 'dcim.delete_frontpanelport' + queryset = FrontPanelPort.objects.all() + parent_model = Device + table = tables.FrontPanelPortTable + + +# +# Rear panel ports +# + +class RearPanelPortCreateView(PermissionRequiredMixin, ComponentCreateView): + permission_required = 'dcim.add_rearpanelport' + parent_model = Device + parent_field = 'device' + model = RearPanelPort + form = forms.RearPanelPortCreateForm + model_form = forms.RearPanelPortForm + template_name = 'dcim/device_component_add.html' + + +class RearPanelPortEditView(PermissionRequiredMixin, ObjectEditView): + permission_required = 'dcim.change_rearpanelport' + model = RearPanelPort + model_form = forms.RearPanelPortForm + + +class RearPanelPortDeleteView(PermissionRequiredMixin, ObjectDeleteView): + permission_required = 'dcim.delete_rearpanelport' + model = RearPanelPort + + +class RearPanelPortBulkRenameView(PermissionRequiredMixin, BulkRenameView): + permission_required = 'dcim.change_rearpanelport' + queryset = RearPanelPort.objects.all() + form = forms.RearPanelPortBulkRenameForm + + +class RearPanelPortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): + permission_required = 'dcim.delete_rearpanelport' + queryset = RearPanelPort.objects.all() + parent_model = Device + table = tables.RearPanelPortTable + + # # Device bays # diff --git a/netbox/templates/dcim/device.html b/netbox/templates/dcim/device.html index 7b56269b1..50f8ebbb5 100644 --- a/netbox/templates/dcim/device.html +++ b/netbox/templates/dcim/device.html @@ -689,6 +689,111 @@ {% endif %} {% endif %} + {% if front_panel_ports or device.device_type.is_patch_panel %} +
+ {% csrf_token %} +
+
+ Front Panel Ports +
+ + + + {% if perms.dcim.change_frontpanelport or perms.dcim.delete_frontpanelport %} + + {% endif %} + + + + + + + + + {% for frontpanelport in front_panel_ports %} + {% include 'dcim/inc/frontpanelport.html' %} + {% empty %} + + + + {% endfor %} + +
NameTypeRear PortPosition
— No front panel ports defined —
+ +
+
+ {% endif %} + {% if rear_panel_ports or device.device_type.is_patch_panel %} +
+ {% csrf_token %} +
+
+ Rear Panel Ports +
+ + + + {% if perms.dcim.change_rearpanelport or perms.dcim.delete_rearpanelport %} + + {% endif %} + + + + + + + + {% for rearpanelport in rear_panel_ports %} + {% include 'dcim/inc/rearpanelport.html' %} + {% empty %} + + + + {% endfor %} + +
NameTypePositions
— No rear panel ports defined —
+ +
+
+ {% endif %}
{% include 'inc/graphs_modal.html' %} diff --git a/netbox/templates/dcim/devicetype.html b/netbox/templates/dcim/devicetype.html index 652c291e6..9d7f94b24 100644 --- a/netbox/templates/dcim/devicetype.html +++ b/netbox/templates/dcim/devicetype.html @@ -135,6 +135,19 @@ This device {% if devicetype.is_network_device %}has{% else %}does not have{% endif %} network interfaces + + + {% if devicetype.is_patch_panel %} + + {% else %} + + {% endif %} + + + Patch Panel
+ This device {% if devicetype.is_patch_panel %}has{% else %}does not have{% endif %} patch panel ports + + {% if devicetype.subdevice_role == True %} @@ -188,6 +201,12 @@ {% if devicetype.is_pdu or poweroutlet_table.rows %} {% include 'dcim/inc/devicetype_component_table.html' with table=poweroutlet_table title='Power Outlets' add_url='dcim:devicetype_add_poweroutlet' delete_url='dcim:devicetype_delete_poweroutlet' %} {% endif %} + {% if devicetype.is_patch_panel or front_panel_port_table.rows %} + {% include 'dcim/inc/devicetype_component_table.html' with table=front_panel_port_table title='Front Panel Ports' add_url='dcim:devicetype_add_frontpanelport' delete_url='dcim:devicetype_delete_frontpanelport' %} + {% endif %} + {% if devicetype.is_patch_panel or rear_panel_port_table.rows %} + {% include 'dcim/inc/devicetype_component_table.html' with table=rear_panel_port_table title='Rear Panel Ports' add_url='dcim:devicetype_add_rearpanelport' delete_url='dcim:devicetype_delete_rearpanelport' %} + {% endif %} {% endblock %} diff --git a/netbox/templates/dcim/devicetype_edit.html b/netbox/templates/dcim/devicetype_edit.html index d0ed2c204..14b8103e3 100644 --- a/netbox/templates/dcim/devicetype_edit.html +++ b/netbox/templates/dcim/devicetype_edit.html @@ -20,6 +20,7 @@ {% render_field form.is_console_server %} {% render_field form.is_pdu %} {% render_field form.is_network_device %} + {% render_field form.is_patch_panel %} {% render_field form.subdevice_role %} diff --git a/netbox/templates/dcim/inc/frontpanelport.html b/netbox/templates/dcim/inc/frontpanelport.html new file mode 100644 index 000000000..bef4c24c7 --- /dev/null +++ b/netbox/templates/dcim/inc/frontpanelport.html @@ -0,0 +1,25 @@ + + {% if perms.dcim.change_frontpanelport or perms.dcim.delete_frontpanelport %} + + + + {% endif %} + + {{ frontpanelport }} + + {{ frontpanelport.get_type_display }} + {{ frontpanelport.rear_port }} + {{ frontpanelport.rear_port_position }} + + {% if perms.dcim.change_frontpanelport %} + + + + {% endif %} + {% if perms.dcim.delete_frontpanelport %} + + + + {% endif %} + + diff --git a/netbox/templates/dcim/inc/rearpanelport.html b/netbox/templates/dcim/inc/rearpanelport.html new file mode 100644 index 000000000..a781de727 --- /dev/null +++ b/netbox/templates/dcim/inc/rearpanelport.html @@ -0,0 +1,24 @@ + + {% if perms.dcim.change_rearpanelport or perms.dcim.delete_rearpanelport %} + + + + {% endif %} + + {{ rearpanelport }} + + {{ rearpanelport.get_type_display }} + {{ rearpanelport.positions }} + + {% if perms.dcim.change_rearpanelport %} + + + + {% endif %} + {% if perms.dcim.delete_rearpanelport %} + + + + {% endif %} + + diff --git a/netbox/utilities/forms.py b/netbox/utilities/forms.py index ac8597dc5..966a0095e 100644 --- a/netbox/utilities/forms.py +++ b/netbox/utilities/forms.py @@ -643,6 +643,9 @@ class ComponentForm(BootstrapMixin, forms.Form): self.parent = parent super(ComponentForm, self).__init__(*args, **kwargs) + def get_iterative_data(self, iteration): + return {} + class BulkEditForm(forms.Form): """ diff --git a/netbox/utilities/views.py b/netbox/utilities/views.py index df0ca63cf..0e36f3220 100644 --- a/netbox/utilities/views.py +++ b/netbox/utilities/views.py @@ -711,10 +711,11 @@ class ComponentCreateView(View): data = deepcopy(request.POST) data[self.parent_field] = parent.pk - for name in form.cleaned_data['name_pattern']: + for i, name in enumerate(form.cleaned_data['name_pattern']): # Initialize the individual component form data['name'] = name + data.update(form.get_iterative_data(i)) component_form = self.model_form(data) if component_form.is_valid(): From ea5121ffe1adbdca08489fd0adf08592780012bb Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 18 Oct 2018 15:43:55 -0400 Subject: [PATCH 010/191] Preliminary work on Cables --- netbox/dcim/constants.py | 17 +++ netbox/dcim/migrations/0066_cables.py | 178 ++++++++++++++++++++++++++ netbox/dcim/models.py | 100 ++++++++++++++- netbox/dcim/signals.py | 28 +++- 4 files changed, 315 insertions(+), 8 deletions(-) create mode 100644 netbox/dcim/migrations/0066_cables.py diff --git a/netbox/dcim/constants.py b/netbox/dcim/constants.py index d51ec97f3..d63735347 100644 --- a/netbox/dcim/constants.py +++ b/netbox/dcim/constants.py @@ -282,3 +282,20 @@ CONNECTION_STATUS_CHOICES = [ [CONNECTION_STATUS_PLANNED, 'Planned'], [CONNECTION_STATUS_CONNECTED, 'Connected'], ] + +# Cable endpoint types +CABLE_ENDPOINT_TYPES = ( + 'consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport', +) +CABLE_CONNECTION_TYPES = CABLE_ENDPOINT_TYPES + ( + 'frontpanelport', 'rearpanelport', +) + +# Cable types +# TODO: Add more types +CABLE_TYPE_COPPER = 1000 +CABLE_TYPE_FIBER = 2000 +CABLE_TYPE_CHOICES = ( + (CABLE_TYPE_COPPER, 'Copper'), + (CABLE_TYPE_FIBER, 'Fiber'), +) diff --git a/netbox/dcim/migrations/0066_cables.py b/netbox/dcim/migrations/0066_cables.py new file mode 100644 index 000000000..2650e1636 --- /dev/null +++ b/netbox/dcim/migrations/0066_cables.py @@ -0,0 +1,178 @@ +# Generated by Django 2.0.8 on 2018-10-18 19:41 + +from django.db import migrations, models +import django.db.models.deletion +import utilities.fields + + +def console_connections_to_cables(apps, schema_editor): + """ + Copy all existing console connections as Cables + """ + ConsolePort = apps.get_model('dcim', 'ConsolePort') + Cable = apps.get_model('dcim', 'Cable') + + # Load content types + ContentType = apps.get_model('contenttypes', 'ContentType') + consoleport_type = ContentType.objects.get(app_label='dcim', model='consoleport') + consoleserverport_type = ContentType.objects.get(app_label='dcim', model='consoleserverport') + + # Create a new Cable instance from each console connection + for consoleport in ConsolePort.objects.filter(cs_port__isnull=False): + c = Cable() + # We have to assign GFK fields manually because we're inside a migration. + c.endpoint_a_type = consoleport_type + c.endpoint_a_id = consoleport.id + c.endpoint_b_type = consoleserverport_type + c.endpoint_b_id = consoleport.cs_port_id + c.connection_status = consoleport.connection_status + c.save() + + +def power_connections_to_cables(apps, schema_editor): + """ + Copy all existing power connections as Cables + """ + PowerPort = apps.get_model('dcim', 'PowerPort') + Cable = apps.get_model('dcim', 'Cable') + + # Load content types + ContentType = apps.get_model('contenttypes', 'ContentType') + powerport_type = ContentType.objects.get(app_label='dcim', model='powerport') + poweroutlet_type = ContentType.objects.get(app_label='dcim', model='poweroutlet') + + # Create a new Cable instance from each power connection + for powerport in PowerPort.objects.filter(power_outlet__isnull=False): + c = Cable() + # We have to assign GFK fields manually because we're inside a migration. + c.endpoint_a_type = powerport_type + c.endpoint_a_id = powerport.id + c.endpoint_b_type = poweroutlet_type + c.endpoint_b_id = powerport.power_outlet_id + c.connection_status = powerport.connection_status + c.save() + + +def interface_connections_to_cables(apps, schema_editor): + """ + Copy all InterfaceConnections as Cables + """ + InterfaceConnection = apps.get_model('dcim', 'InterfaceConnection') + Cable = apps.get_model('dcim', 'Cable') + + # Load content types + ContentType = apps.get_model('contenttypes', 'ContentType') + interface_type = ContentType.objects.get(app_label='dcim', model='interface') + + # Create a new Cable instance from each InterfaceConnection + for conn in InterfaceConnection.objects.all(): + c = Cable() + # We have to assign GFK fields manually because we're inside a migration. + c.endpoint_a_type = interface_type + c.endpoint_a_id = conn.interface_a_id + c.endpoint_b_type = interface_type + c.endpoint_b_id = conn.interface_b_id + c.connection_status = conn.connection_status + c.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + ('dcim', '0065_patch_panel_ports'), + ] + + operations = [ + migrations.CreateModel( + name='Cable', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False)), + ('created', models.DateField(auto_now_add=True, null=True)), + ('last_updated', models.DateTimeField(auto_now=True, null=True)), + ('endpoint_a_id', models.PositiveIntegerField()), + ('endpoint_b_id', models.PositiveIntegerField()), + ('type', models.PositiveSmallIntegerField(blank=True, null=True)), + ('status', models.BooleanField(default=True)), + ('label', models.CharField(blank=True, max_length=100)), + ('color', utilities.fields.ColorField(blank=True, max_length=6)), + ('endpoint_a_type', models.ForeignKey(limit_choices_to={'model__in': ('consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport', 'frontpanelport', 'rearpanelport')}, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType')), + ('endpoint_b_type', models.ForeignKey(limit_choices_to={'model__in': ('consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport', 'frontpanelport', 'rearpanelport')}, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType')), + ], + ), + migrations.AddField( + model_name='consoleport', + name='connected_endpoint_id', + field=models.PositiveIntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name='consoleport', + name='connected_endpoint_type', + field=models.ForeignKey(blank=True, limit_choices_to={'model__in': ('consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport')}, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType'), + ), + migrations.AddField( + model_name='consoleserverport', + name='connected_endpoint_id', + field=models.PositiveIntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name='consoleserverport', + name='connected_endpoint_type', + field=models.ForeignKey(blank=True, limit_choices_to={'model__in': ('consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport')}, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType'), + ), + migrations.AddField( + model_name='consoleserverport', + name='connection_status', + field=models.NullBooleanField(default=True), + ), + migrations.AddField( + model_name='interface', + name='connected_endpoint_id', + field=models.PositiveIntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name='interface', + name='connected_endpoint_type', + field=models.ForeignKey(blank=True, limit_choices_to={'model__in': ('consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport')}, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType'), + ), + migrations.AddField( + model_name='interface', + name='connection_status', + field=models.NullBooleanField(default=True), + ), + migrations.AddField( + model_name='poweroutlet', + name='connected_endpoint_id', + field=models.PositiveIntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name='poweroutlet', + name='connected_endpoint_type', + field=models.ForeignKey(blank=True, limit_choices_to={'model__in': ('consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport')}, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType'), + ), + migrations.AddField( + model_name='poweroutlet', + name='connection_status', + field=models.NullBooleanField(default=True), + ), + migrations.AddField( + model_name='powerport', + name='connected_endpoint_id', + field=models.PositiveIntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name='powerport', + name='connected_endpoint_type', + field=models.ForeignKey(blank=True, limit_choices_to={'model__in': ('consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport')}, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType'), + ), + migrations.AlterUniqueTogether( + name='cable', + unique_together={('endpoint_b_type', 'endpoint_b_id'), ('endpoint_a_type', 'endpoint_a_id')}, + ), + + # Copy console/power/interface connections as Cables + migrations.RunPython(console_connections_to_cables), + migrations.RunPython(power_connections_to_cables), + migrations.RunPython(interface_connections_to_cables), + + ] diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index cdbf78525..0a4d8afc5 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -3,7 +3,8 @@ from itertools import count, groupby from django.conf import settings from django.contrib.auth.models import User -from django.contrib.contenttypes.fields import GenericRelation +from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation +from django.contrib.contenttypes.models import ContentType from django.contrib.postgres.fields import ArrayField, JSONField from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.core.validators import MaxValueValidator, MinValueValidator @@ -65,6 +66,32 @@ class ComponentModel(models.Model): ).save() +class ConnectableModel(models.Model): + connected_endpoint_type = models.ForeignKey( + to=ContentType, + limit_choices_to={'model__in': CABLE_ENDPOINT_TYPES}, + on_delete=models.PROTECT, + related_name='+', + blank=True, + null=True + ) + connected_endpoint_id = models.PositiveIntegerField( + blank=True, + null=True + ) + connected_endpoint = GenericForeignKey( + ct_field='connected_endpoint_type', + fk_field='connected_endpoint_id' + ) + connection_status = models.NullBooleanField( + choices=CONNECTION_STATUS_CHOICES, + default=CONNECTION_STATUS_CONNECTED + ) + + class Meta: + abstract = True + + # # Regions # @@ -1616,7 +1643,7 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel): # Console ports # -class ConsolePort(ComponentModel): +class ConsolePort(ConnectableModel, ComponentModel): """ A physical console port within a Device. ConsolePorts connect to ConsoleServerPorts. """ @@ -1679,7 +1706,7 @@ class ConsoleServerPortManager(models.Manager): }).order_by('device', 'name_padded') -class ConsoleServerPort(ComponentModel): +class ConsoleServerPort(ConnectableModel, ComponentModel): """ A physical port within a Device (typically a designated console server) which provides access to ConsolePorts. """ @@ -1720,7 +1747,7 @@ class ConsoleServerPort(ComponentModel): # Power ports # -class PowerPort(ComponentModel): +class PowerPort(ConnectableModel, ComponentModel): """ A physical power supply (intake) port within a Device. PowerPorts connect to PowerOutlets. """ @@ -1782,7 +1809,7 @@ class PowerOutletManager(models.Manager): }).order_by('device', 'name_padded') -class PowerOutlet(ComponentModel): +class PowerOutlet(ConnectableModel, ComponentModel): """ A physical power outlet (output) within a Device which provides power to a PowerPort. """ @@ -1823,7 +1850,7 @@ class PowerOutlet(ComponentModel): # Interfaces # -class Interface(ComponentModel): +class Interface(ConnectableModel, ComponentModel): """ A network interface within a Device or VirtualMachine. A physical Interface can connect to exactly one other Interface via the creation of an InterfaceConnection. @@ -2423,3 +2450,64 @@ class VirtualChassis(ChangeLoggedModel): self.master, self.domain, ) + + +# +# Cables +# + +class Cable(ChangeLoggedModel): + """ + A physical connection between two endpoints. + """ + endpoint_a_type = models.ForeignKey( + to=ContentType, + limit_choices_to={'model__in': CABLE_CONNECTION_TYPES}, + on_delete=models.PROTECT, + related_name='+' + ) + endpoint_a_id = models.PositiveIntegerField() + endpoint_a = GenericForeignKey( + ct_field='endpoint_a_type', + fk_field='endpoint_a_id' + ) + endpoint_b_type = models.ForeignKey( + to=ContentType, + limit_choices_to={'model__in': CABLE_CONNECTION_TYPES}, + on_delete=models.PROTECT, + related_name='+' + ) + endpoint_b_id = models.PositiveIntegerField() + endpoint_b = GenericForeignKey( + ct_field='endpoint_b_type', + fk_field='endpoint_b_id' + ) + type = models.PositiveSmallIntegerField( + choices=CABLE_TYPE_CHOICES, + blank=True, + null=True + ) + status = models.BooleanField( + choices=CONNECTION_STATUS_CHOICES, + default=CONNECTION_STATUS_CONNECTED + ) + label = models.CharField( + max_length=100, + blank=True + ) + color = ColorField( + blank=True + ) + + class Meta: + unique_together = ( + ('endpoint_a_type', 'endpoint_a_id'), + ('endpoint_b_type', 'endpoint_b_id'), + ) + + # TODO: This should follow all cables in a path + def get_path_endpoints(self): + """ + Return the endpoints connected by this cable path. + """ + return (self.endpoint_a, self.endpoint_b) diff --git a/netbox/dcim/signals.py b/netbox/dcim/signals.py index 2aefdc229..68dce2938 100644 --- a/netbox/dcim/signals.py +++ b/netbox/dcim/signals.py @@ -1,7 +1,7 @@ -from django.db.models.signals import post_save, pre_delete +from django.db.models.signals import post_save, post_delete, pre_delete from django.dispatch import receiver -from .models import Device, VirtualChassis +from .models import Cable, Device, VirtualChassis @receiver(post_save, sender=VirtualChassis) @@ -19,3 +19,27 @@ def clear_virtualchassis_members(instance, **kwargs): When a VirtualChassis is deleted, nullify the vc_position and vc_priority fields of its prior members. """ Device.objects.filter(virtual_chassis=instance.pk).update(vc_position=None, vc_priority=None) + + +@receiver(post_save, sender=Cable) +def update_connected_endpoints(instance, **kwargs): + """ + When a Cable is saved, update its connected endpoints. + """ + endpoint_a, endpoint_b = instance.get_path_endpoints() + endpoint_a.connected_endpoint = endpoint_b + endpoint_a.save() + endpoint_b.connected_endpoint = endpoint_a + endpoint_b.save() + + +@receiver(post_delete, sender=Cable) +def nullify_connected_endpoints(instance, **kwargs): + """ + When a Cable is deleted, nullify its connected endpoints. + """ + endpoint_a, endpoint_b = instance.get_path_endpoints() + endpoint_a.connected_endpoint = None + endpoint_a.save() + endpoint_b.connected_endpoint = None + endpoint_b.save() From d908dffab7a672b9243e8256162f644331e2c4c8 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 19 Oct 2018 10:38:15 -0400 Subject: [PATCH 011/191] Fixed content type assignment within migration --- netbox/dcim/migrations/0066_cables.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/netbox/dcim/migrations/0066_cables.py b/netbox/dcim/migrations/0066_cables.py index 2650e1636..6774f4c34 100644 --- a/netbox/dcim/migrations/0066_cables.py +++ b/netbox/dcim/migrations/0066_cables.py @@ -9,13 +9,14 @@ def console_connections_to_cables(apps, schema_editor): """ Copy all existing console connections as Cables """ + ContentType = apps.get_model('contenttypes', 'ContentType') ConsolePort = apps.get_model('dcim', 'ConsolePort') + ConsoleServerPort = apps.get_model('dcim', 'ConsoleServerPort') Cable = apps.get_model('dcim', 'Cable') # Load content types - ContentType = apps.get_model('contenttypes', 'ContentType') - consoleport_type = ContentType.objects.get(app_label='dcim', model='consoleport') - consoleserverport_type = ContentType.objects.get(app_label='dcim', model='consoleserverport') + consoleport_type = ContentType.objects.get_for_model(ConsolePort) + consoleserverport_type = ContentType.objects.get_for_model(ConsoleServerPort) # Create a new Cable instance from each console connection for consoleport in ConsolePort.objects.filter(cs_port__isnull=False): @@ -33,13 +34,14 @@ def power_connections_to_cables(apps, schema_editor): """ Copy all existing power connections as Cables """ + ContentType = apps.get_model('contenttypes', 'ContentType') PowerPort = apps.get_model('dcim', 'PowerPort') + PowerOutlet = apps.get_model('dcim', 'PowerOutlet') Cable = apps.get_model('dcim', 'Cable') # Load content types - ContentType = apps.get_model('contenttypes', 'ContentType') - powerport_type = ContentType.objects.get(app_label='dcim', model='powerport') - poweroutlet_type = ContentType.objects.get(app_label='dcim', model='poweroutlet') + powerport_type = ContentType.objects.get_for_model(PowerPort) + poweroutlet_type = ContentType.objects.get_for_model(PowerOutlet) # Create a new Cable instance from each power connection for powerport in PowerPort.objects.filter(power_outlet__isnull=False): @@ -57,12 +59,13 @@ def interface_connections_to_cables(apps, schema_editor): """ Copy all InterfaceConnections as Cables """ + ContentType = apps.get_model('contenttypes', 'ContentType') + Interface = apps.get_model('dcim', 'Interface') InterfaceConnection = apps.get_model('dcim', 'InterfaceConnection') Cable = apps.get_model('dcim', 'Cable') # Load content types - ContentType = apps.get_model('contenttypes', 'ContentType') - interface_type = ContentType.objects.get(app_label='dcim', model='interface') + interface_type = ContentType.objects.get_for_model(Interface) # Create a new Cable instance from each InterfaceConnection for conn in InterfaceConnection.objects.all(): From 59af8cc924a08f3954c1eac3498fcdb9e99ebfa4 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 19 Oct 2018 11:56:12 -0400 Subject: [PATCH 012/191] Remove ConnectableModel --- netbox/dcim/migrations/0066_cables.py | 65 --------------------------- netbox/dcim/models.py | 36 +++------------ 2 files changed, 5 insertions(+), 96 deletions(-) diff --git a/netbox/dcim/migrations/0066_cables.py b/netbox/dcim/migrations/0066_cables.py index 6774f4c34..342590d20 100644 --- a/netbox/dcim/migrations/0066_cables.py +++ b/netbox/dcim/migrations/0066_cables.py @@ -103,71 +103,6 @@ class Migration(migrations.Migration): ('endpoint_b_type', models.ForeignKey(limit_choices_to={'model__in': ('consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport', 'frontpanelport', 'rearpanelport')}, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType')), ], ), - migrations.AddField( - model_name='consoleport', - name='connected_endpoint_id', - field=models.PositiveIntegerField(blank=True, null=True), - ), - migrations.AddField( - model_name='consoleport', - name='connected_endpoint_type', - field=models.ForeignKey(blank=True, limit_choices_to={'model__in': ('consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport')}, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType'), - ), - migrations.AddField( - model_name='consoleserverport', - name='connected_endpoint_id', - field=models.PositiveIntegerField(blank=True, null=True), - ), - migrations.AddField( - model_name='consoleserverport', - name='connected_endpoint_type', - field=models.ForeignKey(blank=True, limit_choices_to={'model__in': ('consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport')}, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType'), - ), - migrations.AddField( - model_name='consoleserverport', - name='connection_status', - field=models.NullBooleanField(default=True), - ), - migrations.AddField( - model_name='interface', - name='connected_endpoint_id', - field=models.PositiveIntegerField(blank=True, null=True), - ), - migrations.AddField( - model_name='interface', - name='connected_endpoint_type', - field=models.ForeignKey(blank=True, limit_choices_to={'model__in': ('consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport')}, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType'), - ), - migrations.AddField( - model_name='interface', - name='connection_status', - field=models.NullBooleanField(default=True), - ), - migrations.AddField( - model_name='poweroutlet', - name='connected_endpoint_id', - field=models.PositiveIntegerField(blank=True, null=True), - ), - migrations.AddField( - model_name='poweroutlet', - name='connected_endpoint_type', - field=models.ForeignKey(blank=True, limit_choices_to={'model__in': ('consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport')}, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType'), - ), - migrations.AddField( - model_name='poweroutlet', - name='connection_status', - field=models.NullBooleanField(default=True), - ), - migrations.AddField( - model_name='powerport', - name='connected_endpoint_id', - field=models.PositiveIntegerField(blank=True, null=True), - ), - migrations.AddField( - model_name='powerport', - name='connected_endpoint_type', - field=models.ForeignKey(blank=True, limit_choices_to={'model__in': ('consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport')}, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType'), - ), migrations.AlterUniqueTogether( name='cable', unique_together={('endpoint_b_type', 'endpoint_b_id'), ('endpoint_a_type', 'endpoint_a_id')}, diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index 0a4d8afc5..efa961d5b 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -66,32 +66,6 @@ class ComponentModel(models.Model): ).save() -class ConnectableModel(models.Model): - connected_endpoint_type = models.ForeignKey( - to=ContentType, - limit_choices_to={'model__in': CABLE_ENDPOINT_TYPES}, - on_delete=models.PROTECT, - related_name='+', - blank=True, - null=True - ) - connected_endpoint_id = models.PositiveIntegerField( - blank=True, - null=True - ) - connected_endpoint = GenericForeignKey( - ct_field='connected_endpoint_type', - fk_field='connected_endpoint_id' - ) - connection_status = models.NullBooleanField( - choices=CONNECTION_STATUS_CHOICES, - default=CONNECTION_STATUS_CONNECTED - ) - - class Meta: - abstract = True - - # # Regions # @@ -1643,7 +1617,7 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel): # Console ports # -class ConsolePort(ConnectableModel, ComponentModel): +class ConsolePort(ComponentModel): """ A physical console port within a Device. ConsolePorts connect to ConsoleServerPorts. """ @@ -1706,7 +1680,7 @@ class ConsoleServerPortManager(models.Manager): }).order_by('device', 'name_padded') -class ConsoleServerPort(ConnectableModel, ComponentModel): +class ConsoleServerPort(ComponentModel): """ A physical port within a Device (typically a designated console server) which provides access to ConsolePorts. """ @@ -1747,7 +1721,7 @@ class ConsoleServerPort(ConnectableModel, ComponentModel): # Power ports # -class PowerPort(ConnectableModel, ComponentModel): +class PowerPort(ComponentModel): """ A physical power supply (intake) port within a Device. PowerPorts connect to PowerOutlets. """ @@ -1809,7 +1783,7 @@ class PowerOutletManager(models.Manager): }).order_by('device', 'name_padded') -class PowerOutlet(ConnectableModel, ComponentModel): +class PowerOutlet(ComponentModel): """ A physical power outlet (output) within a Device which provides power to a PowerPort. """ @@ -1850,7 +1824,7 @@ class PowerOutlet(ConnectableModel, ComponentModel): # Interfaces # -class Interface(ConnectableModel, ComponentModel): +class Interface(ComponentModel): """ A network interface within a Device or VirtualMachine. A physical Interface can connect to exactly one other Interface via the creation of an InterfaceConnection. From 471bddea097a6fbd1d2128140919d2ebdeea8234 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 22 Oct 2018 16:58:24 -0400 Subject: [PATCH 013/191] WIP: Initial work on the cable connection form --- netbox/dcim/constants.py | 30 ++++- netbox/dcim/forms.py | 93 ++++++++++++++- netbox/dcim/models.py | 4 +- netbox/dcim/urls.py | 12 +- netbox/dcim/views.py | 27 ++++- netbox/project-static/js/forms.js | 5 +- netbox/templates/dcim/cable_connect.html | 106 ++++++++++++++++++ netbox/templates/dcim/inc/consoleport.html | 2 +- .../templates/dcim/inc/consoleserverport.html | 2 +- netbox/templates/dcim/inc/poweroutlet.html | 2 +- netbox/templates/dcim/inc/powerport.html | 2 +- netbox/utilities/forms.py | 9 ++ .../templates/widgets/select_contenttype.html | 1 + 13 files changed, 275 insertions(+), 20 deletions(-) create mode 100644 netbox/templates/dcim/cable_connect.html create mode 100644 netbox/utilities/templates/widgets/select_contenttype.html diff --git a/netbox/dcim/constants.py b/netbox/dcim/constants.py index d63735347..7b11028d4 100644 --- a/netbox/dcim/constants.py +++ b/netbox/dcim/constants.py @@ -284,12 +284,9 @@ CONNECTION_STATUS_CHOICES = [ ] # Cable endpoint types -CABLE_ENDPOINT_TYPES = ( - 'consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport', -) -CABLE_CONNECTION_TYPES = CABLE_ENDPOINT_TYPES + ( - 'frontpanelport', 'rearpanelport', -) +CABLE_ENDPOINT_TYPES = [ + 'consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport', 'frontpanelport', 'rearpanelport', +] # Cable types # TODO: Add more types @@ -299,3 +296,24 @@ CABLE_TYPE_CHOICES = ( (CABLE_TYPE_COPPER, 'Copper'), (CABLE_TYPE_FIBER, 'Fiber'), ) + +CABLE_ENDPOINT_TYPE_CHOICES = { + # (API endpoint, human-friendly name) + 'consoleport': ('console-ports', 'Console port'), + 'consoleserverport': ('console-server-ports', 'Console server port'), + 'powerport': ('power-ports', 'Power port'), + 'poweroutlet': ('power-outlets', 'Power outlet'), + 'interface': ('interfaces', 'Interface'), + 'frontpanelport': ('front-panel-ports', 'Front panel port'), + 'rearpanelport': ('rear-panel-ports', 'Rear panel port'), +} + +COMPATIBLE_ENDPOINT_TYPES = { + 'consoleport': ['consoleserverport', 'frontpanelport', 'rearpanelport'], + 'consoleserverport': ['consoleport', 'frontpanelport', 'rearpanelport'], + 'powerport': ['poweroutlet'], + 'poweroutlet': ['powerport'], + 'interface': ['interface', 'frontpanelport', 'rearpanelport'], + 'frontpanelport': ['consoleport', 'consoleserverport', 'interface', 'frontpanelport', 'rearpanelport'], + 'rearpanelport': ['consoleport', 'consoleserverport', 'interface', 'frontpanelport', 'rearpanelport'], +} diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 79913b70c..9565d5ef0 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -2,6 +2,7 @@ import re from django import forms from django.contrib.auth.models import User +from django.contrib.contenttypes.models import ContentType from django.contrib.postgres.forms.array import SimpleArrayField from django.db.models import Count, Q from mptt.forms import TreeNodeChoiceField @@ -19,11 +20,12 @@ from utilities.forms import ( BulkEditNullBooleanSelect, ChainedFieldsMixin, ChainedModelChoiceField, CommentField, ComponentForm, ConfirmationForm, CSVChoiceField, ExpandableNameField, FilterChoiceField, FilterTreeNodeMultipleChoiceField, FlexibleModelChoiceField, JSONField, Livesearch, SelectWithDisabled, SelectWithPK, SmallTextarea, SlugField, + ContentTypeSelect ) from virtualization.models import Cluster from .constants import * from .models import ( - DeviceBay, DeviceBayTemplate, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, + Cable, DeviceBay, DeviceBayTemplate, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceRole, DeviceType, FrontPanelPort, FrontPanelPortTemplate, Interface, InterfaceConnection, InterfaceTemplate, Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPanelPort, RearPanelPortTemplate, Region, Site, @@ -2298,6 +2300,95 @@ class RearPanelPortBulkRenameForm(BulkRenameForm): ) +# +# Cables +# + +class CableForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm): + endpoint_b_site = forms.ModelChoiceField( + queryset=Site.objects.all(), + label='Site', + required=False, + widget=forms.Select( + attrs={'filter-for': 'endpoint_b_rack'} + ) + ) + endpoint_b_rack = ChainedModelChoiceField( + queryset=Rack.objects.all(), + chains=( + ('site', 'endpoint_b_site'), + ), + label='Rack', + required=False, + widget=APISelect( + api_url='/api/dcim/racks/?site_id={{endpoint_b_site}}', + attrs={'filter-for': 'endpoint_b_device', 'nullable': 'true'} + ) + ) + endpoint_b_device = ChainedModelChoiceField( + queryset=Device.objects.all(), + chains=( + ('site', 'endpoint_b_site'), + ('rack', 'endpoint_b_rack'), + ), + label='Device', + required=False, + widget=APISelect( + api_url='/api/dcim/devices/?site_id={{endpoint_b_site}}&rack_id={{endpoint_b_rack}}', + display_field='display_name', + attrs={'filter-for': 'endpoint_b_id'} + ) + ) + livesearch = forms.CharField( + required=False, + label='Device', + widget=Livesearch( + query_key='q', + query_url='dcim-api:device-list', + field_to_update='endpoint_b_device' + ) + ) + endpoint_b_type = forms.ModelChoiceField( + queryset=ContentType.objects.all(), + label='Type', + widget=ContentTypeSelect( + attrs={'filter-for': 'endpoint_b_id'} + ) + ) + endpoint_b_id = forms.ChoiceField( + choices=[], + label='Name', + widget=APISelect( + api_url='/api/dcim/{{endpoint_b_type}}s/?device_id={{endpoint_b_device}}', + disabled_indicator='is_connected' + ) + ) + + class Meta: + model = Cable + fields = [ + 'endpoint_b_site', 'endpoint_b_rack', 'endpoint_b_device', 'livesearch', 'endpoint_b_type', + 'endpoint_b_id', 'status', 'label', + ] + + def __init__(self, *args, **kwargs): + super(CableForm, self).__init__(*args, **kwargs) + + # Define available types for endpoint B based on the type of endpoint A + endpoint_a_type = self.instance.endpoint_a._meta.model_name + self.fields['endpoint_b_type'].queryset = ContentType.objects.filter( + model__in=COMPATIBLE_ENDPOINT_TYPES.get(endpoint_a_type) + ) + + def clean(self): + + # Assign endpoint B + cleaned_data = super(CableForm, self).clean() + + + + + # # Device bays # diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index 422f723c7..bc3812c29 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -2455,7 +2455,7 @@ class Cable(ChangeLoggedModel): """ endpoint_a_type = models.ForeignKey( to=ContentType, - limit_choices_to={'model__in': CABLE_CONNECTION_TYPES}, + limit_choices_to={'model__in': CABLE_ENDPOINT_TYPES}, on_delete=models.PROTECT, related_name='+' ) @@ -2466,7 +2466,7 @@ class Cable(ChangeLoggedModel): ) endpoint_b_type = models.ForeignKey( to=ContentType, - limit_choices_to={'model__in': CABLE_CONNECTION_TYPES}, + limit_choices_to={'model__in': CABLE_ENDPOINT_TYPES}, on_delete=models.PROTECT, related_name='+' ) diff --git a/netbox/dcim/urls.py b/netbox/dcim/urls.py index 2d9c8d009..ac283b79f 100644 --- a/netbox/dcim/urls.py +++ b/netbox/dcim/urls.py @@ -161,7 +161,8 @@ urlpatterns = [ url(r'^devices/console-ports/add/$', views.DeviceBulkAddConsolePortView.as_view(), name='device_bulk_add_consoleport'), url(r'^devices/(?P\d+)/console-ports/add/$', views.ConsolePortCreateView.as_view(), name='consoleport_add'), url(r'^devices/(?P\d+)/console-ports/delete/$', views.ConsolePortBulkDeleteView.as_view(), name='consoleport_bulk_delete'), - url(r'^console-ports/(?P\d+)/connect/$', views.ConsolePortConnectView.as_view(), name='consoleport_connect'), + # url(r'^console-ports/(?P\d+)/connect/$', views.ConsolePortConnectView.as_view(), name='consoleport_connect'), + url(r'^console-ports/(?P\d+)/connect/$', views.CableConnectView.as_view(), name='consoleport_connect', kwargs={'endpoint_a_type': 'consoleport'}), url(r'^console-ports/(?P\d+)/disconnect/$', views.ConsolePortDisconnectView.as_view(), name='consoleport_disconnect'), url(r'^console-ports/(?P\d+)/edit/$', views.ConsolePortEditView.as_view(), name='consoleport_edit'), url(r'^console-ports/(?P\d+)/delete/$', views.ConsolePortDeleteView.as_view(), name='consoleport_delete'), @@ -171,7 +172,8 @@ urlpatterns = [ url(r'^devices/(?P\d+)/console-server-ports/add/$', views.ConsoleServerPortCreateView.as_view(), name='consoleserverport_add'), url(r'^devices/(?P\d+)/console-server-ports/disconnect/$', views.ConsoleServerPortBulkDisconnectView.as_view(), name='consoleserverport_bulk_disconnect'), url(r'^devices/(?P\d+)/console-server-ports/delete/$', views.ConsoleServerPortBulkDeleteView.as_view(), name='consoleserverport_bulk_delete'), - url(r'^console-server-ports/(?P\d+)/connect/$', views.ConsoleServerPortConnectView.as_view(), name='consoleserverport_connect'), + # url(r'^console-server-ports/(?P\d+)/connect/$', views.ConsoleServerPortConnectView.as_view(), name='consoleserverport_connect'), + url(r'^console-server-ports/(?P\d+)/connect/$', views.CableConnectView.as_view(), name='consoleserverport_connect', kwargs={'endpoint_a_type': 'consoleserverport'}), url(r'^console-server-ports/(?P\d+)/disconnect/$', views.ConsoleServerPortDisconnectView.as_view(), name='consoleserverport_disconnect'), url(r'^console-server-ports/(?P\d+)/edit/$', views.ConsoleServerPortEditView.as_view(), name='consoleserverport_edit'), url(r'^console-server-ports/(?P\d+)/delete/$', views.ConsoleServerPortDeleteView.as_view(), name='consoleserverport_delete'), @@ -181,7 +183,8 @@ urlpatterns = [ url(r'^devices/power-ports/add/$', views.DeviceBulkAddPowerPortView.as_view(), name='device_bulk_add_powerport'), url(r'^devices/(?P\d+)/power-ports/add/$', views.PowerPortCreateView.as_view(), name='powerport_add'), url(r'^devices/(?P\d+)/power-ports/delete/$', views.PowerPortBulkDeleteView.as_view(), name='powerport_bulk_delete'), - url(r'^power-ports/(?P\d+)/connect/$', views.PowerPortConnectView.as_view(), name='powerport_connect'), + # url(r'^power-ports/(?P\d+)/connect/$', views.PowerPortConnectView.as_view(), name='powerport_connect'), + url(r'^power-ports/(?P\d+)/connect/$', views.CableConnectView.as_view(), name='powerport_connect', kwargs={'endpoint_a_type': 'powerport'}), url(r'^power-ports/(?P\d+)/disconnect/$', views.PowerPortDisconnectView.as_view(), name='powerport_disconnect'), url(r'^power-ports/(?P\d+)/edit/$', views.PowerPortEditView.as_view(), name='powerport_edit'), url(r'^power-ports/(?P\d+)/delete/$', views.PowerPortDeleteView.as_view(), name='powerport_delete'), @@ -191,7 +194,8 @@ urlpatterns = [ url(r'^devices/(?P\d+)/power-outlets/add/$', views.PowerOutletCreateView.as_view(), name='poweroutlet_add'), url(r'^devices/(?P\d+)/power-outlets/disconnect/$', views.PowerOutletBulkDisconnectView.as_view(), name='poweroutlet_bulk_disconnect'), url(r'^devices/(?P\d+)/power-outlets/delete/$', views.PowerOutletBulkDeleteView.as_view(), name='poweroutlet_bulk_delete'), - url(r'^power-outlets/(?P\d+)/connect/$', views.PowerOutletConnectView.as_view(), name='poweroutlet_connect'), + # url(r'^power-outlets/(?P\d+)/connect/$', views.PowerOutletConnectView.as_view(), name='poweroutlet_connect'), + url(r'^power-outlets/(?P\d+)/connect/$', views.CableConnectView.as_view(), name='poweroutlet_connect', kwargs={'endpoint_a_type': 'poweroutlet'}), url(r'^power-outlets/(?P\d+)/disconnect/$', views.PowerOutletDisconnectView.as_view(), name='poweroutlet_disconnect'), url(r'^power-outlets/(?P\d+)/edit/$', views.PowerOutletEditView.as_view(), name='poweroutlet_edit'), url(r'^power-outlets/(?P\d+)/delete/$', views.PowerOutletDeleteView.as_view(), name='poweroutlet_delete'), diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index c7e57b89f..f598d6b51 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -1,12 +1,14 @@ from operator import attrgetter +from django.apps import apps from django.contrib import messages from django.contrib.auth.mixins import PermissionRequiredMixin +from django.core.exceptions import ObjectDoesNotExist from django.core.paginator import EmptyPage, PageNotAnInteger from django.db import transaction from django.db.models import Count, Q from django.forms import modelformset_factory -from django.http import HttpResponseRedirect +from django.http import Http404, HttpResponseRedirect from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse from django.utils.html import escape @@ -30,7 +32,7 @@ from virtualization.models import VirtualMachine from . import filters, forms, tables from .constants import CONNECTION_STATUS_CONNECTED from .models import ( - ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, + Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, DeviceBayTemplate, DeviceRole, DeviceType, FrontPanelPort, FrontPanelPortTemplate, Interface, InterfaceConnection, InterfaceTemplate, Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPanelPort, RearPanelPortTemplate, Region, Site, @@ -2152,6 +2154,27 @@ class InterfaceConnectionsListView(ObjectListView): template_name = 'dcim/interface_connections_list.html' +class CableConnectView(PermissionRequiredMixin, ObjectEditView): + permission_required = 'dcim.add_cable' + model = Cable + model_form = forms.CableForm + template_name = 'dcim/cable_connect.html' + + def alter_obj(self, obj, request, url_args, url_kwargs): + # Retrieve endpoint A based on the given type and PK + endpoint_a_type = url_kwargs.get('endpoint_a_type') + endpoint_a_id = url_kwargs.get('endpoint_a_id') + try: + model = apps.get_model( + app_label='dcim', + model_name=endpoint_a_type + ) + obj.endpoint_a = model.objects.get(pk=endpoint_a_id) + except ObjectDoesNotExist: + raise Http404 + return obj + + # # Inventory items # diff --git a/netbox/project-static/js/forms.js b/netbox/project-static/js/forms.js index 6cb621071..7ea4b152b 100644 --- a/netbox/project-static/js/forms.js +++ b/netbox/project-static/js/forms.js @@ -93,7 +93,10 @@ $(document).ready(function() { var rendered_url = api_url; while (match = filter_regex.exec(api_url)) { var filter_field = $('#id_' + match[1]); - if (filter_field.val()) { + var custom_attr = $('option:selected', filter_field).attr('api-value'); + if (custom_attr) { + rendered_url = rendered_url.replace(match[0], custom_attr); + } else if (filter_field.val()) { rendered_url = rendered_url.replace(match[0], filter_field.val()); } else if (filter_field.attr('nullable') == 'true') { rendered_url = rendered_url.replace(match[0], '0'); diff --git a/netbox/templates/dcim/cable_connect.html b/netbox/templates/dcim/cable_connect.html new file mode 100644 index 000000000..66327170e --- /dev/null +++ b/netbox/templates/dcim/cable_connect.html @@ -0,0 +1,106 @@ +{% extends '_base.html' %} +{% load static from staticfiles %} +{% load form_helpers %} + +{% block content %} +
+ {% csrf_token %} + {% for field in form.hidden_fields %} + {{ field }} + {% endfor %} + {% if form.non_field_errors %} +
+
+
+
Errors
+
+ {{ form.non_field_errors }} +
+
+
+
+ {% endif %} + {% with endpoint_a=form.instance.endpoint_a %} +

{% block title %}Connect {{ endpoint_a.device }} {{ endpoint_a }}{% endblock %}

+
+
+
+
+ A Side +
+
+
+ +
+

{{ endpoint_a.device.site }}

+
+
+
+ +
+

{{ endpoint_a.device.rack|default:"None" }}

+
+
+
+ +
+

{{ endpoint_a.device }}

+
+
+
+ +
+

{{ endpoint_a }}

+
+
+
+
+
+
+ +
+
+
+
+ B Side +
+
+ +
+ +
+ {% render_field form.endpoint_b_site %} + {% render_field form.endpoint_b_rack %} + {% render_field form.endpoint_b_device %} +
+
+ {% render_field form.endpoint_b_type %} + {% render_field form.endpoint_b_id %} +
+
+
+
+
+
+ {% render_field form.status %} + {% render_field form.label %} +
+
+
+
+ + Cancel +
+
+ {% endwith %} +
+{% endblock %} + +{% block javascript %} + +{% endblock %} diff --git a/netbox/templates/dcim/inc/consoleport.html b/netbox/templates/dcim/inc/consoleport.html index 4d75cc65b..91d60f7e3 100644 --- a/netbox/templates/dcim/inc/consoleport.html +++ b/netbox/templates/dcim/inc/consoleport.html @@ -30,7 +30,7 @@ {% else %} - + {% endif %} diff --git a/netbox/templates/dcim/inc/consoleserverport.html b/netbox/templates/dcim/inc/consoleserverport.html index 673f51388..979b040b1 100644 --- a/netbox/templates/dcim/inc/consoleserverport.html +++ b/netbox/templates/dcim/inc/consoleserverport.html @@ -35,7 +35,7 @@ {% else %} - + {% endif %} diff --git a/netbox/templates/dcim/inc/poweroutlet.html b/netbox/templates/dcim/inc/poweroutlet.html index 18cfb7f2c..84c018460 100644 --- a/netbox/templates/dcim/inc/poweroutlet.html +++ b/netbox/templates/dcim/inc/poweroutlet.html @@ -35,7 +35,7 @@ {% else %} - + {% endif %} diff --git a/netbox/templates/dcim/inc/powerport.html b/netbox/templates/dcim/inc/powerport.html index 32e7f20fd..7db2003b0 100644 --- a/netbox/templates/dcim/inc/powerport.html +++ b/netbox/templates/dcim/inc/powerport.html @@ -30,7 +30,7 @@ {% else %} - + {% endif %} diff --git a/netbox/utilities/forms.py b/netbox/utilities/forms.py index 1c2f7dcf0..57e89ac1d 100644 --- a/netbox/utilities/forms.py +++ b/netbox/utilities/forms.py @@ -207,6 +207,15 @@ class SelectWithPK(forms.Select): option_template_name = 'widgets/select_option_with_pk.html' +class ContentTypeSelect(forms.Select): + """ + Appends an `api-value` attribute equal to the slugified model name for each ContentType. For example: + + This attribute can be used to reference the relevant API endpoint for a particular ContentType. + """ + option_template_name = 'widgets/select_contenttype.html' + + class ArrayFieldSelectMultiple(SelectWithDisabled, forms.SelectMultiple): """ MultiSelect widget for a SimpleArrayField. Choices must be populated on the widget. diff --git a/netbox/utilities/templates/widgets/select_contenttype.html b/netbox/utilities/templates/widgets/select_contenttype.html new file mode 100644 index 000000000..ca7fe326e --- /dev/null +++ b/netbox/utilities/templates/widgets/select_contenttype.html @@ -0,0 +1 @@ + From ea0de629df32801e63ceb0dfcb3ae6c7f833bbac Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 23 Oct 2018 13:40:10 -0400 Subject: [PATCH 014/191] Fixed cable creation form --- netbox/dcim/forms.py | 11 +---------- netbox/dcim/urls.py | 12 ++++++------ netbox/dcim/views.py | 11 +++-------- 3 files changed, 10 insertions(+), 24 deletions(-) diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 9565d5ef0..0777d20aa 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -2355,8 +2355,7 @@ class CableForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm): attrs={'filter-for': 'endpoint_b_id'} ) ) - endpoint_b_id = forms.ChoiceField( - choices=[], + endpoint_b_id = forms.IntegerField( label='Name', widget=APISelect( api_url='/api/dcim/{{endpoint_b_type}}s/?device_id={{endpoint_b_device}}', @@ -2380,14 +2379,6 @@ class CableForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm): model__in=COMPATIBLE_ENDPOINT_TYPES.get(endpoint_a_type) ) - def clean(self): - - # Assign endpoint B - cleaned_data = super(CableForm, self).clean() - - - - # # Device bays diff --git a/netbox/dcim/urls.py b/netbox/dcim/urls.py index ac283b79f..3e011b4c3 100644 --- a/netbox/dcim/urls.py +++ b/netbox/dcim/urls.py @@ -5,8 +5,8 @@ from ipam.views import ServiceCreateView from secrets.views import secret_add from . import views from .models import ( - Device, DeviceRole, DeviceType, Interface, Manufacturer, Platform, Rack, RackGroup, RackReservation, RackRole, - Region, Site, VirtualChassis, + ConsolePort, ConsoleServerPort, Device, DeviceRole, DeviceType, Interface, Manufacturer, Platform, PowerPort, + PowerOutlet, Rack, RackGroup, RackReservation, RackRole, Region, Site, VirtualChassis, ) app_name = 'dcim' @@ -162,7 +162,7 @@ urlpatterns = [ url(r'^devices/(?P\d+)/console-ports/add/$', views.ConsolePortCreateView.as_view(), name='consoleport_add'), url(r'^devices/(?P\d+)/console-ports/delete/$', views.ConsolePortBulkDeleteView.as_view(), name='consoleport_bulk_delete'), # url(r'^console-ports/(?P\d+)/connect/$', views.ConsolePortConnectView.as_view(), name='consoleport_connect'), - url(r'^console-ports/(?P\d+)/connect/$', views.CableConnectView.as_view(), name='consoleport_connect', kwargs={'endpoint_a_type': 'consoleport'}), + url(r'^console-ports/(?P\d+)/connect/$', views.CableConnectView.as_view(), name='consoleport_connect', kwargs={'endpoint_a_type': ConsolePort}), url(r'^console-ports/(?P\d+)/disconnect/$', views.ConsolePortDisconnectView.as_view(), name='consoleport_disconnect'), url(r'^console-ports/(?P\d+)/edit/$', views.ConsolePortEditView.as_view(), name='consoleport_edit'), url(r'^console-ports/(?P\d+)/delete/$', views.ConsolePortDeleteView.as_view(), name='consoleport_delete'), @@ -173,7 +173,7 @@ urlpatterns = [ url(r'^devices/(?P\d+)/console-server-ports/disconnect/$', views.ConsoleServerPortBulkDisconnectView.as_view(), name='consoleserverport_bulk_disconnect'), url(r'^devices/(?P\d+)/console-server-ports/delete/$', views.ConsoleServerPortBulkDeleteView.as_view(), name='consoleserverport_bulk_delete'), # url(r'^console-server-ports/(?P\d+)/connect/$', views.ConsoleServerPortConnectView.as_view(), name='consoleserverport_connect'), - url(r'^console-server-ports/(?P\d+)/connect/$', views.CableConnectView.as_view(), name='consoleserverport_connect', kwargs={'endpoint_a_type': 'consoleserverport'}), + url(r'^console-server-ports/(?P\d+)/connect/$', views.CableConnectView.as_view(), name='consoleserverport_connect', kwargs={'endpoint_a_type': ConsoleServerPort}), url(r'^console-server-ports/(?P\d+)/disconnect/$', views.ConsoleServerPortDisconnectView.as_view(), name='consoleserverport_disconnect'), url(r'^console-server-ports/(?P\d+)/edit/$', views.ConsoleServerPortEditView.as_view(), name='consoleserverport_edit'), url(r'^console-server-ports/(?P\d+)/delete/$', views.ConsoleServerPortDeleteView.as_view(), name='consoleserverport_delete'), @@ -184,7 +184,7 @@ urlpatterns = [ url(r'^devices/(?P\d+)/power-ports/add/$', views.PowerPortCreateView.as_view(), name='powerport_add'), url(r'^devices/(?P\d+)/power-ports/delete/$', views.PowerPortBulkDeleteView.as_view(), name='powerport_bulk_delete'), # url(r'^power-ports/(?P\d+)/connect/$', views.PowerPortConnectView.as_view(), name='powerport_connect'), - url(r'^power-ports/(?P\d+)/connect/$', views.CableConnectView.as_view(), name='powerport_connect', kwargs={'endpoint_a_type': 'powerport'}), + url(r'^power-ports/(?P\d+)/connect/$', views.CableConnectView.as_view(), name='powerport_connect', kwargs={'endpoint_a_type': PowerPort}), url(r'^power-ports/(?P\d+)/disconnect/$', views.PowerPortDisconnectView.as_view(), name='powerport_disconnect'), url(r'^power-ports/(?P\d+)/edit/$', views.PowerPortEditView.as_view(), name='powerport_edit'), url(r'^power-ports/(?P\d+)/delete/$', views.PowerPortDeleteView.as_view(), name='powerport_delete'), @@ -195,7 +195,7 @@ urlpatterns = [ url(r'^devices/(?P\d+)/power-outlets/disconnect/$', views.PowerOutletBulkDisconnectView.as_view(), name='poweroutlet_bulk_disconnect'), url(r'^devices/(?P\d+)/power-outlets/delete/$', views.PowerOutletBulkDeleteView.as_view(), name='poweroutlet_bulk_delete'), # url(r'^power-outlets/(?P\d+)/connect/$', views.PowerOutletConnectView.as_view(), name='poweroutlet_connect'), - url(r'^power-outlets/(?P\d+)/connect/$', views.CableConnectView.as_view(), name='poweroutlet_connect', kwargs={'endpoint_a_type': 'poweroutlet'}), + url(r'^power-outlets/(?P\d+)/connect/$', views.CableConnectView.as_view(), name='poweroutlet_connect', kwargs={'endpoint_a_type': PowerOutlet}), url(r'^power-outlets/(?P\d+)/disconnect/$', views.PowerOutletDisconnectView.as_view(), name='poweroutlet_disconnect'), url(r'^power-outlets/(?P\d+)/edit/$', views.PowerOutletEditView.as_view(), name='poweroutlet_edit'), url(r'^power-outlets/(?P\d+)/delete/$', views.PowerOutletDeleteView.as_view(), name='poweroutlet_delete'), diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index f598d6b51..a97b3a98a 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -2161,17 +2161,12 @@ class CableConnectView(PermissionRequiredMixin, ObjectEditView): template_name = 'dcim/cable_connect.html' def alter_obj(self, obj, request, url_args, url_kwargs): + # Retrieve endpoint A based on the given type and PK endpoint_a_type = url_kwargs.get('endpoint_a_type') endpoint_a_id = url_kwargs.get('endpoint_a_id') - try: - model = apps.get_model( - app_label='dcim', - model_name=endpoint_a_type - ) - obj.endpoint_a = model.objects.get(pk=endpoint_a_id) - except ObjectDoesNotExist: - raise Http404 + obj.endpoint_a = endpoint_a_type.objects.get(pk=endpoint_a_id) + return obj From 1595a5ecd72df554dde7f02d714b1c5e158ed3f8 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 24 Oct 2018 10:37:54 -0400 Subject: [PATCH 015/191] Renamed ConsolePort.cs_port to connected_endpoint --- netbox/dcim/api/serializers.py | 12 ++--- netbox/dcim/api/views.py | 6 +-- netbox/dcim/filters.py | 4 +- netbox/dcim/fixtures/dcim.json | 30 +++++------ netbox/dcim/forms.py | 34 ++++++------- netbox/dcim/migrations/0066_cables.py | 29 ++++++++--- netbox/dcim/models.py | 13 +++-- netbox/dcim/tables.py | 12 +++-- netbox/dcim/tests/test_api.py | 16 +++--- netbox/dcim/views.py | 51 +++++++++++-------- netbox/extras/models.py | 4 +- netbox/netbox/views.py | 2 +- .../templates/dcim/consoleport_connect.html | 2 +- .../dcim/consoleport_disconnect.html | 2 +- .../dcim/consoleserverport_disconnect.html | 2 +- netbox/templates/dcim/device.html | 8 +-- netbox/templates/dcim/inc/consoleport.html | 12 ++--- .../templates/dcim/inc/consoleserverport.html | 18 +++---- 18 files changed, 143 insertions(+), 114 deletions(-) diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index df1261d55..80f54a2a3 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -504,8 +504,8 @@ class ConsoleServerPortSerializer(TaggitSerializer, ValidatedModelSerializer): class Meta: model = ConsoleServerPort - fields = ['id', 'device', 'name', 'connected_console', 'tags'] - read_only_fields = ['connected_console'] + fields = ['id', 'device', 'name', 'connected_endpoint', 'tags'] + read_only_fields = ['connected_endpoint'] class NestedConsoleServerPortSerializer(WritableNestedSerializer): @@ -518,7 +518,7 @@ class NestedConsoleServerPortSerializer(WritableNestedSerializer): fields = ['id', 'url', 'device', 'name', 'is_connected'] def get_is_connected(self, obj): - return hasattr(obj, 'connected_console') and obj.connected_console is not None + return hasattr(obj, 'connected_endpoint') and obj.connected_endpoint is not None # @@ -527,12 +527,12 @@ class NestedConsoleServerPortSerializer(WritableNestedSerializer): class ConsolePortSerializer(TaggitSerializer, ValidatedModelSerializer): device = NestedDeviceSerializer() - cs_port = NestedConsoleServerPortSerializer(required=False, allow_null=True) + connected_endpoint = NestedConsoleServerPortSerializer(required=False, allow_null=True) tags = TagListSerializerField(required=False) class Meta: model = ConsolePort - fields = ['id', 'device', 'name', 'cs_port', 'connection_status', 'tags'] + fields = ['id', 'device', 'name', 'connected_endpoint', 'connection_status', 'tags'] class NestedConsolePortSerializer(TaggitSerializer, ValidatedModelSerializer): @@ -545,7 +545,7 @@ class NestedConsolePortSerializer(TaggitSerializer, ValidatedModelSerializer): fields = ['id', 'url', 'device', 'name', 'is_connected'] def get_is_connected(self, obj): - return obj.cs_port is not None + return obj.connected_endpoint is not None # diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index 127784e83..ef4bec9cd 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -331,13 +331,13 @@ class DeviceViewSet(CustomFieldModelViewSet): # class ConsolePortViewSet(ModelViewSet): - queryset = ConsolePort.objects.select_related('device', 'cs_port__device').prefetch_related('tags') + queryset = ConsolePort.objects.select_related('device', 'connected_endpoint__device').prefetch_related('tags') serializer_class = serializers.ConsolePortSerializer filter_class = filters.ConsolePortFilter class ConsoleServerPortViewSet(ModelViewSet): - queryset = ConsoleServerPort.objects.select_related('device', 'connected_console__device').prefetch_related('tags') + queryset = ConsoleServerPort.objects.select_related('device', 'connected_endpoint__device').prefetch_related('tags') serializer_class = serializers.ConsoleServerPortSerializer filter_class = filters.ConsoleServerPortFilter @@ -399,7 +399,7 @@ class InventoryItemViewSet(ModelViewSet): # class ConsoleConnectionViewSet(ListModelMixin, GenericViewSet): - queryset = ConsolePort.objects.select_related('device', 'cs_port__device').filter(cs_port__isnull=False) + queryset = ConsolePort.objects.select_related('device', 'connected_endpoint__device').filter(connected_endpoint__isnull=False) serializer_class = serializers.ConsolePortSerializer filter_class = filters.ConsoleConnectionFilter diff --git a/netbox/dcim/filters.py b/netbox/dcim/filters.py index 3b370654e..3b854dd61 100644 --- a/netbox/dcim/filters.py +++ b/netbox/dcim/filters.py @@ -803,14 +803,14 @@ class ConsoleConnectionFilter(django_filters.FilterSet): def filter_site(self, queryset, name, value): if not value.strip(): return queryset - return queryset.filter(cs_port__device__site__slug=value) + return queryset.filter(connected_endpoint__device__site__slug=value) def filter_device(self, queryset, name, value): if not value.strip(): return queryset return queryset.filter( Q(device__name__icontains=value) | - Q(cs_port__device__name__icontains=value) + Q(connected_endpoint__device__name__icontains=value) ) diff --git a/netbox/dcim/fixtures/dcim.json b/netbox/dcim/fixtures/dcim.json index 709995f95..148ce8ca6 100644 --- a/netbox/dcim/fixtures/dcim.json +++ b/netbox/dcim/fixtures/dcim.json @@ -2151,7 +2151,7 @@ "fields": { "device": 1, "name": "Console (RE0)", - "cs_port": 27, + "connected_endpoint": 27, "connection_status": true } }, @@ -2161,7 +2161,7 @@ "fields": { "device": 1, "name": "Console (RE1)", - "cs_port": 38, + "connected_endpoint": 38, "connection_status": true } }, @@ -2171,7 +2171,7 @@ "fields": { "device": 2, "name": "Console (RE0)", - "cs_port": 5, + "connected_endpoint": 5, "connection_status": true } }, @@ -2181,7 +2181,7 @@ "fields": { "device": 2, "name": "Console (RE1)", - "cs_port": 16, + "connected_endpoint": 16, "connection_status": true } }, @@ -2191,7 +2191,7 @@ "fields": { "device": 3, "name": "Console", - "cs_port": 49, + "connected_endpoint": 49, "connection_status": true } }, @@ -2201,7 +2201,7 @@ "fields": { "device": 4, "name": "Console", - "cs_port": 48, + "connected_endpoint": 48, "connection_status": true } }, @@ -2211,7 +2211,7 @@ "fields": { "device": 5, "name": "Console", - "cs_port": null, + "connected_endpoint": null, "connection_status": true } }, @@ -2221,7 +2221,7 @@ "fields": { "device": 6, "name": "Console", - "cs_port": null, + "connected_endpoint": null, "connection_status": true } }, @@ -2231,7 +2231,7 @@ "fields": { "device": 7, "name": "Console (RE0)", - "cs_port": null, + "connected_endpoint": null, "connection_status": true } }, @@ -2241,7 +2241,7 @@ "fields": { "device": 7, "name": "Console (RE1)", - "cs_port": null, + "connected_endpoint": null, "connection_status": true } }, @@ -2251,7 +2251,7 @@ "fields": { "device": 8, "name": "Console (RE0)", - "cs_port": null, + "connected_endpoint": null, "connection_status": true } }, @@ -2261,7 +2261,7 @@ "fields": { "device": 8, "name": "Console (RE1)", - "cs_port": null, + "connected_endpoint": null, "connection_status": true } }, @@ -2271,7 +2271,7 @@ "fields": { "device": 9, "name": "Console", - "cs_port": null, + "connected_endpoint": null, "connection_status": true } }, @@ -2281,7 +2281,7 @@ "fields": { "device": 11, "name": "Serial", - "cs_port": null, + "connected_endpoint": null, "connection_status": true } }, @@ -2291,7 +2291,7 @@ "fields": { "device": 12, "name": "Serial", - "cs_port": null, + "connected_endpoint": null, "connection_status": true } }, diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 0777d20aa..a59553b49 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -1301,8 +1301,8 @@ class ConsoleConnectionCSVForm(forms.ModelForm): 'invalid_choice': 'Console server not found', } ) - cs_port = forms.CharField( - help_text='Console server port name' + connected_endpoint = forms.CharField( + help_text='Console server port' ) device = FlexibleModelChoiceField( queryset=Device.objects.all(), @@ -1322,7 +1322,7 @@ class ConsoleConnectionCSVForm(forms.ModelForm): class Meta: model = ConsolePort - fields = ['console_server', 'cs_port', 'device', 'console_port', 'connection_status'] + fields = ['console_server', 'connected_endpoint', 'device', 'console_port', 'connection_status'] def clean_console_port(self): @@ -1336,7 +1336,7 @@ class ConsoleConnectionCSVForm(forms.ModelForm): device=self.cleaned_data['device'], name=console_port_name ) # Check if the console port is already connected - if consoleport.cs_port is not None: + if consoleport.connected_endpoint is not None: raise forms.ValidationError("{} {} is already connected".format( self.cleaned_data['device'], console_port_name )) @@ -1348,28 +1348,28 @@ class ConsoleConnectionCSVForm(forms.ModelForm): self.instance = consoleport return consoleport - def clean_cs_port(self): + def clean_connected_endpoint(self): - cs_port_name = self.cleaned_data.get('cs_port') - if not self.cleaned_data.get('console_server') or not cs_port_name: + consoleserverport_name = self.cleaned_data.get('connected_endpoint') + if not self.cleaned_data.get('console_server') or not consoleserverport_name: return None try: # Retrieve console server port by name - cs_port = ConsoleServerPort.objects.get( - device=self.cleaned_data['console_server'], name=cs_port_name + consoleserverport = ConsoleServerPort.objects.get( + device=self.cleaned_data['console_server'], name=consoleserverport_name ) # Check if the console server port is already connected - if ConsolePort.objects.filter(cs_port=cs_port).count(): + if ConsolePort.objects.filter(connected_endpoint=consoleserverport).count(): raise forms.ValidationError("{} {} is already connected".format( - self.cleaned_data['console_server'], cs_port_name + self.cleaned_data['console_server'], consoleserverport_name )) except ConsoleServerPort.DoesNotExist: raise forms.ValidationError("Invalid console server port ({} {})".format( - self.cleaned_data['console_server'], cs_port_name + self.cleaned_data['console_server'], consoleserverport_name )) - return cs_port + return consoleserverport class ConsolePortConnectionForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm): @@ -1403,7 +1403,7 @@ class ConsolePortConnectionForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelF widget=APISelect( api_url='/api/dcim/devices/?site_id={{site}}&rack_id={{rack}}&is_console_server=True', display_field='display_name', - attrs={'filter-for': 'cs_port'} + attrs={'filter-for': 'connected_endpoint'} ) ) livesearch = forms.CharField( @@ -1415,7 +1415,7 @@ class ConsolePortConnectionForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelF field_to_update='console_server', ) ) - cs_port = ChainedModelChoiceField( + connected_endpoint = ChainedModelChoiceField( queryset=ConsoleServerPort.objects.all(), chains=( ('device', 'console_server'), @@ -1429,9 +1429,9 @@ class ConsolePortConnectionForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelF class Meta: model = ConsolePort - fields = ['site', 'rack', 'console_server', 'livesearch', 'cs_port', 'connection_status'] + fields = ['site', 'rack', 'console_server', 'livesearch', 'connected_endpoint', 'connection_status'] labels = { - 'cs_port': 'Port', + 'connected_endpoint': 'Port', 'connection_status': 'Status', } diff --git a/netbox/dcim/migrations/0066_cables.py b/netbox/dcim/migrations/0066_cables.py index 342590d20..d160e57d7 100644 --- a/netbox/dcim/migrations/0066_cables.py +++ b/netbox/dcim/migrations/0066_cables.py @@ -1,5 +1,3 @@ -# Generated by Django 2.0.8 on 2018-10-18 19:41 - from django.db import migrations, models import django.db.models.deletion import utilities.fields @@ -19,13 +17,13 @@ def console_connections_to_cables(apps, schema_editor): consoleserverport_type = ContentType.objects.get_for_model(ConsoleServerPort) # Create a new Cable instance from each console connection - for consoleport in ConsolePort.objects.filter(cs_port__isnull=False): + for consoleport in ConsolePort.objects.filter(connected_endpoint__isnull=False): c = Cable() # We have to assign GFK fields manually because we're inside a migration. c.endpoint_a_type = consoleport_type c.endpoint_a_id = consoleport.id c.endpoint_b_type = consoleserverport_type - c.endpoint_b_id = consoleport.cs_port_id + c.endpoint_b_id = consoleport.connected_endpoint_id c.connection_status = consoleport.connection_status c.save() @@ -87,6 +85,8 @@ class Migration(migrations.Migration): ] operations = [ + + # Create the Cable model migrations.CreateModel( name='Cable', fields=[ @@ -99,8 +99,8 @@ class Migration(migrations.Migration): ('status', models.BooleanField(default=True)), ('label', models.CharField(blank=True, max_length=100)), ('color', utilities.fields.ColorField(blank=True, max_length=6)), - ('endpoint_a_type', models.ForeignKey(limit_choices_to={'model__in': ('consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport', 'frontpanelport', 'rearpanelport')}, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType')), - ('endpoint_b_type', models.ForeignKey(limit_choices_to={'model__in': ('consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport', 'frontpanelport', 'rearpanelport')}, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType')), + ('endpoint_a_type', models.ForeignKey(limit_choices_to={'model__in': ['consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport', 'frontpanelport', 'rearpanelport']}, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType')), + ('endpoint_b_type', models.ForeignKey(limit_choices_to={'model__in': ['consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport', 'frontpanelport', 'rearpanelport']}, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType')), ], ), migrations.AlterUniqueTogether( @@ -108,6 +108,23 @@ class Migration(migrations.Migration): unique_together={('endpoint_b_type', 'endpoint_b_id'), ('endpoint_a_type', 'endpoint_a_id')}, ), + # Rename model fields + migrations.RenameField( + model_name='consoleport', + old_name='cs_port', + new_name='connected_endpoint' + ), + migrations.AlterField( + model_name='consoleport', + name='connected_endpoint', + field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='connected_endpoint', to='dcim.ConsoleServerPort'), + ), + migrations.AlterField( + model_name='consoleserverport', + name='device', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='consoleserverports', to='dcim.Device'), + ), + # Copy console/power/interface connections as Cables migrations.RunPython(console_connections_to_cables), migrations.RunPython(power_connections_to_cables), diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index bc3812c29..412079682 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -1629,11 +1629,10 @@ class ConsolePort(ComponentModel): name = models.CharField( max_length=50 ) - cs_port = models.OneToOneField( + connected_endpoint = models.OneToOneField( to='dcim.ConsoleServerPort', on_delete=models.SET_NULL, - related_name='connected_console', - verbose_name='Console server port', + related_name='connected_endpoint', blank=True, null=True ) @@ -1644,7 +1643,7 @@ class ConsolePort(ComponentModel): tags = TaggableManager() - csv_headers = ['console_server', 'cs_port', 'device', 'console_port', 'connection_status'] + csv_headers = ['console_server', 'connected_endpoint', 'device', 'console_port', 'connection_status'] class Meta: ordering = ['device', 'name'] @@ -1658,8 +1657,8 @@ class ConsolePort(ComponentModel): def to_csv(self): return ( - self.cs_port.device.identifier if self.cs_port else None, - self.cs_port.name if self.cs_port else None, + self.connected_endpoint.device.identifier if self.connected_endpoint else None, + self.connected_endpoint.name if self.connected_endpoint else None, self.device.identifier, self.name, self.get_connection_status_display(), @@ -1687,7 +1686,7 @@ class ConsoleServerPort(ComponentModel): device = models.ForeignKey( to='dcim.Device', on_delete=models.CASCADE, - related_name='cs_ports' + related_name='consoleserverports' ) name = models.CharField( max_length=50 diff --git a/netbox/dcim/tables.py b/netbox/dcim/tables.py index a1a2c7a4d..b5f62e792 100644 --- a/netbox/dcim/tables.py +++ b/netbox/dcim/tables.py @@ -622,15 +622,19 @@ class DeviceBayTable(BaseTable): # class ConsoleConnectionTable(BaseTable): - console_server = tables.LinkColumn('dcim:device', accessor=Accessor('cs_port.device'), - args=[Accessor('cs_port.device.pk')], verbose_name='Console server') - cs_port = tables.Column(verbose_name='Port') + console_server = tables.LinkColumn( + 'dcim:device', + accessor=Accessor('connected_endpoint.device'), + args=[Accessor('connected_endpoint.device.pk')], + verbose_name='Console server' + ) + connected_endpoint = tables.Column(verbose_name='Port') device = tables.LinkColumn('dcim:device', args=[Accessor('device.pk')], verbose_name='Device') name = tables.Column(verbose_name='Console port') class Meta(BaseTable.Meta): model = ConsolePort - fields = ('console_server', 'cs_port', 'device', 'name') + fields = ('console_server', 'connected_endpoint', 'device', 'name') class PowerConnectionTable(BaseTable): diff --git a/netbox/dcim/tests/test_api.py b/netbox/dcim/tests/test_api.py index 52ee16a44..040c6dab5 100644 --- a/netbox/dcim/tests/test_api.py +++ b/netbox/dcim/tests/test_api.py @@ -2005,7 +2005,7 @@ class ConsolePortTest(APITestCase): data = { 'device': self.device.pk, 'name': 'Test Console Port X', - 'cs_port': consoleserverport.pk, + 'connected_endpoint': consoleserverport.pk, } url = reverse('dcim-api:consoleport-detail', kwargs={'pk': self.consoleport1.pk}) @@ -2015,7 +2015,7 @@ class ConsolePortTest(APITestCase): self.assertEqual(ConsolePort.objects.count(), 3) consoleport1 = ConsolePort.objects.get(pk=response.data['id']) self.assertEqual(consoleport1.name, data['name']) - self.assertEqual(consoleport1.cs_port_id, data['cs_port']) + self.assertEqual(consoleport1.connected_endpoint_id, data['connected_endpoint']) def test_delete_consoleport(self): @@ -2820,17 +2820,17 @@ class ConsoleConnectionTest(APITestCase): device2 = Device.objects.create( device_type=devicetype, device_role=devicerole, name='Test Device 2', site=site ) - cs_port1 = ConsoleServerPort.objects.create(device=device1, name='Test CS Port 1') - cs_port2 = ConsoleServerPort.objects.create(device=device1, name='Test CS Port 2') - cs_port3 = ConsoleServerPort.objects.create(device=device1, name='Test CS Port 3') + consoleserverport1 = ConsoleServerPort.objects.create(device=device1, name='Test Console Server Port 1') + consoleserverport2 = ConsoleServerPort.objects.create(device=device1, name='Test Console Server Port 2') + consoleserverport3 = ConsoleServerPort.objects.create(device=device1, name='Test Console Server Port 3') ConsolePort.objects.create( - device=device2, cs_port=cs_port1, name='Test Console Port 1', connection_status=True + device=device2, connected_endpoint=consoleserverport1, name='Test Console Port 1', connection_status=True ) ConsolePort.objects.create( - device=device2, cs_port=cs_port2, name='Test Console Port 2', connection_status=True + device=device2, connected_endpoint=consoleserverport2, name='Test Console Port 2', connection_status=True ) ConsolePort.objects.create( - device=device2, cs_port=cs_port3, name='Test Console Port 3', connection_status=True + device=device2, connected_endpoint=consoleserverport3, name='Test Console Port 3', connection_status=True ) def test_list_consoleconnections(self): diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index a97b3a98a..bcf39f626 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -886,11 +886,11 @@ class DeviceView(View): # Console ports console_ports = natsorted( - ConsolePort.objects.filter(device=device).select_related('cs_port__device'), key=attrgetter('name') + ConsolePort.objects.filter(device=device).select_related('connected_endpoint__device'), key=attrgetter('name') ) # Console server ports - cs_ports = ConsoleServerPort.objects.filter(device=device).select_related('connected_console') + consoleserverports = ConsoleServerPort.objects.filter(device=device).select_related('connected_endpoint') # Power ports power_ports = natsorted( @@ -941,7 +941,7 @@ class DeviceView(View): return render(request, 'dcim/device.html', { 'device': device, 'console_ports': console_ports, - 'cs_ports': cs_ports, + 'consoleserverports': consoleserverports, 'power_ports': power_ports, 'power_outlets': power_outlets, 'interfaces': interfaces, @@ -1133,9 +1133,9 @@ class ConsolePortConnectView(PermissionRequiredMixin, View): consoleport.device.get_absolute_url(), escape(consoleport.device), escape(consoleport.name), - consoleport.cs_port.device.get_absolute_url(), - escape(consoleport.cs_port.device), - escape(consoleport.cs_port.name), + consoleport.connected_endpoint.device.get_absolute_url(), + escape(consoleport.connected_endpoint.device), + escape(consoleport.connected_endpoint.name), ) messages.success(request, mark_safe(msg)) @@ -1156,7 +1156,7 @@ class ConsolePortDisconnectView(PermissionRequiredMixin, View): consoleport = get_object_or_404(ConsolePort, pk=pk) form = ConfirmationForm() - if not consoleport.cs_port: + if not consoleport.connected_endpoint: messages.warning( request, "Cannot disconnect console port {}: It is not connected to anything.".format(consoleport) ) @@ -1174,18 +1174,17 @@ class ConsolePortDisconnectView(PermissionRequiredMixin, View): form = ConfirmationForm(request.POST) if form.is_valid(): - - cs_port = consoleport.cs_port - consoleport.cs_port = None + consoleserverport = consoleport.connected_endpoint + consoleport.connected_endpoint = None consoleport.connection_status = None consoleport.save() msg = 'Disconnected {} {} from {} {}'.format( consoleport.device.get_absolute_url(), escape(consoleport.device), escape(consoleport.name), - cs_port.device.get_absolute_url(), - escape(cs_port.device), - escape(cs_port.name), + consoleserverport.device.get_absolute_url(), + escape(consoleserverport.device), + escape(consoleserverport.name), ) messages.success(request, mark_safe(msg)) @@ -1264,7 +1263,7 @@ class ConsoleServerPortConnectView(PermissionRequiredMixin, View): if form.is_valid(): consoleport = form.cleaned_data['port'] - consoleport.cs_port = consoleserverport + consoleport.connected_endpoint = consoleserverport consoleport.connection_status = form.cleaned_data['connection_status'] consoleport.save() msg = 'Connected {} {} to {} {}'.format( @@ -1294,7 +1293,7 @@ class ConsoleServerPortDisconnectView(PermissionRequiredMixin, View): consoleserverport = get_object_or_404(ConsoleServerPort, pk=pk) form = ConfirmationForm() - if not hasattr(consoleserverport, 'connected_console'): + if not hasattr(consoleserverport, 'connected_endpoint'): messages.warning( request, "Cannot disconnect console server port {}: Nothing is connected to it.".format(consoleserverport) @@ -1314,8 +1313,8 @@ class ConsoleServerPortDisconnectView(PermissionRequiredMixin, View): if form.is_valid(): - consoleport = consoleserverport.connected_console - consoleport.cs_port = None + consoleport = consoleserverport.connected_endpoint + consoleport.connected_endpoint = None consoleport.connection_status = None consoleport.save() msg = 'Disconnected {} {} from {} {}'.format( @@ -1359,8 +1358,13 @@ class ConsoleServerPortBulkDisconnectView(PermissionRequiredMixin, BulkDisconnec model = ConsoleServerPort form = forms.ConsoleServerPortBulkDisconnectForm - def disconnect_objects(self, cs_ports): - return ConsolePort.objects.filter(cs_port__in=cs_ports).update(cs_port=None, connection_status=None) + def disconnect_objects(self, consoleserverports): + return ConsolePort.objects.filter( + connected_endpoint__in=consoleserverports + ).update( + connected_endpoint=None, + connection_status=None + ) class ConsoleServerPortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): @@ -2125,8 +2129,13 @@ class InterfaceConnectionsBulkImportView(PermissionRequiredMixin, BulkImportView # class ConsoleConnectionsListView(ObjectListView): - queryset = ConsolePort.objects.select_related('device', 'cs_port__device').filter(cs_port__isnull=False) \ - .order_by('cs_port__device__name', 'cs_port__name') + queryset = ConsolePort.objects.select_related( + 'device', 'connected_endpoint__device' + ).filter( + connected_endpoint__isnull=False + ).order_by( + 'connected_endpoint__device__name', 'connected_endpoint__name' + ) filter = filters.ConsoleConnectionFilter filter_form = forms.ConsoleConnectionFilterForm table = tables.ConsoleConnectionTable diff --git a/netbox/extras/models.py b/netbox/extras/models.py index a9c0b9ef5..5e7dbac24 100644 --- a/netbox/extras/models.py +++ b/netbox/extras/models.py @@ -526,10 +526,10 @@ class TopologyMap(models.Model): from dcim.models import ConsolePort # Add all console connections to the graph - console_ports = ConsolePort.objects.filter(device__in=devices, cs_port__device__in=devices) + console_ports = ConsolePort.objects.filter(device__in=devices, connected_endpoint__device__in=devices) for cp in console_ports: style = 'solid' if cp.connection_status == CONNECTION_STATUS_CONNECTED else 'dashed' - self.graph.edge(cp.cs_port.device.name, cp.device.name, style=style) + self.graph.edge(cp.connected_endpoint.device.name, cp.device.name, style=style) def add_power_connections(self, devices): diff --git a/netbox/netbox/views.py b/netbox/netbox/views.py index b2f726060..d7bb29f2e 100644 --- a/netbox/netbox/views.py +++ b/netbox/netbox/views.py @@ -167,7 +167,7 @@ class HomeView(View): 'rack_count': Rack.objects.count(), 'device_count': Device.objects.count(), 'interface_connections_count': InterfaceConnection.objects.count(), - 'console_connections_count': ConsolePort.objects.filter(cs_port__isnull=False).count(), + 'console_connections_count': ConsolePort.objects.filter(connected_endpoint__isnull=False).count(), 'power_connections_count': PowerPort.objects.filter(power_outlet__isnull=False).count(), # IPAM diff --git a/netbox/templates/dcim/consoleport_connect.html b/netbox/templates/dcim/consoleport_connect.html index 679540960..5d8596fd5 100644 --- a/netbox/templates/dcim/consoleport_connect.html +++ b/netbox/templates/dcim/consoleport_connect.html @@ -35,7 +35,7 @@ {% render_field form.console_server %} - {% render_field form.cs_port %} + {% render_field form.connected_endpoint %} {% render_field form.connection_status %} diff --git a/netbox/templates/dcim/consoleport_disconnect.html b/netbox/templates/dcim/consoleport_disconnect.html index dfd5cf2e7..f83329832 100644 --- a/netbox/templates/dcim/consoleport_disconnect.html +++ b/netbox/templates/dcim/consoleport_disconnect.html @@ -4,5 +4,5 @@ {% block title %}Disconnect console port {{ consoleport }}?{% endblock %} {% block message %} -

Are you sure you want to disconnect this console port from {{ consoleport.cs_port.device }} {{ consoleport.cs_port }}?

+

Are you sure you want to disconnect this console port from {{ consoleport.connected_endpoint.device }} {{ consoleport.connected_endpoint }}?

{% endblock %} diff --git a/netbox/templates/dcim/consoleserverport_disconnect.html b/netbox/templates/dcim/consoleserverport_disconnect.html index 5c0594464..8404f9e94 100644 --- a/netbox/templates/dcim/consoleserverport_disconnect.html +++ b/netbox/templates/dcim/consoleserverport_disconnect.html @@ -4,5 +4,5 @@ {% block title %}Disconnect {{ consoleserverport.device }} {{ consoleserverport }}?{% endblock %} {% block message %} -

Are you sure you want to disconnect {{ consoleserverport.connected_console.device }} {{ consoleserverport.connected_console }} from this port?

+

Are you sure you want to disconnect {{ consoleserverport.connected_endpoint.device }} {{ consoleserverport.connected_endpoint }} from this port?

{% endblock %} diff --git a/netbox/templates/dcim/device.html b/netbox/templates/dcim/device.html index 50f8ebbb5..194b6ab64 100644 --- a/netbox/templates/dcim/device.html +++ b/netbox/templates/dcim/device.html @@ -573,7 +573,7 @@ {% endif %} {% endif %} - {% if cs_ports or device.device_type.is_console_server %} + {% if consoleserverports or device.device_type.is_console_server %} {% if perms.dcim.delete_consoleserverport %}
{% csrf_token %} @@ -594,7 +594,7 @@ - {% for csp in cs_ports %} + {% for csp in consoleserverports %} {% include 'dcim/inc/consoleserverport.html' %} {% empty %} @@ -604,7 +604,7 @@
- {% render_field form.power_outlet %} + {% render_field form.connected_endpoint %} {% render_field form.connection_status %} diff --git a/netbox/templates/dcim/powerport_disconnect.html b/netbox/templates/dcim/powerport_disconnect.html index f98694d9f..6953b1e2f 100644 --- a/netbox/templates/dcim/powerport_disconnect.html +++ b/netbox/templates/dcim/powerport_disconnect.html @@ -4,5 +4,5 @@ {% block title %}Disconnect power port {{ powerport }}?{% endblock %} {% block message %} -

Are you sure you want to disconnect this power port from {{ powerport.power_outlet.device }} {{ powerport.power_outlet }}?

+

Are you sure you want to disconnect this power port from {{ powerport.connected_endpoint.device }} {{ powerport.connected_endpoint }}?

{% endblock %} From f30367e09420b1c3d49c2c01162db465cd44ad92 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 24 Oct 2018 13:59:44 -0400 Subject: [PATCH 017/191] Deprecated the InterfaceConnection model --- netbox/circuits/forms.py | 4 +- netbox/dcim/api/serializers.py | 55 ++--- netbox/dcim/api/urls.py | 2 +- netbox/dcim/api/views.py | 18 +- netbox/dcim/filters.py | 17 +- netbox/dcim/fixtures/dcim.json | 153 ------------- netbox/dcim/forms.py | 174 +------------- netbox/dcim/migrations/0066_cables.py | 49 ++++ netbox/dcim/models.py | 159 ++----------- netbox/dcim/tables.py | 41 ++-- netbox/dcim/tests/test_api.py | 214 ++++-------------- netbox/dcim/urls.py | 11 +- netbox/dcim/views.py | 139 ++---------- netbox/extras/constants.py | 2 +- netbox/extras/models.py | 15 +- netbox/netbox/views.py | 22 +- .../dcim/console_connections_list.html | 3 - netbox/templates/dcim/device.html | 2 +- netbox/templates/dcim/inc/interface.html | 2 +- .../dcim/interface_connections_list.html | 3 - .../dcim/power_connections_list.html | 3 - netbox/templates/inc/nav_menu.html | 15 -- 22 files changed, 219 insertions(+), 884 deletions(-) diff --git a/netbox/circuits/forms.py b/netbox/circuits/forms.py index b6a8f0efc..b5883e6ec 100644 --- a/netbox/circuits/forms.py +++ b/netbox/circuits/forms.py @@ -237,9 +237,7 @@ class CircuitTerminationForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm ) ) interface = ChainedModelChoiceField( - queryset=Interface.objects.connectable().select_related( - 'circuit_termination', 'connected_as_a', 'connected_as_b' - ), + queryset=Interface.objects.connectable().select_related('circuit_termination'), chains=( ('device', 'device'), ), diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index b2b77b78c..6e113d394 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -6,10 +6,9 @@ from circuits.models import Circuit, CircuitTermination from dcim.constants import * from dcim.models import ( ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, - DeviceBayTemplate, DeviceType, DeviceRole, FrontPanelPort, FrontPanelPortTemplate, Interface, InterfaceConnection, - InterfaceTemplate, Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, - PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPanelPort, RearPanelPortTemplate, Region, Site, - VirtualChassis, + DeviceBayTemplate, DeviceType, DeviceRole, FrontPanelPort, FrontPanelPortTemplate, Interface, InterfaceTemplate, + Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, + RackGroup, RackReservation, RackRole, RearPanelPort, RearPanelPortTemplate, Region, Site, VirtualChassis, ) from extras.api.customfields import CustomFieldModelSerializer from ipam.models import IPAddress, VLAN @@ -614,7 +613,7 @@ class IsConnectedMixin(object): """ Return True if the interface has a connected interface or circuit. """ - if obj.connection: + if obj.connected_endpoint: return True if hasattr(obj, 'circuit_termination') and obj.circuit_termination is not None: return True @@ -662,8 +661,8 @@ class InterfaceSerializer(TaggitSerializer, IsConnectedMixin, ValidatedModelSeri device = NestedDeviceSerializer() form_factor = ChoiceField(choices=IFACE_FF_CHOICES, required=False) lag = NestedInterfaceSerializer(required=False, allow_null=True) + connected_endpoint = NestedInterfaceSerializer(required=False, allow_null=True) is_connected = serializers.SerializerMethodField(read_only=True) - interface_connection = serializers.SerializerMethodField(read_only=True) circuit_termination = InterfaceCircuitTerminationSerializer(read_only=True) mode = ChoiceField(choices=IFACE_MODE_CHOICES, required=False, allow_null=True) untagged_vlan = InterfaceVLANSerializer(required=False, allow_null=True) @@ -679,7 +678,7 @@ class InterfaceSerializer(TaggitSerializer, IsConnectedMixin, ValidatedModelSeri model = Interface fields = [ 'id', 'device', 'name', 'form_factor', 'enabled', 'lag', 'mtu', 'mac_address', 'mgmt_only', 'description', - 'is_connected', 'interface_connection', 'circuit_termination', 'mode', 'untagged_vlan', 'tagged_vlans', + 'is_connected', 'connected_endpoint', 'circuit_termination', 'mode', 'untagged_vlan', 'tagged_vlans', 'tags', ] @@ -702,15 +701,6 @@ class InterfaceSerializer(TaggitSerializer, IsConnectedMixin, ValidatedModelSeri return super(InterfaceSerializer, self).validate(data) - def get_interface_connection(self, obj): - if obj.connection: - context = { - 'request': self.context['request'], - 'interface': obj.connected_interface, - } - return ContextualInterfaceConnectionSerializer(obj.connection, context=context).data - return None - # # Rear panel ports @@ -804,36 +794,17 @@ class InventoryItemSerializer(TaggitSerializer, ValidatedModelSerializer): # class InterfaceConnectionSerializer(ValidatedModelSerializer): - interface_a = NestedInterfaceSerializer() - interface_b = NestedInterfaceSerializer() + interface_a = serializers.SerializerMethodField() + interface_b = NestedInterfaceSerializer(source='connected_endpoint') connection_status = ChoiceField(choices=CONNECTION_STATUS_CHOICES, required=False) class Meta: - model = InterfaceConnection - fields = ['id', 'interface_a', 'interface_b', 'connection_status'] + model = Interface + fields = ['interface_a', 'interface_b', 'connection_status'] - -class NestedInterfaceConnectionSerializer(WritableNestedSerializer): - url = serializers.HyperlinkedIdentityField(view_name='dcim-api:interfaceconnection-detail') - - class Meta: - model = InterfaceConnection - fields = ['id', 'url', 'connection_status'] - - -class ContextualInterfaceConnectionSerializer(serializers.ModelSerializer): - """ - A read-only representation of an InterfaceConnection from the perspective of either of its two connected Interfaces. - """ - interface = serializers.SerializerMethodField(read_only=True) - connection_status = ChoiceField(choices=CONNECTION_STATUS_CHOICES, read_only=True) - - class Meta: - model = InterfaceConnection - fields = ['id', 'interface', 'connection_status'] - - def get_interface(self, obj): - return NestedInterfaceSerializer(self.context['interface'], context=self.context).data + def get_interface_a(self, obj): + context = {'request': self.context['request']} + return NestedInterfaceSerializer(instance=obj, context=context).data # diff --git a/netbox/dcim/api/urls.py b/netbox/dcim/api/urls.py index 0e6a5d344..4ddbaf9a8 100644 --- a/netbox/dcim/api/urls.py +++ b/netbox/dcim/api/urls.py @@ -60,7 +60,7 @@ router.register(r'inventory-items', views.InventoryItemViewSet) # Connections router.register(r'console-connections', views.ConsoleConnectionViewSet, base_name='consoleconnections') router.register(r'power-connections', views.PowerConnectionViewSet, base_name='powerconnections') -router.register(r'interface-connections', views.InterfaceConnectionViewSet) +router.register(r'interface-connections', views.InterfaceConnectionViewSet, base_name='interfaceconnections') # Virtual chassis router.register(r'virtual-chassis', views.VirtualChassisViewSet) diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index db7149d18..6128d3411 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -1,6 +1,7 @@ from collections import OrderedDict from django.conf import settings +from django.db.models import F from django.http import HttpResponseForbidden from django.shortcuts import get_object_or_404 from drf_yasg import openapi @@ -14,10 +15,9 @@ from rest_framework.viewsets import GenericViewSet, ViewSet from dcim import filters from dcim.models import ( ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, - DeviceBayTemplate, DeviceRole, DeviceType, FrontPanelPort, FrontPanelPortTemplate, Interface, InterfaceConnection, - InterfaceTemplate, Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, - PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPanelPort, RearPanelPortTemplate, Region, Site, - VirtualChassis, + DeviceBayTemplate, DeviceRole, DeviceType, FrontPanelPort, FrontPanelPortTemplate, Interface, InterfaceTemplate, + Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, + RackGroup, RackReservation, RackRole, RearPanelPort, RearPanelPortTemplate, Region, Site, VirtualChassis, ) from extras.api.serializers import RenderedGraphSerializer from extras.api.views import CustomFieldModelViewSet @@ -35,8 +35,7 @@ class DCIMFieldChoicesViewSet(FieldChoicesViewSet): fields = ( (Device, ['face', 'status']), (ConsolePort, ['connection_status']), - (Interface, ['form_factor', 'mode']), - (InterfaceConnection, ['connection_status']), + (Interface, ['connection_status', 'form_factor', 'mode']), (InterfaceTemplate, ['form_factor']), (PowerPort, ['connection_status']), (Rack, ['type', 'width']), @@ -419,7 +418,12 @@ class PowerConnectionViewSet(ListModelMixin, GenericViewSet): class InterfaceConnectionViewSet(ModelViewSet): - queryset = InterfaceConnection.objects.select_related('interface_a__device', 'interface_b__device') + queryset = Interface.objects.select_related( + 'device', 'connected_endpoint__device' + ).filter( + connected_endpoint__isnull=False, + pk__lt=F('connected_endpoint') + ) serializer_class = serializers.InterfaceConnectionSerializer filter_class = filters.InterfaceConnectionFilter diff --git a/netbox/dcim/filters.py b/netbox/dcim/filters.py index da2055506..93d46bf01 100644 --- a/netbox/dcim/filters.py +++ b/netbox/dcim/filters.py @@ -14,10 +14,9 @@ from .constants import ( ) from .models import ( ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, - DeviceBayTemplate, DeviceRole, DeviceType, FrontPanelPort, FrontPanelPortTemplate, Interface, InterfaceConnection, - InterfaceTemplate, InventoryItem, Manufacturer, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, - PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPanelPort, RearPanelPortTemplate, Region, Site, - VirtualChassis, + DeviceBayTemplate, DeviceRole, DeviceType, FrontPanelPort, FrontPanelPortTemplate, Interface, InterfaceTemplate, + InventoryItem, Manufacturer, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, + RackGroup, RackReservation, RackRole, RearPanelPort, RearPanelPortTemplate, Region, Site, VirtualChassis, ) @@ -853,21 +852,21 @@ class InterfaceConnectionFilter(django_filters.FilterSet): ) class Meta: - model = InterfaceConnection + model = Interface fields = ['connection_status'] def filter_site(self, queryset, name, value): if not value.strip(): return queryset return queryset.filter( - Q(interface_a__device__site__slug=value) | - Q(interface_b__device__site__slug=value) + Q(device__site__slug=value) | + Q(connected_endpoint__device__site__slug=value) ) def filter_device(self, queryset, name, value): if not value.strip(): return queryset return queryset.filter( - Q(interface_a__device__name__icontains=value) | - Q(interface_b__device__name__icontains=value) + Q(device__name__icontains=value) | + Q(connected_endpoint__device__name__icontains=value) ) diff --git a/netbox/dcim/fixtures/dcim.json b/netbox/dcim/fixtures/dcim.json index 6c3e4b7df..9ebe9d4c7 100644 --- a/netbox/dcim/fixtures/dcim.json +++ b/netbox/dcim/fixtures/dcim.json @@ -5746,158 +5746,5 @@ "mgmt_only": true, "description": "" } -}, -{ - "model": "dcim.interfaceconnection", - "pk": 3, - "fields": { - "interface_a": 99, - "interface_b": 15, - "connection_status": true - } -}, -{ - "model": "dcim.interfaceconnection", - "pk": 4, - "fields": { - "interface_a": 100, - "interface_b": 153, - "connection_status": true - } -}, -{ - "model": "dcim.interfaceconnection", - "pk": 5, - "fields": { - "interface_a": 46, - "interface_b": 14, - "connection_status": true - } -}, -{ - "model": "dcim.interfaceconnection", - "pk": 6, - "fields": { - "interface_a": 47, - "interface_b": 152, - "connection_status": true - } -}, -{ - "model": "dcim.interfaceconnection", - "pk": 7, - "fields": { - "interface_a": 91, - "interface_b": 144, - "connection_status": true - } -}, -{ - "model": "dcim.interfaceconnection", - "pk": 8, - "fields": { - "interface_a": 92, - "interface_b": 145, - "connection_status": true - } -}, -{ - "model": "dcim.interfaceconnection", - "pk": 16, - "fields": { - "interface_a": 189, - "interface_b": 37, - "connection_status": true - } -}, -{ - "model": "dcim.interfaceconnection", - "pk": 17, - "fields": { - "interface_a": 192, - "interface_b": 175, - "connection_status": true - } -}, -{ - "model": "dcim.interfaceconnection", - "pk": 18, - "fields": { - "interface_a": 195, - "interface_b": 41, - "connection_status": true - } -}, -{ - "model": "dcim.interfaceconnection", - "pk": 19, - "fields": { - "interface_a": 198, - "interface_b": 179, - "connection_status": true - } -}, -{ - "model": "dcim.interfaceconnection", - "pk": 20, - "fields": { - "interface_a": 191, - "interface_b": 197, - "connection_status": true - } -}, -{ - "model": "dcim.interfaceconnection", - "pk": 21, - "fields": { - "interface_a": 194, - "interface_b": 200, - "connection_status": true - } -}, -{ - "model": "dcim.interfaceconnection", - "pk": 22, - "fields": { - "interface_a": 9, - "interface_b": 218, - "connection_status": true - } -}, -{ - "model": "dcim.interfaceconnection", - "pk": 23, - "fields": { - "interface_a": 8, - "interface_b": 206, - "connection_status": true - } -}, -{ - "model": "dcim.interfaceconnection", - "pk": 24, - "fields": { - "interface_a": 7, - "interface_b": 212, - "connection_status": true - } -}, -{ - "model": "dcim.interfaceconnection", - "pk": 25, - "fields": { - "interface_a": 217, - "interface_b": 205, - "connection_status": true - } -}, -{ - "model": "dcim.interfaceconnection", - "pk": 26, - "fields": { - "interface_a": 216, - "interface_b": 211, - "connection_status": true - } } ] diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 762fe7c4c..fd31bb1bf 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -26,10 +26,9 @@ from virtualization.models import Cluster from .constants import * from .models import ( Cable, DeviceBay, DeviceBayTemplate, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, - Device, DeviceRole, DeviceType, FrontPanelPort, FrontPanelPortTemplate, Interface, InterfaceConnection, - InterfaceTemplate, Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, - PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPanelPort, RearPanelPortTemplate, Region, Site, - VirtualChassis, + Device, DeviceRole, DeviceType, FrontPanelPort, FrontPanelPortTemplate, Interface, InterfaceTemplate, Manufacturer, + InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, + RackReservation, RackRole, RearPanelPort, RearPanelPortTemplate, Region, Site, VirtualChassis, ) DEVICE_BY_PK_RE = r'{\d+\}' @@ -2017,173 +2016,6 @@ class InterfaceBulkDisconnectForm(ConfirmationForm): pk = forms.ModelMultipleChoiceField(queryset=Interface.objects.all(), widget=forms.MultipleHiddenInput) -# -# Interface connections -# - -class InterfaceConnectionForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm): - interface_a = forms.ChoiceField( - choices=[], - widget=SelectWithDisabled, - label='Interface' - ) - site_b = forms.ModelChoiceField( - queryset=Site.objects.all(), - label='Site', - required=False, - widget=forms.Select( - attrs={'filter-for': 'rack_b'} - ) - ) - rack_b = ChainedModelChoiceField( - queryset=Rack.objects.all(), - chains=( - ('site', 'site_b'), - ), - label='Rack', - required=False, - widget=APISelect( - api_url='/api/dcim/racks/?site_id={{site_b}}', - attrs={'filter-for': 'device_b', 'nullable': 'true'} - ) - ) - device_b = ChainedModelChoiceField( - queryset=Device.objects.all(), - chains=( - ('site', 'site_b'), - ('rack', 'rack_b'), - ), - label='Device', - required=False, - widget=APISelect( - api_url='/api/dcim/devices/?site_id={{site_b}}&rack_id={{rack_b}}', - display_field='display_name', - attrs={'filter-for': 'interface_b'} - ) - ) - livesearch = forms.CharField( - required=False, - label='Device', - widget=Livesearch( - query_key='q', - query_url='dcim-api:device-list', - field_to_update='device_b' - ) - ) - interface_b = ChainedModelChoiceField( - queryset=Interface.objects.connectable().select_related( - 'circuit_termination', 'connected_as_a', 'connected_as_b' - ), - chains=( - ('device', 'device_b'), - ), - label='Interface', - widget=APISelect( - api_url='/api/dcim/interfaces/?device_id={{device_b}}&type=physical', - disabled_indicator='is_connected' - ) - ) - - class Meta: - model = InterfaceConnection - fields = ['interface_a', 'site_b', 'rack_b', 'device_b', 'interface_b', 'livesearch', 'connection_status'] - - def __init__(self, device_a, *args, **kwargs): - - super(InterfaceConnectionForm, self).__init__(*args, **kwargs) - - # Initialize interface A choices - device_a_interfaces = device_a.vc_interfaces.connectable().order_naturally().select_related( - 'circuit_termination', 'connected_as_a', 'connected_as_b' - ) - self.fields['interface_a'].choices = [ - (iface.id, {'label': iface.name, 'disabled': iface.is_connected}) for iface in device_a_interfaces - ] - - # Mark connected interfaces as disabled - if self.data.get('device_b'): - self.fields['interface_b'].choices = [] - for iface in self.fields['interface_b'].queryset: - self.fields['interface_b'].choices.append( - (iface.id, {'label': iface.name, 'disabled': iface.is_connected}) - ) - - -class InterfaceConnectionCSVForm(forms.ModelForm): - device_a = FlexibleModelChoiceField( - queryset=Device.objects.all(), - to_field_name='name', - help_text='Name or ID of device A', - error_messages={'invalid_choice': 'Device A not found.'} - ) - interface_a = forms.CharField( - help_text='Name of interface A' - ) - device_b = FlexibleModelChoiceField( - queryset=Device.objects.all(), - to_field_name='name', - help_text='Name or ID of device B', - error_messages={'invalid_choice': 'Device B not found.'} - ) - interface_b = forms.CharField( - help_text='Name of interface B' - ) - connection_status = CSVChoiceField( - choices=CONNECTION_STATUS_CHOICES, - help_text='Connection status' - ) - - class Meta: - model = InterfaceConnection - fields = InterfaceConnection.csv_headers - - def clean_interface_a(self): - - interface_name = self.cleaned_data.get('interface_a') - if not interface_name: - return None - - try: - # Retrieve interface by name - interface = Interface.objects.get( - device=self.cleaned_data['device_a'], name=interface_name - ) - # Check for an existing connection to this interface - if InterfaceConnection.objects.filter(Q(interface_a=interface) | Q(interface_b=interface)).count(): - raise forms.ValidationError("{} {} is already connected".format( - self.cleaned_data['device_a'], interface_name - )) - except Interface.DoesNotExist: - raise forms.ValidationError("Invalid interface ({} {})".format( - self.cleaned_data['device_a'], interface_name - )) - - return interface - - def clean_interface_b(self): - - interface_name = self.cleaned_data.get('interface_b') - if not interface_name: - return None - - try: - # Retrieve interface by name - interface = Interface.objects.get( - device=self.cleaned_data['device_b'], name=interface_name - ) - # Check for an existing connection to this interface - if InterfaceConnection.objects.filter(Q(interface_a=interface) | Q(interface_b=interface)).count(): - raise forms.ValidationError("{} {} is already connected".format( - self.cleaned_data['device_b'], interface_name - )) - except Interface.DoesNotExist: - raise forms.ValidationError("Invalid interface ({} {})".format( - self.cleaned_data['device_b'], interface_name - )) - - return interface - - # # Front panel ports # diff --git a/netbox/dcim/migrations/0066_cables.py b/netbox/dcim/migrations/0066_cables.py index 2413fa3a5..ce16c4a93 100644 --- a/netbox/dcim/migrations/0066_cables.py +++ b/netbox/dcim/migrations/0066_cables.py @@ -17,6 +17,7 @@ def console_connections_to_cables(apps, schema_editor): consoleserverport_type = ContentType.objects.get_for_model(ConsoleServerPort) # Create a new Cable instance from each console connection + print("\n Adding console connections... ", end='', flush=True) for consoleport in ConsolePort.objects.filter(connected_endpoint__isnull=False): c = Cable() # We have to assign GFK fields manually because we're inside a migration. @@ -27,6 +28,9 @@ def console_connections_to_cables(apps, schema_editor): c.connection_status = consoleport.connection_status c.save() + cable_count = Cable.objects.filter(endpoint_a_type=consoleport_type).count() + print("{} cables created".format(cable_count)) + def power_connections_to_cables(apps, schema_editor): """ @@ -42,6 +46,7 @@ def power_connections_to_cables(apps, schema_editor): poweroutlet_type = ContentType.objects.get_for_model(PowerOutlet) # Create a new Cable instance from each power connection + print(" Adding power connections... ", end='', flush=True) for powerport in PowerPort.objects.filter(connected_endpoint__isnull=False): c = Cable() # We have to assign GFK fields manually because we're inside a migration. @@ -52,6 +57,9 @@ def power_connections_to_cables(apps, schema_editor): c.connection_status = powerport.connection_status c.save() + cable_count = Cable.objects.filter(endpoint_a_type=powerport_type).count() + print("{} cables created".format(cable_count)) + def interface_connections_to_cables(apps, schema_editor): """ @@ -66,6 +74,7 @@ def interface_connections_to_cables(apps, schema_editor): interface_type = ContentType.objects.get_for_model(Interface) # Create a new Cable instance from each InterfaceConnection + print(" Adding interface connections... ", end='', flush=True) for conn in InterfaceConnection.objects.all(): c = Cable() # We have to assign GFK fields manually because we're inside a migration. @@ -76,8 +85,23 @@ def interface_connections_to_cables(apps, schema_editor): c.connection_status = conn.connection_status c.save() + # connected_endpoint and connection_status must be manually assigned + # since these are new fields on Interface + Interface.objects.filter(pk=conn.interface_a_id).update( + connected_endpoint=conn.interface_b_id, + connection_status=conn.connection_status + ) + Interface.objects.filter(pk=conn.interface_b_id).update( + connected_endpoint=conn.interface_a_id, + connection_status=conn.connection_status + ) + + cable_count = Cable.objects.filter(endpoint_a_type=interface_type).count() + print("{} cables created".format(cable_count)) + class Migration(migrations.Migration): + atomic = False dependencies = [ ('contenttypes', '0002_remove_content_type_name'), @@ -142,9 +166,34 @@ class Migration(migrations.Migration): field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='poweroutlets', to='dcim.Device'), ), + # Alter the Interface model + migrations.AddField( + model_name='interface', + name='connected_endpoint', + field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.Interface'), + ), + migrations.AddField( + model_name='interface', + name='connection_status', + field=models.NullBooleanField(default=True), + ), + # Copy console/power/interface connections as Cables migrations.RunPython(console_connections_to_cables), migrations.RunPython(power_connections_to_cables), migrations.RunPython(interface_connections_to_cables), + # Delete the InterfaceConnection model + migrations.RemoveField( + model_name='interfaceconnection', + name='interface_a', + ), + migrations.RemoveField( + model_name='interfaceconnection', + name='interface_b', + ), + migrations.DeleteModel( + name='InterfaceConnection', + ), + ] diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index 53459143c..d69718013 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -1826,7 +1826,7 @@ class PowerOutlet(ComponentModel): class Interface(ComponentModel): """ A network interface within a Device or VirtualMachine. A physical Interface can connect to exactly one other - Interface via the creation of an InterfaceConnection. + Interface. """ device = models.ForeignKey( to='Device', @@ -1842,6 +1842,20 @@ class Interface(ComponentModel): null=True, blank=True ) + name = models.CharField( + max_length=64 + ) + connected_endpoint = models.OneToOneField( + to='self', + on_delete=models.SET_NULL, + related_name='+', + blank=True, + null=True + ) + connection_status = models.NullBooleanField( + choices=CONNECTION_STATUS_CHOICES, + default=CONNECTION_STATUS_CONNECTED + ) lag = models.ForeignKey( to='self', on_delete=models.SET_NULL, @@ -1850,9 +1864,6 @@ class Interface(ComponentModel): blank=True, verbose_name='Parent LAG' ) - name = models.CharField( - max_length=64 - ) form_factor = models.PositiveSmallIntegerField( choices=IFACE_FF_CHOICES, default=IFACE_FF_10GE_SFP_PLUS @@ -2002,10 +2013,7 @@ class Interface(ComponentModel): changed_object=self, related_object=parent_obj, action=action, - object_data=serialize_object(self, extra={ - 'connected_interface': self.connected_interface.pk if self.connection else None, - 'connection_status': self.connection.connection_status if self.connection else None, - }) + object_data=serialize_object(self) ).save() @property @@ -2034,140 +2042,7 @@ class Interface(ComponentModel): return bool(self.circuit_termination) except ObjectDoesNotExist: pass - return bool(self.connection) - - @property - def connection(self): - try: - return self.connected_as_a - except ObjectDoesNotExist: - pass - try: - return self.connected_as_b - except ObjectDoesNotExist: - pass - return None - - @property - def connected_interface(self): - try: - if self.connected_as_a: - return self.connected_as_a.interface_b - except ObjectDoesNotExist: - pass - try: - if self.connected_as_b: - return self.connected_as_b.interface_a - except ObjectDoesNotExist: - pass - return None - - -class InterfaceConnection(models.Model): - """ - An InterfaceConnection represents a symmetrical, one-to-one connection between two Interfaces. There is no - significant difference between the interface_a and interface_b fields. - """ - interface_a = models.OneToOneField( - to='dcim.Interface', - on_delete=models.CASCADE, - related_name='connected_as_a' - ) - interface_b = models.OneToOneField( - to='dcim.Interface', - on_delete=models.CASCADE, - related_name='connected_as_b' - ) - connection_status = models.BooleanField( - choices=CONNECTION_STATUS_CHOICES, - default=CONNECTION_STATUS_CONNECTED, - verbose_name='Status' - ) - - csv_headers = ['device_a', 'interface_a', 'device_b', 'interface_b', 'connection_status'] - - def clean(self): - - # An interface cannot be connected to itself - if self.interface_a == self.interface_b: - raise ValidationError({ - 'interface_b': "Cannot connect an interface to itself." - }) - - # Only connectable interface types are permitted - if self.interface_a.form_factor in NONCONNECTABLE_IFACE_TYPES: - raise ValidationError({ - 'interface_a': '{} is not a connectable interface type.'.format( - self.interface_a.get_form_factor_display() - ) - }) - if self.interface_b.form_factor in NONCONNECTABLE_IFACE_TYPES: - raise ValidationError({ - 'interface_b': '{} is not a connectable interface type.'.format( - self.interface_b.get_form_factor_display() - ) - }) - - # Prevent the A side of one connection from being the B side of another - interface_a_connections = InterfaceConnection.objects.filter( - Q(interface_a=self.interface_a) | - Q(interface_b=self.interface_a) - ).exclude(pk=self.pk) - if interface_a_connections.exists(): - raise ValidationError({ - 'interface_a': "This interface is already connected." - }) - interface_b_connections = InterfaceConnection.objects.filter( - Q(interface_a=self.interface_b) | - Q(interface_b=self.interface_b) - ).exclude(pk=self.pk) - if interface_b_connections.exists(): - raise ValidationError({ - 'interface_b': "This interface is already connected." - }) - - def to_csv(self): - return ( - self.interface_a.device.identifier, - self.interface_a.name, - self.interface_b.device.identifier, - self.interface_b.name, - self.get_connection_status_display(), - ) - - def log_change(self, user, request_id, action): - """ - Create a new ObjectChange for each of the two affected Interfaces. - """ - interfaces = ( - (self.interface_a, self.interface_b), - (self.interface_b, self.interface_a), - ) - - for interface, peer_interface in interfaces: - if action == OBJECTCHANGE_ACTION_DELETE: - connection_data = { - 'connected_interface': None, - } - else: - connection_data = { - 'connected_interface': peer_interface.pk, - 'connection_status': self.connection_status - } - - try: - parent_obj = interface.parent - except ObjectDoesNotExist: - parent_obj = None - - ObjectChange( - user=user, - request_id=request_id, - changed_object=interface, - related_object=parent_obj, - action=OBJECTCHANGE_ACTION_UPDATE, - object_data=serialize_object(interface, extra=connection_data) - ).save() + return bool(self.connected_endpoint) # diff --git a/netbox/dcim/tables.py b/netbox/dcim/tables.py index 6174593af..d72497ecd 100644 --- a/netbox/dcim/tables.py +++ b/netbox/dcim/tables.py @@ -5,10 +5,9 @@ from tenancy.tables import COL_TENANT from utilities.tables import BaseTable, BooleanColumn, ToggleColumn from .models import ( ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, - DeviceBayTemplate, DeviceRole, DeviceType, FrontPanelPort, FrontPanelPortTemplate, Interface, InterfaceConnection, - InterfaceTemplate, InventoryItem, Manufacturer, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, - PowerPortTemplate, Rack, RackGroup, RackReservation, RearPanelPort, RearPanelPortTemplate, Region, Site, - VirtualChassis, + DeviceBayTemplate, DeviceRole, DeviceType, FrontPanelPort, FrontPanelPortTemplate, Interface, InterfaceTemplate, + InventoryItem, Manufacturer, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, + RackGroup, RackReservation, RearPanelPort, RearPanelPortTemplate, Region, Site, VirtualChassis, ) REGION_LINK = """ @@ -654,17 +653,33 @@ class PowerConnectionTable(BaseTable): class InterfaceConnectionTable(BaseTable): - device_a = tables.LinkColumn('dcim:device', accessor=Accessor('interface_a.device'), - args=[Accessor('interface_a.device.pk')], verbose_name='Device A') - interface_a = tables.LinkColumn('dcim:interface', accessor=Accessor('interface_a'), - args=[Accessor('interface_a.pk')], verbose_name='Interface A') - device_b = tables.LinkColumn('dcim:device', accessor=Accessor('interface_b.device'), - args=[Accessor('interface_b.device.pk')], verbose_name='Device B') - interface_b = tables.LinkColumn('dcim:interface', accessor=Accessor('interface_b'), - args=[Accessor('interface_b.pk')], verbose_name='Interface B') + device_a = tables.LinkColumn( + viewname='dcim:device', + accessor=Accessor('device'), + args=[Accessor('device.pk')], + verbose_name='Device A' + ) + interface_a = tables.LinkColumn( + viewname='dcim:interface', + accessor=Accessor('name'), + args=[Accessor('pk')], + verbose_name='Interface A' + ) + device_b = tables.LinkColumn( + viewname='dcim:device', + accessor=Accessor('connected_endpoint.device'), + args=[Accessor('connected_endpoint.device.pk')], + verbose_name='Device B' + ) + interface_b = tables.LinkColumn( + viewname='dcim:interface', + accessor=Accessor('connected_endpoint.name'), + args=[Accessor('connected_endpoint.pk')], + verbose_name='Interface B' + ) class Meta(BaseTable.Meta): - model = InterfaceConnection + model = Interface fields = ('device_a', 'interface_a', 'device_b', 'interface_b') diff --git a/netbox/dcim/tests/test_api.py b/netbox/dcim/tests/test_api.py index e6cc02537..774725725 100644 --- a/netbox/dcim/tests/test_api.py +++ b/netbox/dcim/tests/test_api.py @@ -8,7 +8,7 @@ from dcim.constants import ( ) from dcim.models import ( ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, - DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceConnection, InterfaceTemplate, Manufacturer, + DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceTemplate, Manufacturer, InventoryItem, Platform, PowerPort, PowerPortTemplate, PowerOutlet, PowerOutletTemplate, Rack, RackGroup, RackReservation, RackRole, Region, Site, VirtualChassis, ) @@ -2393,6 +2393,7 @@ class InterfaceTest(APITestCase): url = reverse('dcim-api:interface-detail', kwargs={'pk': self.interface1.pk}) response = self.client.get(url, **self.header) + self.assertHttpStatus(response, status.HTTP_200_OK) self.assertEqual(response.data['name'], self.interface1.name) def test_get_interface_graphs(self): @@ -2882,179 +2883,44 @@ class PowerConnectionTest(APITestCase): self.assertEqual(response.data['count'], 3) -class InterfaceConnectionTest(APITestCase): - - def setUp(self): - - super(InterfaceConnectionTest, self).setUp() - - 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' - ) - devicerole = DeviceRole.objects.create( - name='Test Device Role 1', slug='test-device-role-1', color='ff0000' - ) - self.device = Device.objects.create( - device_type=devicetype, device_role=devicerole, name='Test Device 1', site=site - ) - self.interface1 = Interface.objects.create(device=self.device, name='Test Interface 1') - self.interface2 = Interface.objects.create(device=self.device, name='Test Interface 2') - self.interface3 = Interface.objects.create(device=self.device, name='Test Interface 3') - self.interface4 = Interface.objects.create(device=self.device, name='Test Interface 4') - self.interface5 = Interface.objects.create(device=self.device, name='Test Interface 5') - self.interface6 = Interface.objects.create(device=self.device, name='Test Interface 6') - self.interface7 = Interface.objects.create(device=self.device, name='Test Interface 7') - self.interface8 = Interface.objects.create(device=self.device, name='Test Interface 8') - self.interface9 = Interface.objects.create(device=self.device, name='Test Interface 9') - self.interface10 = Interface.objects.create(device=self.device, name='Test Interface 10') - self.interface11 = Interface.objects.create(device=self.device, name='Test Interface 11') - self.interface12 = Interface.objects.create(device=self.device, name='Test Interface 12') - self.interfaceconnection1 = InterfaceConnection.objects.create( - interface_a=self.interface1, interface_b=self.interface2 - ) - self.interfaceconnection2 = InterfaceConnection.objects.create( - interface_a=self.interface3, interface_b=self.interface4 - ) - self.interfaceconnection3 = InterfaceConnection.objects.create( - interface_a=self.interface5, interface_b=self.interface6 - ) - - def test_get_interfaceconnection(self): - - url = reverse('dcim-api:interfaceconnection-detail', kwargs={'pk': self.interfaceconnection1.pk}) - response = self.client.get(url, **self.header) - - self.assertEqual(response.data['interface_a']['id'], self.interfaceconnection1.interface_a_id) - self.assertEqual(response.data['interface_b']['id'], self.interfaceconnection1.interface_b_id) - - def test_list_interfaceconnections(self): - - url = reverse('dcim-api:interfaceconnection-list') - response = self.client.get(url, **self.header) - - self.assertEqual(response.data['count'], 3) - - def test_list_interfaceconnections_brief(self): - - url = reverse('dcim-api:interfaceconnection-list') - response = self.client.get('{}?brief=1'.format(url), **self.header) - - self.assertEqual( - sorted(response.data['results'][0]), - ['connection_status', 'id', 'url'] - ) - - def test_create_interfaceconnection(self): - - data = { - 'interface_a': self.interface7.pk, - 'interface_b': self.interface8.pk, - } - - url = reverse('dcim-api:interfaceconnection-list') - response = self.client.post(url, data, format='json', **self.header) - - self.assertHttpStatus(response, status.HTTP_201_CREATED) - self.assertEqual(InterfaceConnection.objects.count(), 4) - interfaceconnection4 = InterfaceConnection.objects.get(pk=response.data['id']) - self.assertEqual(interfaceconnection4.interface_a_id, data['interface_a']) - self.assertEqual(interfaceconnection4.interface_b_id, data['interface_b']) - - def test_create_interfaceconnection_bulk(self): - - data = [ - { - 'interface_a': self.interface7.pk, - 'interface_b': self.interface8.pk, - }, - { - 'interface_a': self.interface9.pk, - 'interface_b': self.interface10.pk, - }, - { - 'interface_a': self.interface11.pk, - 'interface_b': self.interface12.pk, - }, - ] - - url = reverse('dcim-api:interfaceconnection-list') - response = self.client.post(url, data, format='json', **self.header) - - self.assertHttpStatus(response, status.HTTP_201_CREATED) - self.assertEqual(InterfaceConnection.objects.count(), 6) - for i in range(0, 3): - self.assertEqual(response.data[i]['interface_a']['id'], data[i]['interface_a']) - self.assertEqual(response.data[i]['interface_b']['id'], data[i]['interface_b']) - - def test_update_interfaceconnection(self): - - new_connection_status = not self.interfaceconnection1.connection_status - - data = { - 'interface_a': self.interface7.pk, - 'interface_b': self.interface8.pk, - 'connection_status': new_connection_status, - } - - url = reverse('dcim-api:interfaceconnection-detail', kwargs={'pk': self.interfaceconnection1.pk}) - response = self.client.put(url, data, format='json', **self.header) - - self.assertHttpStatus(response, status.HTTP_200_OK) - self.assertEqual(InterfaceConnection.objects.count(), 3) - interfaceconnection1 = InterfaceConnection.objects.get(pk=response.data['id']) - self.assertEqual(interfaceconnection1.interface_a_id, data['interface_a']) - self.assertEqual(interfaceconnection1.interface_b_id, data['interface_b']) - self.assertEqual(interfaceconnection1.connection_status, data['connection_status']) - - def test_delete_interfaceconnection(self): - - url = reverse('dcim-api:interfaceconnection-detail', kwargs={'pk': self.interfaceconnection1.pk}) - response = self.client.delete(url, **self.header) - - self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT) - self.assertEqual(InterfaceConnection.objects.count(), 2) - - -class ConnectedDeviceTest(APITestCase): - - def setUp(self): - - super(ConnectedDeviceTest, self).setUp() - - self.site1 = Site.objects.create(name='Test Site 1', slug='test-site-1') - self.site2 = Site.objects.create(name='Test Site 2', slug='test-site-2') - manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1') - self.devicetype1 = DeviceType.objects.create( - manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1' - ) - self.devicetype2 = DeviceType.objects.create( - manufacturer=manufacturer, model='Test Device Type 2', slug='test-device-type-2' - ) - self.devicerole1 = DeviceRole.objects.create( - name='Test Device Role 1', slug='test-device-role-1', color='ff0000' - ) - self.devicerole2 = DeviceRole.objects.create( - name='Test Device Role 2', slug='test-device-role-2', color='00ff00' - ) - self.device1 = Device.objects.create( - device_type=self.devicetype1, device_role=self.devicerole1, name='TestDevice1', site=self.site1 - ) - self.device2 = Device.objects.create( - device_type=self.devicetype1, device_role=self.devicerole1, name='TestDevice2', site=self.site1 - ) - self.interface1 = Interface.objects.create(device=self.device1, name='eth0') - self.interface2 = Interface.objects.create(device=self.device2, name='eth0') - InterfaceConnection.objects.create(interface_a=self.interface1, interface_b=self.interface2) - - def test_get_connected_device(self): - - url = reverse('dcim-api:connected-device-list') - response = self.client.get(url + '?peer-device=TestDevice2&peer-interface=eth0', **self.header) - - self.assertHttpStatus(response, status.HTTP_200_OK) - self.assertEqual(response.data['name'], self.device1.name) +# class ConnectedDeviceTest(APITestCase): +# +# def setUp(self): +# +# super(ConnectedDeviceTest, self).setUp() +# +# self.site1 = Site.objects.create(name='Test Site 1', slug='test-site-1') +# self.site2 = Site.objects.create(name='Test Site 2', slug='test-site-2') +# manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1') +# self.devicetype1 = DeviceType.objects.create( +# manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1' +# ) +# self.devicetype2 = DeviceType.objects.create( +# manufacturer=manufacturer, model='Test Device Type 2', slug='test-device-type-2' +# ) +# self.devicerole1 = DeviceRole.objects.create( +# name='Test Device Role 1', slug='test-device-role-1', color='ff0000' +# ) +# self.devicerole2 = DeviceRole.objects.create( +# name='Test Device Role 2', slug='test-device-role-2', color='00ff00' +# ) +# self.device1 = Device.objects.create( +# device_type=self.devicetype1, device_role=self.devicerole1, name='TestDevice1', site=self.site1 +# ) +# self.device2 = Device.objects.create( +# device_type=self.devicetype1, device_role=self.devicerole1, name='TestDevice2', site=self.site1 +# ) +# self.interface1 = Interface.objects.create(device=self.device1, name='eth0') +# self.interface2 = Interface.objects.create(device=self.device2, name='eth0') +# InterfaceConnection.objects.create(interface_a=self.interface1, interface_b=self.interface2) +# +# def test_get_connected_device(self): +# +# url = reverse('dcim-api:connected-device-list') +# response = self.client.get(url + '?peer-device=TestDevice2&peer-interface=eth0', **self.header) +# +# self.assertHttpStatus(response, status.HTTP_200_OK) +# self.assertEqual(response.data['name'], self.device1.name) class VirtualChassisTest(APITestCase): diff --git a/netbox/dcim/urls.py b/netbox/dcim/urls.py index 3e011b4c3..a7f52b80c 100644 --- a/netbox/dcim/urls.py +++ b/netbox/dcim/urls.py @@ -207,8 +207,9 @@ urlpatterns = [ url(r'^devices/(?P\d+)/interfaces/edit/$', views.InterfaceBulkEditView.as_view(), name='interface_bulk_edit'), url(r'^devices/(?P\d+)/interfaces/disconnect/$', views.InterfaceBulkDisconnectView.as_view(), name='interface_bulk_disconnect'), url(r'^devices/(?P\d+)/interfaces/delete/$', views.InterfaceBulkDeleteView.as_view(), name='interface_bulk_delete'), - url(r'^devices/(?P\d+)/interface-connections/add/$', views.InterfaceConnectionAddView.as_view(), name='interfaceconnection_add'), - url(r'^interface-connections/(?P\d+)/delete/$', views.InterfaceConnectionDeleteView.as_view(), name='interfaceconnection_delete'), + # url(r'^devices/(?P\d+)/interface-connections/add/$', views.InterfaceConnectionAddView.as_view(), name='interfaceconnection_add'), + url(r'^interfaces/(?P\d+)/connect/$', views.CableConnectView.as_view(), name='interface_connect', kwargs={'endpoint_a_type': Interface}), + # url(r'^interface-connections/(?P\d+)/delete/$', views.InterfaceConnectionDeleteView.as_view(), name='interfaceconnection_delete'), url(r'^interfaces/(?P\d+)/$', views.InterfaceView.as_view(), name='interface'), url(r'^interfaces/(?P\d+)/edit/$', views.InterfaceEditView.as_view(), name='interface_edit'), url(r'^interfaces/(?P\d+)/assign-vlans/$', views.InterfaceAssignVLANsView.as_view(), name='interface_assign_vlans'), @@ -253,11 +254,11 @@ urlpatterns = [ # Console/power/interface connections url(r'^console-connections/$', views.ConsoleConnectionsListView.as_view(), name='console_connections_list'), - url(r'^console-connections/import/$', views.ConsoleConnectionsBulkImportView.as_view(), name='console_connections_import'), + # url(r'^console-connections/import/$', views.ConsoleConnectionsBulkImportView.as_view(), name='console_connections_import'), url(r'^power-connections/$', views.PowerConnectionsListView.as_view(), name='power_connections_list'), - url(r'^power-connections/import/$', views.PowerConnectionsBulkImportView.as_view(), name='power_connections_import'), + # url(r'^power-connections/import/$', views.PowerConnectionsBulkImportView.as_view(), name='power_connections_import'), url(r'^interface-connections/$', views.InterfaceConnectionsListView.as_view(), name='interface_connections_list'), - url(r'^interface-connections/import/$', views.InterfaceConnectionsBulkImportView.as_view(), name='interface_connections_import'), + # url(r'^interface-connections/import/$', views.InterfaceConnectionsBulkImportView.as_view(), name='interface_connections_import'), # Virtual chassis url(r'^virtual-chassis/$', views.VirtualChassisListView.as_view(), name='virtualchassis_list'), diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 5dcb70c0e..991ec2ac0 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -6,7 +6,7 @@ from django.contrib.auth.mixins import PermissionRequiredMixin from django.core.exceptions import ObjectDoesNotExist from django.core.paginator import EmptyPage, PageNotAnInteger from django.db import transaction -from django.db.models import Count, Q +from django.db.models import Count, F, Q from django.forms import modelformset_factory from django.http import Http404, HttpResponseRedirect from django.shortcuts import get_object_or_404, redirect, render @@ -33,10 +33,9 @@ from . import filters, forms, tables from .constants import CONNECTION_STATUS_CONNECTED from .models import ( Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, - DeviceBayTemplate, DeviceRole, DeviceType, FrontPanelPort, FrontPanelPortTemplate, Interface, InterfaceConnection, - InterfaceTemplate, Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, - PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPanelPort, RearPanelPortTemplate, Region, Site, - VirtualChassis, + DeviceBayTemplate, DeviceRole, DeviceType, FrontPanelPort, FrontPanelPortTemplate, Interface, InterfaceTemplate, + Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, + RackGroup, RackReservation, RackRole, RearPanelPort, RearPanelPortTemplate, Region, Site, VirtualChassis, ) @@ -905,8 +904,7 @@ class DeviceView(View): interfaces = device.vc_interfaces.order_naturally( device.device_type.interface_ordering ).select_related( - 'connected_as_a__interface_b__device', 'connected_as_b__interface_a__device', - 'circuit_termination__circuit' + 'connected_endpoint__device', 'circuit_termination__circuit' ).prefetch_related('ip_addresses') # Front panel ports @@ -999,7 +997,7 @@ class DeviceLLDPNeighborsView(PermissionRequiredMixin, View): interfaces = device.vc_interfaces.order_naturally( device.device_type.interface_ordering ).connectable().select_related( - 'connected_as_a', 'connected_as_b' + 'connected_endpoint__device' ) return render(request, 'dcim/device_lldp_neighbors.html', { @@ -1736,10 +1734,9 @@ class InterfaceBulkDisconnectView(PermissionRequiredMixin, BulkDisconnectView): form = forms.InterfaceBulkDisconnectForm def disconnect_objects(self, interfaces): - count, _ = InterfaceConnection.objects.filter( - Q(interface_a__in=interfaces) | Q(interface_b__in=interfaces) - ).delete() - return count + return Interface.objects.filter(connected_endpoint__in=interfaces).update( + connected_endpoint=None, connection_status=None + ) class InterfaceBulkEditView(PermissionRequiredMixin, BulkEditView): @@ -2016,115 +2013,6 @@ class DeviceBulkAddDeviceBayView(PermissionRequiredMixin, BulkComponentCreateVie default_return_url = 'dcim:device_list' -# -# Interface connections -# - -class InterfaceConnectionAddView(PermissionRequiredMixin, GetReturnURLMixin, View): - permission_required = 'dcim.add_interfaceconnection' - default_return_url = 'dcim:device_list' - - def get(self, request, pk): - - device = get_object_or_404(Device, pk=pk) - form = forms.InterfaceConnectionForm(device, initial={ - 'interface_a': request.GET.get('interface_a'), - 'site_b': request.GET.get('site_b'), - 'rack_b': request.GET.get('rack_b'), - 'device_b': request.GET.get('device_b'), - 'interface_b': request.GET.get('interface_b'), - }) - - return render(request, 'dcim/interfaceconnection_edit.html', { - 'device': device, - 'form': form, - 'return_url': device.get_absolute_url(), - }) - - def post(self, request, pk): - - device = get_object_or_404(Device, pk=pk) - form = forms.InterfaceConnectionForm(device, request.POST) - - if form.is_valid(): - - interfaceconnection = form.save() - msg = 'Connected {} {} to {} {}'.format( - interfaceconnection.interface_a.device.get_absolute_url(), - escape(interfaceconnection.interface_a.device), - escape(interfaceconnection.interface_a.name), - interfaceconnection.interface_b.device.get_absolute_url(), - escape(interfaceconnection.interface_b.device), - escape(interfaceconnection.interface_b.name), - ) - messages.success(request, mark_safe(msg)) - - if '_addanother' in request.POST: - base_url = reverse('dcim:interfaceconnection_add', kwargs={'pk': device.pk}) - device_b = interfaceconnection.interface_b.device - params = urlencode({ - 'rack_b': device_b.rack.pk if device_b.rack else '', - 'device_b': device_b.pk, - }) - return HttpResponseRedirect('{}?{}'.format(base_url, params)) - else: - return redirect('dcim:device', pk=device.pk) - - return render(request, 'dcim/interfaceconnection_edit.html', { - 'device': device, - 'form': form, - 'return_url': device.get_absolute_url(), - }) - - -class InterfaceConnectionDeleteView(PermissionRequiredMixin, GetReturnURLMixin, View): - permission_required = 'dcim.delete_interfaceconnection' - default_return_url = 'dcim:device_list' - - def get(self, request, pk): - - interfaceconnection = get_object_or_404(InterfaceConnection, pk=pk) - form = forms.ConfirmationForm() - - return render(request, 'dcim/interfaceconnection_delete.html', { - 'interfaceconnection': interfaceconnection, - 'form': form, - 'return_url': self.get_return_url(request, interfaceconnection), - }) - - def post(self, request, pk): - - interfaceconnection = get_object_or_404(InterfaceConnection, pk=pk) - form = forms.ConfirmationForm(request.POST) - - if form.is_valid(): - interfaceconnection.delete() - msg = 'Disconnected {} {} from {} {}'.format( - interfaceconnection.interface_a.device.get_absolute_url(), - escape(interfaceconnection.interface_a.device), - escape(interfaceconnection.interface_a.name), - interfaceconnection.interface_b.device.get_absolute_url(), - escape(interfaceconnection.interface_b.device), - escape(interfaceconnection.interface_b.name), - ) - messages.success(request, mark_safe(msg)) - - return redirect(self.get_return_url(request, interfaceconnection)) - - return render(request, 'dcim/interfaceconnection_delete.html', { - 'interfaceconnection': interfaceconnection, - 'form': form, - 'return_url': self.get_return_url(request, interfaceconnection), - }) - - -class InterfaceConnectionsBulkImportView(PermissionRequiredMixin, BulkImportView): - permission_required = 'dcim.change_interface' - model_form = forms.InterfaceConnectionCSVForm - table = tables.InterfaceConnectionTable - default_return_url = 'dcim:interface_connections_list' - - # # Connections # @@ -2158,10 +2046,11 @@ class PowerConnectionsListView(ObjectListView): class InterfaceConnectionsListView(ObjectListView): - queryset = InterfaceConnection.objects.select_related( - 'interface_a__device', 'interface_b__device' - ).order_by( - 'interface_a__device__name', 'interface_a__name' + queryset = Interface.objects.select_related( + 'connected_endpoint__device', + ).filter( + connected_endpoint__isnull=False, + pk__lt=F('connected_endpoint'), ) filter = filters.InterfaceConnectionFilter filter_form = forms.InterfaceConnectionFilterForm diff --git a/netbox/extras/constants.py b/netbox/extras/constants.py index 2397ece7b..5c7939e68 100644 --- a/netbox/extras/constants.py +++ b/netbox/extras/constants.py @@ -49,7 +49,7 @@ GRAPH_TYPE_CHOICES = ( EXPORTTEMPLATE_MODELS = [ 'provider', 'circuit', # Circuits 'site', 'region', 'rack', 'rackgroup', 'manufacturer', 'devicetype', 'device', # DCIM - 'consoleport', 'powerport', 'interfaceconnection', 'virtualchassis', # DCIM + 'consoleport', 'powerport', 'interface', 'virtualchassis', # DCIM 'aggregate', 'prefix', 'ipaddress', 'vlan', 'vrf', 'service', # IPAM 'secret', # Secrets 'tenant', # Tenancy diff --git a/netbox/extras/models.py b/netbox/extras/models.py index 8bc0a8726..abd9cf49d 100644 --- a/netbox/extras/models.py +++ b/netbox/extras/models.py @@ -504,15 +504,18 @@ class TopologyMap(models.Model): def add_network_connections(self, devices): from circuits.models import CircuitTermination - from dcim.models import InterfaceConnection + from dcim.models import Interface # Add all interface connections to the graph - connections = InterfaceConnection.objects.filter( - interface_a__device__in=devices, interface_b__device__in=devices + connected_interfaces = Interface.objects.select_related( + 'connected_endpoint__device' + ).filter( + Q(device__in=devices) | Q(connected_endpoint__device__in=devices), + connected_endpoint__isnull=False, ) - for c in connections: - style = 'solid' if c.connection_status == CONNECTION_STATUS_CONNECTED else 'dashed' - self.graph.edge(c.interface_a.device.name, c.interface_b.device.name, style=style) + for interface in connected_interfaces: + style = 'solid' if interface.connection_status == CONNECTION_STATUS_CONNECTED else 'dashed' + self.graph.edge(interface.device.name, interface.connected_endpoint.device.name, style=style) # Add all circuits to the graph for termination in CircuitTermination.objects.filter(term_side='A', interface__device__in=devices): diff --git a/netbox/netbox/views.py b/netbox/netbox/views.py index e13a3df5a..b75debefe 100644 --- a/netbox/netbox/views.py +++ b/netbox/netbox/views.py @@ -1,6 +1,6 @@ from collections import OrderedDict -from django.db.models import Count +from django.db.models import Count, F from django.shortcuts import render from django.views.generic import View from rest_framework.response import Response @@ -14,8 +14,7 @@ from dcim.filters import ( DeviceFilter, DeviceTypeFilter, RackFilter, RackGroupFilter, SiteFilter, VirtualChassisFilter ) from dcim.models import ( - ConsolePort, Device, DeviceType, InterfaceConnection, PowerPort, Rack, RackGroup, Site, - VirtualChassis + ConsolePort, Device, DeviceType, Interface, PowerPort, Rack, RackGroup, Site, VirtualChassis ) from dcim.tables import ( DeviceDetailTable, DeviceTypeTable, RackTable, RackGroupTable, SiteTable, VirtualChassisTable @@ -157,6 +156,17 @@ class HomeView(View): def get(self, request): + connected_consoleports = ConsolePort.objects.filter( + connected_endpoint__isnull=False + ) + connected_powerports = PowerPort.objects.filter( + connected_endpoint__isnull=False + ) + connected_interfaces = Interface.objects.filter( + connected_endpoint__isnull=False, + pk__lt=F('connected_endpoint') + ) + stats = { # Organization @@ -166,9 +176,9 @@ class HomeView(View): # DCIM 'rack_count': Rack.objects.count(), 'device_count': Device.objects.count(), - 'interface_connections_count': InterfaceConnection.objects.count(), - 'console_connections_count': ConsolePort.objects.filter(connected_endpoint__isnull=False).count(), - 'power_connections_count': PowerPort.objects.filter(connected_endpoint__isnull=False).count(), + 'interface_connections_count': connected_interfaces.count(), + 'console_connections_count': connected_consoleports.count(), + 'power_connections_count': connected_powerports.count(), # IPAM 'vrf_count': VRF.objects.count(), diff --git a/netbox/templates/dcim/console_connections_list.html b/netbox/templates/dcim/console_connections_list.html index 89eb0822d..cf47d426c 100644 --- a/netbox/templates/dcim/console_connections_list.html +++ b/netbox/templates/dcim/console_connections_list.html @@ -3,9 +3,6 @@ {% block content %}
- {% if perms.dcim.change_consoleport %} - {% import_button 'dcim:console_connections_import' %} - {% endif %} {% export_button content_type %}

{% block title %}Console Connections{% endblock %}

diff --git a/netbox/templates/dcim/device.html b/netbox/templates/dcim/device.html index 2d838704c..165a86595 100644 --- a/netbox/templates/dcim/device.html +++ b/netbox/templates/dcim/device.html @@ -549,7 +549,7 @@ Edit {% endif %} - {% if interfaces and perms.dcim.delete_interfaceconnection %} + {% if interfaces and perms.dcim.change_interface %} diff --git a/netbox/templates/dcim/inc/interface.html b/netbox/templates/dcim/inc/interface.html index 229f6f2eb..723f14760 100644 --- a/netbox/templates/dcim/inc/interface.html +++ b/netbox/templates/dcim/inc/interface.html @@ -106,7 +106,7 @@ {% else %} - + {% endif %} diff --git a/netbox/templates/dcim/interface_connections_list.html b/netbox/templates/dcim/interface_connections_list.html index 950eb2f0b..e81eb55ee 100644 --- a/netbox/templates/dcim/interface_connections_list.html +++ b/netbox/templates/dcim/interface_connections_list.html @@ -3,9 +3,6 @@ {% block content %}
- {% if perms.dcim.add_interfaceconnection %} - {% import_button 'dcim:interface_connections_import' %} - {% endif %} {% export_button content_type %}

{% block title %}Interface Connections{% endblock %}

diff --git a/netbox/templates/dcim/power_connections_list.html b/netbox/templates/dcim/power_connections_list.html index 4e351eb6a..a41d571fb 100644 --- a/netbox/templates/dcim/power_connections_list.html +++ b/netbox/templates/dcim/power_connections_list.html @@ -3,9 +3,6 @@ {% block content %}
- {% if perms.dcim.change_powerport %} - {% import_button 'dcim:power_connections_import' %} - {% endif %} {% export_button content_type %}

{% block title %}Power Connections{% endblock %}

diff --git a/netbox/templates/inc/nav_menu.html b/netbox/templates/inc/nav_menu.html index aeddf1969..08ecb926d 100644 --- a/netbox/templates/inc/nav_menu.html +++ b/netbox/templates/inc/nav_menu.html @@ -180,27 +180,12 @@
  • - {% if perms.dcim.change_consoleport %} -
    - -
    - {% endif %} Console Connections
  • - {% if perms.dcim.change_powerport %} -
    - -
    - {% endif %} Power Connections
  • - {% if perms.dcim.add_interfaceconnection %} -
    - -
    - {% endif %} Interface Connections
  • From 9985f2cb82af6b37e70f5d06a04953201a33f3ab Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 24 Oct 2018 14:24:02 -0400 Subject: [PATCH 018/191] Added CableListView --- netbox/dcim/tables.py | 38 ++++++++++++++++- netbox/dcim/urls.py | 16 ++++---- netbox/dcim/views.py | 59 +++++++++++++++++---------- netbox/templates/dcim/cable_list.html | 17 ++++++++ netbox/templates/inc/nav_menu.html | 5 ++- 5 files changed, 105 insertions(+), 30 deletions(-) create mode 100644 netbox/templates/dcim/cable_list.html diff --git a/netbox/dcim/tables.py b/netbox/dcim/tables.py index d72497ecd..df7b6d89b 100644 --- a/netbox/dcim/tables.py +++ b/netbox/dcim/tables.py @@ -4,7 +4,7 @@ from django_tables2.utils import Accessor from tenancy.tables import COL_TENANT from utilities.tables import BaseTable, BooleanColumn, ToggleColumn from .models import ( - ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, + Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, DeviceBayTemplate, DeviceRole, DeviceType, FrontPanelPort, FrontPanelPortTemplate, Interface, InterfaceTemplate, InventoryItem, Manufacturer, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, RackReservation, RearPanelPort, RearPanelPortTemplate, Region, Site, VirtualChassis, @@ -616,6 +616,42 @@ class DeviceBayTable(BaseTable): fields = ('name',) +# +# Cables +# + +class CableTable(BaseTable): + device_a = tables.LinkColumn( + viewname='dcim:device', + accessor=Accessor('endpoint_a.device'), + args=[Accessor('endpoint_a.device.pk')], + verbose_name='Device A' + ) + termination_a = tables.Column( + accessor=Accessor('endpoint_a.name'), + verbose_name='Component' + ) + device_b = tables.LinkColumn( + viewname='dcim:device', + accessor=Accessor('endpoint_b.device'), + args=[Accessor('endpoint_b.device.pk')], + verbose_name='Device B' + ) + termination_b = tables.Column( + accessor=Accessor('endpoint_b.name'), + verbose_name='Component' + ) + # django-tables2 adds CSS `class="label"` which causes rendering issues + _label = tables.Column( + accessor=Accessor('label'), + verbose_name='Label' + ) + + class Meta(BaseTable.Meta): + model = Cable + fields = ('device_a', 'termination_a', 'device_b', 'termination_b', 'status', '_label', 'color') + + # # Device connections # diff --git a/netbox/dcim/urls.py b/netbox/dcim/urls.py index a7f52b80c..0a71c698f 100644 --- a/netbox/dcim/urls.py +++ b/netbox/dcim/urls.py @@ -162,7 +162,7 @@ urlpatterns = [ url(r'^devices/(?P\d+)/console-ports/add/$', views.ConsolePortCreateView.as_view(), name='consoleport_add'), url(r'^devices/(?P\d+)/console-ports/delete/$', views.ConsolePortBulkDeleteView.as_view(), name='consoleport_bulk_delete'), # url(r'^console-ports/(?P\d+)/connect/$', views.ConsolePortConnectView.as_view(), name='consoleport_connect'), - url(r'^console-ports/(?P\d+)/connect/$', views.CableConnectView.as_view(), name='consoleport_connect', kwargs={'endpoint_a_type': ConsolePort}), + url(r'^console-ports/(?P\d+)/connect/$', views.CableCreateView.as_view(), name='consoleport_connect', kwargs={'endpoint_a_type': ConsolePort}), url(r'^console-ports/(?P\d+)/disconnect/$', views.ConsolePortDisconnectView.as_view(), name='consoleport_disconnect'), url(r'^console-ports/(?P\d+)/edit/$', views.ConsolePortEditView.as_view(), name='consoleport_edit'), url(r'^console-ports/(?P\d+)/delete/$', views.ConsolePortDeleteView.as_view(), name='consoleport_delete'), @@ -173,7 +173,7 @@ urlpatterns = [ url(r'^devices/(?P\d+)/console-server-ports/disconnect/$', views.ConsoleServerPortBulkDisconnectView.as_view(), name='consoleserverport_bulk_disconnect'), url(r'^devices/(?P\d+)/console-server-ports/delete/$', views.ConsoleServerPortBulkDeleteView.as_view(), name='consoleserverport_bulk_delete'), # url(r'^console-server-ports/(?P\d+)/connect/$', views.ConsoleServerPortConnectView.as_view(), name='consoleserverport_connect'), - url(r'^console-server-ports/(?P\d+)/connect/$', views.CableConnectView.as_view(), name='consoleserverport_connect', kwargs={'endpoint_a_type': ConsoleServerPort}), + url(r'^console-server-ports/(?P\d+)/connect/$', views.CableCreateView.as_view(), name='consoleserverport_connect', kwargs={'endpoint_a_type': ConsoleServerPort}), url(r'^console-server-ports/(?P\d+)/disconnect/$', views.ConsoleServerPortDisconnectView.as_view(), name='consoleserverport_disconnect'), url(r'^console-server-ports/(?P\d+)/edit/$', views.ConsoleServerPortEditView.as_view(), name='consoleserverport_edit'), url(r'^console-server-ports/(?P\d+)/delete/$', views.ConsoleServerPortDeleteView.as_view(), name='consoleserverport_delete'), @@ -184,7 +184,7 @@ urlpatterns = [ url(r'^devices/(?P\d+)/power-ports/add/$', views.PowerPortCreateView.as_view(), name='powerport_add'), url(r'^devices/(?P\d+)/power-ports/delete/$', views.PowerPortBulkDeleteView.as_view(), name='powerport_bulk_delete'), # url(r'^power-ports/(?P\d+)/connect/$', views.PowerPortConnectView.as_view(), name='powerport_connect'), - url(r'^power-ports/(?P\d+)/connect/$', views.CableConnectView.as_view(), name='powerport_connect', kwargs={'endpoint_a_type': PowerPort}), + url(r'^power-ports/(?P\d+)/connect/$', views.CableCreateView.as_view(), name='powerport_connect', kwargs={'endpoint_a_type': PowerPort}), url(r'^power-ports/(?P\d+)/disconnect/$', views.PowerPortDisconnectView.as_view(), name='powerport_disconnect'), url(r'^power-ports/(?P\d+)/edit/$', views.PowerPortEditView.as_view(), name='powerport_edit'), url(r'^power-ports/(?P\d+)/delete/$', views.PowerPortDeleteView.as_view(), name='powerport_delete'), @@ -195,7 +195,7 @@ urlpatterns = [ url(r'^devices/(?P\d+)/power-outlets/disconnect/$', views.PowerOutletBulkDisconnectView.as_view(), name='poweroutlet_bulk_disconnect'), url(r'^devices/(?P\d+)/power-outlets/delete/$', views.PowerOutletBulkDeleteView.as_view(), name='poweroutlet_bulk_delete'), # url(r'^power-outlets/(?P\d+)/connect/$', views.PowerOutletConnectView.as_view(), name='poweroutlet_connect'), - url(r'^power-outlets/(?P\d+)/connect/$', views.CableConnectView.as_view(), name='poweroutlet_connect', kwargs={'endpoint_a_type': PowerOutlet}), + url(r'^power-outlets/(?P\d+)/connect/$', views.CableCreateView.as_view(), name='poweroutlet_connect', kwargs={'endpoint_a_type': PowerOutlet}), url(r'^power-outlets/(?P\d+)/disconnect/$', views.PowerOutletDisconnectView.as_view(), name='poweroutlet_disconnect'), url(r'^power-outlets/(?P\d+)/edit/$', views.PowerOutletEditView.as_view(), name='poweroutlet_edit'), url(r'^power-outlets/(?P\d+)/delete/$', views.PowerOutletDeleteView.as_view(), name='poweroutlet_delete'), @@ -207,9 +207,7 @@ urlpatterns = [ url(r'^devices/(?P\d+)/interfaces/edit/$', views.InterfaceBulkEditView.as_view(), name='interface_bulk_edit'), url(r'^devices/(?P\d+)/interfaces/disconnect/$', views.InterfaceBulkDisconnectView.as_view(), name='interface_bulk_disconnect'), url(r'^devices/(?P\d+)/interfaces/delete/$', views.InterfaceBulkDeleteView.as_view(), name='interface_bulk_delete'), - # url(r'^devices/(?P\d+)/interface-connections/add/$', views.InterfaceConnectionAddView.as_view(), name='interfaceconnection_add'), - url(r'^interfaces/(?P\d+)/connect/$', views.CableConnectView.as_view(), name='interface_connect', kwargs={'endpoint_a_type': Interface}), - # url(r'^interface-connections/(?P\d+)/delete/$', views.InterfaceConnectionDeleteView.as_view(), name='interfaceconnection_delete'), + url(r'^interfaces/(?P\d+)/connect/$', views.CableCreateView.as_view(), name='interface_connect', kwargs={'endpoint_a_type': Interface}), url(r'^interfaces/(?P\d+)/$', views.InterfaceView.as_view(), name='interface'), url(r'^interfaces/(?P\d+)/edit/$', views.InterfaceEditView.as_view(), name='interface_edit'), url(r'^interfaces/(?P\d+)/assign-vlans/$', views.InterfaceAssignVLANsView.as_view(), name='interface_assign_vlans'), @@ -252,6 +250,10 @@ urlpatterns = [ url(r'^inventory-items/(?P\d+)/delete/$', views.InventoryItemDeleteView.as_view(), name='inventoryitem_delete'), url(r'^devices/(?P\d+)/inventory-items/add/$', views.InventoryItemEditView.as_view(), name='inventoryitem_add'), + # Cables + url(r'^cables/$', views.CableListView.as_view(), name='cable_list'), + url(r'^cables/(?P\d+)/delete/$', views.CableDeleteView.as_view(), name='cable_delete'), + # Console/power/interface connections url(r'^console-connections/$', views.ConsoleConnectionsListView.as_view(), name='console_connections_list'), # url(r'^console-connections/import/$', views.ConsoleConnectionsBulkImportView.as_view(), name='console_connections_import'), diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 991ec2ac0..3246ca281 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -1,18 +1,14 @@ from operator import attrgetter -from django.apps import apps from django.contrib import messages from django.contrib.auth.mixins import PermissionRequiredMixin -from django.core.exceptions import ObjectDoesNotExist from django.core.paginator import EmptyPage, PageNotAnInteger from django.db import transaction -from django.db.models import Count, F, Q +from django.db.models import Count, F from django.forms import modelformset_factory -from django.http import Http404, HttpResponseRedirect from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse 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 @@ -2013,6 +2009,43 @@ class DeviceBulkAddDeviceBayView(PermissionRequiredMixin, BulkComponentCreateVie default_return_url = 'dcim:device_list' +# +# Cables +# + +class CableListView(ObjectListView): + queryset = Cable.objects.prefetch_related( + 'endpoint_a__device', 'endpoint_b__device' + ) + # filter = filters.CableFilter + # filter_form = forms.CableFilterForm + table = tables.CableTable + template_name = 'dcim/cable_list.html' + + +class CableCreateView(PermissionRequiredMixin, ObjectEditView): + permission_required = 'dcim.add_cable' + model = Cable + model_form = forms.CableForm + template_name = 'dcim/cable_connect.html' + + def alter_obj(self, obj, request, url_args, url_kwargs): + + # Retrieve endpoint A based on the given type and PK + endpoint_a_type = url_kwargs.get('endpoint_a_type') + endpoint_a_id = url_kwargs.get('endpoint_a_id') + obj.endpoint_a = endpoint_a_type.objects.get(pk=endpoint_a_id) + + return obj + + +class CableDeleteView(PermissionRequiredMixin, ObjectDeleteView): + permission_required = 'dcim.delete_cable' + model = Cable + default_return_url = 'dcim:cable_list' + + + # # Connections # @@ -2058,22 +2091,6 @@ class InterfaceConnectionsListView(ObjectListView): template_name = 'dcim/interface_connections_list.html' -class CableConnectView(PermissionRequiredMixin, ObjectEditView): - permission_required = 'dcim.add_cable' - model = Cable - model_form = forms.CableForm - template_name = 'dcim/cable_connect.html' - - def alter_obj(self, obj, request, url_args, url_kwargs): - - # Retrieve endpoint A based on the given type and PK - endpoint_a_type = url_kwargs.get('endpoint_a_type') - endpoint_a_id = url_kwargs.get('endpoint_a_id') - obj.endpoint_a = endpoint_a_type.objects.get(pk=endpoint_a_id) - - return obj - - # # Inventory items # diff --git a/netbox/templates/dcim/cable_list.html b/netbox/templates/dcim/cable_list.html new file mode 100644 index 000000000..b942986e3 --- /dev/null +++ b/netbox/templates/dcim/cable_list.html @@ -0,0 +1,17 @@ +{% extends '_base.html' %} +{% load buttons %} + +{% block content %} +
    + {% export_button content_type %} +
    +

    {% block title %}Cables{% endblock %}

    +
    +
    + {% include 'responsive_table.html' %} +
    +
    + {% include 'inc/search_panel.html' %} +
    +
    +{% endblock %} diff --git a/netbox/templates/inc/nav_menu.html b/netbox/templates/inc/nav_menu.html index 08ecb926d..ef6329564 100644 --- a/netbox/templates/inc/nav_menu.html +++ b/netbox/templates/inc/nav_menu.html @@ -113,7 +113,7 @@ -