Filter queryset updates (#4571)

* Update build API filters

* Update company API filetrs

* Update unit tests
This commit is contained in:
Oliver 2023-04-04 23:56:22 +10:00 committed by GitHub
parent 5ad4152270
commit 7656f30d02
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 77 additions and 97 deletions

View File

@ -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."""

View File

@ -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)

View File

@ -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',