Skip to content

Commit 66c4d23

Browse files
Merge pull request #7510 from netbox-community/develop
Release v3.0.7
2 parents 3eef636 + d66fc8f commit 66c4d23

File tree

22 files changed

+177
-120
lines changed

22 files changed

+177
-120
lines changed

.github/ISSUE_TEMPLATE/bug_report.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ body:
1717
What version of NetBox are you currently running? (If you don't have access to the most
1818
recent NetBox release, consider testing on our [demo instance](https://demo.netbox.dev/)
1919
before opening a bug report to see if your issue has already been addressed.)
20-
placeholder: v3.0.6
20+
placeholder: v3.0.7
2121
validations:
2222
required: true
2323
- type: dropdown

.github/ISSUE_TEMPLATE/feature_request.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ body:
1414
attributes:
1515
label: NetBox version
1616
description: What version of NetBox are you currently running?
17-
placeholder: v3.0.6
17+
placeholder: v3.0.7
1818
validations:
1919
required: true
2020
- type: dropdown

docs/models/extras/customlink.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# Custom Links
22

3-
Custom links allow users to display arbitrary hyperlinks to external content within NetBox object views. These are helpful for cross-referencing related records in systems outside NetBox. For example, you might create a custom link on the device view which links to the current device in a network monitoring system.
3+
Custom links allow users to display arbitrary hyperlinks to external content within NetBox object views. These are helpful for cross-referencing related records in systems outside NetBox. For example, you might create a custom link on the device view which links to the current device in a Network Monitoring System (NMS).
44

5-
Custom links are created by navigating to Customization > Custom Links. Each link is associated with a particular NetBox object type (site, device, prefix, etc.) and will be displayed on relevant views. Each link is assigned text and a URL, both of which support Jinja2 templating. The text and URL are rendered with the context variable `obj` representing the current object.
5+
Custom links are created by navigating to Customization > Custom Links. Each link is associated with a particular NetBox object type (site, device, prefix, etc.) and will be displayed on relevant views. Each link has display text and a URL, and data from the Netbox item being viewed can be included in the link using [Jinja2 template code](https://jinja2docs.readthedocs.io/en/stable/) through the variable `obj`, and custom fields through `obj.cf`.
66

77
For example, you might define a link like this:
88

docs/release-notes/version-3.0.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
11
# NetBox v3.0
22

3+
## v3.0.7 (2021-10-08)
4+
5+
### Enhancements
6+
7+
* [#6879](https://github.com/netbox-community/netbox/issues/6879) - Improve ability to toggle images/labels in rack elevations
8+
* [#7485](https://github.com/netbox-community/netbox/issues/7485) - Add USB micro AB type
9+
10+
### Bug Fixes
11+
12+
* [#7051](https://github.com/netbox-community/netbox/issues/7051) - Fix permissions evaluation and improve error handling for connected device REST API endpoint
13+
* [#7471](https://github.com/netbox-community/netbox/issues/7471) - Correct redirect URL when attaching images via "add another" button
14+
* [#7474](https://github.com/netbox-community/netbox/issues/7474) - Fix AttributeError exception when rendering a report or custom script
15+
* [#7479](https://github.com/netbox-community/netbox/issues/7479) - Fix parent interface choices when bulk editing VM interfaces
16+
17+
---
18+
319
## v3.0.6 (2021-10-06)
420

521
### Enhancements

netbox/dcim/api/views.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from collections import OrderedDict
33

44
from django.conf import settings
5-
from django.http import HttpResponseForbidden, HttpResponse
5+
from django.http import Http404, HttpResponse, HttpResponseForbidden
66
from django.shortcuts import get_object_or_404
77
from drf_yasg import openapi
88
from drf_yasg.openapi import Parameter
@@ -17,10 +17,10 @@
1717
from dcim.models import *
1818
from extras.api.views import ConfigContextQuerySetMixin, CustomFieldModelViewSet
1919
from ipam.models import Prefix, VLAN
20-
from netbox.api.views import ModelViewSet
2120
from netbox.api.authentication import IsAuthenticatedOrLoginNotRequired
2221
from netbox.api.exceptions import ServiceUnavailable
2322
from netbox.api.metadata import ContentTypeMetadata
23+
from netbox.api.views import ModelViewSet
2424
from utilities.api import get_serializer_for_model
2525
from utilities.utils import count_related, decode_dict
2626
from virtualization.models import VirtualMachine
@@ -675,15 +675,25 @@ def list(self, request):
675675
if not peer_device_name or not peer_interface_name:
676676
raise MissingFilterException(detail='Request must include "peer_device" and "peer_interface" filters.')
677677

678-
# Determine local interface from peer interface's connection
678+
# Determine local endpoint from peer interface's connection
679+
peer_device = get_object_or_404(
680+
Device.objects.restrict(request.user, 'view'),
681+
name=peer_device_name
682+
)
679683
peer_interface = get_object_or_404(
680-
Interface.objects.all(),
681-
device__name=peer_device_name,
684+
Interface.objects.restrict(request.user, 'view'),
685+
device=peer_device,
682686
name=peer_interface_name
683687
)
684-
local_interface = peer_interface.connected_endpoint
688+
endpoint = peer_interface.connected_endpoint
685689

686-
if local_interface is None:
687-
return Response()
690+
# If an Interface, return the parent device
691+
if type(endpoint) is Interface:
692+
device = get_object_or_404(
693+
Device.objects.restrict(request.user, 'view'),
694+
pk=endpoint.device_id
695+
)
696+
return Response(serializers.DeviceSerializer(device, context={'request': request}).data)
688697

689-
return Response(serializers.DeviceSerializer(local_interface.device, context={'request': request}).data)
698+
# Connected endpoint is none or not an Interface
699+
raise Http404

netbox/dcim/choices.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ class ConsolePortTypeChoices(ChoiceSet):
192192
TYPE_USB_MINI_B = 'usb-mini-b'
193193
TYPE_USB_MICRO_A = 'usb-micro-a'
194194
TYPE_USB_MICRO_B = 'usb-micro-b'
195+
TYPE_USB_MICRO_AB = 'usb-micro-ab'
195196
TYPE_OTHER = 'other'
196197

197198
CHOICES = (
@@ -210,6 +211,7 @@ class ConsolePortTypeChoices(ChoiceSet):
210211
(TYPE_USB_MINI_B, 'USB Mini B'),
211212
(TYPE_USB_MICRO_A, 'USB Micro A'),
212213
(TYPE_USB_MICRO_B, 'USB Micro B'),
214+
(TYPE_USB_MICRO_AB, 'USB Micro AB'),
213215
)),
214216
('Other', (
215217
(TYPE_OTHER, 'Other'),
@@ -337,6 +339,7 @@ class PowerPortTypeChoices(ChoiceSet):
337339
TYPE_USB_MINI_B = 'usb-mini-b'
338340
TYPE_USB_MICRO_A = 'usb-micro-a'
339341
TYPE_USB_MICRO_B = 'usb-micro-b'
342+
TYPE_USB_MICRO_AB = 'usb-micro-ab'
340343
TYPE_USB_3_B = 'usb-3-b'
341344
TYPE_USB_3_MICROB = 'usb-3-micro-b'
342345
# Direct current (DC)
@@ -444,6 +447,7 @@ class PowerPortTypeChoices(ChoiceSet):
444447
(TYPE_USB_MINI_B, 'USB Mini B'),
445448
(TYPE_USB_MICRO_A, 'USB Micro A'),
446449
(TYPE_USB_MICRO_B, 'USB Micro B'),
450+
(TYPE_USB_MICRO_AB, 'USB Micro AB'),
447451
(TYPE_USB_3_B, 'USB 3.0 Type B'),
448452
(TYPE_USB_3_MICROB, 'USB 3.0 Micro B'),
449453
)),

netbox/dcim/filtersets.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,12 +480,21 @@ def _device_bays(self, queryset, name, value):
480480

481481

482482
class DeviceTypeComponentFilterSet(django_filters.FilterSet):
483+
q = django_filters.CharFilter(
484+
method='search',
485+
label='Search',
486+
)
483487
devicetype_id = django_filters.ModelMultipleChoiceFilter(
484488
queryset=DeviceType.objects.all(),
485489
field_name='device_type_id',
486490
label='Device type (ID)',
487491
)
488492

493+
def search(self, queryset, name, value):
494+
if not value.strip():
495+
return queryset
496+
return queryset.filter(name__icontains=value)
497+
489498

490499
class ConsolePortTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet):
491500

netbox/dcim/svg.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,9 @@ def _draw_device_front(self, drawing, device, start, end, text):
112112
)
113113
image.fit(scale='slice')
114114
link.add(image)
115+
link.add(drawing.text(str(name), insert=text, stroke='black',
116+
stroke_width='0.2em', stroke_linejoin='round', class_='device-image-label'))
117+
link.add(drawing.text(str(name), insert=text, fill='white', class_='device-image-label'))
115118

116119
def _draw_device_rear(self, drawing, device, start, end, text):
117120
rect = drawing.rect(start, end, class_="slot blocked")
@@ -129,6 +132,9 @@ def _draw_device_rear(self, drawing, device, start, end, text):
129132
)
130133
image.fit(scale='slice')
131134
drawing.add(image)
135+
drawing.add(drawing.text(str(device), insert=text, stroke='black',
136+
stroke_width='0.2em', stroke_linejoin='round', class_='device-image-label'))
137+
drawing.add(drawing.text(str(device), insert=text, fill='white', class_='device-image-label'))
132138

133139
@staticmethod
134140
def _draw_empty(drawing, rack, start, end, text, id_, face_id, class_, reservation):

netbox/dcim/tests/test_api.py

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from django.contrib.auth.models import User
2+
from django.test import override_settings
23
from django.urls import reverse
34
from rest_framework import status
45

@@ -1490,40 +1491,35 @@ def setUp(self):
14901491

14911492
super().setUp()
14921493

1493-
self.site1 = Site.objects.create(name='Test Site 1', slug='test-site-1')
1494-
self.site2 = Site.objects.create(name='Test Site 2', slug='test-site-2')
1495-
manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
1496-
self.devicetype1 = DeviceType.objects.create(
1497-
manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1'
1498-
)
1499-
self.devicetype2 = DeviceType.objects.create(
1500-
manufacturer=manufacturer, model='Test Device Type 2', slug='test-device-type-2'
1501-
)
1502-
self.devicerole1 = DeviceRole.objects.create(
1503-
name='Test Device Role 1', slug='test-device-role-1', color='ff0000'
1504-
)
1505-
self.devicerole2 = DeviceRole.objects.create(
1506-
name='Test Device Role 2', slug='test-device-role-2', color='00ff00'
1507-
)
1494+
site = Site.objects.create(name='Site 1', slug='site-1')
1495+
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
1496+
devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1')
1497+
devicerole = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1', color='ff0000')
15081498
self.device1 = Device.objects.create(
1509-
device_type=self.devicetype1, device_role=self.devicerole1, name='TestDevice1', site=self.site1
1499+
device_type=devicetype, device_role=devicerole, name='TestDevice1', site=site
15101500
)
15111501
self.device2 = Device.objects.create(
1512-
device_type=self.devicetype1, device_role=self.devicerole1, name='TestDevice2', site=self.site1
1502+
device_type=devicetype, device_role=devicerole, name='TestDevice2', site=site
15131503
)
15141504
self.interface1 = Interface.objects.create(device=self.device1, name='eth0')
15151505
self.interface2 = Interface.objects.create(device=self.device2, name='eth0')
1506+
self.interface3 = Interface.objects.create(device=self.device1, name='eth1') # Not connected
15161507

15171508
cable = Cable(termination_a=self.interface1, termination_b=self.interface2)
15181509
cable.save()
15191510

1511+
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
15201512
def test_get_connected_device(self):
1521-
15221513
url = reverse('dcim-api:connected-device-list')
1523-
response = self.client.get(url + '?peer_device=TestDevice2&peer_interface=eth0', **self.header)
15241514

1515+
url_params = f'?peer_device={self.device1.name}&peer_interface={self.interface1.name}'
1516+
response = self.client.get(url + url_params, **self.header)
15251517
self.assertHttpStatus(response, status.HTTP_200_OK)
1526-
self.assertEqual(response.data['name'], self.device1.name)
1518+
self.assertEqual(response.data['name'], self.device2.name)
1519+
1520+
url_params = f'?peer_device={self.device1.name}&peer_interface={self.interface3.name}'
1521+
response = self.client.get(url + url_params, **self.header)
1522+
self.assertHttpStatus(response, status.HTTP_404_NOT_FOUND)
15271523

15281524

15291525
class VirtualChassisTest(APIViewTestCases.APIViewTestCase):

netbox/netbox/settings.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
# Environment setup
1717
#
1818

19-
VERSION = '3.0.6'
19+
VERSION = '3.0.7'
2020

2121
# Hostname
2222
HOSTNAME = platform.node()

0 commit comments

Comments
 (0)