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 __future__ import unicode_literals
from django_filters.rest_framework import DjangoFilterBackend 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 import status
from rest_framework.response import Response 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.conf.urls import url, include
from django.urls import reverse from django.urls import reverse
import os
from .models import Part, PartCategory, BomItem, PartStar from .models import Part, PartCategory, BomItem, PartStar
from .serializers import PartSerializer, BomItemSerializer from .serializers import PartSerializer, BomItemSerializer
@ -99,6 +104,61 @@ class PartList(generics.ListCreateAPIView):
serializer_class = PartSerializer 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): def get_queryset(self):
# Does the user wish to filter by category? # Does the user wish to filter by category?

View File

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