2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-05-04 14:28:48 +00:00

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.admin
import build.serializers import build.serializers
from build.models import Build, BuildItem, BuildOrderAttachment from build.models import Build, BuildItem, BuildOrderAttachment
import part.models
from users.models import Owner from users.models import Owner
class BuildFilter(rest_filters.FilterSet): class BuildFilter(rest_filters.FilterSet):
"""Custom filterset for BuildList API endpoint.""" """Custom filterset for BuildList API endpoint."""
class Meta:
"""Metaclass options"""
model = Build
fields = [
'parent',
'sales_order',
'part',
]
status = rest_filters.NumberFilter(label='Status') status = rest_filters.NumberFilter(label='Status')
active = rest_filters.BooleanFilter(label='Build is active', method='filter_active') 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): def filter_active(self, queryset, name, value):
"""Filter the queryset to either include or exclude orders which are active.""" """Filter the queryset to either include or exclude orders which are active."""
if str2bool(value): if str2bool(value):
queryset = queryset.filter(status__in=BuildStatus.ACTIVE_CODES) return queryset.filter(status__in=BuildStatus.ACTIVE_CODES)
else: else:
queryset = queryset.exclude(status__in=BuildStatus.ACTIVE_CODES) return queryset.exclude(status__in=BuildStatus.ACTIVE_CODES)
return queryset
overdue = rest_filters.BooleanFilter(label='Build is overdue', method='filter_overdue') overdue = rest_filters.BooleanFilter(label='Build is overdue', method='filter_overdue')
def filter_overdue(self, queryset, name, value): def filter_overdue(self, queryset, name, value):
"""Filter the queryset to either include or exclude orders which are overdue.""" """Filter the queryset to either include or exclude orders which are overdue."""
if str2bool(value): if str2bool(value):
queryset = queryset.filter(Build.OVERDUE_FILTER) return queryset.filter(Build.OVERDUE_FILTER)
else: else:
queryset = queryset.exclude(Build.OVERDUE_FILTER) return queryset.exclude(Build.OVERDUE_FILTER)
return queryset
assigned_to_me = rest_filters.BooleanFilter(label='assigned_to_me', method='filter_assigned_to_me') 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) owners = Owner.get_owners_matching_user(self.request.user)
if value: if value:
queryset = queryset.filter(responsible__in=owners) return queryset.filter(responsible__in=owners)
else: else:
queryset = queryset.exclude(responsible__in=owners) return queryset.exclude(responsible__in=owners)
return queryset
assigned_to = rest_filters.NumberFilter(label='responsible', method='filter_responsible') 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': if len(owners) > 0 and owners[0].label() == 'user':
owners = Owner.get_owners_matching_user(User.objects.get(pk=owners[0].owner_id)) owners = Owner.get_owners_matching_user(User.objects.get(pk=owners[0].owner_id))
queryset = queryset.filter(responsible__in=owners) return queryset.filter(responsible__in=owners)
return queryset
# Exact match for reference # Exact match for reference
reference = rest_filters.CharFilter( reference = rest_filters.CharFilter(
@ -171,18 +172,6 @@ class BuildList(APIDownloadMixin, ListCreateAPI):
except (ValueError, Build.DoesNotExist): except (ValueError, Build.DoesNotExist):
pass 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 # Filter by "ancestor" builds
ancestor = params.get('ancestor', None) ancestor = params.get('ancestor', None)
@ -199,12 +188,6 @@ class BuildList(APIDownloadMixin, ListCreateAPI):
except (ValueError, Build.DoesNotExist): except (ValueError, Build.DoesNotExist):
pass pass
# Filter by associated part?
part = params.get('part', None)
if part is not None:
queryset = queryset.filter(part=part)
# Filter by 'date range' # Filter by 'date range'
min_date = params.get('min_date', None) min_date = params.get('min_date', None)
max_date = params.get('max_date', None) max_date = params.get('max_date', None)
@ -373,6 +356,34 @@ class BuildItemDetail(RetrieveUpdateDestroyAPI):
serializer_class = build.serializers.BuildItemSerializer 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): class BuildItemList(ListCreateAPI):
"""API endpoint for accessing a list of BuildItem objects. """API endpoint for accessing a list of BuildItem objects.
@ -381,6 +392,7 @@ class BuildItemList(ListCreateAPI):
""" """
serializer_class = build.serializers.BuildItemSerializer serializer_class = build.serializers.BuildItemSerializer
filterset_class = BuildItemFilter
def get_serializer(self, *args, **kwargs): def get_serializer(self, *args, **kwargs):
"""Returns a BuildItemSerializer instance based on the request.""" """Returns a BuildItemSerializer instance based on the request."""
@ -418,24 +430,6 @@ class BuildItemList(ListCreateAPI):
params = self.request.query_params 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 # Filter by output target
output = params.get('output', None) output = params.get('output', None)
@ -452,13 +446,6 @@ class BuildItemList(ListCreateAPI):
DjangoFilterBackend, DjangoFilterBackend,
] ]
filterset_fields = [
'build',
'stock_item',
'bom_item',
'install_into',
]
class BuildAttachmentList(AttachmentMixin, ListCreateDestroyAPIView): class BuildAttachmentList(AttachmentMixin, ListCreateDestroyAPIView):
"""API endpoint for listing (and creating) BuildOrderAttachment objects.""" """API endpoint for listing (and creating) BuildOrderAttachment objects."""

View File

@ -37,49 +37,50 @@ class TestBuildAPI(InvenTreeAPITestCase):
def test_get_build_list(self): def test_get_build_list(self):
"""Test that we can retrieve list of build objects.""" """Test that we can retrieve list of build objects."""
url = reverse('api-build-list') 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(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data), 5) self.assertEqual(len(response.data), 5)
# Filter query by build status # 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) self.assertEqual(len(response.data), 4)
# Filter by "active" status # 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(len(response.data), 1)
self.assertEqual(response.data[0]['pk'], 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) self.assertEqual(len(response.data), 4)
# Filter by 'part' status # 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) self.assertEqual(len(response.data), 1)
# Filter by an invalid part # Filter by an invalid part
response = self.client.get(url, {'part': 99999}, format='json') response = self.get(url, {'part': 99999}, expected_code=400)
self.assertEqual(len(response.data), 0) self.assertIn('Select a valid choice', str(response.data))
# Get a certain reference # 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) self.assertEqual(len(response.data), 1)
# Get a certain reference # 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) self.assertEqual(len(response.data), 0)
def test_get_build_item_list(self): def test_get_build_item_list(self):
"""Test that we can retrieve list of BuildItem objects.""" """Test that we can retrieve list of BuildItem objects."""
url = reverse('api-build-item-list') 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) self.assertEqual(response.status_code, status.HTTP_200_OK)
# Test again, filtering by park ID # 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) self.assertEqual(response.status_code, status.HTTP_200_OK)

View File

@ -242,11 +242,30 @@ class ManufacturerPartAttachmentDetail(AttachmentMixin, RetrieveUpdateDestroyAPI
serializer_class = ManufacturerPartAttachmentSerializer 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): class ManufacturerPartParameterList(ListCreateDestroyAPIView):
"""API endpoint for list view of ManufacturerPartParamater model.""" """API endpoint for list view of ManufacturerPartParamater model."""
queryset = ManufacturerPartParameter.objects.all() queryset = ManufacturerPartParameter.objects.all()
serializer_class = ManufacturerPartParameterSerializer serializer_class = ManufacturerPartParameterSerializer
filterset_class = ManufacturerPartParameterFilter
def get_serializer(self, *args, **kwargs): def get_serializer(self, *args, **kwargs):
"""Return serializer instance for this endpoint""" """Return serializer instance for this endpoint"""
@ -268,39 +287,12 @@ class ManufacturerPartParameterList(ListCreateDestroyAPIView):
return self.serializer_class(*args, **kwargs) 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 = [ filter_backends = [
DjangoFilterBackend, DjangoFilterBackend,
InvenTreeSearchFilter, InvenTreeSearchFilter,
filters.OrderingFilter, filters.OrderingFilter,
] ]
filterset_fields = [
'name',
'value',
'units',
'manufacturer_part',
]
search_fields = [ search_fields = [
'name', 'name',
'value', 'value',