2
0
mirror of https://github.com/inventree/InvenTree.git synced 2026-03-30 08:01:07 +00:00

Improvements for get_bulk_queryset (#11581)

* Improvements for get_bulk_queryset

- Limit scope to base view queryset
- Remove ability to provide arbitrary filters
- Remove feedback if zero items are found

* Adjust unit test

* Remove filter test

* Update CHANGELOG.md
This commit is contained in:
Oliver
2026-03-21 17:17:35 +11:00
committed by GitHub
parent c5bf915d10
commit 5adf33d354
5 changed files with 20 additions and 45 deletions

View File

@@ -26,6 +26,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Removed
- [#11581](https://github.com/inventree/InvenTree/pull/11581) removes the ability to specify arbitrary filters when performing bulk operations via the API. This functionality represented a significant security risk, and was not required for any existing use cases. Bulk operations now only work with a provided list of primary keys.
## 1.2.0 - 2026-02-12
### Breaking Changes

View File

@@ -423,23 +423,19 @@ class BulkOperationMixin:
def get_bulk_queryset(self, request):
"""Return a queryset based on the selection made in the request.
Selection can be made by providing either:
- items: A list of primary key values
- filters: A dictionary of filter values
Selection can be made by providing a list of primary key values,
which will be used to filter the queryset.
"""
model = self.serializer_class.Meta.model
items = request.data.pop('items', None)
filters = request.data.pop('filters', None)
all_filter = request.GET.get('all', None)
queryset = model.objects.all()
# Return the base queryset for this model
queryset = self.get_queryset()
if not items and not filters and all_filter is None:
if not items and all_filter is None:
raise ValidationError({
'non_field_errors': _(
'List of items or filters must be provided for bulk operation'
'List of items must be provided for bulk operation'
)
})
@@ -457,19 +453,6 @@ class BulkOperationMixin:
'non_field_errors': _('Invalid items list provided')
})
if filters:
if type(filters) is not dict:
raise ValidationError({
'non_field_errors': _('Filters must be provided as a dict')
})
try:
queryset = queryset.filter(**filters)
except Exception:
raise ValidationError({
'non_field_errors': _('Invalid filters provided')
})
if all_filter and not helpers.str2bool(all_filter):
raise ValidationError({
'non_field_errors': _('All filter must only be used with true')

View File

@@ -278,8 +278,7 @@ class BulkDeleteTests(InvenTreeAPITestCase):
response = self.delete(url, {}, expected_code=400)
self.assertIn(
'List of items or filters must be provided for bulk operation',
str(response.data),
'List of items must be provided for bulk operation', str(response.data)
)
# DELETE with invalid 'items'
@@ -287,11 +286,6 @@ class BulkDeleteTests(InvenTreeAPITestCase):
self.assertIn('Items must be provided as a list', str(response.data))
# DELETE with invalid 'filters'
response = self.delete(url, {'filters': [1, 2, 3]}, expected_code=400)
self.assertIn('Filters must be provided as a dict', str(response.data))
class SearchTests(InvenTreeAPITestCase):
"""Unit tests for global search endpoint."""

View File

@@ -1352,12 +1352,18 @@ class NotificationTest(InvenTreeAPITestCase):
# Now, let's bulk delete all 'unread' notifications via the API,
# but only associated with the logged in user
response = self.delete(url, {'filters': {'read': False}}, expected_code=200)
read_notifications = NotificationMessage.objects.filter(read=True)
response = self.delete(
url, {'items': [ntf.pk for ntf in read_notifications]}, expected_code=200
)
# Only 7 notifications should have been deleted,
# Only 3 notifications should have been deleted,
# as the notifications associated with other users must remain untouched
self.assertEqual(NotificationMessage.objects.count(), 13)
self.assertEqual(NotificationMessage.objects.filter(user=self.user).count(), 3)
self.assertEqual(NotificationMessage.objects.count(), 17)
self.assertEqual(NotificationMessage.objects.filter(user=self.user).count(), 7)
self.assertEqual(
NotificationMessage.objects.filter(user=self.user, read=True).count(), 0
)
def test_simple(self):
"""Test that a simple notification can be created."""

View File

@@ -2434,17 +2434,7 @@ class StockTestResultTest(StockAPITestCase):
self.delete(url, {}, expected_code=400)
# Now, let's delete all the newly created items with a single API request
# However, we will provide incorrect filters
response = self.delete(
url, {'items': tests, 'filters': {'stock_item': 10}}, expected_code=400
)
self.assertEqual(StockItemTestResult.objects.count(), n + 50)
# Try again, but with the correct filters this time
response = self.delete(
url, {'items': tests, 'filters': {'stock_item': 1}}, expected_code=200
)
response = self.delete(url, {'items': tests}, expected_code=200)
self.assertEqual(StockItemTestResult.objects.count(), n)