mirror of
https://github.com/inventree/InvenTree.git
synced 2026-09-27 14:16:02 +00:00
Selection list fix (#12908)
* Enable filtering / searching / sorting on the SelectionListList API endpoint * Add regression tests * Bump API version
This commit is contained in:
@@ -1,11 +1,14 @@
|
||||
"""InvenTree API version information."""
|
||||
|
||||
# InvenTree API version
|
||||
INVENTREE_API_VERSION = 548
|
||||
INVENTREE_API_VERSION = 549
|
||||
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
||||
|
||||
INVENTREE_API_TEXT = """
|
||||
|
||||
v549 -> 2026-09-22 : https://github.com/inventree/InvenTree/pull/12908
|
||||
- Adds filtering / ordering / searching options to the SelectionList API endpoint
|
||||
|
||||
v548 -> 2026-09-19 : https://github.com/inventree/InvenTree/pull/12886
|
||||
- Fix API permissions for multiple endpoints
|
||||
|
||||
|
||||
@@ -1584,6 +1584,16 @@ class SelectionListMixin(OutputOptionsMixin):
|
||||
class SelectionListList(SelectionListMixin, ListCreateAPI):
|
||||
"""List view for SelectionList objects."""
|
||||
|
||||
filter_backends = SEARCH_ORDER_FILTER
|
||||
|
||||
filterset_fields = ['active', 'locked']
|
||||
|
||||
search_fields = ['name', 'description']
|
||||
|
||||
ordering_fields = ['name', 'active', 'locked']
|
||||
|
||||
ordering = 'name'
|
||||
|
||||
|
||||
class SelectionListDetail(SelectionListMixin, RetrieveUpdateDestroyAPI):
|
||||
"""Detail view for a SelectionList object."""
|
||||
|
||||
@@ -2583,6 +2583,95 @@ class SelectionListStaffPermissionAPITests(InvenTreeAPITestCase):
|
||||
)
|
||||
|
||||
|
||||
class SelectionListFilterAPITests(InvenTreeAPITestCase):
|
||||
"""Tests for search / filter / ordering options on the SelectionList list endpoint."""
|
||||
|
||||
def setUp(self):
|
||||
"""Create a handful of SelectionList objects to filter/search/order over."""
|
||||
super().setUp()
|
||||
|
||||
self.list_url = reverse('api-selectionlist-list')
|
||||
|
||||
self.list_a = SelectionList.objects.create(
|
||||
name='Colors', description='A list of colors', active=True, locked=False
|
||||
)
|
||||
self.list_b = SelectionList.objects.create(
|
||||
name='Shapes', description='A list of shapes', active=False, locked=False
|
||||
)
|
||||
self.list_c = SelectionList.objects.create(
|
||||
name='Sizes', description='Locked list of sizes', active=True, locked=True
|
||||
)
|
||||
|
||||
def test_list_all(self):
|
||||
"""With no filters applied, all SelectionList objects are returned."""
|
||||
response = self.get(self.list_url, expected_code=200)
|
||||
names = {item['name'] for item in response.data}
|
||||
self.assertEqual(names, {'Colors', 'Shapes', 'Sizes'})
|
||||
|
||||
def test_filter_active(self):
|
||||
"""The 'active' filter restricts results to matching SelectionList objects."""
|
||||
response = self.get(self.list_url, {'active': True}, expected_code=200)
|
||||
names = {item['name'] for item in response.data}
|
||||
self.assertEqual(names, {'Colors', 'Sizes'})
|
||||
|
||||
response = self.get(self.list_url, {'active': False}, expected_code=200)
|
||||
names = {item['name'] for item in response.data}
|
||||
self.assertEqual(names, {'Shapes'})
|
||||
|
||||
def test_filter_locked(self):
|
||||
"""The 'locked' filter restricts results to matching SelectionList objects."""
|
||||
response = self.get(self.list_url, {'locked': True}, expected_code=200)
|
||||
names = {item['name'] for item in response.data}
|
||||
self.assertEqual(names, {'Sizes'})
|
||||
|
||||
response = self.get(self.list_url, {'locked': False}, expected_code=200)
|
||||
names = {item['name'] for item in response.data}
|
||||
self.assertEqual(names, {'Colors', 'Shapes'})
|
||||
|
||||
def test_filter_active_and_locked(self):
|
||||
"""Multiple filters can be combined."""
|
||||
response = self.get(
|
||||
self.list_url, {'active': True, 'locked': False}, expected_code=200
|
||||
)
|
||||
names = {item['name'] for item in response.data}
|
||||
self.assertEqual(names, {'Colors'})
|
||||
|
||||
def test_search_name(self):
|
||||
"""Searching matches against the 'name' field."""
|
||||
response = self.get(self.list_url, {'search': 'Shape'}, expected_code=200)
|
||||
names = {item['name'] for item in response.data}
|
||||
self.assertEqual(names, {'Shapes'})
|
||||
|
||||
def test_search_description(self):
|
||||
"""Searching matches against the 'description' field."""
|
||||
response = self.get(self.list_url, {'search': 'Locked list'}, expected_code=200)
|
||||
names = {item['name'] for item in response.data}
|
||||
self.assertEqual(names, {'Sizes'})
|
||||
|
||||
def test_search_no_match(self):
|
||||
"""A search term which matches nothing returns an empty result set."""
|
||||
response = self.get(self.list_url, {'search': 'nonexistent'}, expected_code=200)
|
||||
self.assertEqual(len(response.data), 0)
|
||||
|
||||
def test_default_ordering(self):
|
||||
"""By default, results are ordered by name (ascending)."""
|
||||
response = self.get(self.list_url, expected_code=200)
|
||||
names = [item['name'] for item in response.data]
|
||||
self.assertEqual(names, ['Colors', 'Shapes', 'Sizes'])
|
||||
|
||||
def test_ordering_name_descending(self):
|
||||
"""Results can be ordered by name in descending order."""
|
||||
response = self.get(self.list_url, {'ordering': '-name'}, expected_code=200)
|
||||
names = [item['name'] for item in response.data]
|
||||
self.assertEqual(names, ['Sizes', 'Shapes', 'Colors'])
|
||||
|
||||
def test_ordering_active(self):
|
||||
"""Results can be ordered by the 'active' field."""
|
||||
response = self.get(self.list_url, {'ordering': 'active'}, expected_code=200)
|
||||
active_values = [item['active'] for item in response.data]
|
||||
self.assertEqual(active_values, sorted(active_values))
|
||||
|
||||
|
||||
class NotePermissionAPITests(InvenTreeAPITestCase):
|
||||
"""Tests for Note API permission enforcement.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user