Skip to content

Commit b0573f8

Browse files
committed
Merge branch 'develop' into feature
2 parents a3721a9 + 348fca7 commit b0573f8

File tree

17 files changed

+104
-72
lines changed

17 files changed

+104
-72
lines changed

docs/additional-features/export-templates.md

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

2222
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`.
2323

24+
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:
25+
```
26+
{% for server in queryset %}
27+
{% set data = server.get_config_context() %}
28+
{{ data.syslog }}
29+
{% endfor %}
30+
```
31+
2432
The `as_attachment` attribute of an export template controls its behavior when rendered. If true, the rendered content will be returned to the user as a downloadable file. If false, it will be displayed within the browser. (This may be handy e.g. for generating HTML content.)
2533

2634
A MIME type and file extension can optionally be defined for each export template. The default MIME type is `text/plain`.

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: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,17 @@
88
* [#5756](https://github.com/netbox-community/netbox/issues/5756) - Omit child devices from non-racked devices list under rack view
99
* [#5840](https://github.com/netbox-community/netbox/issues/5840) - Add column to cable termination objects to display cable color
1010
* [#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
1112

1213
### Bug Fixes
1314

1415
* [#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
1517
* [#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+
* [#6108](https://github.com/netbox-community/netbox/issues/6108) - Do not infer tenant assignment from parent objects for prefixes, IP addresses
20+
* [#6117](https://github.com/netbox-community/netbox/issues/6117) - Handle exception when attempting to assign an MPTT-enabled model as its own parent
21+
* [#6131](https://github.com/netbox-community/netbox/issues/6131) - Correct handling of boolean fields when cloning objects
1622

1723
---
1824

netbox/dcim/api/serializers.py

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

843843
class InterfaceConnectionSerializer(ValidatedModelSerializer):
844844
interface_a = serializers.SerializerMethodField()
845-
interface_b = NestedInterfaceSerializer(source='connected_endpoint')
845+
interface_b = NestedInterfaceSerializer(source='_path.destination')
846846
connected_endpoint_reachable = serializers.SerializerMethodField(read_only=True)
847847

848848
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
@@ -596,6 +597,8 @@ class PowerConnectionViewSet(ListModelMixin, GenericViewSet):
596597
class InterfaceConnectionViewSet(ListModelMixin, GenericViewSet):
597598
queryset = Interface.objects.prefetch_related('device', '_path').filter(
598599
# Avoid duplicate connections by only selecting the lower PK in a connected pair
600+
_path__destination_type__app_label='dcim',
601+
_path__destination_type__model='interface',
599602
_path__destination_id__isnull=False,
600603
pk__lt=F('_path__destination_id')
601604
)

netbox/dcim/models/device_components.py

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

508508
return super().save(*args, **kwargs)
509509

510+
@property
511+
def count_ipaddresses(self):
512+
return self.ip_addresses.count()
513+
510514

511515
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
512516
class Interface(ComponentModel, BaseInterface, CableTermination, PathEndpoint):
@@ -674,10 +678,6 @@ def is_wireless(self):
674678
def is_lag(self):
675679
return self.type == InterfaceTypeChoices.TYPE_LAG
676680

677-
@property
678-
def count_ipaddresses(self):
679-
return self.ip_addresses.count()
680-
681681

682682
#
683683
# Pass-through ports

netbox/dcim/models/sites.py

Lines changed: 1 addition & 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.utils import extras_features
1213
from netbox.models import NestedGroupModel, PrimaryModel

netbox/netbox/configuration.example.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@
3434
'PASSWORD': '',
3535
'DATABASE': 0,
3636
'SSL': False,
37+
# Set this to True to skip TLS certificate verification
38+
# This can expose the connection to attacks, be careful
39+
# 'INSECURE_SKIP_TLS_VERIFY': False,
3740
},
3841
'caching': {
3942
'HOST': 'localhost',
@@ -44,6 +47,9 @@
4447
'PASSWORD': '',
4548
'DATABASE': 1,
4649
'SSL': False,
50+
# Set this to True to skip TLS certificate verification
51+
# This can expose the connection to attacks, be careful
52+
# 'INSECURE_SKIP_TLS_VERIFY': False,
4753
}
4854
}
4955

netbox/netbox/models.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,15 @@ class MPTTMeta:
195195
def __str__(self):
196196
return self.name
197197

198+
def clean(self):
199+
super().clean()
200+
201+
# An MPTT model cannot be its own parent
202+
if self.pk and self.parent_id == self.pk:
203+
raise ValidationError({
204+
"parent": "Cannot assign self as parent."
205+
})
206+
198207

199208
class OrganizationalModel(ChangeLoggingMixin, CustomFieldsMixin, BigIDModel):
200209
"""

netbox/netbox/settings.py

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ def _setting(name, default=None):
221221
TASKS_REDIS_PASSWORD = TASKS_REDIS.get('PASSWORD', '')
222222
TASKS_REDIS_DATABASE = TASKS_REDIS.get('DATABASE', 0)
223223
TASKS_REDIS_SSL = TASKS_REDIS.get('SSL', False)
224+
TASKS_REDIS_SKIP_TLS_VERIFY = TASKS_REDIS.get('INSECURE_SKIP_TLS_VERIFY', False)
224225

225226
# Caching
226227
if 'caching' not in REDIS:
@@ -239,6 +240,7 @@ def _setting(name, default=None):
239240
CACHING_REDIS_PASSWORD = CACHING_REDIS.get('PASSWORD', '')
240241
CACHING_REDIS_DATABASE = CACHING_REDIS.get('DATABASE', 0)
241242
CACHING_REDIS_SSL = CACHING_REDIS.get('SSL', False)
243+
CACHING_REDIS_SKIP_TLS_VERIFY = CACHING_REDIS.get('INSECURE_SKIP_TLS_VERIFY', False)
242244

243245

244246
#
@@ -406,21 +408,14 @@ def _setting(name, default=None):
406408
'password': CACHING_REDIS_PASSWORD,
407409
}
408410
else:
409-
if CACHING_REDIS_SSL:
410-
REDIS_CACHE_CON_STRING = 'rediss://'
411-
else:
412-
REDIS_CACHE_CON_STRING = 'redis://'
413-
414-
if CACHING_REDIS_PASSWORD:
415-
REDIS_CACHE_CON_STRING = '{}:{}@'.format(REDIS_CACHE_CON_STRING, CACHING_REDIS_PASSWORD)
416-
417-
REDIS_CACHE_CON_STRING = '{}{}:{}/{}'.format(
418-
REDIS_CACHE_CON_STRING,
419-
CACHING_REDIS_HOST,
420-
CACHING_REDIS_PORT,
421-
CACHING_REDIS_DATABASE
422-
)
423-
CACHEOPS_REDIS = REDIS_CACHE_CON_STRING
411+
CACHEOPS_REDIS = {
412+
'host': CACHING_REDIS_HOST,
413+
'port': CACHING_REDIS_PORT,
414+
'db': CACHING_REDIS_DATABASE,
415+
'password': CACHING_REDIS_PASSWORD,
416+
'ssl': CACHING_REDIS_SSL,
417+
'ssl_cert_reqs': None if CACHING_REDIS_SKIP_TLS_VERIFY else 'required',
418+
}
424419

425420
if not CACHE_TIMEOUT:
426421
CACHEOPS_ENABLED = False
@@ -568,6 +563,7 @@ def _setting(name, default=None):
568563
'DB': TASKS_REDIS_DATABASE,
569564
'PASSWORD': TASKS_REDIS_PASSWORD,
570565
'SSL': TASKS_REDIS_SSL,
566+
'SSL_CERT_REQS': None if TASKS_REDIS_SKIP_TLS_VERIFY else 'required',
571567
'DEFAULT_TIMEOUT': RQ_DEFAULT_TIMEOUT,
572568
}
573569

netbox/templates/generic/object_list.html

Lines changed: 46 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{% extends 'base.html' %}
22
{% load buttons %}
33
{% load helpers %}
4+
{% load render_table from django_tables2 %}
45
{% load static %}
56

67
{% block content %}
@@ -28,54 +29,56 @@ <h1>{% block title %}{{ content_type.model_class|meta:"verbose_name_plural"|bett
2829
{% block sidebar %}{% endblock %}
2930
</div>
3031
{% endif %}
32+
<div class="table-responsive">
3133
{% with bulk_edit_url=content_type.model_class|validated_viewname:"bulk_edit" bulk_delete_url=content_type.model_class|validated_viewname:"bulk_delete" %}
32-
{% if permissions.change or permissions.delete %}
33-
<form method="post" class="form form-horizontal">
34-
{% csrf_token %}
35-
<input type="hidden" name="return_url" value="{% if return_url %}{{ return_url }}{% else %}{{ request.path }}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}{% endif %}" />
36-
{% if table.paginator.num_pages > 1 %}
37-
<div id="select_all_box" class="hidden panel panel-default noprint">
38-
<div class="panel-body">
39-
<div class="checkbox-inline">
40-
<label for="select_all">
41-
<input type="checkbox" id="select_all" name="_all" />
42-
Select <strong>all {{ table.rows|length }} {{ table.data.verbose_name_plural }}</strong> matching query
43-
</label>
44-
</div>
45-
<div class="pull-right">
46-
{% if bulk_edit_url and permissions.change %}
47-
<button type="submit" name="_edit" formaction="{% url bulk_edit_url %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-warning btn-sm" disabled="disabled">
48-
<span class="mdi mdi-pencil" aria-hidden="true"></span> Edit All
49-
</button>
50-
{% endif %}
51-
{% if bulk_delete_url and permissions.delete %}
52-
<button type="submit" name="_delete" formaction="{% url bulk_delete_url %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-danger btn-sm" disabled="disabled">
53-
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Delete All
54-
</button>
55-
{% endif %}
34+
{% if permissions.change or permissions.delete %}
35+
<form method="post" class="form form-horizontal">
36+
{% csrf_token %}
37+
<input type="hidden" name="return_url" value="{% if return_url %}{{ return_url }}{% else %}{{ request.path }}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}{% endif %}" />
38+
{% if table.paginator.num_pages > 1 %}
39+
<div id="select_all_box" class="hidden panel panel-default noprint">
40+
<div class="panel-body">
41+
<div class="checkbox-inline">
42+
<label for="select_all">
43+
<input type="checkbox" id="select_all" name="_all" />
44+
Select <strong>all {{ table.rows|length }} {{ table.data.verbose_name_plural }}</strong> matching query
45+
</label>
46+
</div>
47+
<div class="pull-right">
48+
{% if bulk_edit_url and permissions.change %}
49+
<button type="submit" name="_edit" formaction="{% url bulk_edit_url %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-warning btn-sm" disabled="disabled">
50+
<span class="mdi mdi-pencil" aria-hidden="true"></span> Edit All
51+
</button>
52+
{% endif %}
53+
{% if bulk_delete_url and permissions.delete %}
54+
<button type="submit" name="_delete" formaction="{% url bulk_delete_url %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-danger btn-sm" disabled="disabled">
55+
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Delete All
56+
</button>
57+
{% endif %}
58+
</div>
5659
</div>
5760
</div>
58-
</div>
59-
{% endif %}
60-
{% include table_template|default:'responsive_table.html' %}
61-
<div class="pull-left noprint">
62-
{% block bulk_buttons %}{% endblock %}
63-
{% if bulk_edit_url and permissions.change %}
64-
<button type="submit" name="_edit" formaction="{% url bulk_edit_url %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-warning btn-sm">
65-
<span class="mdi mdi-pencil" aria-hidden="true"></span> Edit Selected
66-
</button>
67-
{% endif %}
68-
{% if bulk_delete_url and permissions.delete %}
69-
<button type="submit" name="_delete" formaction="{% url bulk_delete_url %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-danger btn-sm">
70-
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Delete Selected
71-
</button>
7261
{% endif %}
73-
</div>
74-
</form>
75-
{% else %}
76-
{% include table_template|default:'responsive_table.html' %}
77-
{% endif %}
62+
{% render_table table 'inc/table.html' %}
63+
<div class="pull-left noprint">
64+
{% block bulk_buttons %}{% endblock %}
65+
{% if bulk_edit_url and permissions.change %}
66+
<button type="submit" name="_edit" formaction="{% url bulk_edit_url %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-warning btn-sm">
67+
<span class="mdi mdi-pencil" aria-hidden="true"></span> Edit Selected
68+
</button>
69+
{% endif %}
70+
{% if bulk_delete_url and permissions.delete %}
71+
<button type="submit" name="_delete" formaction="{% url bulk_delete_url %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-danger btn-sm">
72+
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Delete Selected
73+
</button>
74+
{% endif %}
75+
</div>
76+
</form>
77+
{% else %}
78+
{% render_table table 'inc/table.html' %}
79+
{% endif %}
7880
{% endwith %}
81+
</div>
7982
{% include 'inc/paginator.html' with paginator=table.paginator page=table.page %}
8083
<div class="clearfix"></div>
8184
</div>

netbox/tenancy/models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from django.core.exceptions import ValidationError
12
from django.db import models
23
from django.urls import reverse
34
from mptt.models import MPTTModel, TreeForeignKey

netbox/utilities/utils.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -226,12 +226,12 @@ def prepare_cloned_fields(instance):
226226
field = instance._meta.get_field(field_name)
227227
field_value = field.value_from_object(instance)
228228

229-
# Swap out False with URL-friendly value
229+
# Pass False as null for boolean fields
230230
if field_value is False:
231-
field_value = ''
231+
params.append((field_name, ''))
232232

233233
# Omit empty values
234-
if field_value not in (None, ''):
234+
elif field_value not in (None, ''):
235235
params.append((field_name, field_value))
236236

237237
# Copy tags

netbox/virtualization/api/serializers.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,12 +115,14 @@ class VMInterfaceSerializer(PrimaryModelSerializer):
115115
required=False,
116116
many=True
117117
)
118+
count_ipaddresses = serializers.IntegerField(read_only=True)
118119

119120
class Meta:
120121
model = VMInterface
121122
fields = [
122123
'id', 'url', 'display', 'virtual_machine', 'name', 'enabled', 'parent', 'mtu', 'mac_address', 'description',
123124
'mode', 'untagged_vlan', 'tagged_vlans', 'tags', 'custom_fields', 'created', 'last_updated',
125+
'count_ipaddresses',
124126
]
125127

126128
def validate(self, data):

netbox/virtualization/api/views.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ def get_serializer_class(self):
8080

8181
class VMInterfaceViewSet(ModelViewSet):
8282
queryset = VMInterface.objects.prefetch_related(
83-
'virtual_machine', 'parent', 'tags', 'tagged_vlans'
83+
'virtual_machine', 'parent', 'tags', 'tagged_vlans', 'ip_addresses'
8484
)
8585
serializer_class = serializers.VMInterfaceSerializer
8686
filterset_class = filters.VMInterfaceFilterSet

netbox/virtualization/models.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,3 @@ def to_objectchange(self, action):
477477
@property
478478
def parent_object(self):
479479
return self.virtual_machine
480-
481-
@property
482-
def count_ipaddresses(self):
483-
return self.ip_addresses.count()

0 commit comments

Comments
 (0)