Skip to content

Commit cca1b0a

Browse files
Merge pull request #16132 from netbox-community/develop
Release v4.0.2
2 parents a3f7dc0 + 70c0aec commit cca1b0a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+2253
-2111
lines changed

.github/ISSUE_TEMPLATE/bug_report.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ body:
2626
attributes:
2727
label: NetBox Version
2828
description: What version of NetBox are you currently running?
29-
placeholder: v4.0.1
29+
placeholder: v4.0.2
3030
validations:
3131
required: true
3232
- 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: v4.0.1
17+
placeholder: v4.0.2
1818
validations:
1919
required: true
2020
- type: dropdown

.github/workflows/close-stale-issues.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ on:
77
workflow_dispatch:
88

99
permissions:
10+
actions: write
1011
issues: write
1112
pull-requests: write
1213

base_requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ social-auth-app-django
131131
strawberry-graphql
132132

133133
# Strawberry GraphQL Django extension
134-
# https://github.com/strawberry-graphql/strawberry-django/blob/main/CHANGELOG.md
134+
# https://github.com/strawberry-graphql/strawberry-django/releases
135135
strawberry-graphql-django
136136

137137
# SVG image rendering (used for rack elevations)

docs/configuration/security.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,9 +159,12 @@ Note that enabling this setting causes NetBox to update a user's session in the
159159

160160
## LOGIN_REQUIRED
161161

162-
Default: False
162+
Default: True
163+
164+
When enabled, only authenticated users are permitted to access any part of NetBox. Disabling this will allow unauthenticated users to access most areas of NetBox (but not make any changes).
163165

164-
Setting this to True will permit only authenticated users to access any part of NetBox. By default, anonymous users are permitted to access most data in NetBox but not make any changes.
166+
!!! info "Changed in NetBox v4.0.2"
167+
Prior to NetBox v4.0.2, this setting was disabled by default.
165168

166169
---
167170

docs/configuration/system.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,3 +198,11 @@ If `STORAGE_BACKEND` is not defined, this setting will be ignored.
198198
Default: UTC
199199

200200
The time zone NetBox will use when dealing with dates and times. It is recommended to use UTC time unless you have a specific need to use a local time zone. Please see the [list of available time zones](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones).
201+
202+
---
203+
204+
## TRANSLATION_ENABLED
205+
206+
Default: True
207+
208+
Enables language translation for the user interface. (This parameter maps to Django's [USE_I18N](https://docs.djangoproject.com/en/stable/ref/settings/#std-setting-USE_I18N) setting.)

docs/release-notes/version-4.0.md

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

3+
## v4.0.2 (2024-05-14)
4+
5+
!!! warning "Important"
6+
This release includes an important security fix, and is a strongly recommended update for all users. More details will follow.
7+
8+
### Enhancements
9+
10+
* [#15119](https://github.com/netbox-community/netbox/issues/15119) - Add cluster & cluster group UI filter fields for VLAN groups
11+
* [#16090](https://github.com/netbox-community/netbox/issues/16090) - Include current NetBox version when an unsupported plugin is detected
12+
* [#16096](https://github.com/netbox-community/netbox/issues/16096) - Introduce the `ENABLE_TRANSLATION` configuration parameter
13+
* [#16107](https://github.com/netbox-community/netbox/issues/16107) - Change the default value for `LOGIN_REQUIRED` to True
14+
* [#16127](https://github.com/netbox-community/netbox/issues/16127) - Add integration point for unsupported settings
15+
16+
### Bug Fixes
17+
18+
* [#16077](https://github.com/netbox-community/netbox/issues/16077) - Fix display of parameter values when viewing configuration revisions
19+
* [#16078](https://github.com/netbox-community/netbox/issues/16078) - Fix integer filters mistakenly marked as required for GraphQL API
20+
* [#16101](https://github.com/netbox-community/netbox/issues/16101) - Fix initial loading of pagination widget for dynamic object tables
21+
* [#16123](https://github.com/netbox-community/netbox/issues/16123) - Fix custom script execution via REST API
22+
* [#16124](https://github.com/netbox-community/netbox/issues/16124) - Fix GraphQL API support for querying virtual machine interfaces
23+
24+
---
25+
326
## v4.0.1 (2024-05-09)
427

528
### Enhancements

netbox/dcim/views.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2093,7 +2093,6 @@ class DeviceVirtualMachinesView(generic.ObjectChildrenView):
20932093
child_model = VirtualMachine
20942094
table = VirtualMachineTable
20952095
filterset = VirtualMachineFilterSet
2096-
template_name = 'generic/object_children.html'
20972096
tab = ViewTab(
20982097
label=_('Virtual Machines'),
20992098
badge=lambda obj: VirtualMachine.objects.filter(cluster=obj.cluster, device=obj).count(),
@@ -2986,7 +2985,6 @@ class InventoryItemChildrenView(generic.ObjectChildrenView):
29862985
child_model = InventoryItem
29872986
table = tables.InventoryItemTable
29882987
filterset = filtersets.InventoryItemFilterSet
2989-
template_name = 'generic/object_children.html'
29902988
tab = ViewTab(
29912989
label=_('Children'),
29922990
badge=lambda obj: obj.child_items.count(),

netbox/extras/api/views.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -240,9 +240,9 @@ def post(self, request, pk):
240240
raise RQWorkerNotRunningException()
241241

242242
if input_serializer.is_valid():
243-
script.result = Job.enqueue(
243+
Job.enqueue(
244244
run_script,
245-
instance=script.module,
245+
instance=script,
246246
name=script.python_class.class_name,
247247
user=request.user,
248248
data=input_serializer.data['data'],

netbox/extras/tables/tables.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import json
22

33
import django_tables2 as tables
4-
from django.conf import settings
54
from django.utils.translation import gettext_lazy as _
65

76
from extras.models import *
7+
from netbox.constants import EMPTY_TABLE_TEXT
88
from netbox.tables import BaseTable, NetBoxTable, columns
99
from .template_code import *
1010

@@ -550,7 +550,7 @@ class ScriptResultsTable(BaseTable):
550550
)
551551

552552
class Meta(BaseTable.Meta):
553-
empty_text = _('No results found')
553+
empty_text = _(EMPTY_TABLE_TEXT)
554554
fields = (
555555
'index', 'time', 'status', 'message',
556556
)
@@ -581,7 +581,7 @@ class ReportResultsTable(BaseTable):
581581
)
582582

583583
class Meta(BaseTable.Meta):
584-
empty_text = _('No results found')
584+
empty_text = _(EMPTY_TABLE_TEXT)
585585
fields = (
586586
'index', 'method', 'time', 'status', 'object', 'url', 'message',
587587
)

netbox/ipam/forms/filtersets.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, add_blank_choice
1111
from utilities.forms.fields import DynamicModelChoiceField, DynamicModelMultipleChoiceField, TagFilterField
1212
from utilities.forms.rendering import FieldSet
13-
from virtualization.models import VirtualMachine
13+
from virtualization.models import VirtualMachine, ClusterGroup, Cluster
1414
from vpn.models import L2VPN
1515

1616
__all__ = (
@@ -405,6 +405,7 @@ class VLANGroupFilterForm(NetBoxModelFilterSetForm):
405405
fieldsets = (
406406
FieldSet('q', 'filter_id', 'tag'),
407407
FieldSet('region', 'sitegroup', 'site', 'location', 'rack', name=_('Location')),
408+
FieldSet('cluster_group', 'cluster', name=_('Cluster')),
408409
FieldSet('min_vid', 'max_vid', name=_('VLAN ID')),
409410
)
410411
model = VLANGroup
@@ -445,6 +446,17 @@ class VLANGroupFilterForm(NetBoxModelFilterSetForm):
445446
max_value=VLAN_VID_MAX,
446447
label=_('Maximum VID')
447448
)
449+
cluster = DynamicModelMultipleChoiceField(
450+
queryset=Cluster.objects.all(),
451+
required=False,
452+
label=_('Cluster')
453+
)
454+
cluster_group = DynamicModelMultipleChoiceField(
455+
queryset=ClusterGroup.objects.all(),
456+
required=False,
457+
label=_('Cluster group')
458+
)
459+
448460
tag = TagFilterField(model)
449461

450462

netbox/ipam/tests/test_api.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,9 @@ class IPAddressTest(APIViewTestCases.APIViewTestCase):
648648
bulk_update_data = {
649649
'description': 'New description',
650650
}
651+
graphql_filter = {
652+
'address': '192.168.0.1/24',
653+
}
651654

652655
@classmethod
653656
def setUpTestData(cls):

netbox/ipam/views.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,6 @@ class ASNRangeASNsView(generic.ObjectChildrenView):
214214
child_model = ASN
215215
table = tables.ASNTable
216216
filterset = filtersets.ASNFilterSet
217-
template_name = 'generic/object_children.html'
218217
tab = ViewTab(
219218
label=_('ASNs'),
220219
badge=lambda x: x.get_child_asns().count(),
@@ -883,7 +882,6 @@ class IPAddressRelatedIPsView(generic.ObjectChildrenView):
883882
child_model = IPAddress
884883
table = tables.IPAddressTable
885884
filterset = filtersets.IPAddressFilterSet
886-
template_name = 'generic/object_children.html'
887885
tab = ViewTab(
888886
label=_('Related IPs'),
889887
badge=lambda x: x.get_related_ips().count(),
@@ -955,7 +953,6 @@ class VLANGroupVLANsView(generic.ObjectChildrenView):
955953
child_model = VLAN
956954
table = tables.VLANTable
957955
filterset = filtersets.VLANFilterSet
958-
template_name = 'generic/object_children.html'
959956
tab = ViewTab(
960957
label=_('VLANs'),
961958
badge=lambda x: x.get_child_vlans().count(),
@@ -1111,7 +1108,6 @@ class VLANInterfacesView(generic.ObjectChildrenView):
11111108
child_model = Interface
11121109
table = tables.VLANDevicesTable
11131110
filterset = InterfaceFilterSet
1114-
template_name = 'generic/object_children.html'
11151111
tab = ViewTab(
11161112
label=_('Device Interfaces'),
11171113
badge=lambda x: x.get_interfaces().count(),
@@ -1129,7 +1125,6 @@ class VLANVMInterfacesView(generic.ObjectChildrenView):
11291125
child_model = VMInterface
11301126
table = tables.VLANVirtualMachinesTable
11311127
filterset = VMInterfaceFilterSet
1132-
template_name = 'generic/object_children.html'
11331128
tab = ViewTab(
11341129
label=_('VM Interfaces'),
11351130
badge=lambda x: x.get_vminterfaces().count(),

netbox/netbox/configuration_example.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -157,9 +157,8 @@
157157
# authenticated to NetBox indefinitely.
158158
LOGIN_PERSISTENCE = False
159159

160-
# Setting this to True will permit only authenticated users to access any part of NetBox. By default, anonymous users
161-
# are permitted to access most data in NetBox but not make any changes.
162-
LOGIN_REQUIRED = False
160+
# Setting this to False will permit unauthenticated users to access most areas of NetBox (but not make any changes).
161+
LOGIN_REQUIRED = True
163162

164163
# The length of time (in seconds) for which a user will remain logged into the web UI before being prompted to
165164
# re-authenticate. (Default: 1209600 [14 days])

netbox/netbox/constants.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,6 @@
4141
# General-purpose tokens
4242
CENSOR_TOKEN = '********'
4343
CENSOR_TOKEN_CHANGED = '***CHANGED***'
44+
45+
# Placeholder text for empty tables
46+
EMPTY_TABLE_TEXT = 'No results found'

netbox/netbox/graphql/filter_mixins.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ def map_strawberry_type(field):
8787
pass
8888
elif issubclass(type(field), django_filters.NumberFilter):
8989
should_create_function = True
90-
attr_type = int
90+
attr_type = int | None
9191
elif issubclass(type(field), django_filters.ModelMultipleChoiceFilter):
9292
should_create_function = True
9393
attr_type = List[str] | None

netbox/netbox/plugins/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,13 +138,15 @@ def validate(cls, user_config, netbox_version):
138138
min_version = version.parse(cls.min_version)
139139
if current_version < min_version:
140140
raise ImproperlyConfigured(
141-
f"Plugin {cls.__module__} requires NetBox minimum version {cls.min_version}."
141+
f"Plugin {cls.__module__} requires NetBox minimum version {cls.min_version} (current: "
142+
f"{netbox_version})."
142143
)
143144
if cls.max_version is not None:
144145
max_version = version.parse(cls.max_version)
145146
if current_version > max_version:
146147
raise ImproperlyConfigured(
147-
f"Plugin {cls.__module__} requires NetBox maximum version {cls.max_version}."
148+
f"Plugin {cls.__module__} requires NetBox maximum version {cls.max_version} (current: "
149+
f"{netbox_version})."
148150
)
149151

150152
# Verify required configuration settings

netbox/netbox/preferences.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,20 @@ def get_page_lengths():
2323
),
2424
description=_('Enable dynamic UI navigation'),
2525
default=False,
26-
experimental=True
26+
warning=_('Experimental feature')
2727
),
2828
'locale.language': UserPreference(
2929
label=_('Language'),
3030
choices=(
3131
('', _('Auto')),
3232
*settings.LANGUAGES,
3333
),
34-
description=_('Forces UI translation to the specified language.')
34+
description=_('Forces UI translation to the specified language'),
35+
warning=(
36+
_("Support for translation has been disabled locally")
37+
if not settings.TRANSLATION_ENABLED
38+
else ''
39+
)
3540
),
3641
'pagination.per_page': UserPreference(
3742
label=_('Page length'),

netbox/netbox/settings.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
# Environment setup
2626
#
2727

28-
VERSION = '4.0.1'
28+
VERSION = '4.0.2'
2929
HOSTNAME = platform.node()
3030
# Set the base directory two levels up
3131
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@@ -105,7 +105,7 @@
105105
LANGUAGE_COOKIE_PATH = CSRF_COOKIE_PATH
106106
LOGGING = getattr(configuration, 'LOGGING', {})
107107
LOGIN_PERSISTENCE = getattr(configuration, 'LOGIN_PERSISTENCE', False)
108-
LOGIN_REQUIRED = getattr(configuration, 'LOGIN_REQUIRED', False)
108+
LOGIN_REQUIRED = getattr(configuration, 'LOGIN_REQUIRED', True)
109109
LOGIN_TIMEOUT = getattr(configuration, 'LOGIN_TIMEOUT', None)
110110
LOGOUT_REDIRECT_URL = getattr(configuration, 'LOGOUT_REDIRECT_URL', 'home')
111111
MEDIA_ROOT = getattr(configuration, 'MEDIA_ROOT', os.path.join(BASE_DIR, 'media')).rstrip('/')
@@ -156,6 +156,7 @@
156156
STORAGE_BACKEND = getattr(configuration, 'STORAGE_BACKEND', None)
157157
STORAGE_CONFIG = getattr(configuration, 'STORAGE_CONFIG', {})
158158
TIME_ZONE = getattr(configuration, 'TIME_ZONE', 'UTC')
159+
TRANSLATION_ENABLED = getattr(configuration, 'TRANSLATION_ENABLED', True)
159160

160161
# Load any dynamic configuration parameters which have been hard-coded in the configuration file
161162
for param in CONFIG_PARAMS:
@@ -445,6 +446,9 @@ def _setting(name, default=None):
445446
# Use timezone-aware datetime objects
446447
USE_TZ = True
447448

449+
# Toggle language translation support
450+
USE_I18N = TRANSLATION_ENABLED
451+
448452
# WSGI
449453
WSGI_APPLICATION = 'netbox.wsgi.application'
450454
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
@@ -801,3 +805,10 @@ def _setting(name, default=None):
801805
RQ_QUEUES.update({
802806
f"{plugin_name}.{queue}": RQ_PARAMS for queue in plugin_config.queues
803807
})
808+
809+
# UNSUPPORTED FUNCTIONALITY: Import any local overrides.
810+
try:
811+
from .local_settings import *
812+
_UNSUPPORTED_SETTINGS = True
813+
except ImportError:
814+
pass

netbox/netbox/tables/tables.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from core.models import ObjectType
1515
from extras.choices import *
1616
from extras.models import CustomField, CustomLink
17+
from netbox.constants import EMPTY_TABLE_TEXT
1718
from netbox.registry import registry
1819
from netbox.tables import columns
1920
from utilities.paginator import EnhancedPaginator, get_paginate_count
@@ -258,7 +259,7 @@ class Meta:
258259
attrs = {
259260
'class': 'table table-hover object-list',
260261
}
261-
empty_text = _('No results found')
262+
empty_text = _(EMPTY_TABLE_TEXT)
262263

263264
def __init__(self, data, highlight=None, **kwargs):
264265
self.highlight = highlight

netbox/netbox/tests/test_plugins.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ def test_admin(self):
4242
url = reverse('admin:dummy_plugin_dummymodel_add')
4343
self.assertEqual(url, '/admin/dummy_plugin/dummymodel/add/')
4444

45+
@override_settings(LOGIN_REQUIRED=False)
4546
def test_views(self):
4647

4748
# Test URL resolution
@@ -53,7 +54,7 @@ def test_views(self):
5354
response = client.get(url)
5455
self.assertEqual(response.status_code, 200)
5556

56-
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
57+
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'], LOGIN_REQUIRED=False)
5758
def test_api_views(self):
5859

5960
# Test URL resolution
@@ -65,6 +66,7 @@ def test_api_views(self):
6566
response = client.get(url)
6667
self.assertEqual(response.status_code, 200)
6768

69+
@override_settings(LOGIN_REQUIRED=False)
6870
def test_registered_views(self):
6971

7072
# Test URL resolution

0 commit comments

Comments
 (0)