Skip to content

Commit

Permalink
[API] Improvements for API endpoints (#7794)
Browse files Browse the repository at this point in the history
* Build allocation API updates

- Improve API query efficiency
- Add extra export fields to the BuildItemSerializer

* Remove commented code

* Improve query efficiency for BuildLine serializer

* Further improvements

* Improve StockList API endpoint

- Reduce from ~700ms to ~300ms with 250 results

* Improve query efficiency when fetching part parameter data

* Bump API version
  • Loading branch information
SchrodingersGat authored Aug 3, 2024
1 parent 85fc709 commit dee519e
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 11 deletions.
5 changes: 4 additions & 1 deletion src/backend/InvenTree/InvenTree/api_version.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
"""InvenTree API version information."""

# InvenTree API version
INVENTREE_API_VERSION = 230
INVENTREE_API_VERSION = 231

"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""


INVENTREE_API_TEXT = """
v231 - 2024-08-03 : https://github.com/inventree/InvenTree/pull/7794
- Optimize BuildItem and BuildLine serializers to improve API efficiency
v230 - 2024-05-05 : https://github.com/inventree/InvenTree/pull/7164
- Adds test statistics endpoint
Expand Down
8 changes: 6 additions & 2 deletions src/backend/InvenTree/build/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -575,19 +575,23 @@ def get_serializer(self, *args, **kwargs):
return self.serializer_class(*args, **kwargs)

def get_queryset(self):
"""Override the queryset method, to allow filtering by stock_item.part."""
"""Override the queryset method, to perform custom prefetch."""
queryset = super().get_queryset()

queryset = queryset.select_related(
'build_line',
'build_line__build',
'build_line__bom_item',
'build_line__bom_item__part',
'build_line__bom_item__sub_part',
'install_into',
'stock_item',
'stock_item__location',
'stock_item__part',
'stock_item__supplier_part',
'stock_item__supplier_part__part',
'stock_item__supplier_part__supplier',
'stock_item__supplier_part__manufacturer_part',
'stock_item__supplier_part__manufacturer_part__manufacturer',
).prefetch_related(
'stock_item__location__tags',
)
Expand Down
37 changes: 30 additions & 7 deletions src/backend/InvenTree/build/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

from stock.generators import generate_batch_code
from stock.models import StockItem, StockLocation
from stock.serializers import StockItemSerializerBrief, LocationSerializer
from stock.serializers import StockItemSerializerBrief, LocationBriefSerializer

import common.models
from common.serializers import ProjectCodeSerializer
Expand Down Expand Up @@ -1064,16 +1064,20 @@ class BuildItemSerializer(DataImportExportSerializerMixin, InvenTreeModelSeriali

# These fields are only used for data export
export_only_fields = [
'bom_part_id',
'bom_part_name',
'build_reference',
'sku',
'mpn',
'location_name',
'part_id',
'part_name',
'part_ipn',
'part_description',
'available_quantity',
'item_batch_code',
'item_serial',
'item_packaging',
]

class Meta:
Expand All @@ -1097,16 +1101,20 @@ class Meta:

# The following fields are only used for data export
'bom_reference',
'bom_part_id',
'bom_part_name',
'build_reference',
'location_name',
'mpn',
'sku',
'part_id',
'part_name',
'part_ipn',
'part_description',
'available_quantity',
'item_batch_code',
'item_serial_number',
'item_packaging',
]

def __init__(self, *args, **kwargs):
Expand Down Expand Up @@ -1136,11 +1144,17 @@ def __init__(self, *args, **kwargs):
location_name = serializers.CharField(source='stock_item.location.name', label=_('Location Name'), read_only=True)
build_reference = serializers.CharField(source='build.reference', label=_('Build Reference'), read_only=True)
bom_reference = serializers.CharField(source='build_line.bom_item.reference', label=_('BOM Reference'), read_only=True)
item_packaging = serializers.CharField(source='stock_item.packaging', label=_('Packaging'), read_only=True)

# Part detail fields
part_id = serializers.PrimaryKeyRelatedField(source='stock_item.part', label=_('Part ID'), many=False, read_only=True)
part_name = serializers.CharField(source='stock_item.part.name', label=_('Part Name'), read_only=True)
part_ipn = serializers.CharField(source='stock_item.part.IPN', label=_('Part IPN'), read_only=True)
part_description = serializers.CharField(source='stock_item.part.description', label=_('Part Description'), read_only=True)

# BOM Item Part ID (it may be different to the allocated part)
bom_part_id = serializers.PrimaryKeyRelatedField(source='build_line.bom_item.sub_part', label=_('BOM Part ID'), many=False, read_only=True)
bom_part_name = serializers.CharField(source='build_line.bom_item.sub_part.name', label=_('BOM Part Name'), read_only=True)

item_batch_code = serializers.CharField(source='stock_item.batch', label=_('Batch Code'), read_only=True)
item_serial_number = serializers.CharField(source='stock_item.serial', label=_('Serial Number'), read_only=True)
Expand All @@ -1152,9 +1166,9 @@ def __init__(self, *args, **kwargs):
part_detail = part_serializers.PartBriefSerializer(source='stock_item.part', many=False, read_only=True, pricing=False)
stock_item_detail = StockItemSerializerBrief(source='stock_item', read_only=True)
location = serializers.PrimaryKeyRelatedField(source='stock_item.location', many=False, read_only=True)
location_detail = LocationSerializer(source='stock_item.location', read_only=True)
location_detail = LocationBriefSerializer(source='stock_item.location', read_only=True)
build_detail = BuildSerializer(source='build_line.build', many=False, read_only=True)
supplier_part_detail = company.serializers.SupplierPartSerializer(source='stock_item.supplier_part', many=False, read_only=True)
supplier_part_detail = company.serializers.SupplierPartSerializer(source='stock_item.supplier_part', many=False, read_only=True, brief=True)

quantity = InvenTreeDecimalField(label=_('Allocated Quantity'))
available_quantity = InvenTreeDecimalField(source='stock_item.quantity', read_only=True, label=_('Available Quantity'))
Expand Down Expand Up @@ -1243,7 +1257,7 @@ class Meta:

# Foreign key fields
bom_item_detail = part_serializers.BomItemSerializer(source='bom_item', many=False, read_only=True, pricing=False)
part_detail = part_serializers.PartSerializer(source='bom_item.sub_part', many=False, read_only=True, pricing=False)
part_detail = part_serializers.PartBriefSerializer(source='bom_item.sub_part', many=False, read_only=True, pricing=False)
allocations = BuildItemSerializer(many=True, read_only=True)

# Annotated (calculated) fields
Expand Down Expand Up @@ -1289,16 +1303,20 @@ def annotate_queryset(queryset, build=None):
"""
queryset = queryset.select_related(
'build', 'bom_item',
'build',
'bom_item',
'bom_item__part',
'bom_item__part__pricing_data',
'bom_item__sub_part',
'bom_item__sub_part__pricing_data',
)

# Pre-fetch related fields
queryset = queryset.prefetch_related(
'bom_item__sub_part',
'bom_item__sub_part__tags',
'bom_item__sub_part__stock_items',
'bom_item__sub_part__stock_items__allocations',
'bom_item__sub_part__stock_items__sales_order_allocations',
'bom_item__sub_part__tags',

'bom_item__substitutes',
'bom_item__substitutes__part__stock_items',
Expand All @@ -1310,6 +1328,11 @@ def annotate_queryset(queryset, build=None):
'allocations__stock_item__part',
'allocations__stock_item__location',
'allocations__stock_item__location__tags',
'allocations__stock_item__supplier_part',
'allocations__stock_item__supplier_part__part',
'allocations__stock_item__supplier_part__supplier',
'allocations__stock_item__supplier_part__manufacturer_part',
'allocations__stock_item__supplier_part__manufacturer_part__manufacturer',
)

# Annotate the "allocated" quantity
Expand Down
7 changes: 6 additions & 1 deletion src/backend/InvenTree/company/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -381,9 +381,14 @@ def __init__(self, *args, **kwargs):
self.fields.pop('manufacturer_detail', None)
self.fields.pop('manufacturer_part_detail', None)

if prettify is not True:
if brief or prettify is not True:
self.fields.pop('pretty_name', None)

if brief:
self.fields.pop('tags')
self.fields.pop('available')
self.fields.pop('availability_updated')

# Annotated field showing total in-stock quantity
in_stock = serializers.FloatField(read_only=True, label=_('In Stock'))

Expand Down
4 changes: 4 additions & 0 deletions src/backend/InvenTree/part/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1195,6 +1195,10 @@ def get_queryset(self, *args, **kwargs):

queryset = part_serializers.PartSerializer.annotate_queryset(queryset)

# Annotate with parameter template data?
if str2bool(self.request.query_params.get('parameters', False)):
queryset = queryset.prefetch_related('parameters', 'parameters__template')

return queryset

def get_serializer(self, *args, **kwargs):
Expand Down
3 changes: 3 additions & 0 deletions src/backend/InvenTree/stock/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,10 @@ def annotate_queryset(queryset):
'part__category',
'part__pricing_data',
'supplier_part',
'supplier_part__part',
'supplier_part__supplier',
'supplier_part__manufacturer_part',
'supplier_part__manufacturer_part__manufacturer',
'supplier_part__tags',
'test_results',
'tags',
Expand Down

0 comments on commit dee519e

Please sign in to comment.