From 6ec8ac7597d190671774dba5c9494058657cb8c5 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 31 Mar 2021 13:25:06 -0400 Subject: [PATCH 1/7] Fixes #6073: Permit users to manage their own REST API tokens without needing explicit permission --- docs/release-notes/version-2.10.md | 8 ++++++++ netbox/templates/users/api_tokens.html | 22 ++++++---------------- netbox/users/views.py | 14 +++++--------- 3 files changed, 19 insertions(+), 25 deletions(-) diff --git a/docs/release-notes/version-2.10.md b/docs/release-notes/version-2.10.md index 7031f7fb8..2b47faec0 100644 --- a/docs/release-notes/version-2.10.md +++ b/docs/release-notes/version-2.10.md @@ -1,5 +1,13 @@ # NetBox v2.10 +## v2.10.9 (FUTURE) + +### Bug Fixes + +* [#6073](https://github.com/netbox-community/netbox/issues/6073) - Permit users to manage their own REST API tokens without needing explicit permission + +--- + ## v2.10.8 (2021-03-26) ### Bug Fixes diff --git a/netbox/templates/users/api_tokens.html b/netbox/templates/users/api_tokens.html index 04e7cb23d..f14773293 100644 --- a/netbox/templates/users/api_tokens.html +++ b/netbox/templates/users/api_tokens.html @@ -11,12 +11,8 @@
Copy - {% if perms.users.change_token %} - Edit - {% endif %} - {% if perms.users.delete_token %} - Delete - {% endif %} + Edit + Delete
{{ token.key }} @@ -55,16 +51,10 @@ {% empty %}

You do not have any API tokens.

{% endfor %} - {% if perms.users.add_token %} - - - Add a token - - {% else %} - - {% endif %} + + + Add a token +
{% endblock %} diff --git a/netbox/users/views.py b/netbox/users/views.py index a6d28ecd2..cf7ed6430 100644 --- a/netbox/users/views.py +++ b/netbox/users/views.py @@ -6,7 +6,7 @@ from django.contrib.auth import login as auth_login, logout as auth_logout, upda from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.models import update_last_login from django.contrib.auth.signals import user_logged_in -from django.http import HttpResponseForbidden, HttpResponseRedirect +from django.http import HttpResponseRedirect from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse from django.utils.decorators import method_decorator @@ -282,13 +282,9 @@ class TokenEditView(LoginRequiredMixin, View): def get(self, request, pk=None): - if pk is not None: - if not request.user.has_perm('users.change_token'): - return HttpResponseForbidden() + if pk: token = get_object_or_404(Token.objects.filter(user=request.user), pk=pk) else: - if not request.user.has_perm('users.add_token'): - return HttpResponseForbidden() token = Token(user=request.user) form = TokenForm(instance=token) @@ -302,11 +298,11 @@ class TokenEditView(LoginRequiredMixin, View): def post(self, request, pk=None): - if pk is not None: + if pk: token = get_object_or_404(Token.objects.filter(user=request.user), pk=pk) form = TokenForm(request.POST, instance=token) else: - token = Token() + token = Token(user=request.user) form = TokenForm(request.POST) if form.is_valid(): @@ -314,7 +310,7 @@ class TokenEditView(LoginRequiredMixin, View): token.user = request.user token.save() - msg = "Modified token {}".format(token) if pk else "Created token {}".format(token) + msg = f"Modified token {token}" if pk else f"Created token {token}" messages.success(request, msg) if '_addanother' in request.POST: From b7309d5c695a798e2e611a2f650df9a871065053 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 31 Mar 2021 15:21:07 -0400 Subject: [PATCH 2/7] Closes #6054: Display NAPALM-enabled device tabs only when relevant --- docs/additional-features/napalm.md | 7 +++++++ docs/release-notes/version-2.10.md | 4 ++++ netbox/templates/dcim/device/base.html | 21 ++++++++++--------- .../dcim/inc/device_napalm_tabs.html | 15 ------------- 4 files changed, 22 insertions(+), 25 deletions(-) delete mode 100644 netbox/templates/dcim/inc/device_napalm_tabs.html diff --git a/docs/additional-features/napalm.md b/docs/additional-features/napalm.md index d2d69fc3c..957a5a214 100644 --- a/docs/additional-features/napalm.md +++ b/docs/additional-features/napalm.md @@ -2,6 +2,13 @@ NetBox supports integration with the [NAPALM automation](https://napalm-automation.net/) library. NAPALM allows NetBox to serve a proxy for operational data, fetching live data from network devices and returning it to a requester via its REST API. Note that NetBox does not store any NAPALM data locally. +The NetBox UI will display tabs for status, LLDP neighbors, and configuration under the device view if the following conditions are met: + +* Device status is "Active" +* A primary IP has been assigned to the device +* A platform with a NAPALM driver has been assigned +* The authenticated user has the `dcim.napalm_read_device` permission + !!! note To enable this integration, the NAPALM library must be installed. See [installation steps](../../installation/3-netbox/#napalm) for more information. diff --git a/docs/release-notes/version-2.10.md b/docs/release-notes/version-2.10.md index 2b47faec0..d9eb9b668 100644 --- a/docs/release-notes/version-2.10.md +++ b/docs/release-notes/version-2.10.md @@ -2,6 +2,10 @@ ## v2.10.9 (FUTURE) +### Enhancements + +* [#6054](https://github.com/netbox-community/netbox/issues/6054) - Display NAPALM-enabled device tabs only when relevant + ### Bug Fixes * [#6073](https://github.com/netbox-community/netbox/issues/6073) - Permit users to manage their own REST API tokens without needing explicit permission diff --git a/netbox/templates/dcim/device/base.html b/netbox/templates/dcim/device/base.html index 8f488b284..f2f8202a1 100644 --- a/netbox/templates/dcim/device/base.html +++ b/netbox/templates/dcim/device/base.html @@ -153,16 +153,17 @@ {% endif %} {% endwith %} - {% if perms.dcim.napalm_read_device %} - {% if object.status != 'active' %} - {% include 'dcim/inc/device_napalm_tabs.html' with disabled_message='Device must be in active status' %} - {% elif not object.platform %} - {% include 'dcim/inc/device_napalm_tabs.html' with disabled_message='No platform assigned to this device' %} - {% elif not object.platform.napalm_driver %} - {% include 'dcim/inc/device_napalm_tabs.html' with disabled_message='No NAPALM driver assigned for this platform' %} - {% else %} - {% include 'dcim/inc/device_napalm_tabs.html' %} - {% endif %} + {% if perms.dcim.napalm_read_device and object.status == 'active' and object.primary_ip and object.platform.napalm_driver %} + {# NAPALM-enabled tabs #} + + + {% endif %} {% if perms.extras.view_configcontext %} - - -{% else %} - - - -{% endif %} From f2f0ea8d04e8b8d9b18fa8235431eeeb74b7ec21 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 31 Mar 2021 15:27:38 -0400 Subject: [PATCH 3/7] Closes #5526: Add MAC address search field to VM interfaces list --- docs/release-notes/version-2.10.md | 1 + netbox/virtualization/forms.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/docs/release-notes/version-2.10.md b/docs/release-notes/version-2.10.md index d9eb9b668..79bc2ffde 100644 --- a/docs/release-notes/version-2.10.md +++ b/docs/release-notes/version-2.10.md @@ -4,6 +4,7 @@ ### Enhancements +* [#5526](https://github.com/netbox-community/netbox/issues/5526) - Add MAC address search field to VM interfaces list * [#6054](https://github.com/netbox-community/netbox/issues/6054) - Display NAPALM-enabled device tabs only when relevant ### Bug Fixes diff --git a/netbox/virtualization/forms.py b/netbox/virtualization/forms.py index edacb3e07..20d0e4ad8 100644 --- a/netbox/virtualization/forms.py +++ b/netbox/virtualization/forms.py @@ -786,6 +786,10 @@ class VMInterfaceFilterForm(forms.Form): choices=BOOLEAN_WITH_BLANK_CHOICES ) ) + mac_address = forms.CharField( + required=False, + label='MAC address' + ) tag = TagFilterField(model) From 6242e195be53c4a3abb14ea0bf5ac00ec778db33 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 31 Mar 2021 15:33:06 -0400 Subject: [PATCH 4/7] Closes #5756: Omit child devices from non-racked devices list under rack view --- docs/release-notes/version-2.10.md | 1 + netbox/dcim/views.py | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/release-notes/version-2.10.md b/docs/release-notes/version-2.10.md index 79bc2ffde..113cac78e 100644 --- a/docs/release-notes/version-2.10.md +++ b/docs/release-notes/version-2.10.md @@ -5,6 +5,7 @@ ### Enhancements * [#5526](https://github.com/netbox-community/netbox/issues/5526) - Add MAC address search field to VM interfaces list +* [#5756](https://github.com/netbox-community/netbox/issues/5756) - Omit child devices from non-racked devices list under rack view * [#6054](https://github.com/netbox-community/netbox/issues/6054) - Display NAPALM-enabled device tabs only when relevant ### Bug Fixes diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index ffdbf933f..735db9abb 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -342,10 +342,11 @@ class RackView(generic.ObjectView): queryset = Rack.objects.prefetch_related('site__region', 'tenant__group', 'group', 'role') def get_extra_context(self, request, instance): - # Get 0U and child devices located within the rack + # Get 0U devices located within the rack nonracked_devices = Device.objects.filter( rack=instance, - position__isnull=True + position__isnull=True, + parent_bay__isnull=True ).prefetch_related('device_type__manufacturer') peer_racks = Rack.objects.restrict(request.user, 'view').filter(site=instance.site) From 9df2130e11b0ec4181ba83ffa09e321da2f4d13c Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 31 Mar 2021 15:49:29 -0400 Subject: [PATCH 5/7] Closes #5840: Add column to cable termination objects to display cable color --- docs/release-notes/version-2.10.md | 1 + netbox/dcim/tables/devices.py | 53 ++++++++++++++++++++---------- netbox/dcim/tables/power.py | 3 +- 3 files changed, 37 insertions(+), 20 deletions(-) diff --git a/docs/release-notes/version-2.10.md b/docs/release-notes/version-2.10.md index 113cac78e..a80305965 100644 --- a/docs/release-notes/version-2.10.md +++ b/docs/release-notes/version-2.10.md @@ -6,6 +6,7 @@ * [#5526](https://github.com/netbox-community/netbox/issues/5526) - Add MAC address search field to VM interfaces list * [#5756](https://github.com/netbox-community/netbox/issues/5756) - Omit child devices from non-racked devices list under rack view +* [#5840](https://github.com/netbox-community/netbox/issues/5840) - Add column to cable termination objects to display cable color * [#6054](https://github.com/netbox-community/netbox/issues/6054) - Display NAPALM-enabled device tabs only when relevant ### Bug Fixes diff --git a/netbox/dcim/tables/devices.py b/netbox/dcim/tables/devices.py index 52f4449af..311e7aea5 100644 --- a/netbox/dcim/tables/devices.py +++ b/netbox/dcim/tables/devices.py @@ -230,6 +230,11 @@ class CableTerminationTable(BaseTable): cable = tables.Column( linkify=True ) + cable_color = ColorColumn( + accessor='cable.color', + orderable=False, + verbose_name='Cable Color' + ) cable_peer = tables.TemplateColumn( accessor='_cable_peer', template_code=CABLETERMINATION, @@ -255,7 +260,8 @@ class ConsolePortTable(DeviceComponentTable, PathEndpointTable): class Meta(DeviceComponentTable.Meta): model = ConsolePort fields = ( - 'pk', 'device', 'name', 'label', 'type', 'description', 'cable', 'cable_peer', 'connection', 'tags', + 'pk', 'device', 'name', 'label', 'type', 'description', 'cable', 'cable_color', 'cable_peer', 'connection', + 'tags', ) default_columns = ('pk', 'device', 'name', 'label', 'type', 'description') @@ -274,7 +280,8 @@ class DeviceConsolePortTable(ConsolePortTable): class Meta(DeviceComponentTable.Meta): model = ConsolePort fields = ( - 'pk', 'name', 'label', 'type', 'description', 'cable', 'cable_peer', 'connection', 'tags', 'actions' + 'pk', 'name', 'label', 'type', 'description', 'cable', 'cable_color', 'cable_peer', 'connection', 'tags', + 'actions' ) default_columns = ('pk', 'name', 'label', 'type', 'description', 'cable', 'connection', 'actions') row_attrs = { @@ -289,7 +296,10 @@ class ConsoleServerPortTable(DeviceComponentTable, PathEndpointTable): class Meta(DeviceComponentTable.Meta): model = ConsoleServerPort - fields = ('pk', 'device', 'name', 'label', 'type', 'description', 'cable', 'cable_peer', 'connection', 'tags') + fields = ( + 'pk', 'device', 'name', 'label', 'type', 'description', 'cable', 'cable_color', 'cable_peer', 'connection', + 'tags', + ) default_columns = ('pk', 'device', 'name', 'label', 'type', 'description') @@ -308,7 +318,8 @@ class DeviceConsoleServerPortTable(ConsoleServerPortTable): class Meta(DeviceComponentTable.Meta): model = ConsoleServerPort fields = ( - 'pk', 'name', 'label', 'type', 'description', 'cable', 'cable_peer', 'connection', 'tags', 'actions' + 'pk', 'name', 'label', 'type', 'description', 'cable', 'cable_color', 'cable_peer', 'connection', 'tags', + 'actions' ) default_columns = ('pk', 'name', 'label', 'type', 'description', 'cable', 'connection', 'actions') row_attrs = { @@ -325,7 +336,7 @@ class PowerPortTable(DeviceComponentTable, PathEndpointTable): model = PowerPort fields = ( 'pk', 'device', 'name', 'label', 'type', 'description', 'maximum_draw', 'allocated_draw', 'cable', - 'cable_peer', 'connection', 'tags', + 'cable_color', 'cable_peer', 'connection', 'tags', ) default_columns = ('pk', 'device', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description') @@ -345,8 +356,8 @@ class DevicePowerPortTable(PowerPortTable): class Meta(DeviceComponentTable.Meta): model = PowerPort fields = ( - 'pk', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description', 'cable', 'cable_peer', - 'connection', 'tags', 'actions', + 'pk', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description', 'cable', 'cable_color', + 'cable_peer', 'connection', 'tags', 'actions', ) default_columns = ( 'pk', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description', 'cable', 'connection', @@ -368,8 +379,8 @@ class PowerOutletTable(DeviceComponentTable, PathEndpointTable): class Meta(DeviceComponentTable.Meta): model = PowerOutlet fields = ( - 'pk', 'device', 'name', 'label', 'type', 'description', 'power_port', 'feed_leg', 'cable', 'cable_peer', - 'connection', 'tags', + 'pk', 'device', 'name', 'label', 'type', 'description', 'power_port', 'feed_leg', 'cable', 'cable_color', + 'cable_peer', 'connection', 'tags', ) default_columns = ('pk', 'device', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description') @@ -388,8 +399,8 @@ class DevicePowerOutletTable(PowerOutletTable): class Meta(DeviceComponentTable.Meta): model = PowerOutlet fields = ( - 'pk', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description', 'cable', 'cable_peer', 'connection', - 'tags', 'actions', + 'pk', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description', 'cable', 'cable_color', + 'cable_peer', 'connection', 'tags', 'actions', ) default_columns = ( 'pk', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description', 'cable', 'connection', 'actions', @@ -424,7 +435,8 @@ class InterfaceTable(DeviceComponentTable, BaseInterfaceTable, PathEndpointTable model = Interface fields = ( 'pk', 'device', 'name', 'label', 'enabled', 'type', 'mgmt_only', 'mtu', 'mode', 'mac_address', - 'description', 'cable', 'cable_peer', 'connection', 'tags', 'ip_addresses', 'untagged_vlan', 'tagged_vlans', + 'description', 'cable', 'cable_color', 'cable_peer', 'connection', 'tags', 'ip_addresses', 'untagged_vlan', + 'tagged_vlans', ) default_columns = ('pk', 'device', 'name', 'label', 'enabled', 'type', 'description') @@ -450,7 +462,8 @@ class DeviceInterfaceTable(InterfaceTable): model = Interface fields = ( 'pk', 'name', 'label', 'enabled', 'type', 'lag', 'mgmt_only', 'mtu', 'mode', 'mac_address', 'description', - 'cable', 'cable_peer', 'connection', 'tags', 'ip_addresses', 'untagged_vlan', 'tagged_vlans', 'actions', + 'cable', 'cable_color', 'cable_peer', 'connection', 'tags', 'ip_addresses', 'untagged_vlan', 'tagged_vlans', + 'actions', ) default_columns = ( 'pk', 'name', 'label', 'enabled', 'type', 'lag', 'mtu', 'mode', 'description', 'ip_addresses', 'cable', @@ -477,7 +490,7 @@ class FrontPortTable(DeviceComponentTable, CableTerminationTable): model = FrontPort fields = ( 'pk', 'device', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description', 'cable', - 'cable_peer', 'tags', + 'cable_color', 'cable_peer', 'tags', ) default_columns = ('pk', 'device', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description') @@ -497,8 +510,8 @@ class DeviceFrontPortTable(FrontPortTable): class Meta(DeviceComponentTable.Meta): model = FrontPort fields = ( - 'pk', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description', 'cable', 'cable_peer', - 'tags', 'actions', + 'pk', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description', 'cable', 'cable_color', + 'cable_peer', 'tags', 'actions', ) default_columns = ( 'pk', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description', 'cable', 'cable_peer', @@ -516,7 +529,10 @@ class RearPortTable(DeviceComponentTable, CableTerminationTable): class Meta(DeviceComponentTable.Meta): model = RearPort - fields = ('pk', 'device', 'name', 'label', 'type', 'positions', 'description', 'cable', 'cable_peer', 'tags') + fields = ( + 'pk', 'device', 'name', 'label', 'type', 'positions', 'description', 'cable', 'cable_color', 'cable_peer', + 'tags', + ) default_columns = ('pk', 'device', 'name', 'label', 'type', 'description') @@ -535,7 +551,8 @@ class DeviceRearPortTable(RearPortTable): class Meta(DeviceComponentTable.Meta): model = RearPort fields = ( - 'pk', 'name', 'label', 'type', 'positions', 'description', 'cable', 'cable_peer', 'tags', 'actions', + 'pk', 'name', 'label', 'type', 'positions', 'description', 'cable', 'cable_color', 'cable_peer', 'tags', + 'actions', ) default_columns = ( 'pk', 'name', 'label', 'type', 'positions', 'description', 'cable', 'cable_peer', 'actions', diff --git a/netbox/dcim/tables/power.py b/netbox/dcim/tables/power.py index ae5c2a5c8..55f2f868f 100644 --- a/netbox/dcim/tables/power.py +++ b/netbox/dcim/tables/power.py @@ -4,7 +4,6 @@ from django_tables2.utils import Accessor from dcim.models import PowerFeed, PowerPanel from utilities.tables import BaseTable, ChoiceFieldColumn, LinkedCountColumn, TagColumn, ToggleColumn from .devices import CableTerminationTable -from .template_code import POWERFEED_CABLE, POWERFEED_CABLETERMINATION __all__ = ( 'PowerFeedTable', @@ -69,7 +68,7 @@ class PowerFeedTable(CableTerminationTable): model = PowerFeed fields = ( 'pk', 'name', 'power_panel', 'rack', 'status', 'type', 'supply', 'voltage', 'amperage', 'phase', - 'max_utilization', 'cable', 'cable_peer', 'connection', 'available_power', 'tags', + 'max_utilization', 'cable', 'cable_color', 'cable_peer', 'connection', 'available_power', 'tags', ) default_columns = ( 'pk', 'name', 'power_panel', 'rack', 'status', 'type', 'supply', 'voltage', 'amperage', 'phase', 'cable', From 861a52d27ce7773ef6f57099dcdcf01b1ad830ad Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 31 Mar 2021 16:35:28 -0400 Subject: [PATCH 6/7] Closes #5965: Mention cf property on CustomFieldModel in docs --- docs/additional-features/custom-fields.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/additional-features/custom-fields.md b/docs/additional-features/custom-fields.md index 91bf03379..2c3c723a2 100644 --- a/docs/additional-features/custom-fields.md +++ b/docs/additional-features/custom-fields.md @@ -39,6 +39,12 @@ Each custom selection field must have at least two choices. These are specified If a default value is specified for a selection field, it must exactly match one of the provided choices. +## Custom Fields in Templates + +Several features within NetBox, such as export templates and webhooks, utilize Jinja2 templating. For convenience, objects which support custom field assignment expose custom field data through the `cf` property. This is a bit cleaner than accessing custom field data through the actual field (`custom_field_data`). + +For example, a custom field named `foo123` on the Site model is accessible on an instance as `{{ site.cf.foo123 }}`. + ## Custom Fields and the REST API When retrieving an object via the REST API, all of its custom data will be included within the `custom_fields` attribute. For example, below is the partial output of a site with two custom fields defined: From 7bd853e87b51470c50663dedfc07b29774f2707b Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 31 Mar 2021 17:02:21 -0400 Subject: [PATCH 7/7] Fixes #5805: Fix missing custom field filters for cables, rack reservations --- docs/release-notes/version-2.10.md | 1 + netbox/dcim/forms.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/release-notes/version-2.10.md b/docs/release-notes/version-2.10.md index a80305965..95f8bd8ab 100644 --- a/docs/release-notes/version-2.10.md +++ b/docs/release-notes/version-2.10.md @@ -11,6 +11,7 @@ ### Bug Fixes +* [#5805](https://github.com/netbox-community/netbox/issues/5805) - Fix missing custom field filters for cables, rack reservations * [#6073](https://github.com/netbox-community/netbox/issues/6073) - Permit users to manage their own REST API tokens without needing explicit permission --- diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 9b9760ad1..77061d556 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -868,7 +868,7 @@ class RackReservationBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomField nullable_fields = [] -class RackReservationFilterForm(BootstrapMixin, TenancyFilterForm): +class RackReservationFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm): model = RackReservation field_order = ['q', 'region', 'site', 'group_id', 'user_id', 'tenant_group', 'tenant'] q = forms.CharField( @@ -3966,7 +3966,7 @@ class CableBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFo }) -class CableFilterForm(BootstrapMixin, forms.Form): +class CableFilterForm(BootstrapMixin, CustomFieldFilterForm): model = Cable q = forms.CharField( required=False,