From 8c583750a247db3316bec1ab8fc673808ad7de35 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 28 May 2019 21:45:27 +1000 Subject: [PATCH] Update the stock list API - Custom data serialization is MUCH faster (~400ms compared to 3s) - Cache location queries - Flatten related field data - Update stock table javascript to match --- InvenTree/InvenTree/settings.py | 1 - InvenTree/static/script/inventree/stock.js | 41 +++++++++---- InvenTree/stock/api.py | 71 +++++++++++----------- 3 files changed, 66 insertions(+), 47 deletions(-) diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index 058920c134..74c9eac71a 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -104,7 +104,6 @@ MIDDLEWARE = [ 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'InvenTree.middleware.AuthRequiredMiddleware', - 'InvenTree.middleware.QueryCountMiddleware', ] if DEBUG: diff --git a/InvenTree/static/script/inventree/stock.js b/InvenTree/static/script/inventree/stock.js index 58c8f5096e..eeae749461 100644 --- a/InvenTree/static/script/inventree/stock.js +++ b/InvenTree/static/script/inventree/stock.js @@ -388,13 +388,22 @@ function loadStockTable(table, options) { groupByField: options.groupByField || 'part', groupByFormatter: function(field, id, data) { + var row = data[0]; + if (field == 'Part') { - return imageHoverIcon(data[0].part_detail.image_url) + - data[0].part_detail.full_name + - ' (' + data.length + ' items)'; + + var name = row.part__IPN; + + if (name) { + name += ' | '; + } + + name += row.part__name; + + return imageHoverIcon(row.part__image) + name + ' (' + data.length + ' items)'; } else if (field == 'Description') { - return data[0].part_detail.description; + return row.part__description; } else if (field == 'Stock') { var stock = 0; @@ -419,7 +428,8 @@ function loadStockTable(table, options) { if (locations.length > 1) { return "In " + locations.length + " locations"; } else { - return renderLink(data[0].location_detail.pathstring, data[0].location_detail.url); + // A single location! + return renderLink(row.location__path, '/stock/location/' + row.location + '/') } } else { @@ -438,15 +448,24 @@ function loadStockTable(table, options) { visible: false, }, { - field: 'part_detail', + field: 'part__name', title: 'Part', sortable: true, formatter: function(value, row, index, field) { - return imageHoverIcon(value.image_url) + renderLink(value.full_name, value.url + 'stock/'); + + var name = row.part__IPN; + + if (name) { + name += ' | '; + } + + name += row.part__name; + + return imageHoverIcon(row.part__image) + renderLink(name, '/part/' + row.part + '/stock/'); } }, { - field: 'part_detail.description', + field: 'part__description', title: 'Description', sortable: true, }, @@ -463,19 +482,19 @@ function loadStockTable(table, options) { val = '# ' + row.serial; } - var text = renderLink(val, row.url); + var text = renderLink(val, '/stock/item/' + row.pk + '/'); text = text + "" + row.status_text + ""; return text; } }, { - field: 'location_detail', + field: 'location__path', title: 'Location', sortable: true, formatter: function(value, row, index, field) { if (value) { - return renderLink(value.pathstring, value.url); + return renderLink(value, '/stock/location/' + row.location + '/'); } else { return 'No stock location set'; diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py index 53bda40b03..f0387a77cd 100644 --- a/InvenTree/stock/api.py +++ b/InvenTree/stock/api.py @@ -5,6 +5,7 @@ JSON API for the Stock app from django_filters.rest_framework import FilterSet, DjangoFilterBackend from django_filters import NumberFilter +from django.conf import settings from django.conf.urls import url, include from django.urls import reverse @@ -22,6 +23,8 @@ from .serializers import StockTrackingSerializer from InvenTree.views import TreeSerializer from InvenTree.helpers import str2bool +import os + from rest_framework.serializers import ValidationError from rest_framework.views import APIView from rest_framework.response import Response @@ -264,47 +267,43 @@ class StockList(generics.ListCreateAPIView): queryset = self.filter_queryset(self.get_queryset()) - if str2bool(self.request.GET.get('aggregate', None)): - # Aggregate stock by part type - queryset = queryset.values( - 'part', - 'part__name', - 'part__image', - 'location', - 'location__name').annotate( - stock=Sum('quantity'), - items=Count('part')) + # Instead of using the DRF serializer to LIST, + # we will serialize the objects manually. + # This is significantly faster - for result in queryset: - # If there is only 1 stock item (which will be a lot of the time), - # Add that data to the dict - if result['items'] == 1: - items = StockItem.objects.filter( - part=result['part'], - location=result['location'] - ) + data = queryset.values( + 'pk', + 'quantity', + 'serial', + 'batch', + 'status', + 'notes', + 'location', + 'location__name', + 'part', + 'part__IPN', + 'part__name', + 'part__description', + 'part__image', + 'part__category', + 'part__category__name' + ) - if items.count() == 1: - result.pop('items') + # Reduce the number of lookups we need to do for categories + # Cache location lookups for this query + locations = {} - item = items[0] + for item in data: + item['part__image'] = os.path.join(settings.MEDIA_URL, item['part__image']) - # Add in some extra information specific to this StockItem - result['pk'] = item.id - result['serial'] = item.serial - result['batch'] = item.batch - result['notes'] = item.notes + loc_id = item['location'] - return Response(queryset) + if loc_id not in locations: + locations[loc_id] = StockLocation.objects.get(pk=loc_id).pathstring + + item['location__path'] = locations[loc_id] - page = self.paginate_queryset(queryset) - if page is not None: - serializer = self.get_serializer(page, many=True) - return self.get_paginated_response(serializer.data) - - serializer = self.get_serializer(queryset, many=True) - return Response(serializer.data) - + return Response(data) def get_queryset(self): """ @@ -366,6 +365,8 @@ class StockList(generics.ListCreateAPIView): 'location' ) + stock_list = stock_list.order_by('part__name') + return stock_list serializer_class = StockItemSerializer