Custom aggregation of Part API

- Reduced full part query from 2.5s to 200ms!
This commit is contained in:
Oliver Walters 2019-06-18 01:31:30 +10:00
parent 37dba91b4a
commit 79cd05423c
2 changed files with 77 additions and 8 deletions

View File

@ -6,6 +6,9 @@ Provides a JSON API for the Part app
from __future__ import unicode_literals
from django_filters.rest_framework import DjangoFilterBackend
from django.conf import settings
from django.db.models import Sum
from rest_framework import status
from rest_framework.response import Response
@ -15,6 +18,8 @@ from rest_framework import generics, permissions
from django.conf.urls import url, include
from django.urls import reverse
import os
from .models import Part, PartCategory, BomItem, PartStar
from .serializers import PartSerializer, BomItemSerializer
@ -99,6 +104,61 @@ class PartList(generics.ListCreateAPIView):
serializer_class = PartSerializer
def list(self, request, *args, **kwargs):
"""
Instead of using the DRF serialiser to LIST,
we serialize the objects manuually.
This turns out to be significantly faster.
"""
queryset = self.filter_queryset(self.get_queryset())
data = queryset.values(
'pk',
'category',
'image',
'name',
'IPN',
'description',
'keywords',
'is_template',
'URL',
'units',
'trackable',
'assembly',
'component',
'salable',
'active',
).annotate(
in_stock=Sum('stock_items__quantity'),
)
# TODO - Annotate total being built
# TODO - Annotate total on order
# TODO - Annotate
# Reduce the number of lookups we need to do for the part categories
categories = {}
for item in data:
if item['image']:
item['image'] = os.path.join(settings.MEDIA_URL, item['image'])
cat_id = item['category']
if cat_id:
if cat_id not in categories:
categories[cat_id] = PartCategory.objects.get(pk=cat_id).pathstring
item['category__name'] = categories[cat_id]
else:
item['category__name'] = None
return Response(data)
def get_queryset(self):
# Does the user wish to filter by category?

View File

@ -112,16 +112,25 @@ function loadPartTable(table, url, options={}) {
}
columns.push({
field: 'full_name',
field: 'name',
title: 'Part',
sortable: true,
formatter: function(value, row, index, field) {
if (row.is_template) {
value = '<i>' + value + '</i>';
var name = '';
if (row.IPN) {
name += row.IPN;
name += ' | ';
}
var display = imageHoverIcon(row.image_url) + renderLink(value, row.url);
name += value;
if (row.is_template) {
name = '<i>' + name + '</i>';
}
var display = imageHoverIcon(row.image) + renderLink(name, '/part/' + row.pk + '/');
if (!row.active) {
display = display + "<span class='label label-warning' style='float: right;'>INACTIVE</span>";
@ -146,11 +155,11 @@ function loadPartTable(table, url, options={}) {
columns.push({
sortable: true,
field: 'category_name',
field: 'category__name',
title: 'Category',
formatter: function(value, row, index, field) {
if (row.category) {
return renderLink(row.category_name, "/part/category/" + row.category + "/");
return renderLink(row.category__name, "/part/category/" + row.category + "/");
}
else {
return '';
@ -159,13 +168,13 @@ function loadPartTable(table, url, options={}) {
});
columns.push({
field: 'total_stock',
field: 'in_stock',
title: 'Stock',
searchable: false,
sortable: true,
formatter: function(value, row, index, field) {
if (value) {
return renderLink(value, row.url + 'stock/');
return renderLink(value, '/part/' + row.pk + '/stock/');
}
else {
return "<span class='label label-warning'>No Stock</span>";