Skip to content

Commit b493d73

Browse files
Merge pull request #6143 from netbox-community/develop
Release v2.10.9
2 parents 3d286fb + 92fb43a commit b493d73

30 files changed

+230
-169
lines changed

docs/additional-features/custom-fields.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ Each custom selection field must have at least two choices. These are specified
3939

4040
If a default value is specified for a selection field, it must exactly match one of the provided choices.
4141

42+
## Custom Fields in Templates
43+
44+
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`).
45+
46+
For example, a custom field named `foo123` on the Site model is accessible on an instance as `{{ site.cf.foo123 }}`.
47+
4248
## Custom Fields and the REST API
4349

4450
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:

docs/additional-features/export-templates.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@ Height: {{ rack.u_height }}U
1818

1919
To access custom fields of an object within a template, use the `cf` attribute. For example, `{{ obj.cf.color }}` will return the value (if any) for a custom field named `color` on `obj`.
2020

21+
If you need to use the config context data in an export template, you'll should use the function `get_config_context` to get all the config context data. For example:
22+
```
23+
{% for server in queryset %}
24+
{% set data = server.get_config_context() %}
25+
{{ data.syslog }}
26+
{% endfor %}
27+
```
28+
2129
A MIME type and file extension can optionally be defined for each export template. The default MIME type is `text/plain`.
2230

2331
## Example

docs/additional-features/napalm.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22

33
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.
44

5+
The NetBox UI will display tabs for status, LLDP neighbors, and configuration under the device view if the following conditions are met:
6+
7+
* Device status is "Active"
8+
* A primary IP has been assigned to the device
9+
* A platform with a NAPALM driver has been assigned
10+
* The authenticated user has the `dcim.napalm_read_device` permission
11+
512
!!! note
613
To enable this integration, the NAPALM library must be installed. See [installation steps](../../installation/3-netbox/#napalm) for more information.
714

docs/administration/permissions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ NetBox v2.9 introduced a new object-based permissions framework, which replace's
1010
| ----------- | ----------- |
1111
| `{"status": "active"}` | Status is active |
1212
| `{"status__in": ["planned", "reserved"]}` | Status is active **OR** reserved |
13-
| `{"status": "active", "role": "testing"}` | Status is active **OR** role is testing |
13+
| `{"status": "active", "role": "testing"}` | Status is active **AND** role is testing |
1414
| `{"name__startswith": "Foo"}` | Name starts with "Foo" (case-sensitive) |
1515
| `{"name__iendswith": "bar"}` | Name ends with "bar" (case-insensitive) |
1616
| `{"vid__gte": 100, "vid__lt": 200}` | VLAN ID is greater than or equal to 100 **AND** less than 200 |

docs/configuration/required-settings.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ Redis is configured using a configuration setting similar to `DATABASE` and thes
6666
* `PASSWORD` - Redis password (if set)
6767
* `DATABASE` - Numeric database ID
6868
* `SSL` - Use SSL connection to Redis
69+
* `INSECURE_SKIP_TLS_VERIFY` - Set to `True` to **disable** TLS certificate verification (not recommended)
6970

7071
An example configuration is provided below:
7172

docs/release-notes/version-2.10.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,28 @@
11
# NetBox v2.10
22

3+
## v2.10.9 (2021-04-12)
4+
5+
### Enhancements
6+
7+
* [#5526](https://github.com/netbox-community/netbox/issues/5526) - Add MAC address search field to VM interfaces list
8+
* [#5756](https://github.com/netbox-community/netbox/issues/5756) - Omit child devices from non-racked devices list under rack view
9+
* [#5840](https://github.com/netbox-community/netbox/issues/5840) - Add column to cable termination objects to display cable color
10+
* [#6054](https://github.com/netbox-community/netbox/issues/6054) - Display NAPALM-enabled device tabs only when relevant
11+
* [#6083](https://github.com/netbox-community/netbox/issues/6083) - Support disabling TLS certificate validation for Redis
12+
13+
### Bug Fixes
14+
15+
* [#5805](https://github.com/netbox-community/netbox/issues/5805) - Fix missing custom field filters for cables, rack reservations
16+
* [#6070](https://github.com/netbox-community/netbox/issues/6070) - Add missing `count_ipaddresses` attribute to VMInterface serializer
17+
* [#6073](https://github.com/netbox-community/netbox/issues/6073) - Permit users to manage their own REST API tokens without needing explicit permission
18+
* [#6081](https://github.com/netbox-community/netbox/issues/6081) - Fix interface connections REST API endpoint
19+
* [#6082](https://github.com/netbox-community/netbox/issues/6082) - Support colons in webhook header values
20+
* [#6108](https://github.com/netbox-community/netbox/issues/6108) - Do not infer tenant assignment from parent objects for prefixes, IP addresses
21+
* [#6117](https://github.com/netbox-community/netbox/issues/6117) - Handle exception when attempting to assign an MPTT-enabled model as its own parent
22+
* [#6131](https://github.com/netbox-community/netbox/issues/6131) - Correct handling of boolean fields when cloning objects
23+
24+
---
25+
326
## v2.10.8 (2021-03-26)
427

528
### Bug Fixes

netbox/dcim/api/serializers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -779,7 +779,7 @@ def get_path(self, obj):
779779

780780
class InterfaceConnectionSerializer(ValidatedModelSerializer):
781781
interface_a = serializers.SerializerMethodField()
782-
interface_b = NestedInterfaceSerializer(source='connected_endpoint')
782+
interface_b = NestedInterfaceSerializer(source='_path.destination')
783783
connected_endpoint_reachable = serializers.SerializerMethodField(read_only=True)
784784

785785
class Meta:

netbox/dcim/api/views.py

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

44
from django.conf import settings
5+
from django.contrib.contenttypes.models import ContentType
56
from django.db.models import F
67
from django.http import HttpResponseForbidden, HttpResponse
78
from django.shortcuts import get_object_or_404
@@ -580,6 +581,8 @@ class PowerConnectionViewSet(ListModelMixin, GenericViewSet):
580581
class InterfaceConnectionViewSet(ListModelMixin, GenericViewSet):
581582
queryset = Interface.objects.prefetch_related('device', '_path').filter(
582583
# Avoid duplicate connections by only selecting the lower PK in a connected pair
584+
_path__destination_type__app_label='dcim',
585+
_path__destination_type__model='interface',
583586
_path__destination_id__isnull=False,
584587
pk__lt=F('_path__destination_id')
585588
)

netbox/dcim/forms.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -868,7 +868,7 @@ class Meta:
868868
nullable_fields = []
869869

870870

871-
class RackReservationFilterForm(BootstrapMixin, TenancyFilterForm):
871+
class RackReservationFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
872872
model = RackReservation
873873
field_order = ['q', 'region', 'site', 'group_id', 'user_id', 'tenant_group', 'tenant']
874874
q = forms.CharField(
@@ -3966,7 +3966,7 @@ def clean(self):
39663966
})
39673967

39683968

3969-
class CableFilterForm(BootstrapMixin, forms.Form):
3969+
class CableFilterForm(BootstrapMixin, CustomFieldFilterForm):
39703970
model = Cable
39713971
q = forms.CharField(
39723972
required=False,

netbox/dcim/models/device_components.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,10 @@ def save(self, *args, **kwargs):
478478

479479
return super().save(*args, **kwargs)
480480

481+
@property
482+
def count_ipaddresses(self):
483+
return self.ip_addresses.count()
484+
481485

482486
@extras_features('export_templates', 'webhooks', 'custom_links')
483487
class Interface(CableTermination, PathEndpoint, ComponentModel, BaseInterface):
@@ -615,10 +619,6 @@ def is_wireless(self):
615619
def is_lag(self):
616620
return self.type == InterfaceTypeChoices.TYPE_LAG
617621

618-
@property
619-
def count_ipaddresses(self):
620-
return self.ip_addresses.count()
621-
622622

623623
#
624624
# Pass-through ports

netbox/dcim/models/racks.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,12 @@ def to_objectchange(self, action):
111111
def clean(self):
112112
super().clean()
113113

114+
# An MPTT model cannot be its own parent
115+
if self.pk and self.parent_id == self.pk:
116+
raise ValidationError({
117+
"parent": "Cannot assign self as parent."
118+
})
119+
114120
# Parent RackGroup (if any) must belong to the same Site
115121
if self.parent and self.parent.site != self.site:
116122
raise ValidationError(f"Parent rack group ({self.parent}) must belong to the same site ({self.site})")

netbox/dcim/models/sites.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from dcim.choices import *
99
from dcim.constants import *
10+
from django.core.exceptions import ValidationError
1011
from dcim.fields import ASNField
1112
from extras.models import ChangeLoggedModel, CustomFieldModel, ObjectChange, TaggedItem
1213
from extras.utils import extras_features
@@ -87,6 +88,15 @@ def to_objectchange(self, action):
8788
object_data=serialize_object(self, exclude=['level', 'lft', 'rght', 'tree_id'])
8889
)
8990

91+
def clean(self):
92+
super().clean()
93+
94+
# An MPTT model cannot be its own parent
95+
if self.pk and self.parent_id == self.pk:
96+
raise ValidationError({
97+
"parent": "Cannot assign self as parent."
98+
})
99+
90100

91101
#
92102
# Sites

netbox/dcim/tables/devices.py

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,11 @@ class CableTerminationTable(BaseTable):
230230
cable = tables.Column(
231231
linkify=True
232232
)
233+
cable_color = ColorColumn(
234+
accessor='cable.color',
235+
orderable=False,
236+
verbose_name='Cable Color'
237+
)
233238
cable_peer = tables.TemplateColumn(
234239
accessor='_cable_peer',
235240
template_code=CABLETERMINATION,
@@ -255,7 +260,8 @@ class ConsolePortTable(DeviceComponentTable, PathEndpointTable):
255260
class Meta(DeviceComponentTable.Meta):
256261
model = ConsolePort
257262
fields = (
258-
'pk', 'device', 'name', 'label', 'type', 'description', 'cable', 'cable_peer', 'connection', 'tags',
263+
'pk', 'device', 'name', 'label', 'type', 'description', 'cable', 'cable_color', 'cable_peer', 'connection',
264+
'tags',
259265
)
260266
default_columns = ('pk', 'device', 'name', 'label', 'type', 'description')
261267

@@ -274,7 +280,8 @@ class DeviceConsolePortTable(ConsolePortTable):
274280
class Meta(DeviceComponentTable.Meta):
275281
model = ConsolePort
276282
fields = (
277-
'pk', 'name', 'label', 'type', 'description', 'cable', 'cable_peer', 'connection', 'tags', 'actions'
283+
'pk', 'name', 'label', 'type', 'description', 'cable', 'cable_color', 'cable_peer', 'connection', 'tags',
284+
'actions'
278285
)
279286
default_columns = ('pk', 'name', 'label', 'type', 'description', 'cable', 'connection', 'actions')
280287
row_attrs = {
@@ -289,7 +296,10 @@ class ConsoleServerPortTable(DeviceComponentTable, PathEndpointTable):
289296

290297
class Meta(DeviceComponentTable.Meta):
291298
model = ConsoleServerPort
292-
fields = ('pk', 'device', 'name', 'label', 'type', 'description', 'cable', 'cable_peer', 'connection', 'tags')
299+
fields = (
300+
'pk', 'device', 'name', 'label', 'type', 'description', 'cable', 'cable_color', 'cable_peer', 'connection',
301+
'tags',
302+
)
293303
default_columns = ('pk', 'device', 'name', 'label', 'type', 'description')
294304

295305

@@ -308,7 +318,8 @@ class DeviceConsoleServerPortTable(ConsoleServerPortTable):
308318
class Meta(DeviceComponentTable.Meta):
309319
model = ConsoleServerPort
310320
fields = (
311-
'pk', 'name', 'label', 'type', 'description', 'cable', 'cable_peer', 'connection', 'tags', 'actions'
321+
'pk', 'name', 'label', 'type', 'description', 'cable', 'cable_color', 'cable_peer', 'connection', 'tags',
322+
'actions'
312323
)
313324
default_columns = ('pk', 'name', 'label', 'type', 'description', 'cable', 'connection', 'actions')
314325
row_attrs = {
@@ -325,7 +336,7 @@ class Meta(DeviceComponentTable.Meta):
325336
model = PowerPort
326337
fields = (
327338
'pk', 'device', 'name', 'label', 'type', 'description', 'maximum_draw', 'allocated_draw', 'cable',
328-
'cable_peer', 'connection', 'tags',
339+
'cable_color', 'cable_peer', 'connection', 'tags',
329340
)
330341
default_columns = ('pk', 'device', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description')
331342

@@ -345,8 +356,8 @@ class DevicePowerPortTable(PowerPortTable):
345356
class Meta(DeviceComponentTable.Meta):
346357
model = PowerPort
347358
fields = (
348-
'pk', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description', 'cable', 'cable_peer',
349-
'connection', 'tags', 'actions',
359+
'pk', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description', 'cable', 'cable_color',
360+
'cable_peer', 'connection', 'tags', 'actions',
350361
)
351362
default_columns = (
352363
'pk', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description', 'cable', 'connection',
@@ -368,8 +379,8 @@ class PowerOutletTable(DeviceComponentTable, PathEndpointTable):
368379
class Meta(DeviceComponentTable.Meta):
369380
model = PowerOutlet
370381
fields = (
371-
'pk', 'device', 'name', 'label', 'type', 'description', 'power_port', 'feed_leg', 'cable', 'cable_peer',
372-
'connection', 'tags',
382+
'pk', 'device', 'name', 'label', 'type', 'description', 'power_port', 'feed_leg', 'cable', 'cable_color',
383+
'cable_peer', 'connection', 'tags',
373384
)
374385
default_columns = ('pk', 'device', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description')
375386

@@ -388,8 +399,8 @@ class DevicePowerOutletTable(PowerOutletTable):
388399
class Meta(DeviceComponentTable.Meta):
389400
model = PowerOutlet
390401
fields = (
391-
'pk', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description', 'cable', 'cable_peer', 'connection',
392-
'tags', 'actions',
402+
'pk', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description', 'cable', 'cable_color',
403+
'cable_peer', 'connection', 'tags', 'actions',
393404
)
394405
default_columns = (
395406
'pk', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description', 'cable', 'connection', 'actions',
@@ -424,7 +435,8 @@ class Meta(DeviceComponentTable.Meta):
424435
model = Interface
425436
fields = (
426437
'pk', 'device', 'name', 'label', 'enabled', 'type', 'mgmt_only', 'mtu', 'mode', 'mac_address',
427-
'description', 'cable', 'cable_peer', 'connection', 'tags', 'ip_addresses', 'untagged_vlan', 'tagged_vlans',
438+
'description', 'cable', 'cable_color', 'cable_peer', 'connection', 'tags', 'ip_addresses', 'untagged_vlan',
439+
'tagged_vlans',
428440
)
429441
default_columns = ('pk', 'device', 'name', 'label', 'enabled', 'type', 'description')
430442

@@ -450,7 +462,8 @@ class Meta(DeviceComponentTable.Meta):
450462
model = Interface
451463
fields = (
452464
'pk', 'name', 'label', 'enabled', 'type', 'lag', 'mgmt_only', 'mtu', 'mode', 'mac_address', 'description',
453-
'cable', 'cable_peer', 'connection', 'tags', 'ip_addresses', 'untagged_vlan', 'tagged_vlans', 'actions',
465+
'cable', 'cable_color', 'cable_peer', 'connection', 'tags', 'ip_addresses', 'untagged_vlan', 'tagged_vlans',
466+
'actions',
454467
)
455468
default_columns = (
456469
'pk', 'name', 'label', 'enabled', 'type', 'lag', 'mtu', 'mode', 'description', 'ip_addresses', 'cable',
@@ -477,7 +490,7 @@ class Meta(DeviceComponentTable.Meta):
477490
model = FrontPort
478491
fields = (
479492
'pk', 'device', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description', 'cable',
480-
'cable_peer', 'tags',
493+
'cable_color', 'cable_peer', 'tags',
481494
)
482495
default_columns = ('pk', 'device', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description')
483496

@@ -497,8 +510,8 @@ class DeviceFrontPortTable(FrontPortTable):
497510
class Meta(DeviceComponentTable.Meta):
498511
model = FrontPort
499512
fields = (
500-
'pk', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description', 'cable', 'cable_peer',
501-
'tags', 'actions',
513+
'pk', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description', 'cable', 'cable_color',
514+
'cable_peer', 'tags', 'actions',
502515
)
503516
default_columns = (
504517
'pk', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description', 'cable', 'cable_peer',
@@ -516,7 +529,10 @@ class RearPortTable(DeviceComponentTable, CableTerminationTable):
516529

517530
class Meta(DeviceComponentTable.Meta):
518531
model = RearPort
519-
fields = ('pk', 'device', 'name', 'label', 'type', 'positions', 'description', 'cable', 'cable_peer', 'tags')
532+
fields = (
533+
'pk', 'device', 'name', 'label', 'type', 'positions', 'description', 'cable', 'cable_color', 'cable_peer',
534+
'tags',
535+
)
520536
default_columns = ('pk', 'device', 'name', 'label', 'type', 'description')
521537

522538

@@ -535,7 +551,8 @@ class DeviceRearPortTable(RearPortTable):
535551
class Meta(DeviceComponentTable.Meta):
536552
model = RearPort
537553
fields = (
538-
'pk', 'name', 'label', 'type', 'positions', 'description', 'cable', 'cable_peer', 'tags', 'actions',
554+
'pk', 'name', 'label', 'type', 'positions', 'description', 'cable', 'cable_color', 'cable_peer', 'tags',
555+
'actions',
539556
)
540557
default_columns = (
541558
'pk', 'name', 'label', 'type', 'positions', 'description', 'cable', 'cable_peer', 'actions',

netbox/dcim/tables/power.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
from dcim.models import PowerFeed, PowerPanel
55
from utilities.tables import BaseTable, ChoiceFieldColumn, LinkedCountColumn, TagColumn, ToggleColumn
66
from .devices import CableTerminationTable
7-
from .template_code import POWERFEED_CABLE, POWERFEED_CABLETERMINATION
87

98
__all__ = (
109
'PowerFeedTable',
@@ -69,7 +68,7 @@ class Meta(BaseTable.Meta):
6968
model = PowerFeed
7069
fields = (
7170
'pk', 'name', 'power_panel', 'rack', 'status', 'type', 'supply', 'voltage', 'amperage', 'phase',
72-
'max_utilization', 'cable', 'cable_peer', 'connection', 'available_power', 'tags',
71+
'max_utilization', 'cable', 'cable_color', 'cable_peer', 'connection', 'available_power', 'tags',
7372
)
7473
default_columns = (
7574
'pk', 'name', 'power_panel', 'rack', 'status', 'type', 'supply', 'voltage', 'amperage', 'phase', 'cable',

netbox/dcim/views.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -342,10 +342,11 @@ class RackView(generic.ObjectView):
342342
queryset = Rack.objects.prefetch_related('site__region', 'tenant__group', 'group', 'role')
343343

344344
def get_extra_context(self, request, instance):
345-
# Get 0U and child devices located within the rack
345+
# Get 0U devices located within the rack
346346
nonracked_devices = Device.objects.filter(
347347
rack=instance,
348-
position__isnull=True
348+
position__isnull=True,
349+
parent_bay__isnull=True
349350
).prefetch_related('device_type__manufacturer')
350351

351352
peer_racks = Rack.objects.restrict(request.user, 'view').filter(site=instance.site)

netbox/extras/models/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ def render_headers(self, context):
140140
ret = {}
141141
data = render_jinja2(self.additional_headers, context)
142142
for line in data.splitlines():
143-
header, value = line.split(':')
143+
header, value = line.split(':', 1)
144144
ret[header.strip()] = value.strip()
145145
return ret
146146

0 commit comments

Comments
 (0)