mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge remote-tracking branch 'inventree/master'
This commit is contained in:
commit
a604c828e4
@ -18,7 +18,7 @@ from rest_framework.response import Response
|
|||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
|
|
||||||
from .views import AjaxView
|
from .views import AjaxView
|
||||||
from .version import inventreeVersion, inventreeInstanceName
|
from .version import inventreeVersion, inventreeApiVersion, inventreeInstanceName
|
||||||
|
|
||||||
from plugins import plugins as inventree_plugins
|
from plugins import plugins as inventree_plugins
|
||||||
|
|
||||||
@ -43,11 +43,29 @@ class InfoView(AjaxView):
|
|||||||
'server': 'InvenTree',
|
'server': 'InvenTree',
|
||||||
'version': inventreeVersion(),
|
'version': inventreeVersion(),
|
||||||
'instance': inventreeInstanceName(),
|
'instance': inventreeInstanceName(),
|
||||||
|
'apiVersion': inventreeApiVersion(),
|
||||||
}
|
}
|
||||||
|
|
||||||
return JsonResponse(data)
|
return JsonResponse(data)
|
||||||
|
|
||||||
|
|
||||||
|
class NotFoundView(AjaxView):
|
||||||
|
"""
|
||||||
|
Simple JSON view when accessing an invalid API view.
|
||||||
|
"""
|
||||||
|
|
||||||
|
permission_classes = [permissions.AllowAny]
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'details': _('API endpoint not found'),
|
||||||
|
'url': request.build_absolute_uri(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return JsonResponse(data, status=404)
|
||||||
|
|
||||||
|
|
||||||
class AttachmentMixin:
|
class AttachmentMixin:
|
||||||
"""
|
"""
|
||||||
Mixin for creating attachment objects,
|
Mixin for creating attachment objects,
|
||||||
|
@ -275,12 +275,13 @@ REST_FRAMEWORK = {
|
|||||||
'rest_framework.authentication.SessionAuthentication',
|
'rest_framework.authentication.SessionAuthentication',
|
||||||
'rest_framework.authentication.TokenAuthentication',
|
'rest_framework.authentication.TokenAuthentication',
|
||||||
),
|
),
|
||||||
|
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
|
||||||
'DEFAULT_PERMISSION_CLASSES': (
|
'DEFAULT_PERMISSION_CLASSES': (
|
||||||
'rest_framework.permissions.IsAuthenticated',
|
'rest_framework.permissions.IsAuthenticated',
|
||||||
'rest_framework.permissions.DjangoModelPermissions',
|
'rest_framework.permissions.DjangoModelPermissions',
|
||||||
'InvenTree.permissions.RolePermission',
|
'InvenTree.permissions.RolePermission',
|
||||||
),
|
),
|
||||||
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema'
|
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
|
||||||
}
|
}
|
||||||
|
|
||||||
WSGI_APPLICATION = 'InvenTree.wsgi.application'
|
WSGI_APPLICATION = 'InvenTree.wsgi.application'
|
||||||
|
@ -725,6 +725,7 @@ input[type="submit"] {
|
|||||||
top: 70px;
|
top: 70px;
|
||||||
position: sticky;
|
position: sticky;
|
||||||
font-size: 115%;
|
font-size: 115%;
|
||||||
|
margin-left: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidenav-right svg {
|
.sidenav-right svg {
|
||||||
|
@ -67,79 +67,91 @@ function loadTree(url, tree, options={}) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function openSideNav(navId) {
|
|
||||||
// document.getElementById("sidenav").style.display = "block";
|
|
||||||
// document.getElementById("sidenav").style.width = "250px";
|
|
||||||
|
|
||||||
if (!navId) {
|
/**
|
||||||
navId = '#sidenav-left';
|
* Initialize navigation tree display
|
||||||
|
*/
|
||||||
|
function initNavTree(options) {
|
||||||
|
|
||||||
|
var resize = true;
|
||||||
|
|
||||||
|
if ('resize' in options) {
|
||||||
|
resize = options.resize;
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionStorage.setItem('inventree-sidenav-state', 'open');
|
var label = options.label || 'nav';
|
||||||
|
|
||||||
$(navId).animate({
|
var stateLabel = `${label}-tree-state`;
|
||||||
width: '250px',
|
var widthLabel = `${label}-tree-width`;
|
||||||
'min-width': '200px',
|
|
||||||
display: 'block'
|
|
||||||
}, 50);
|
|
||||||
|
|
||||||
|
var treeId = options.treeId || '#sidenav-left';
|
||||||
|
var toggleId = options.toggleId;
|
||||||
|
|
||||||
}
|
// Initially hide the tree
|
||||||
|
$(treeId).animate({
|
||||||
function closeSideNav(navId) {
|
|
||||||
|
|
||||||
if (!navId) {
|
|
||||||
navId = '#sidenav-left';
|
|
||||||
}
|
|
||||||
|
|
||||||
sessionStorage.setItem('inventree-sidenav-state', 'closed');
|
|
||||||
|
|
||||||
$(navId).animate({
|
|
||||||
width: '0px',
|
width: '0px',
|
||||||
'min-width': '0px',
|
}, 0, function() {
|
||||||
display: 'none',
|
|
||||||
|
if (resize) {
|
||||||
|
$(treeId).resizable({
|
||||||
|
minWidth: '0px',
|
||||||
|
maxWidth: '500px',
|
||||||
|
handles: 'e, se',
|
||||||
|
grid: [5, 5],
|
||||||
|
stop: function(event, ui) {
|
||||||
|
var width = Math.round(ui.element.width());
|
||||||
|
|
||||||
|
if (width < 75) {
|
||||||
|
$(treeId).animate({
|
||||||
|
width: '0px'
|
||||||
}, 50);
|
}, 50);
|
||||||
|
|
||||||
//document.getElementById("sidenav").style.display = "none";
|
sessionStorage.setItem(stateLabel, 'closed');
|
||||||
//document.getElementById("sidenav").style.width = "0";
|
} else {
|
||||||
//document.getElementById("inventree-content").style.marginLeft = "0px";
|
sessionStorage.setItem(stateLabel, 'open');
|
||||||
|
sessionStorage.setItem(widthLabel, `${width}px`);
|
||||||
}
|
|
||||||
|
|
||||||
function toggleSideNav(nav) {
|
|
||||||
if ($(nav).width() <= 0) {
|
|
||||||
openSideNav(nav);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
closeSideNav(nav);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
function initSideNav(navId) {
|
|
||||||
|
|
||||||
// Make it resizable
|
|
||||||
|
|
||||||
if (!navId) {
|
|
||||||
navId = '#sidenav-left';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$(navId).resizable({
|
var state = sessionStorage.getItem(stateLabel);
|
||||||
minWidth: '100px',
|
var width = sessionStorage.getItem(widthLabel) || '300px';
|
||||||
maxWidth: '500px',
|
|
||||||
stop: function(event, ui) {
|
if (state && state == 'open') {
|
||||||
console.log(ui.element.width());
|
|
||||||
//console.log(ui.size.width);
|
$(treeId).animate({
|
||||||
|
width: width,
|
||||||
|
}, 50);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (sessionStorage.getItem("inventree-sidenav-state") && sessionStorage.getItem('inventree-sidenav-state') == 'open') {
|
// Register callback for 'toggle' button
|
||||||
openSideNav(navId);
|
if (toggleId) {
|
||||||
|
|
||||||
|
$(toggleId).click(function() {
|
||||||
|
|
||||||
|
var state = sessionStorage.getItem(stateLabel) || 'closed';
|
||||||
|
var width = sessionStorage.getItem(widthLabel) || '300px';
|
||||||
|
|
||||||
|
if (state == 'open') {
|
||||||
|
$(treeId).animate({
|
||||||
|
width: '0px'
|
||||||
|
}, 50);
|
||||||
|
|
||||||
|
sessionStorage.setItem(stateLabel, 'closed');
|
||||||
|
} else {
|
||||||
|
$(treeId).animate({
|
||||||
|
width: width,
|
||||||
|
}, 50);
|
||||||
|
|
||||||
|
sessionStorage.setItem(stateLabel, 'open');
|
||||||
}
|
}
|
||||||
else {
|
});
|
||||||
closeSideNav(navId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle left-hand icon menubar display
|
* Handle left-hand icon menubar display
|
||||||
*/
|
*/
|
||||||
|
@ -43,7 +43,7 @@ from .views import DynamicJsView
|
|||||||
|
|
||||||
from common.views import SettingEdit
|
from common.views import SettingEdit
|
||||||
|
|
||||||
from .api import InfoView
|
from .api import InfoView, NotFoundView
|
||||||
from .api import ActionPluginView
|
from .api import ActionPluginView
|
||||||
|
|
||||||
from users.urls import user_urls
|
from users.urls import user_urls
|
||||||
@ -70,6 +70,9 @@ apipatterns = [
|
|||||||
|
|
||||||
# InvenTree information endpoint
|
# InvenTree information endpoint
|
||||||
url(r'^$', InfoView.as_view(), name='api-inventree-info'),
|
url(r'^$', InfoView.as_view(), name='api-inventree-info'),
|
||||||
|
|
||||||
|
# Unknown endpoint
|
||||||
|
url(r'^.*$', NotFoundView.as_view(), name='api-404'),
|
||||||
]
|
]
|
||||||
|
|
||||||
settings_urls = [
|
settings_urls = [
|
||||||
|
@ -9,6 +9,9 @@ import common.models
|
|||||||
|
|
||||||
INVENTREE_SW_VERSION = "0.1.7 pre"
|
INVENTREE_SW_VERSION = "0.1.7 pre"
|
||||||
|
|
||||||
|
# Increment this number whenever there is a significant change to the API that any clients need to know about
|
||||||
|
INVENTREE_API_VERSION = 2
|
||||||
|
|
||||||
|
|
||||||
def inventreeInstanceName():
|
def inventreeInstanceName():
|
||||||
""" Returns the InstanceName settings for the current database """
|
""" Returns the InstanceName settings for the current database """
|
||||||
@ -20,6 +23,10 @@ def inventreeVersion():
|
|||||||
return INVENTREE_SW_VERSION
|
return INVENTREE_SW_VERSION
|
||||||
|
|
||||||
|
|
||||||
|
def inventreeApiVersion():
|
||||||
|
return INVENTREE_API_VERSION
|
||||||
|
|
||||||
|
|
||||||
def inventreeDjangoVersion():
|
def inventreeDjangoVersion():
|
||||||
""" Return the version of Django library """
|
""" Return the version of Django library """
|
||||||
return django.get_version()
|
return django.get_version()
|
||||||
|
@ -22,7 +22,7 @@ InvenTree | {% trans "Supplier List" %}
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<table class='table table-striped' id='company-table' data-toolbar='#button-toolbar'>
|
<table class='table table-striped table-condensed' id='company-table' data-toolbar='#button-toolbar'>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -397,10 +397,10 @@ class PartList(generics.ListCreateAPIView):
|
|||||||
queryset = self.filter_queryset(self.get_queryset())
|
queryset = self.filter_queryset(self.get_queryset())
|
||||||
|
|
||||||
page = self.paginate_queryset(queryset)
|
page = self.paginate_queryset(queryset)
|
||||||
|
|
||||||
if page is not None:
|
if page is not None:
|
||||||
serializer = self.get_serializer(page, many=True)
|
serializer = self.get_serializer(page, many=True)
|
||||||
return self.get_paginated_response(serializer.data)
|
else:
|
||||||
|
|
||||||
serializer = self.get_serializer(queryset, many=True)
|
serializer = self.get_serializer(queryset, many=True)
|
||||||
|
|
||||||
data = serializer.data
|
data = serializer.data
|
||||||
@ -445,7 +445,9 @@ class PartList(generics.ListCreateAPIView):
|
|||||||
a) For HTTP requests (e.g. via the browseable API) return a DRF response
|
a) For HTTP requests (e.g. via the browseable API) return a DRF response
|
||||||
b) For AJAX requests, simply return a JSON rendered response.
|
b) For AJAX requests, simply return a JSON rendered response.
|
||||||
"""
|
"""
|
||||||
if request.is_ajax():
|
if page is not None:
|
||||||
|
return self.get_paginated_response(data)
|
||||||
|
elif request.is_ajax():
|
||||||
return JsonResponse(data, safe=False)
|
return JsonResponse(data, safe=False)
|
||||||
else:
|
else:
|
||||||
return Response(data)
|
return Response(data)
|
||||||
@ -641,15 +643,18 @@ class PartList(generics.ListCreateAPIView):
|
|||||||
|
|
||||||
queryset = queryset.filter(pk__in=parts_need_stock)
|
queryset = queryset.filter(pk__in=parts_need_stock)
|
||||||
|
|
||||||
# Limit number of results
|
# Optionally limit the maximum number of returned results
|
||||||
limit = params.get('limit', None)
|
# e.g. for displaying "recent part" list
|
||||||
|
max_results = params.get('max_results', None)
|
||||||
|
|
||||||
if limit is not None:
|
if max_results is not None:
|
||||||
try:
|
try:
|
||||||
limit = int(limit)
|
max_results = int(max_results)
|
||||||
if limit > 0:
|
|
||||||
queryset = queryset[:limit]
|
if max_results > 0:
|
||||||
except ValueError:
|
queryset = queryset[:max_results]
|
||||||
|
|
||||||
|
except (ValueError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return queryset
|
return queryset
|
||||||
@ -674,6 +679,8 @@ class PartList(generics.ListCreateAPIView):
|
|||||||
ordering_fields = [
|
ordering_fields = [
|
||||||
'name',
|
'name',
|
||||||
'creation_date',
|
'creation_date',
|
||||||
|
'IPN',
|
||||||
|
'in_stock',
|
||||||
]
|
]
|
||||||
|
|
||||||
# Default ordering
|
# Default ordering
|
||||||
@ -685,6 +692,7 @@ class PartList(generics.ListCreateAPIView):
|
|||||||
'IPN',
|
'IPN',
|
||||||
'revision',
|
'revision',
|
||||||
'keywords',
|
'keywords',
|
||||||
|
'category__name',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -29,8 +29,6 @@ InvenTree | {% trans "Part List" %}
|
|||||||
{% block js_ready %}
|
{% block js_ready %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
|
|
||||||
closeSideNav();
|
|
||||||
|
|
||||||
loadTree("{% url 'api-part-tree' %}",
|
loadTree("{% url 'api-part-tree' %}",
|
||||||
"#part-tree",
|
"#part-tree",
|
||||||
{
|
{
|
||||||
@ -38,11 +36,9 @@ InvenTree | {% trans "Part List" %}
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
$("#toggle-part-tree").click(function() {
|
initNavTree({
|
||||||
toggleSideNav("#sidenav-left");
|
label: 'part',
|
||||||
return false;
|
treeId: '#sidenav-left',
|
||||||
|
toggleId: '#toggle-part-tree',
|
||||||
});
|
});
|
||||||
|
|
||||||
initSideNav();
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -235,6 +235,21 @@ class PartAPITest(InvenTreeAPITestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
def test_paginate(self):
|
||||||
|
"""
|
||||||
|
Test pagination of the Part list API
|
||||||
|
"""
|
||||||
|
|
||||||
|
for n in [1, 5, 10]:
|
||||||
|
response = self.get(reverse('api-part-list'), {'limit': n})
|
||||||
|
|
||||||
|
data = response.data
|
||||||
|
|
||||||
|
self.assertIn('count', data)
|
||||||
|
self.assertIn('results', data)
|
||||||
|
|
||||||
|
self.assertEqual(len(data['results']), n)
|
||||||
|
|
||||||
|
|
||||||
class PartAPIAggregationTest(InvenTreeAPITestCase):
|
class PartAPIAggregationTest(InvenTreeAPITestCase):
|
||||||
"""
|
"""
|
||||||
|
@ -381,6 +381,11 @@ class StockList(generics.ListCreateAPIView):
|
|||||||
|
|
||||||
queryset = self.filter_queryset(self.get_queryset())
|
queryset = self.filter_queryset(self.get_queryset())
|
||||||
|
|
||||||
|
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)
|
serializer = self.get_serializer(queryset, many=True)
|
||||||
|
|
||||||
data = serializer.data
|
data = serializer.data
|
||||||
@ -465,7 +470,9 @@ 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 request.is_ajax():
|
if page is not None:
|
||||||
|
return self.get_paginated_response(data)
|
||||||
|
elif request.is_ajax():
|
||||||
return JsonResponse(data, safe=False)
|
return JsonResponse(data, safe=False)
|
||||||
else:
|
else:
|
||||||
return Response(data)
|
return Response(data)
|
||||||
@ -806,16 +813,15 @@ class StockList(generics.ListCreateAPIView):
|
|||||||
print("After error:", str(updated_after))
|
print("After error:", str(updated_after))
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Limit number of results
|
# Optionally, limit the maximum number of returned results
|
||||||
limit = params.get('limit', None)
|
max_results = params.get('max_results', None)
|
||||||
|
|
||||||
if limit is not None:
|
if max_results is not None:
|
||||||
try:
|
try:
|
||||||
limit = int(limit)
|
max_results = int(max_results)
|
||||||
|
|
||||||
if limit > 0:
|
|
||||||
queryset = queryset[:limit]
|
|
||||||
|
|
||||||
|
if max_results > 0:
|
||||||
|
queryset = queryset[:max_results]
|
||||||
except (ValueError):
|
except (ValueError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -839,9 +845,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 +860,8 @@ class StockList(generics.ListCreateAPIView):
|
|||||||
'batch',
|
'batch',
|
||||||
'part__name',
|
'part__name',
|
||||||
'part__IPN',
|
'part__IPN',
|
||||||
'part__description'
|
'part__description',
|
||||||
|
'location__name',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -52,12 +52,10 @@
|
|||||||
|
|
||||||
|
|
||||||
loadStockTrackingTable($("#track-table"), {
|
loadStockTrackingTable($("#track-table"), {
|
||||||
params: function(p) {
|
params: {
|
||||||
return {
|
|
||||||
ordering: '-date',
|
ordering: '-date',
|
||||||
item: {{ item.pk }},
|
item: {{ item.pk }},
|
||||||
user_detail: true,
|
user_detail: true,
|
||||||
};
|
|
||||||
},
|
},
|
||||||
url: "{% url 'api-stock-track' %}",
|
url: "{% url 'api-stock-track' %}",
|
||||||
});
|
});
|
||||||
|
@ -11,7 +11,9 @@ InvenTree | Stock
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block sidenav %}
|
{% block sidenav %}
|
||||||
<div id='stock-tree'></div>
|
<div id='stock-tree'>
|
||||||
|
{% trans "Loading..." %}
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block pre_content %}
|
{% block pre_content %}
|
||||||
@ -24,6 +26,7 @@ InvenTree | Stock
|
|||||||
|
|
||||||
{% block js_ready %}
|
{% block js_ready %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
|
|
||||||
loadTree("{% url 'api-stock-tree' %}",
|
loadTree("{% url 'api-stock-tree' %}",
|
||||||
"#stock-tree",
|
"#stock-tree",
|
||||||
{
|
{
|
||||||
@ -31,10 +34,10 @@ InvenTree | Stock
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
$("#toggle-stock-tree").click(function() {
|
initNavTree({
|
||||||
toggleSideNav("#sidenav-left");
|
label: 'stock',
|
||||||
return false;
|
treeId: '#sidenav-left',
|
||||||
})
|
toggleId: '#toggle-stock-tree',
|
||||||
|
});
|
||||||
|
|
||||||
initSideNav();
|
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -244,6 +244,19 @@ class StockItemListTest(StockAPITestCase):
|
|||||||
response = self.get_stock(expired=0)
|
response = self.get_stock(expired=0)
|
||||||
self.assertEqual(len(response), 16)
|
self.assertEqual(len(response), 16)
|
||||||
|
|
||||||
|
def test_paginate(self):
|
||||||
|
"""
|
||||||
|
Test that we can paginate results correctly
|
||||||
|
"""
|
||||||
|
|
||||||
|
for n in [1, 5, 10]:
|
||||||
|
response = self.get_stock(limit=n)
|
||||||
|
|
||||||
|
self.assertIn('count', response)
|
||||||
|
self.assertIn('results', response)
|
||||||
|
|
||||||
|
self.assertEqual(len(response['results']), n)
|
||||||
|
|
||||||
|
|
||||||
class StockItemTest(StockAPITestCase):
|
class StockItemTest(StockAPITestCase):
|
||||||
"""
|
"""
|
||||||
|
@ -102,7 +102,7 @@ addHeaderAction('bom-validation', '{% trans "BOM Waiting Validation" %}', 'fa-ti
|
|||||||
loadSimplePartTable("#table-latest-parts", "{% url 'api-part-list' %}", {
|
loadSimplePartTable("#table-latest-parts", "{% url 'api-part-list' %}", {
|
||||||
params: {
|
params: {
|
||||||
ordering: "-creation_date",
|
ordering: "-creation_date",
|
||||||
limit: {% settings_value "PART_RECENT_COUNT" %},
|
max_results: {% settings_value "PART_RECENT_COUNT" %},
|
||||||
},
|
},
|
||||||
name: 'latest_parts',
|
name: 'latest_parts',
|
||||||
});
|
});
|
||||||
@ -132,7 +132,7 @@ addHeaderAction('stock-to-build', '{% trans "Required for Build Orders" %}', 'fa
|
|||||||
loadStockTable($('#table-recently-updated-stock'), {
|
loadStockTable($('#table-recently-updated-stock'), {
|
||||||
params: {
|
params: {
|
||||||
ordering: "-updated",
|
ordering: "-updated",
|
||||||
limit: {% settings_value "STOCK_RECENT_COUNT" %},
|
max_results: {% settings_value "STOCK_RECENT_COUNT" %},
|
||||||
},
|
},
|
||||||
name: 'recently-updated-stock',
|
name: 'recently-updated-stock',
|
||||||
grouping: false,
|
grouping: false,
|
||||||
|
@ -630,6 +630,7 @@ function loadBuildTable(table, options) {
|
|||||||
url: options.url,
|
url: options.url,
|
||||||
queryParams: filters,
|
queryParams: filters,
|
||||||
groupBy: false,
|
groupBy: false,
|
||||||
|
sidePagination: 'server',
|
||||||
name: 'builds',
|
name: 'builds',
|
||||||
original: params,
|
original: params,
|
||||||
columns: [
|
columns: [
|
||||||
|
@ -93,6 +93,7 @@ function loadCompanyTable(table, url, options={}) {
|
|||||||
method: 'get',
|
method: 'get',
|
||||||
queryParams: filters,
|
queryParams: filters,
|
||||||
groupBy: false,
|
groupBy: false,
|
||||||
|
sidePagination: 'server',
|
||||||
formatNoMatches: function() { return "{% trans "No company information found" %}"; },
|
formatNoMatches: function() { return "{% trans "No company information found" %}"; },
|
||||||
showColumns: true,
|
showColumns: true,
|
||||||
name: options.pagetype || 'company',
|
name: options.pagetype || 'company',
|
||||||
|
@ -131,6 +131,7 @@ function loadPurchaseOrderTable(table, options) {
|
|||||||
queryParams: filters,
|
queryParams: filters,
|
||||||
name: 'purchaseorder',
|
name: 'purchaseorder',
|
||||||
groupBy: false,
|
groupBy: false,
|
||||||
|
sidePagination: 'server',
|
||||||
original: options.params,
|
original: options.params,
|
||||||
formatNoMatches: function() { return '{% trans "No purchase orders found" %}'; },
|
formatNoMatches: function() { return '{% trans "No purchase orders found" %}'; },
|
||||||
columns: [
|
columns: [
|
||||||
@ -225,6 +226,7 @@ function loadSalesOrderTable(table, options) {
|
|||||||
queryParams: filters,
|
queryParams: filters,
|
||||||
name: 'salesorder',
|
name: 'salesorder',
|
||||||
groupBy: false,
|
groupBy: false,
|
||||||
|
sidePagination: 'server',
|
||||||
original: options.params,
|
original: options.params,
|
||||||
formatNoMatches: function() { return '{% trans "No sales orders found" %}'; },
|
formatNoMatches: function() { return '{% trans "No sales orders found" %}'; },
|
||||||
columns: [
|
columns: [
|
||||||
|
@ -366,7 +366,6 @@ function loadPartTable(table, url, options={}) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
columns.push({
|
columns.push({
|
||||||
sortable: true,
|
|
||||||
field: 'description',
|
field: 'description',
|
||||||
title: '{% trans "Description" %}',
|
title: '{% trans "Description" %}',
|
||||||
formatter: function(value, row, index, field) {
|
formatter: function(value, row, index, field) {
|
||||||
@ -442,12 +441,13 @@ function loadPartTable(table, url, options={}) {
|
|||||||
|
|
||||||
$(table).inventreeTable({
|
$(table).inventreeTable({
|
||||||
url: url,
|
url: url,
|
||||||
sortName: 'name',
|
|
||||||
method: 'get',
|
method: 'get',
|
||||||
queryParams: filters,
|
queryParams: filters,
|
||||||
groupBy: false,
|
groupBy: false,
|
||||||
name: options.name || 'part',
|
name: options.name || 'part',
|
||||||
original: params,
|
original: params,
|
||||||
|
sidePagination: 'server',
|
||||||
|
pagination: 'true',
|
||||||
formatNoMatches: function() { return '{% trans "No parts found" %}'; },
|
formatNoMatches: function() { return '{% trans "No parts found" %}'; },
|
||||||
columns: columns,
|
columns: columns,
|
||||||
showColumns: true,
|
showColumns: true,
|
||||||
|
@ -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',
|
||||||
@ -927,7 +932,6 @@ function loadStockTrackingTable(table, options) {
|
|||||||
cols.push({
|
cols.push({
|
||||||
field: 'title',
|
field: 'title',
|
||||||
title: '{% trans "Description" %}',
|
title: '{% trans "Description" %}',
|
||||||
sortable: true,
|
|
||||||
formatter: function(value, row, index, field) {
|
formatter: function(value, row, index, field) {
|
||||||
var html = "<b>" + value + "</b>";
|
var html = "<b>" + value + "</b>";
|
||||||
|
|
||||||
@ -952,7 +956,6 @@ function loadStockTrackingTable(table, options) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
cols.push({
|
cols.push({
|
||||||
sortable: true,
|
|
||||||
field: 'user',
|
field: 'user',
|
||||||
title: '{% trans "User" %}',
|
title: '{% trans "User" %}',
|
||||||
formatter: function(value, row, index, field) {
|
formatter: function(value, row, index, field) {
|
||||||
|
@ -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" %}',
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -5,8 +5,8 @@ from . import api
|
|||||||
user_urls = [
|
user_urls = [
|
||||||
url(r'^(?P<pk>[0-9]+)/?$', api.UserDetail.as_view(), name='user-detail'),
|
url(r'^(?P<pk>[0-9]+)/?$', api.UserDetail.as_view(), name='user-detail'),
|
||||||
|
|
||||||
url(r'roles', api.RoleDetails.as_view(), name='api-user-roles'),
|
url(r'roles/?$', api.RoleDetails.as_view(), name='api-user-roles'),
|
||||||
url(r'token', api.GetAuthToken.as_view(), name='api-token'),
|
url(r'token/?$', api.GetAuthToken.as_view(), name='api-token'),
|
||||||
|
|
||||||
url(r'^$', api.UserList.as_view()),
|
url(r'^$', api.UserList.as_view()),
|
||||||
]
|
]
|
||||||
|
@ -9,6 +9,14 @@ InvenTree is designed to be lightweight and easy to use for SME or hobbyist appl
|
|||||||
|
|
||||||
However, powerful business logic works in the background to ensure that stock tracking history is maintained, and users have ready access to stock level information.
|
However, powerful business logic works in the background to ensure that stock tracking history is maintained, and users have ready access to stock level information.
|
||||||
|
|
||||||
|
# Companion App
|
||||||
|
|
||||||
|
InvenTree is supported by a [companion mobile app](https://inventree.readthedocs.io/en/latest/app/app/) which allows users access to stock control information and functionality.
|
||||||
|
|
||||||
|
[**Download InvenTree from the Android Play Store**](https://play.google.com/store/apps/details?id=inventree.inventree_app)
|
||||||
|
|
||||||
|
*Currently the mobile app is only availble for Android*
|
||||||
|
|
||||||
# Documentation
|
# Documentation
|
||||||
|
|
||||||
For InvenTree documentation, refer to the [InvenTree documentation website](https://inventree.readthedocs.io/en/latest/).
|
For InvenTree documentation, refer to the [InvenTree documentation website](https://inventree.readthedocs.io/en/latest/).
|
||||||
|
Loading…
Reference in New Issue
Block a user