diff --git a/InvenTree/InvenTree/api_version.py b/InvenTree/InvenTree/api_version.py index 7851efd8dd..f767148199 100644 --- a/InvenTree/InvenTree/api_version.py +++ b/InvenTree/InvenTree/api_version.py @@ -4,11 +4,14 @@ InvenTree API version information # InvenTree API version -INVENTREE_API_VERSION = 40 +INVENTREE_API_VERSION = 41 """ Increment this API version number whenever there is a significant change to the API that any clients need to know about +v41 -> 2022-04-26 + - Fixes 'variant_of' filter for Part list endpoint + v40 -> 2022-04-19 - Adds ability to filter StockItem list by "tracked" parameter - This checks the serial number or batch code fields diff --git a/InvenTree/part/api.py b/InvenTree/part/api.py index de6cd4a974..b025791a7f 100644 --- a/InvenTree/part/api.py +++ b/InvenTree/part/api.py @@ -1175,6 +1175,18 @@ class PartList(generics.ListCreateAPIView): except (ValueError, Part.DoesNotExist): pass + # Filter by 'variant_of' + # Note that this is subtly different from 'ancestor' filter (above) + variant_of = params.get('variant_of', None) + + if variant_of is not None: + try: + template = Part.objects.get(pk=variant_of) + variants = template.get_children() + queryset = queryset.filter(pk__in=[v.pk for v in variants]) + except (ValueError, Part.DoesNotExist): + pass + # Filter only parts which are in the "BOM" for a given part in_bom_for = params.get('in_bom_for', None) @@ -1339,10 +1351,6 @@ class PartList(generics.ListCreateAPIView): filters.OrderingFilter, ] - filter_fields = [ - 'variant_of', - ] - ordering_fields = [ 'name', 'creation_date', diff --git a/InvenTree/part/fixtures/part.yaml b/InvenTree/part/fixtures/part.yaml index d0a2d949b1..fd38036fa9 100644 --- a/InvenTree/part/fixtures/part.yaml +++ b/InvenTree/part/fixtures/part.yaml @@ -177,6 +177,7 @@ fields: name: 'Green chair variant' variant_of: 10003 + is_template: true category: 7 trackable: true tree_id: 1 diff --git a/InvenTree/part/test_api.py b/InvenTree/part/test_api.py index 5a8acbecd9..ddfaa44e94 100644 --- a/InvenTree/part/test_api.py +++ b/InvenTree/part/test_api.py @@ -567,6 +567,97 @@ class PartAPITest(InvenTreeAPITestCase): self.assertEqual(response.data['name'], name) self.assertEqual(response.data['description'], description) + def test_template_filters(self): + """ + Unit tests for API filters related to template parts: + + - variant_of : Return children of specified part + - ancestor : Return descendants of specified part + + Uses the 'chair template' part (pk=10000) + """ + + # Rebuild the MPTT structure before running these tests + Part.objects.rebuild() + + url = reverse('api-part-list') + + response = self.get( + url, + { + 'variant_of': 10000, + }, + expected_code=200 + ) + + # 3 direct children of template part + self.assertEqual(len(response.data), 3) + + response = self.get( + url, + { + 'ancestor': 10000, + }, + expected_code=200, + ) + + # 4 total descendants + self.assertEqual(len(response.data), 4) + + # Use the 'green chair' as our reference + response = self.get( + url, + { + 'variant_of': 10003, + }, + expected_code=200, + ) + + self.assertEqual(len(response.data), 1) + + response = self.get( + url, + { + 'ancestor': 10003, + }, + expected_code=200, + ) + + self.assertEqual(len(response.data), 1) + + # Add some more variants + + p = Part.objects.get(pk=10004) + + for i in range(100): + Part.objects.create( + name=f'Chair variant {i}', + description='A new chair variant', + variant_of=p, + ) + + # There should still be only one direct variant + response = self.get( + url, + { + 'variant_of': 10003, + }, + expected_code=200, + ) + + self.assertEqual(len(response.data), 1) + + # However, now should be 101 descendants + response = self.get( + url, + { + 'ancestor': 10003, + }, + expected_code=200, + ) + + self.assertEqual(len(response.data), 101) + class PartDetailTests(InvenTreeAPITestCase): """