From c392b9cf03b0ca22d9a9d2d1ac98e3e3682d5cbb Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 22 Sep 2026 11:19:21 +1000 Subject: [PATCH] Selection list fix (#12908) * Enable filtering / searching / sorting on the SelectionListList API endpoint * Add regression tests * Bump API version --- .../InvenTree/InvenTree/api_version.py | 5 +- src/backend/InvenTree/common/api.py | 10 +++ src/backend/InvenTree/common/test_api.py | 89 +++++++++++++++++++ 3 files changed, 103 insertions(+), 1 deletion(-) diff --git a/src/backend/InvenTree/InvenTree/api_version.py b/src/backend/InvenTree/InvenTree/api_version.py index f492313abe..20d59d51c1 100644 --- a/src/backend/InvenTree/InvenTree/api_version.py +++ b/src/backend/InvenTree/InvenTree/api_version.py @@ -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 diff --git a/src/backend/InvenTree/common/api.py b/src/backend/InvenTree/common/api.py index a095d121a0..2922905718 100644 --- a/src/backend/InvenTree/common/api.py +++ b/src/backend/InvenTree/common/api.py @@ -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.""" diff --git a/src/backend/InvenTree/common/test_api.py b/src/backend/InvenTree/common/test_api.py index dff21d122c..0ba7fa4f02 100644 --- a/src/backend/InvenTree/common/test_api.py +++ b/src/backend/InvenTree/common/test_api.py @@ -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.