mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Update part filters (#7264)
* Expose filter for "bom_valid" status * Expose part filter for "starred" status * Bump API version * Add simple unit test * Add unit test for "starred" filtering
This commit is contained in:
parent
2ebe785a75
commit
5cb61d5ad0
@ -1,11 +1,15 @@
|
|||||||
"""InvenTree API version information."""
|
"""InvenTree API version information."""
|
||||||
|
|
||||||
# InvenTree API version
|
# InvenTree API version
|
||||||
INVENTREE_API_VERSION = 198
|
INVENTREE_API_VERSION = 199
|
||||||
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
||||||
|
|
||||||
INVENTREE_API_TEXT = """
|
INVENTREE_API_TEXT = """
|
||||||
|
|
||||||
|
v199 - 2024-05-20 : https://github.com/inventree/InvenTree/pull/7264
|
||||||
|
- Expose "bom_valid" filter for the Part API
|
||||||
|
- Expose "starred" filter for the Part API
|
||||||
|
|
||||||
v198 - 2024-05-19 : https://github.com/inventree/InvenTree/pull/7258
|
v198 - 2024-05-19 : https://github.com/inventree/InvenTree/pull/7258
|
||||||
- Fixed lookup field conflicts in the plugins API
|
- Fixed lookup field conflicts in the plugins API
|
||||||
|
|
||||||
|
@ -1106,6 +1106,42 @@ class PartFilter(rest_filters.FilterSet):
|
|||||||
label='Default Location', queryset=StockLocation.objects.all()
|
label='Default Location', queryset=StockLocation.objects.all()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
bom_valid = rest_filters.BooleanFilter(
|
||||||
|
label=_('BOM Valid'), method='filter_bom_valid'
|
||||||
|
)
|
||||||
|
|
||||||
|
def filter_bom_valid(self, queryset, name, value):
|
||||||
|
"""Filter by whether the BOM for the part is valid or not."""
|
||||||
|
# Limit queryset to active assemblies
|
||||||
|
queryset = queryset.filter(active=True, assembly=True).distinct()
|
||||||
|
|
||||||
|
# Iterate through the queryset
|
||||||
|
# TODO: We should cache BOM checksums to make this process more efficient
|
||||||
|
pks = []
|
||||||
|
|
||||||
|
for part in queryset:
|
||||||
|
if part.is_bom_valid() == value:
|
||||||
|
pks.append(part.pk)
|
||||||
|
|
||||||
|
return queryset.filter(pk__in=pks)
|
||||||
|
|
||||||
|
starred = rest_filters.BooleanFilter(label='Starred', method='filter_starred')
|
||||||
|
|
||||||
|
def filter_starred(self, queryset, name, value):
|
||||||
|
"""Filter by whether the Part is 'starred' by the current user."""
|
||||||
|
if self.request.user.is_anonymous:
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
starred_parts = [
|
||||||
|
star.part.pk
|
||||||
|
for star in self.request.user.starred_parts.all().prefetch_related('part')
|
||||||
|
]
|
||||||
|
|
||||||
|
if value:
|
||||||
|
return queryset.filter(pk__in=starred_parts)
|
||||||
|
else:
|
||||||
|
return queryset.exclude(pk__in=starred_parts)
|
||||||
|
|
||||||
is_template = rest_filters.BooleanFilter()
|
is_template = rest_filters.BooleanFilter()
|
||||||
|
|
||||||
assembly = rest_filters.BooleanFilter()
|
assembly = rest_filters.BooleanFilter()
|
||||||
@ -1235,26 +1271,6 @@ class PartList(PartMixin, APIDownloadMixin, ListCreateAPI):
|
|||||||
|
|
||||||
queryset = queryset.exclude(pk__in=id_values)
|
queryset = queryset.exclude(pk__in=id_values)
|
||||||
|
|
||||||
# Filter by whether the BOM has been validated (or not)
|
|
||||||
bom_valid = params.get('bom_valid', None)
|
|
||||||
|
|
||||||
# TODO: Querying bom_valid status may be quite expensive
|
|
||||||
# TODO: (It needs to be profiled!)
|
|
||||||
# TODO: It might be worth caching the bom_valid status to a database column
|
|
||||||
if bom_valid is not None:
|
|
||||||
bom_valid = str2bool(bom_valid)
|
|
||||||
|
|
||||||
# Limit queryset to active assemblies
|
|
||||||
queryset = queryset.filter(active=True, assembly=True)
|
|
||||||
|
|
||||||
pks = []
|
|
||||||
|
|
||||||
for prt in queryset:
|
|
||||||
if prt.is_bom_valid() == bom_valid:
|
|
||||||
pks.append(prt.pk)
|
|
||||||
|
|
||||||
queryset = queryset.filter(pk__in=pks)
|
|
||||||
|
|
||||||
# Filter by 'related' parts?
|
# Filter by 'related' parts?
|
||||||
related = params.get('related', None)
|
related = params.get('related', None)
|
||||||
exclude_related = params.get('exclude_related', None)
|
exclude_related = params.get('exclude_related', None)
|
||||||
@ -1288,20 +1304,6 @@ class PartList(PartMixin, APIDownloadMixin, ListCreateAPI):
|
|||||||
except (ValueError, Part.DoesNotExist):
|
except (ValueError, Part.DoesNotExist):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Filter by 'starred' parts?
|
|
||||||
starred = params.get('starred', None)
|
|
||||||
|
|
||||||
if starred is not None:
|
|
||||||
starred = str2bool(starred)
|
|
||||||
starred_parts = [
|
|
||||||
star.part.pk for star in self.request.user.starred_parts.all()
|
|
||||||
]
|
|
||||||
|
|
||||||
if starred:
|
|
||||||
queryset = queryset.filter(pk__in=starred_parts)
|
|
||||||
else:
|
|
||||||
queryset = queryset.exclude(pk__in=starred_parts)
|
|
||||||
|
|
||||||
# Cascade? (Default = True)
|
# Cascade? (Default = True)
|
||||||
cascade = str2bool(params.get('cascade', True))
|
cascade = str2bool(params.get('cascade', True))
|
||||||
|
|
||||||
|
@ -281,7 +281,7 @@ class PartCategory(InvenTree.models.InvenTreeTree):
|
|||||||
"""Returns True if the specified user subscribes to this category."""
|
"""Returns True if the specified user subscribes to this category."""
|
||||||
return user in self.get_subscribers(**kwargs)
|
return user in self.get_subscribers(**kwargs)
|
||||||
|
|
||||||
def set_starred(self, user, status):
|
def set_starred(self, user, status: bool) -> None:
|
||||||
"""Set the "subscription" status of this PartCategory against the specified user."""
|
"""Set the "subscription" status of this PartCategory against the specified user."""
|
||||||
if not user:
|
if not user:
|
||||||
return
|
return
|
||||||
|
@ -747,6 +747,50 @@ class PartAPITest(PartAPITestBase):
|
|||||||
response = self.get(url, {'related': 1}, expected_code=200)
|
response = self.get(url, {'related': 1}, expected_code=200)
|
||||||
self.assertEqual(len(response.data), 2)
|
self.assertEqual(len(response.data), 2)
|
||||||
|
|
||||||
|
def test_filter_by_bom_valid(self):
|
||||||
|
"""Test the 'bom_valid' Part API filter."""
|
||||||
|
url = reverse('api-part-list')
|
||||||
|
|
||||||
|
n = Part.objects.filter(active=True, assembly=True).count()
|
||||||
|
|
||||||
|
# Initially, there are no parts with a valid BOM
|
||||||
|
response = self.get(url, {'bom_valid': False}, expected_code=200)
|
||||||
|
n1 = len(response.data)
|
||||||
|
|
||||||
|
for item in response.data:
|
||||||
|
self.assertTrue(item['assembly'])
|
||||||
|
self.assertTrue(item['active'])
|
||||||
|
|
||||||
|
response = self.get(url, {'bom_valid': True}, expected_code=200)
|
||||||
|
n2 = len(response.data)
|
||||||
|
|
||||||
|
self.assertEqual(n1 + n2, n)
|
||||||
|
|
||||||
|
def test_filter_by_starred(self):
|
||||||
|
"""Test by 'starred' filter."""
|
||||||
|
url = reverse('api-part-list')
|
||||||
|
|
||||||
|
# All parts
|
||||||
|
n = Part.objects.count()
|
||||||
|
|
||||||
|
# Initially, there are no starred parts
|
||||||
|
response = self.get(url, {'starred': True}, expected_code=200)
|
||||||
|
self.assertEqual(len(response.data), 0)
|
||||||
|
|
||||||
|
response = self.get(url, {'starred': False, 'limit': 1}, expected_code=200)
|
||||||
|
self.assertEqual(response.data['count'], n)
|
||||||
|
|
||||||
|
# Star a part
|
||||||
|
part = Part.objects.first()
|
||||||
|
part.set_starred(self.user, True)
|
||||||
|
|
||||||
|
# Fetch data again
|
||||||
|
response = self.get(url, {'starred': True}, expected_code=200)
|
||||||
|
self.assertEqual(len(response.data), 1)
|
||||||
|
|
||||||
|
response = self.get(url, {'starred': False, 'limit': 1}, expected_code=200)
|
||||||
|
self.assertEqual(response.data['count'], n - 1)
|
||||||
|
|
||||||
def test_filter_by_convert(self):
|
def test_filter_by_convert(self):
|
||||||
"""Test that we can correctly filter the Part list by conversion options."""
|
"""Test that we can correctly filter the Part list by conversion options."""
|
||||||
category = PartCategory.objects.get(pk=3)
|
category = PartCategory.objects.get(pk=3)
|
||||||
|
Loading…
Reference in New Issue
Block a user