mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-28 03:16:25 -06:00
Merge branch 'netbox-community:develop' into 17686-config_option_for_disk_divider
This commit is contained in:
commit
391e4e6966
@ -1,5 +1,22 @@
|
|||||||
# NetBox v4.1
|
# NetBox v4.1
|
||||||
|
|
||||||
|
## v4.1.7 (FUTURE)
|
||||||
|
|
||||||
|
### Enhancements
|
||||||
|
|
||||||
|
* [#15239](https://github.com/netbox-community/netbox/issues/15239) - Enable adding/removing individual VLANs while bulk editing device interfaces
|
||||||
|
* [#17871](https://github.com/netbox-community/netbox/issues/17871) - Enable the assignment/removal of virtualization cluster via device bulk edit
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* [#17901](https://github.com/netbox-community/netbox/issues/17901) - Ensure GraphiQL UI resources are served locally
|
||||||
|
* [#17963](https://github.com/netbox-community/netbox/issues/17963) - Fix selection of all listed objects during bulk edit
|
||||||
|
* [#17969](https://github.com/netbox-community/netbox/issues/17969) - Fix system info export when a config revision exists
|
||||||
|
* [#17972](https://github.com/netbox-community/netbox/issues/17972) - Force evaluation of `LOGIN_REQUIRED` when requesting static media
|
||||||
|
* [#17986](https://github.com/netbox-community/netbox/issues/17986) - Correct labels for virtual machine & virtual disk size properties
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## v4.1.6 (2024-10-31)
|
## v4.1.6 (2024-10-31)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
@ -346,3 +346,32 @@ class BackgroundTaskTestCase(TestCase):
|
|||||||
self.assertIn(str(worker1.name), str(response.content))
|
self.assertIn(str(worker1.name), str(response.content))
|
||||||
self.assertIn('Birth', str(response.content))
|
self.assertIn('Birth', str(response.content))
|
||||||
self.assertIn('Total working time', str(response.content))
|
self.assertIn('Total working time', str(response.content))
|
||||||
|
|
||||||
|
|
||||||
|
class SystemTestCase(TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.user.is_staff = True
|
||||||
|
self.user.save()
|
||||||
|
|
||||||
|
def test_system_view_default(self):
|
||||||
|
# Test UI render
|
||||||
|
response = self.client.get(reverse('core:system'))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
# Test export
|
||||||
|
response = self.client.get(f"{reverse('core:system')}?export=true")
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
def test_system_view_with_config_revision(self):
|
||||||
|
ConfigRevision.objects.create()
|
||||||
|
|
||||||
|
# Test UI render
|
||||||
|
response = self.client.get(reverse('core:system'))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
# Test export
|
||||||
|
response = self.client.get(f"{reverse('core:system')}?export=true")
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
@ -626,11 +626,7 @@ class SystemView(UserPassesTestMixin, View):
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
try:
|
config = get_config()
|
||||||
config = ConfigRevision.objects.get(pk=cache.get('config_version'))
|
|
||||||
except ConfigRevision.DoesNotExist:
|
|
||||||
# Fall back to using the active config data if no record is found
|
|
||||||
config = get_config()
|
|
||||||
|
|
||||||
# Raw data export
|
# Raw data export
|
||||||
if 'export' in request.GET:
|
if 'export' in request.GET:
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.test import override_settings
|
from django.test import Client, override_settings
|
||||||
|
|
||||||
from dcim.models import Site
|
from dcim.models import Site
|
||||||
from netbox.constants import EMPTY_TABLE_TEXT
|
from netbox.constants import EMPTY_TABLE_TEXT
|
||||||
@ -74,3 +74,21 @@ class SearchViewTestCase(TestCase):
|
|||||||
self.assertHttpStatus(response, 200)
|
self.assertHttpStatus(response, 200)
|
||||||
content = str(response.content)
|
content = str(response.content)
|
||||||
self.assertIn(EMPTY_TABLE_TEXT, content)
|
self.assertIn(EMPTY_TABLE_TEXT, content)
|
||||||
|
|
||||||
|
|
||||||
|
class MediaViewTestCase(TestCase):
|
||||||
|
|
||||||
|
def test_media_login_required(self):
|
||||||
|
url = reverse('media', kwargs={'path': 'foo.txt'})
|
||||||
|
response = Client().get(url)
|
||||||
|
|
||||||
|
# Unauthenticated request should redirect to login page
|
||||||
|
self.assertHttpStatus(response, 302)
|
||||||
|
|
||||||
|
@override_settings(LOGIN_REQUIRED=False)
|
||||||
|
def test_media_login_not_required(self):
|
||||||
|
url = reverse('media', kwargs={'path': 'foo.txt'})
|
||||||
|
response = Client().get(url)
|
||||||
|
|
||||||
|
# Unauthenticated request should return a 404 (not found)
|
||||||
|
self.assertHttpStatus(response, 404)
|
||||||
|
@ -2,7 +2,6 @@ from django.conf import settings
|
|||||||
from django.conf.urls import include
|
from django.conf.urls import include
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
from django.views.decorators.cache import cache_page
|
from django.views.decorators.cache import cache_page
|
||||||
from django.views.static import serve
|
|
||||||
from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView
|
from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView
|
||||||
|
|
||||||
from account.views import LoginView, LogoutView
|
from account.views import LoginView, LogoutView
|
||||||
@ -10,7 +9,7 @@ from netbox.api.views import APIRootView, StatusView
|
|||||||
from netbox.graphql.schema import schema
|
from netbox.graphql.schema import schema
|
||||||
from netbox.graphql.views import NetBoxGraphQLView
|
from netbox.graphql.views import NetBoxGraphQLView
|
||||||
from netbox.plugins.urls import plugin_patterns, plugin_api_patterns
|
from netbox.plugins.urls import plugin_patterns, plugin_api_patterns
|
||||||
from netbox.views import HomeView, StaticMediaFailureView, SearchView, htmx
|
from netbox.views import HomeView, MediaView, StaticMediaFailureView, SearchView, htmx
|
||||||
|
|
||||||
_patterns = [
|
_patterns = [
|
||||||
|
|
||||||
@ -69,7 +68,7 @@ _patterns = [
|
|||||||
path('graphql/', NetBoxGraphQLView.as_view(schema=schema), name='graphql'),
|
path('graphql/', NetBoxGraphQLView.as_view(schema=schema), name='graphql'),
|
||||||
|
|
||||||
# Serving static media in Django to pipe it through LoginRequiredMiddleware
|
# Serving static media in Django to pipe it through LoginRequiredMiddleware
|
||||||
path('media/<path:path>', serve, {'document_root': settings.MEDIA_ROOT}),
|
path('media/<path:path>', MediaView.as_view(), name='media'),
|
||||||
path('media-failure/', StaticMediaFailureView.as_view(), name='media_failure'),
|
path('media-failure/', StaticMediaFailureView.as_view(), name='media_failure'),
|
||||||
|
|
||||||
# Plugins
|
# Plugins
|
||||||
|
@ -698,7 +698,7 @@ class BulkEditView(GetReturnURLMixin, BaseMultiObjectView):
|
|||||||
logger.debug("Form validation failed")
|
logger.debug("Form validation failed")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
form = self.form(request.POST, initial=initial_data)
|
form = self.form(initial=initial_data)
|
||||||
restrict_form_fields(form, request.user)
|
restrict_form_fields(form, request.user)
|
||||||
|
|
||||||
# Retrieve objects being edited
|
# Retrieve objects being edited
|
||||||
|
@ -8,6 +8,7 @@ from django.core.cache import cache
|
|||||||
from django.shortcuts import redirect, render
|
from django.shortcuts import redirect, render
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views.generic import View
|
from django.views.generic import View
|
||||||
|
from django.views.static import serve
|
||||||
from django_tables2 import RequestConfig
|
from django_tables2 import RequestConfig
|
||||||
from packaging import version
|
from packaging import version
|
||||||
|
|
||||||
@ -23,6 +24,7 @@ from utilities.views import ConditionalLoginRequiredMixin
|
|||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'HomeView',
|
'HomeView',
|
||||||
|
'MediaView',
|
||||||
'SearchView',
|
'SearchView',
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -115,3 +117,11 @@ class SearchView(ConditionalLoginRequiredMixin, View):
|
|||||||
'form': form,
|
'form': form,
|
||||||
'table': table,
|
'table': table,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
class MediaView(ConditionalLoginRequiredMixin, View):
|
||||||
|
"""
|
||||||
|
Wrap Django's serve() view to enforce LOGIN_REQUIRED for static media.
|
||||||
|
"""
|
||||||
|
def get(self, request, path):
|
||||||
|
return serve(request, path, document_root=settings.MEDIA_ROOT)
|
||||||
|
@ -8,7 +8,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-11-08 05:01+0000\n"
|
"POT-Creation-Date: 2024-11-13 05:01+0000\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@ -2452,11 +2452,11 @@ msgstr ""
|
|||||||
msgid "Failed to stop job {id}"
|
msgid "Failed to stop job {id}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/core/views.py:678
|
#: netbox/core/views.py:674
|
||||||
msgid "Plugins catalog could not be loaded"
|
msgid "Plugins catalog could not be loaded"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/core/views.py:712
|
#: netbox/core/views.py:708
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Plugin {name} not found"
|
msgid "Plugin {name} not found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -11141,7 +11141,7 @@ msgstr ""
|
|||||||
msgid "{class_name} must implement get_children()"
|
msgid "{class_name} must implement get_children()"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/netbox/views/misc.py:44
|
#: netbox/netbox/views/misc.py:46
|
||||||
msgid ""
|
msgid ""
|
||||||
"There was an error loading the dashboard configuration. A default dashboard "
|
"There was an error loading the dashboard configuration. A default dashboard "
|
||||||
"is in use."
|
"is in use."
|
||||||
@ -14848,12 +14848,12 @@ msgid "Memory (MB)"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/virtualization/forms/bulk_edit.py:174
|
#: netbox/virtualization/forms/bulk_edit.py:174
|
||||||
msgid "Disk (GB)"
|
msgid "Disk (MB)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/virtualization/forms/bulk_edit.py:334
|
#: netbox/virtualization/forms/bulk_edit.py:334
|
||||||
#: netbox/virtualization/forms/filtersets.py:251
|
#: netbox/virtualization/forms/filtersets.py:251
|
||||||
msgid "Size (GB)"
|
msgid "Size (MB)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/virtualization/forms/bulk_import.py:44
|
#: netbox/virtualization/forms/bulk_import.py:44
|
||||||
|
@ -171,7 +171,7 @@ class VirtualMachineBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
)
|
)
|
||||||
disk = forms.IntegerField(
|
disk = forms.IntegerField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Disk (GB)')
|
label=_('Disk (MB)')
|
||||||
)
|
)
|
||||||
description = forms.CharField(
|
description = forms.CharField(
|
||||||
label=_('Description'),
|
label=_('Description'),
|
||||||
@ -331,7 +331,7 @@ class VirtualDiskBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
)
|
)
|
||||||
size = forms.IntegerField(
|
size = forms.IntegerField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Size (GB)')
|
label=_('Size (MB)')
|
||||||
)
|
)
|
||||||
description = forms.CharField(
|
description = forms.CharField(
|
||||||
label=_('Description'),
|
label=_('Description'),
|
||||||
|
@ -248,7 +248,7 @@ class VirtualDiskFilterForm(NetBoxModelFilterSetForm):
|
|||||||
label=_('Virtual machine')
|
label=_('Virtual machine')
|
||||||
)
|
)
|
||||||
size = forms.IntegerField(
|
size = forms.IntegerField(
|
||||||
label=_('Size (GB)'),
|
label=_('Size (MB)'),
|
||||||
required=False,
|
required=False,
|
||||||
min_value=1
|
min_value=1
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user