Optionally paginate the StockList table on the server

- This makes the bootstrap table interface SO FREAKING MUCH FASTER
- Search is now performed on the server too!
This commit is contained in:
Oliver Walters 2021-02-28 16:18:45 +11:00
parent 5cdae04c62
commit 8ce7b572cc
4 changed files with 74 additions and 20 deletions

View File

@ -40,6 +40,7 @@ from decimal import Decimal, InvalidOperation
from datetime import datetime, timedelta from datetime import datetime, timedelta
from rest_framework.pagination import LimitOffsetPagination
from rest_framework.serializers import ValidationError from rest_framework.serializers import ValidationError
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework.response import Response from rest_framework.response import Response
@ -337,6 +338,8 @@ class StockList(generics.ListCreateAPIView):
serializer_class = StockItemSerializer serializer_class = StockItemSerializer
queryset = StockItem.objects.all() queryset = StockItem.objects.all()
pagination_class = LimitOffsetPagination
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
""" """
Create a new StockItem object via the API. Create a new StockItem object via the API.
@ -381,7 +384,13 @@ class StockList(generics.ListCreateAPIView):
queryset = self.filter_queryset(self.get_queryset()) queryset = self.filter_queryset(self.get_queryset())
serializer = self.get_serializer(queryset, many=True) page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
else:
serializer = self.get_serializer(queryset, many=True)
data = serializer.data data = serializer.data
@ -465,6 +474,8 @@ class StockList(generics.ListCreateAPIView):
Note: b) is about 100x quicker than a), because the DRF framework adds a lot of cruft Note: b) is about 100x quicker than a), because the DRF framework adds a lot of cruft
""" """
if page is not None:
return self.get_paginated_response(data)
if request.is_ajax(): if request.is_ajax():
return JsonResponse(data, safe=False) return JsonResponse(data, safe=False)
else: else:
@ -806,19 +817,6 @@ class StockList(generics.ListCreateAPIView):
print("After error:", str(updated_after)) print("After error:", str(updated_after))
pass pass
# Limit number of results
limit = params.get('limit', None)
if limit is not None:
try:
limit = int(limit)
if limit > 0:
queryset = queryset[:limit]
except (ValueError):
pass
# Also ensure that we pre-fecth all the related items # Also ensure that we pre-fecth all the related items
queryset = queryset.prefetch_related( queryset = queryset.prefetch_related(
'part', 'part',
@ -839,9 +837,12 @@ class StockList(generics.ListCreateAPIView):
ordering_fields = [ ordering_fields = [
'part__name', 'part__name',
'part__IPN',
'updated', 'updated',
'stocktake_date', 'stocktake_date',
'expiry_date', 'expiry_date',
'quantity',
'status',
] ]
ordering = ['part__name'] ordering = ['part__name']
@ -851,7 +852,8 @@ class StockList(generics.ListCreateAPIView):
'batch', 'batch',
'part__name', 'part__name',
'part__IPN', 'part__IPN',
'part__description' 'part__description',
'location__name',
] ]

View File

@ -325,6 +325,12 @@ function loadStockTable(table, options) {
grouping = options.grouping; grouping = options.grouping;
} }
// Explicitly disable part grouping functionality
// Might be able to add this in later on,
// but there is a bug which makes this crash if paginating on the server side.
// Ref: https://github.com/wenzhixin/bootstrap-table/issues/3250
grouping = false;
table.inventreeTable({ table.inventreeTable({
method: 'get', method: 'get',
formatNoMatches: function() { formatNoMatches: function() {
@ -332,7 +338,7 @@ function loadStockTable(table, options) {
}, },
url: options.url || "{% url 'api-stock-list' %}", url: options.url || "{% url 'api-stock-list' %}",
queryParams: filters, queryParams: filters,
customSort: customGroupSorter, sidePagination: 'server',
name: 'stock', name: 'stock',
original: original, original: original,
showColumns: true, showColumns: true,
@ -516,6 +522,7 @@ function loadStockTable(table, options) {
{ {
field: 'part_detail.full_name', field: 'part_detail.full_name',
title: '{% trans "Part" %}', title: '{% trans "Part" %}',
sortName: 'part__name',
sortable: true, sortable: true,
switchable: false, switchable: false,
formatter: function(value, row, index, field) { formatter: function(value, row, index, field) {
@ -534,6 +541,7 @@ function loadStockTable(table, options) {
{ {
field: 'part_detail.IPN', field: 'part_detail.IPN',
title: 'IPN', title: 'IPN',
sortName: 'part__IPN',
sortable: true, sortable: true,
formatter: function(value, row, index, field) { formatter: function(value, row, index, field) {
return row.part_detail.IPN; return row.part_detail.IPN;
@ -542,7 +550,6 @@ function loadStockTable(table, options) {
{ {
field: 'part_detail.description', field: 'part_detail.description',
title: '{% trans "Description" %}', title: '{% trans "Description" %}',
sortable: true,
formatter: function(value, row, index, field) { formatter: function(value, row, index, field) {
return row.part_detail.description; return row.part_detail.description;
} }
@ -654,8 +661,6 @@ function loadStockTable(table, options) {
{ {
field: 'packaging', field: 'packaging',
title: '{% trans "Packaging" %}', title: '{% trans "Packaging" %}',
sortable: true,
searchable: true,
}, },
{ {
field: 'notes', field: 'notes',

View File

@ -1,5 +1,6 @@
{% load i18n %} {% load i18n %}
{% load status_codes %} {% load status_codes %}
{% load inventree_extras %}
{% include "status_codes.html" with label='stock' options=StockStatus.list %} {% include "status_codes.html" with label='stock' options=StockStatus.list %}
{% include "status_codes.html" with label='build' options=BuildStatus.list %} {% include "status_codes.html" with label='build' options=BuildStatus.list %}
@ -110,6 +111,8 @@ function getAvailableTableFilters(tableKey) {
title: '{% trans "Depleted" %}', title: '{% trans "Depleted" %}',
description: '{% trans "Show stock items which are depleted" %}', description: '{% trans "Show stock items which are depleted" %}',
}, },
{% settings_value "STOCK_ENABLE_EXPIRY" as expiry %}
{% if expiry %}
expired: { expired: {
type: 'bool', type: 'bool',
title: '{% trans "Expired" %}', title: '{% trans "Expired" %}',
@ -120,6 +123,7 @@ function getAvailableTableFilters(tableKey) {
title: '{% trans "Stale" %}', title: '{% trans "Stale" %}',
description: '{% trans "Show stock which is close to expiring" %}', description: '{% trans "Show stock which is close to expiring" %}',
}, },
{% endif %}
in_stock: { in_stock: {
type: 'bool', type: 'bool',
title: '{% trans "In Stock" %}', title: '{% trans "In Stock" %}',

View File

@ -93,7 +93,14 @@ function reloadTable(table, filters) {
} }
} }
options.queryParams = params; options.queryParams = function(tableParams) {
for (key in params) {
tableParams[key] = params[key];
}
return tableParams;
}
table.bootstrapTable('refreshOptions', options); table.bootstrapTable('refreshOptions', options);
table.bootstrapTable('refresh'); table.bootstrapTable('refresh');
@ -126,9 +133,45 @@ $.fn.inventreeTable = function(options) {
var varName = tableName + '-pagesize'; var varName = tableName + '-pagesize';
// Pagingation options (can be server-side or client-side as specified by the caller)
options.pagination = true; options.pagination = true;
options.paginationVAlign = 'both';
options.pageSize = inventreeLoad(varName, 25); options.pageSize = inventreeLoad(varName, 25);
options.pageList = [25, 50, 100, 250, 'all']; options.pageList = [25, 50, 100, 250, 'all'];
options.totalField = 'count';
options.dataField = 'results';
// Extract query params
var filters = options.queryParams || options.filters || {};
options.queryParams = function(params) {
for (var key in filters) {
params[key] = filters[key];
}
// Override the way that we ask the server to sort results
// It seems bootstrap-table does not offer a "native" way to do this...
if ('sort' in params) {
var order = params['order'];
var ordering = params['sort'] || null;
if (ordering) {
if (order == 'desc') {
ordering = `-${ordering}`;
}
params['ordering'] = ordering;
}
delete params['sort'];
delete params['order'];
}
return params;
}
options.rememberOrder = true; options.rememberOrder = true;