mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Custom aggregation of Part API
- Reduced full part query from 2.5s to 200ms!
This commit is contained in:
parent
37dba91b4a
commit
79cd05423c
@ -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?
|
||||||
|
@ -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>";
|
||||||
|
Loading…
Reference in New Issue
Block a user