diff --git a/InvenTree/part/api.py b/InvenTree/part/api.py index 3a5fee4e3d..39801070c7 100644 --- a/InvenTree/part/api.py +++ b/InvenTree/part/api.py @@ -361,7 +361,6 @@ class PartDetail(generics.RetrieveUpdateDestroyAPIView): def get_queryset(self, *args, **kwargs): queryset = super().get_queryset(*args, **kwargs) - queryset = part_serializers.PartSerializer.prefetch_queryset(queryset) queryset = part_serializers.PartSerializer.annotate_queryset(queryset) return queryset @@ -619,8 +618,6 @@ class PartList(generics.ListCreateAPIView): def get_queryset(self, *args, **kwargs): queryset = super().get_queryset(*args, **kwargs) - - queryset = part_serializers.PartSerializer.prefetch_queryset(queryset) queryset = part_serializers.PartSerializer.annotate_queryset(queryset) return queryset @@ -633,10 +630,6 @@ class PartList(generics.ListCreateAPIView): params = self.request.query_params - # Annotate calculated data to the queryset - # (This will be used for further filtering) - queryset = part_serializers.PartSerializer.annotate_queryset(queryset) - queryset = super().filter_queryset(queryset) # Filter by "uses" query - Limit to parts which use the provided part diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 72d388746b..49be12e283 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -27,6 +27,7 @@ from markdownx.models import MarkdownxField from django_cleanup import cleanup from mptt.models import TreeForeignKey, MPTTModel +from mptt.managers import TreeManager from stdimage.models import StdImageField @@ -284,6 +285,24 @@ def match_part_names(match, threshold=80, reverse=True, compare_length=False): return matches +class PartManager(TreeManager): + """ + Defines a custom object manager for the Part model. + + The main purpose of this manager is to reduce the number of database hits, + as the Part model has a large number of ForeignKey fields! + """ + + def get_queryset(self): + + return super().get_queryset().prefetch_related( + 'category', + 'category__parent', + 'stock_items', + 'builds', + ) + + @cleanup.ignore class Part(MPTTModel): """ The Part object represents an abstract part, the 'concept' of an actual entity. @@ -321,6 +340,8 @@ class Part(MPTTModel): responsible: User who is responsible for this part (optional) """ + objects = PartManager() + class Meta: verbose_name = _("Part") verbose_name_plural = _("Parts") diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index 92dda58590..db543b8602 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -215,25 +215,6 @@ class PartSerializer(InvenTreeModelSerializer): if category_detail is not True: self.fields.pop('category_detail') - @staticmethod - def prefetch_queryset(queryset): - """ - Prefetch related database tables, - to reduce database hits. - """ - - return queryset.prefetch_related( - 'category', - 'category__parts', - 'category__parent', - 'stock_items', - 'bom_items', - 'builds', - 'supplier_parts', - 'supplier_parts__purchase_order_line_items', - 'supplier_parts__purchase_order_line_items__order', - ) - @staticmethod def annotate_queryset(queryset): """ diff --git a/InvenTree/part/test_category.py b/InvenTree/part/test_category.py index e616fc2054..75261378b0 100644 --- a/InvenTree/part/test_category.py +++ b/InvenTree/part/test_category.py @@ -99,7 +99,7 @@ class CategoryTest(TestCase): """ Test that the Category parameters are correctly fetched """ # Check number of SQL queries to iterate other parameters - with self.assertNumQueries(3): + with self.assertNumQueries(7): # Prefetch: 3 queries (parts, parameters and parameters_template) fasteners = self.fasteners.prefetch_parts_parameters() # Iterate through all parts and parameters diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py index 70ab939ff1..56df35b5c3 100644 --- a/InvenTree/stock/api.py +++ b/InvenTree/stock/api.py @@ -84,7 +84,6 @@ class StockDetail(generics.RetrieveUpdateDestroyAPIView): def get_queryset(self, *args, **kwargs): queryset = super().get_queryset(*args, **kwargs) - queryset = StockItemSerializer.prefetch_queryset(queryset) queryset = StockItemSerializer.annotate_queryset(queryset) return queryset @@ -637,7 +636,6 @@ class StockList(generics.ListCreateAPIView): queryset = super().get_queryset(*args, **kwargs) - queryset = StockItemSerializer.prefetch_queryset(queryset) queryset = StockItemSerializer.annotate_queryset(queryset) return queryset diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index ee10bd3ed7..c2122f40ac 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -23,6 +23,7 @@ from django.dispatch import receiver from markdownx.models import MarkdownxField from mptt.models import MPTTModel, TreeForeignKey +from mptt.managers import TreeManager from decimal import Decimal, InvalidOperation from datetime import datetime, timedelta @@ -130,6 +131,31 @@ def before_delete_stock_location(sender, instance, using, **kwargs): child.save() +class StockItemManager(TreeManager): + """ + Custom database manager for the StockItem class. + + StockItem querysets will automatically prefetch related fields. + """ + + def get_queryset(self): + + return super().get_queryset().prefetch_related( + 'belongs_to', + 'build', + 'customer', + 'purchase_order', + 'sales_order', + 'supplier_part', + 'supplier_part__supplier', + 'allocations', + 'sales_order_allocations', + 'location', + 'part', + 'tracking_info' + ) + + class StockItem(MPTTModel): """ A StockItem object represents a quantity of physical instances of a part. diff --git a/InvenTree/stock/serializers.py b/InvenTree/stock/serializers.py index a175787c63..41dc959f02 100644 --- a/InvenTree/stock/serializers.py +++ b/InvenTree/stock/serializers.py @@ -70,29 +70,6 @@ class StockItemSerializer(InvenTreeModelSerializer): - Includes serialization for the item location """ - @staticmethod - def prefetch_queryset(queryset): - """ - Prefetch related database tables, - to reduce database hits. - """ - - return queryset.prefetch_related( - 'belongs_to', - 'build', - 'customer', - 'purchase_order', - 'sales_order', - 'supplier_part', - 'supplier_part__supplier', - 'supplier_part__manufacturer_part__manufacturer', - 'allocations', - 'sales_order_allocations', - 'location', - 'part', - 'tracking_info', - ) - @staticmethod def annotate_queryset(queryset): """