Merge remote-tracking branch 'inventree/master'

This commit is contained in:
Oliver Walters 2019-05-23 23:24:11 +10:00
commit e07d7e3874
16 changed files with 133 additions and 23 deletions

View File

@ -11,6 +11,8 @@ from rest_framework import generics, permissions
from django.conf.urls import url, include from django.conf.urls import url, include
from InvenTree.helpers import str2bool
from .models import Company from .models import Company
from .models import SupplierPart, SupplierPriceBreak from .models import SupplierPart, SupplierPriceBreak
@ -84,6 +86,16 @@ class SupplierPartList(generics.ListCreateAPIView):
'supplier', 'supplier',
'pricebreaks') 'pricebreaks')
def get_serializer(self, *args, **kwargs):
# Do we wish to include extra detail?
part_detail = str2bool(self.request.GET.get('part_detail', None))
kwargs['part_detail'] = part_detail
kwargs['context'] = self.get_serializer_context()
return self.serializer_class(*args, **kwargs)
serializer_class = SupplierPartSerializer serializer_class = SupplierPartSerializer
permission_classes = [ permission_classes = [

View File

@ -64,6 +64,15 @@ class SupplierPartSerializer(serializers.ModelSerializer):
pricing = serializers.CharField(source='unit_pricing', read_only=True) pricing = serializers.CharField(source='unit_pricing', read_only=True)
def __init__(self, *args, **kwargs):
part_detail = kwargs.pop('part_detail', False)
super(SupplierPartSerializer, self).__init__(*args, **kwargs)
if part_detail is not True:
self.fields.pop('part_detail')
class Meta: class Meta:
model = SupplierPart model = SupplierPart
fields = [ fields = [

View File

@ -47,10 +47,13 @@
$("#part-table").bootstrapTable({ $("#part-table").bootstrapTable({
sortable: true, sortable: true,
search: true, search: true,
pagination: true,
pageSize: 50,
formatNoMatches: function() { return "No supplier parts found for {{ company.name }}"; }, formatNoMatches: function() { return "No supplier parts found for {{ company.name }}"; },
queryParams: function(p) { queryParams: function(p) {
return { return {
supplier: {{ company.id }} supplier: {{ company.id }},
part_detail: true,
} }
}, },
columns: [ columns: [

View File

@ -17,6 +17,8 @@
url: "{% url 'api-stock-list' %}", url: "{% url 'api-stock-list' %}",
params: { params: {
supplier: {{ company.id }}, supplier: {{ company.id }},
part_detail: true,
location_detail: true,
}, },
buttons: [ buttons: [
'#stock-options', '#stock-options',

View File

@ -45,6 +45,7 @@ InvenTree | Supplier List
sortable: true, sortable: true,
search: true, search: true,
pagination: true, pagination: true,
pageSize: 50,
formatNoMatches: function() { return "No company information found"; }, formatNoMatches: function() { return "No company information found"; },
columns: [ columns: [
{ {

View File

@ -22,6 +22,7 @@ from .serializers import CategorySerializer
from .serializers import PartStarSerializer from .serializers import PartStarSerializer
from InvenTree.views import TreeSerializer from InvenTree.views import TreeSerializer
from InvenTree.helpers import str2bool
class PartCategoryTree(TreeSerializer): class PartCategoryTree(TreeSerializer):
@ -206,6 +207,18 @@ class BomList(generics.ListCreateAPIView):
serializer_class = BomItemSerializer serializer_class = BomItemSerializer
def get_serializer(self, *args, **kwargs):
# Do we wish to include extra detail?
part_detail = str2bool(self.request.GET.get('part_detail', None))
sub_part_detail = str2bool(self.request.GET.get('sub_part_detail', None))
kwargs['part_detail'] = part_detail
kwargs['sub_part_detail'] = sub_part_detail
kwargs['context'] = self.get_serializer_context()
return self.serializer_class(*args, **kwargs)
def get_queryset(self): def get_queryset(self):
queryset = BomItem.objects.all() queryset = BomItem.objects.all()
queryset = self.get_serializer_class().setup_eager_loading(queryset) queryset = self.get_serializer_class().setup_eager_loading(queryset)

View File

@ -125,6 +125,21 @@ class BomItemSerializer(InvenTreeModelSerializer):
sub_part_detail = PartBriefSerializer(source='sub_part', many=False, read_only=True) sub_part_detail = PartBriefSerializer(source='sub_part', many=False, read_only=True)
price_range = serializers.CharField(read_only=True) price_range = serializers.CharField(read_only=True)
def __init__(self, *args, **kwargs):
# part_detail and sub_part_detail serializers are only included if requested.
# This saves a bunch of database requests
part_detail = kwargs.pop('part_detail', False)
sub_part_detail = kwargs.pop('sub_part_detail', False)
super(BomItemSerializer, self).__init__(*args, **kwargs)
if part_detail is not True:
self.fields.pop('part_detail')
if sub_part_detail is not True:
self.fields.pop('sub_part_detail')
@staticmethod @staticmethod
def setup_eager_loading(queryset): def setup_eager_loading(queryset):
queryset = queryset.prefetch_related('part') queryset = queryset.prefetch_related('part')

View File

@ -72,7 +72,8 @@
editable: {{ editing_enabled }}, editable: {{ editing_enabled }},
bom_url: "{% url 'api-bom-list' %}", bom_url: "{% url 'api-bom-list' %}",
part_url: "{% url 'api-part-list' %}", part_url: "{% url 'api-part-list' %}",
parent_id: {{ part.id }} parent_id: {{ part.id }} ,
sub_part_detail: true,
}); });
{% if editing_enabled %} {% if editing_enabled %}

View File

@ -35,6 +35,7 @@
sortable: true, sortable: true,
search: true, search: true,
pagination: true, pagination: true,
pageSize: 50,
queryParams: function(p) { queryParams: function(p) {
return { return {
part: {{ part.id }}, part: {{ part.id }},

View File

@ -37,6 +37,8 @@
loadStockTable($("#stock-table"), { loadStockTable($("#stock-table"), {
params: { params: {
part: {{ part.id }}, part: {{ part.id }},
location_detail: true,
part_detail: true,
}, },
buttons: [ buttons: [
'#stock-options', '#stock-options',

View File

@ -30,7 +30,8 @@
formatNoMatches: function() { return "{{ part.full_name }} is not used to make any other parts"; }, formatNoMatches: function() { return "{{ part.full_name }} is not used to make any other parts"; },
queryParams: function(p) { queryParams: function(p) {
return { return {
sub_part: {{ part.id }} sub_part: {{ part.id }},
part_detail: true,
} }
}, },
columns: [ columns: [

View File

@ -121,7 +121,16 @@ function loadBomTable(table, options) {
} }
return text; return text;
} },
footerFormatter: function(data) {
var quantity = 0;
data.forEach(function(item) {
quantity += item.quantity;
});
return quantity;
},
} }
); );
@ -190,20 +199,31 @@ function loadBomTable(table, options) {
// Configure the table (bootstrap-table) // Configure the table (bootstrap-table)
var params = {
part: options.parent_id,
ordering: 'name',
}
if (options.part_detail) {
params.part_detail = true;
}
if (options.sub_part_detail) {
params.sub_part_detail = true;
}
table.bootstrapTable({ table.bootstrapTable({
sortable: true, sortable: true,
search: true, search: true,
formatNoMatches: function() { return "No BOM items found"; }, formatNoMatches: function() { return "No BOM items found"; },
clickToSelect: true, clickToSelect: true,
showFooter: true,
queryParams: function(p) { queryParams: function(p) {
return { return params;
part: options.parent_id, },
ordering: 'name', columns: cols,
} url: options.bom_url
}, });
columns: cols,
url: options.bom_url
});
// In editing mode, attached editables to the appropriate table elements // In editing mode, attached editables to the appropriate table elements
if (options.editable) { if (options.editable) {

View File

@ -377,7 +377,7 @@ function loadStockTable(table, options) {
search: true, search: true,
method: 'get', method: 'get',
pagination: true, pagination: true,
pageSize: 25, pageSize: 50,
rememberOrder: true, rememberOrder: true,
queryParams: options.params, queryParams: options.params,
columns: [ columns: [
@ -392,25 +392,25 @@ function loadStockTable(table, options) {
visible: false, visible: false,
}, },
{ {
field: 'part.full_name', field: 'part_detail',
title: 'Part', title: 'Part',
sortable: true, sortable: true,
formatter: function(value, row, index, field) { formatter: function(value, row, index, field) {
return imageHoverIcon(row.part.image_url) + renderLink(value, row.part.url + 'stock/'); return imageHoverIcon(value.image_url) + renderLink(value.full_name, value.url + 'stock/');
} }
}, },
{ {
field: 'part.description', field: 'part_detail.description',
title: 'Description', title: 'Description',
sortable: true, sortable: true,
}, },
{ {
field: 'location', field: 'location_detail',
title: 'Location', title: 'Location',
sortable: true, sortable: true,
formatter: function(value, row, index, field) { formatter: function(value, row, index, field) {
if (row.location) { if (value) {
return renderLink(row.location.pathstring, row.location.url); return renderLink(value.pathstring, value.url);
} }
else { else {
return '<i>No stock location set</i>'; return '<i>No stock location set</i>';
@ -558,6 +558,7 @@ function loadStockTrackingTable(table, options) {
queryParams: options.params, queryParams: options.params,
columns: cols, columns: cols,
pagination: true, pagination: true,
pageSize: 50,
url: options.url, url: options.url,
}); });

View File

@ -18,6 +18,7 @@ from .serializers import LocationSerializer
from .serializers import StockTrackingSerializer from .serializers import StockTrackingSerializer
from InvenTree.views import TreeSerializer from InvenTree.views import TreeSerializer
from InvenTree.helpers import str2bool
from rest_framework.serializers import ValidationError from rest_framework.serializers import ValidationError
from rest_framework.views import APIView from rest_framework.views import APIView
@ -245,6 +246,17 @@ class StockList(generics.ListCreateAPIView):
- supplier: Filter by supplier - supplier: Filter by supplier
""" """
def get_serializer(self, *args, **kwargs):
part_detail = str2bool(self.request.GET.get('part_detail', None))
location_detail = str2bool(self.request.GET.get('location_detail', None))
kwargs['part_detail'] = part_detail
kwargs['location_detail'] = location_detail
kwargs['context'] = self.get_serializer_context()
return self.serializer_class(*args, **kwargs)
def get_queryset(self): def get_queryset(self):
""" """
If the query includes a particular location, If the query includes a particular location,

View File

@ -55,11 +55,11 @@ class StockItemSerializer(serializers.ModelSerializer):
""" """
url = serializers.CharField(source='get_absolute_url', read_only=True) url = serializers.CharField(source='get_absolute_url', read_only=True)
part = PartBriefSerializer(many=False, read_only=True)
location = LocationBriefSerializer(many=False, read_only=True)
status_text = serializers.CharField(source='get_status_display', read_only=True) status_text = serializers.CharField(source='get_status_display', read_only=True)
part_detail = PartBriefSerializer(source='part', many=False, read_only=True)
location_detail = LocationBriefSerializer(source='location', many=False, read_only=True)
@staticmethod @staticmethod
def setup_eager_loading(queryset): def setup_eager_loading(queryset):
queryset = queryset.prefetch_related('part') queryset = queryset.prefetch_related('part')
@ -69,14 +69,29 @@ class StockItemSerializer(serializers.ModelSerializer):
return queryset return queryset
def __init__(self, *args, **kwargs):
part_detail = kwargs.pop('part_detail', False)
location_detail = kwargs.pop('location_detail', False)
super(StockItemSerializer, self).__init__(*args, **kwargs)
if part_detail is not True:
self.fields.pop('part_detail')
if location_detail is not True:
self.fields.pop('location_detail')
class Meta: class Meta:
model = StockItem model = StockItem
fields = [ fields = [
'pk', 'pk',
'url', 'url',
'part', 'part',
'part_detail',
'supplier_part', 'supplier_part',
'location', 'location',
'location_detail',
'in_stock', 'in_stock',
'quantity', 'quantity',
'serial', 'serial',

View File

@ -141,6 +141,8 @@
{% if location %} {% if location %}
location: {{ location.id }}, location: {{ location.id }},
{% endif %} {% endif %}
part_detail: true,
location_detail: true,
}, },
url: "{% url 'api-stock-list' %}", url: "{% url 'api-stock-list' %}",
}); });