diff --git a/docs/configuration/miscellaneous.md b/docs/configuration/miscellaneous.md index fd410a9d4..f143be139 100644 --- a/docs/configuration/miscellaneous.md +++ b/docs/configuration/miscellaneous.md @@ -80,6 +80,14 @@ changes in the database indefinitely. --- +## DATA_UPLOAD_MAX_MEMORY_SIZE + +Default: `2621440` (2.5 MB) + +The maximum size (in bytes) of an incoming HTTP request (i.e. `GET` or `POST` data). Requests which exceed this size will raise a `RequestDataTooBig` exception. + +--- + ## ENFORCE_GLOBAL_UNIQUE !!! tip "Dynamic Configuration Parameter" @@ -90,9 +98,9 @@ By default, NetBox will permit users to create duplicate prefixes and IP address --- -## `FILE_UPLOAD_MAX_MEMORY_SIZE` +## FILE_UPLOAD_MAX_MEMORY_SIZE -Default: `2621440` (2.5 MB). +Default: `2621440` (2.5 MB) The maximum amount (in bytes) of uploaded data that will be held in memory before being written to the filesystem. Changing this setting can be useful for example to be able to upload files bigger than 2.5MB to custom scripts for processing. diff --git a/docs/release-notes/version-3.6.md b/docs/release-notes/version-3.6.md index 6cbcf3e19..762302ddc 100644 --- a/docs/release-notes/version-3.6.md +++ b/docs/release-notes/version-3.6.md @@ -5,6 +5,7 @@ ### Enhancements * [#12831](https://github.com/netbox-community/netbox/issues/12831) - Include circuit description in cable trace SVG image +* [#12872](https://github.com/netbox-community/netbox/issues/12872) - Introduce the `DATA_UPLOAD_MAX_MEMORY_SIZE` configuration parameter * [#13950](https://github.com/netbox-community/netbox/issues/13950) - Display custom choice field labels rather than values in UI ### Bug Fixes @@ -19,6 +20,9 @@ * [#13910](https://github.com/netbox-community/netbox/issues/13910) - Correct "add device" button link under platform view * [#13944](https://github.com/netbox-community/netbox/issues/13944) - Correct serialization of several report attributes in the REST API * [#13966](https://github.com/netbox-community/netbox/issues/13966) - Restore "last login" column on users table +* [#14013](https://github.com/netbox-community/netbox/issues/14013) - Fix device role filter choices under inventory items list filters +* [#14023](https://github.com/netbox-community/netbox/issues/14023) - Fix exception when bulk disconnecting interfaces connected to the same cable +* [#14026](https://github.com/netbox-community/netbox/issues/14026) - Optimize the automatic creation of available IP addresses for large prefixes --- diff --git a/netbox/dcim/forms/filtersets.py b/netbox/dcim/forms/filtersets.py index 43e5f4481..7f99d1ca4 100644 --- a/netbox/dcim/forms/filtersets.py +++ b/netbox/dcim/forms/filtersets.py @@ -109,7 +109,7 @@ class DeviceComponentFilterForm(NetBoxModelFilterSetForm): required=False, label=_('Device type') ) - role_id = DynamicModelMultipleChoiceField( + device_role_id = DynamicModelMultipleChoiceField( queryset=DeviceRole.objects.all(), required=False, label=_('Device role') @@ -1136,7 +1136,7 @@ class ConsolePortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm): (None, ('q', 'filter_id', 'tag')), (_('Attributes'), ('name', 'label', 'type', 'speed')), (_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')), - (_('Device'), ('device_type_id', 'role_id', 'device_id', 'virtual_chassis_id')), + (_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')), (_('Connection'), ('cabled', 'connected', 'occupied')), ) type = forms.MultipleChoiceField( @@ -1158,7 +1158,7 @@ class ConsoleServerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterF (None, ('q', 'filter_id', 'tag')), (_('Attributes'), ('name', 'label', 'type', 'speed')), (_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')), - (_('Device'), ('device_type_id', 'role_id', 'device_id', 'virtual_chassis_id')), + (_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')), (_('Connection'), ('cabled', 'connected', 'occupied')), ) type = forms.MultipleChoiceField( @@ -1180,7 +1180,7 @@ class PowerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm): (None, ('q', 'filter_id', 'tag')), (_('Attributes'), ('name', 'label', 'type')), (_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')), - (_('Device'), ('device_type_id', 'role_id', 'device_id', 'virtual_chassis_id')), + (_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')), (_('Connection'), ('cabled', 'connected', 'occupied')), ) type = forms.MultipleChoiceField( @@ -1197,7 +1197,7 @@ class PowerOutletFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm): (None, ('q', 'filter_id', 'tag')), (_('Attributes'), ('name', 'label', 'type')), (_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')), - (_('Device'), ('device_type_id', 'role_id', 'device_id', 'virtual_chassis_id')), + (_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')), (_('Connection'), ('cabled', 'connected', 'occupied')), ) type = forms.MultipleChoiceField( @@ -1217,7 +1217,7 @@ class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm): (_('PoE'), ('poe_mode', 'poe_type')), (_('Wireless'), ('rf_role', 'rf_channel', 'rf_channel_width', 'tx_power')), (_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')), - (_('Device'), ('device_type_id', 'role_id', 'device_id', 'virtual_chassis_id', 'vdc_id')), + (_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', 'vdc_id')), (_('Connection'), ('cabled', 'connected', 'occupied')), ) vdc_id = DynamicModelMultipleChoiceField( @@ -1324,7 +1324,7 @@ class FrontPortFilterForm(CabledFilterForm, DeviceComponentFilterForm): (None, ('q', 'filter_id', 'tag')), (_('Attributes'), ('name', 'label', 'type', 'color')), (_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')), - (_('Device'), ('device_type_id', 'role_id', 'device_id', 'virtual_chassis_id')), + (_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')), (_('Cable'), ('cabled', 'occupied')), ) model = FrontPort @@ -1346,7 +1346,7 @@ class RearPortFilterForm(CabledFilterForm, DeviceComponentFilterForm): (None, ('q', 'filter_id', 'tag')), (_('Attributes'), ('name', 'label', 'type', 'color')), (_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')), - (_('Device'), ('device_type_id', 'role_id', 'device_id', 'virtual_chassis_id')), + (_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')), (_('Cable'), ('cabled', 'occupied')), ) type = forms.MultipleChoiceField( @@ -1367,7 +1367,7 @@ class ModuleBayFilterForm(DeviceComponentFilterForm): (None, ('q', 'filter_id', 'tag')), (_('Attributes'), ('name', 'label', 'position')), (_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')), - (_('Device'), ('device_type_id', 'role_id', 'device_id', 'virtual_chassis_id')), + (_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')), ) tag = TagFilterField(model) position = forms.CharField( @@ -1382,7 +1382,7 @@ class DeviceBayFilterForm(DeviceComponentFilterForm): (None, ('q', 'filter_id', 'tag')), (_('Attributes'), ('name', 'label')), (_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')), - (_('Device'), ('device_type_id', 'role_id', 'device_id', 'virtual_chassis_id')), + (_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')), ) tag = TagFilterField(model) @@ -1393,7 +1393,7 @@ class InventoryItemFilterForm(DeviceComponentFilterForm): (None, ('q', 'filter_id', 'tag')), (_('Attributes'), ('name', 'label', 'role_id', 'manufacturer_id', 'serial', 'asset_tag', 'discovered')), (_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')), - (_('Device'), ('device_type_id', 'role_id', 'device_id', 'virtual_chassis_id')), + (_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')), ) role_id = DynamicModelMultipleChoiceField( queryset=InventoryItemRole.objects.all(), diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 2f661e613..7c75dd26e 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -122,16 +122,18 @@ class BulkDisconnectView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View) if form.is_valid(): with transaction.atomic(): - count = 0 + cable_ids = set() for obj in self.queryset.filter(pk__in=form.cleaned_data['pk']): - if obj.cable is None: - continue - obj.cable.delete() - count += 1 + if obj.cable: + cable_ids.add(obj.cable.pk) + count += 1 + for cable in Cable.objects.filter(pk__in=cable_ids): + cable.delete() - messages.success(request, "Disconnected {} {}".format( - count, self.queryset.model._meta.verbose_name_plural + messages.success(request, _("Disconnected {count} {type}").format( + count=count, + type=self.queryset.model._meta.verbose_name_plural )) return redirect(return_url) diff --git a/netbox/ipam/api/views.py b/netbox/ipam/api/views.py index 643e6a8e0..662b393de 100644 --- a/netbox/ipam/api/views.py +++ b/netbox/ipam/api/views.py @@ -266,6 +266,7 @@ class AvailableObjectsView(ObjectValidationMixin, APIView): # Normalize request data to a list of objects requested_objects = request.data if isinstance(request.data, list) else [request.data] + limit = len(requested_objects) # Serialize and validate the request data serializer = self.write_serializer_class(data=requested_objects, many=True, context={ @@ -279,7 +280,7 @@ class AvailableObjectsView(ObjectValidationMixin, APIView): ) with advisory_lock(ADVISORY_LOCK_KEYS[self.advisory_lock_key]): - available_objects = self.get_available_objects(parent) + available_objects = self.get_available_objects(parent, limit) # Determine if the requested number of objects is available if not self.check_sufficient_available(serializer.validated_data, available_objects): diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 975e86858..15c456584 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -95,6 +95,7 @@ CORS_ORIGIN_WHITELIST = getattr(configuration, 'CORS_ORIGIN_WHITELIST', []) CSRF_COOKIE_NAME = getattr(configuration, 'CSRF_COOKIE_NAME', 'csrftoken') CSRF_COOKIE_SECURE = getattr(configuration, 'CSRF_COOKIE_SECURE', False) CSRF_TRUSTED_ORIGINS = getattr(configuration, 'CSRF_TRUSTED_ORIGINS', []) +DATA_UPLOAD_MAX_MEMORY_SIZE = getattr(configuration, 'DATA_UPLOAD_MAX_MEMORY_SIZE', 2621440) DATE_FORMAT = getattr(configuration, 'DATE_FORMAT', 'N j, Y') DATETIME_FORMAT = getattr(configuration, 'DATETIME_FORMAT', 'N j, Y g:i a') DEBUG = getattr(configuration, 'DEBUG', False)