From 7656f30d028636902d34f1ec54e75805c22defde Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 4 Apr 2023 23:56:22 +1000 Subject: [PATCH] Filter queryset updates (#4571) * Update build API filters * Update company API filetrs * Update unit tests --- InvenTree/build/api.py | 105 ++++++++++++++++-------------------- InvenTree/build/test_api.py | 23 ++++---- InvenTree/company/api.py | 46 +++++++--------- 3 files changed, 77 insertions(+), 97 deletions(-) diff --git a/InvenTree/build/api.py b/InvenTree/build/api.py index ca839441e7..05fb8c7b84 100644 --- a/InvenTree/build/api.py +++ b/InvenTree/build/api.py @@ -18,13 +18,22 @@ from InvenTree.mixins import CreateAPI, RetrieveUpdateDestroyAPI, ListCreateAPI import build.admin import build.serializers from build.models import Build, BuildItem, BuildOrderAttachment - +import part.models from users.models import Owner class BuildFilter(rest_filters.FilterSet): """Custom filterset for BuildList API endpoint.""" + class Meta: + """Metaclass options""" + model = Build + fields = [ + 'parent', + 'sales_order', + 'part', + ] + status = rest_filters.NumberFilter(label='Status') active = rest_filters.BooleanFilter(label='Build is active', method='filter_active') @@ -32,22 +41,18 @@ class BuildFilter(rest_filters.FilterSet): def filter_active(self, queryset, name, value): """Filter the queryset to either include or exclude orders which are active.""" if str2bool(value): - queryset = queryset.filter(status__in=BuildStatus.ACTIVE_CODES) + return queryset.filter(status__in=BuildStatus.ACTIVE_CODES) else: - queryset = queryset.exclude(status__in=BuildStatus.ACTIVE_CODES) - - return queryset + return queryset.exclude(status__in=BuildStatus.ACTIVE_CODES) overdue = rest_filters.BooleanFilter(label='Build is overdue', method='filter_overdue') def filter_overdue(self, queryset, name, value): """Filter the queryset to either include or exclude orders which are overdue.""" if str2bool(value): - queryset = queryset.filter(Build.OVERDUE_FILTER) + return queryset.filter(Build.OVERDUE_FILTER) else: - queryset = queryset.exclude(Build.OVERDUE_FILTER) - - return queryset + return queryset.exclude(Build.OVERDUE_FILTER) assigned_to_me = rest_filters.BooleanFilter(label='assigned_to_me', method='filter_assigned_to_me') @@ -59,11 +64,9 @@ class BuildFilter(rest_filters.FilterSet): owners = Owner.get_owners_matching_user(self.request.user) if value: - queryset = queryset.filter(responsible__in=owners) + return queryset.filter(responsible__in=owners) else: - queryset = queryset.exclude(responsible__in=owners) - - return queryset + return queryset.exclude(responsible__in=owners) assigned_to = rest_filters.NumberFilter(label='responsible', method='filter_responsible') @@ -75,9 +78,7 @@ class BuildFilter(rest_filters.FilterSet): if len(owners) > 0 and owners[0].label() == 'user': owners = Owner.get_owners_matching_user(User.objects.get(pk=owners[0].owner_id)) - queryset = queryset.filter(responsible__in=owners) - - return queryset + return queryset.filter(responsible__in=owners) # Exact match for reference reference = rest_filters.CharFilter( @@ -171,18 +172,6 @@ class BuildList(APIDownloadMixin, ListCreateAPI): except (ValueError, Build.DoesNotExist): pass - # Filter by "parent" - parent = params.get('parent', None) - - if parent is not None: - queryset = queryset.filter(parent=parent) - - # Filter by sales_order - sales_order = params.get('sales_order', None) - - if sales_order is not None: - queryset = queryset.filter(sales_order=sales_order) - # Filter by "ancestor" builds ancestor = params.get('ancestor', None) @@ -199,12 +188,6 @@ class BuildList(APIDownloadMixin, ListCreateAPI): except (ValueError, Build.DoesNotExist): pass - # Filter by associated part? - part = params.get('part', None) - - if part is not None: - queryset = queryset.filter(part=part) - # Filter by 'date range' min_date = params.get('min_date', None) max_date = params.get('max_date', None) @@ -373,6 +356,34 @@ class BuildItemDetail(RetrieveUpdateDestroyAPI): serializer_class = build.serializers.BuildItemSerializer +class BuildItemFilter(rest_filters.FilterSet): + """Custom filterset for the BuildItemList API endpoint""" + + class Meta: + """Metaclass option""" + model = BuildItem + fields = [ + 'build', + 'stock_item', + 'bom_item', + 'install_into', + ] + + part = rest_filters.ModelChoiceFilter( + queryset=part.models.Part.objects.all(), + field_name='stock_item__part', + ) + + tracked = rest_filters.BooleanFilter(label='Tracked', method='filter_tracked') + + def filter_tracked(self, queryset, name, value): + """Filter the queryset based on whether build items are tracked""" + if str2bool(value): + return queryset.exclude(install_into=None) + else: + return queryset.filter(install_into=None) + + class BuildItemList(ListCreateAPI): """API endpoint for accessing a list of BuildItem objects. @@ -381,6 +392,7 @@ class BuildItemList(ListCreateAPI): """ serializer_class = build.serializers.BuildItemSerializer + filterset_class = BuildItemFilter def get_serializer(self, *args, **kwargs): """Returns a BuildItemSerializer instance based on the request.""" @@ -418,24 +430,6 @@ class BuildItemList(ListCreateAPI): params = self.request.query_params - # Does the user wish to filter by part? - part_pk = params.get('part', None) - - if part_pk: - queryset = queryset.filter(stock_item__part=part_pk) - - # Filter by "tracked" status - # Tracked means that the item is "installed" into a build output (stock item) - tracked = params.get('tracked', None) - - if tracked is not None: - tracked = str2bool(tracked) - - if tracked: - queryset = queryset.exclude(install_into=None) - else: - queryset = queryset.filter(install_into=None) - # Filter by output target output = params.get('output', None) @@ -452,13 +446,6 @@ class BuildItemList(ListCreateAPI): DjangoFilterBackend, ] - filterset_fields = [ - 'build', - 'stock_item', - 'bom_item', - 'install_into', - ] - class BuildAttachmentList(AttachmentMixin, ListCreateDestroyAPIView): """API endpoint for listing (and creating) BuildOrderAttachment objects.""" diff --git a/InvenTree/build/test_api.py b/InvenTree/build/test_api.py index bb15aaec8b..d27102e53d 100644 --- a/InvenTree/build/test_api.py +++ b/InvenTree/build/test_api.py @@ -37,49 +37,50 @@ class TestBuildAPI(InvenTreeAPITestCase): def test_get_build_list(self): """Test that we can retrieve list of build objects.""" url = reverse('api-build-list') - response = self.client.get(url, format='json') + + response = self.get(url, expected_code=200) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(len(response.data), 5) # Filter query by build status - response = self.client.get(url, {'status': 40}, format='json') + response = self.get(url, {'status': 40}, expected_code=200) self.assertEqual(len(response.data), 4) # Filter by "active" status - response = self.client.get(url, {'active': True}, format='json') + response = self.get(url, {'active': True}, expected_code=200) self.assertEqual(len(response.data), 1) self.assertEqual(response.data[0]['pk'], 1) - response = self.client.get(url, {'active': False}, format='json') + response = self.get(url, {'active': False}, expected_code=200) self.assertEqual(len(response.data), 4) # Filter by 'part' status - response = self.client.get(url, {'part': 25}, format='json') + response = self.get(url, {'part': 25}, expected_code=200) self.assertEqual(len(response.data), 1) # Filter by an invalid part - response = self.client.get(url, {'part': 99999}, format='json') - self.assertEqual(len(response.data), 0) + response = self.get(url, {'part': 99999}, expected_code=400) + self.assertIn('Select a valid choice', str(response.data)) # Get a certain reference - response = self.client.get(url, {'reference': 'BO-0001'}, format='json') + response = self.get(url, {'reference': 'BO-0001'}, expected_code=200) self.assertEqual(len(response.data), 1) # Get a certain reference - response = self.client.get(url, {'reference': 'BO-9999XX'}, format='json') + response = self.get(url, {'reference': 'BO-9999XX'}, expected_code=200) self.assertEqual(len(response.data), 0) def test_get_build_item_list(self): """Test that we can retrieve list of BuildItem objects.""" url = reverse('api-build-item-list') - response = self.client.get(url, format='json') + response = self.get(url, expected_code=200) self.assertEqual(response.status_code, status.HTTP_200_OK) # Test again, filtering by park ID - response = self.client.get(url, {'part': '1'}, format='json') + response = self.get(url, {'part': '1'}, expected_code=200) self.assertEqual(response.status_code, status.HTTP_200_OK) diff --git a/InvenTree/company/api.py b/InvenTree/company/api.py index 0f0ba01c0f..8be1181772 100644 --- a/InvenTree/company/api.py +++ b/InvenTree/company/api.py @@ -242,11 +242,30 @@ class ManufacturerPartAttachmentDetail(AttachmentMixin, RetrieveUpdateDestroyAPI serializer_class = ManufacturerPartAttachmentSerializer +class ManufacturerPartParameterFilter(rest_filters.FilterSet): + """Custom filterset for the ManufacturerPartParameterList API endpoint""" + + class Meta: + """Metaclass options""" + model = ManufacturerPartParameter + fields = [ + 'name', + 'value', + 'units', + 'manufacturer_part', + ] + + manufacturer = rest_filters.ModelChoiceFilter(queryset=Company.objects.all(), field_name='manufacturer_part__manufacturer') + + part = rest_filters.ModelChoiceFilter(queryset=part.models.Part.objects.all(), field_name='manufacturer_part__part') + + class ManufacturerPartParameterList(ListCreateDestroyAPIView): """API endpoint for list view of ManufacturerPartParamater model.""" queryset = ManufacturerPartParameter.objects.all() serializer_class = ManufacturerPartParameterSerializer + filterset_class = ManufacturerPartParameterFilter def get_serializer(self, *args, **kwargs): """Return serializer instance for this endpoint""" @@ -268,39 +287,12 @@ class ManufacturerPartParameterList(ListCreateDestroyAPIView): return self.serializer_class(*args, **kwargs) - def filter_queryset(self, queryset): - """Custom filtering for the queryset.""" - queryset = super().filter_queryset(queryset) - - params = self.request.query_params - - # Filter by manufacturer? - manufacturer = params.get('manufacturer', None) - - if manufacturer is not None: - queryset = queryset.filter(manufacturer_part__manufacturer=manufacturer) - - # Filter by part? - part = params.get('part', None) - - if part is not None: - queryset = queryset.filter(manufacturer_part__part=part) - - return queryset - filter_backends = [ DjangoFilterBackend, InvenTreeSearchFilter, filters.OrderingFilter, ] - filterset_fields = [ - 'name', - 'value', - 'units', - 'manufacturer_part', - ] - search_fields = [ 'name', 'value',