diff --git a/InvenTree/InvenTree/version.py b/InvenTree/InvenTree/version.py
index 85dc275690..6d4848f436 100644
--- a/InvenTree/InvenTree/version.py
+++ b/InvenTree/InvenTree/version.py
@@ -12,11 +12,16 @@ import common.models
INVENTREE_SW_VERSION = "0.7.0 dev"
# InvenTree API version
-INVENTREE_API_VERSION = 31
+INVENTREE_API_VERSION = 32
"""
Increment this API version number whenever there is a significant change to the API that any clients need to know about
+v32 -> 2022-03-19
+ - Adds "parameters" detail to Part API endpoint (use ¶meters=true)
+ - Adds ability to filter PartParameterTemplate API by Part instance
+ - Adds ability to filter PartParameterTemplate API by PartCategory instance
+
v31 -> 2022-03-14
- Adds "updated" field to SupplierPriceBreakList and SupplierPriceBreakDetail API endpoints
diff --git a/InvenTree/common/settings.py b/InvenTree/common/settings.py
index 6973f9ecd3..2dd7756eba 100644
--- a/InvenTree/common/settings.py
+++ b/InvenTree/common/settings.py
@@ -17,7 +17,7 @@ def currency_code_default():
from common.models import InvenTreeSetting
try:
- code = InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY')
+ code = InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY', create=False)
except ProgrammingError: # pragma: no cover
# database is not initialized yet
code = ''
diff --git a/InvenTree/part/api.py b/InvenTree/part/api.py
index f542e36c68..3a2bb6eeb3 100644
--- a/InvenTree/part/api.py
+++ b/InvenTree/part/api.py
@@ -855,6 +855,14 @@ class PartList(generics.ListCreateAPIView):
kwargs['starred_parts'] = self.starred_parts
+ try:
+ params = self.request.query_params
+
+ kwargs['parameters'] = str2bool(params.get('parameters', None))
+
+ except AttributeError:
+ pass
+
return self.serializer_class(*args, **kwargs)
def list(self, request, *args, **kwargs):
@@ -1405,6 +1413,44 @@ class PartParameterTemplateList(generics.ListCreateAPIView):
'name',
]
+ def filter_queryset(self, queryset):
+ """
+ Custom filtering for the PartParameterTemplate API
+ """
+
+ queryset = super().filter_queryset(queryset)
+
+ params = self.request.query_params
+
+ # Filtering against a "Part" - return only parameter templates which are referenced by a part
+ part = params.get('part', None)
+
+ if part is not None:
+
+ try:
+ part = Part.objects.get(pk=part)
+ parameters = PartParameter.objects.filter(part=part)
+ template_ids = parameters.values_list('template').distinct()
+ queryset = queryset.filter(pk__in=[el[0] for el in template_ids])
+ except (ValueError, Part.DoesNotExist):
+ pass
+
+ # Filtering against a "PartCategory" - return only parameter templates which are referenced by parts in this category
+ category = params.get('category', None)
+
+ if category is not None:
+
+ try:
+ category = PartCategory.objects.get(pk=category)
+ cats = category.get_descendants(include_self=True)
+ parameters = PartParameter.objects.filter(part__category__in=cats)
+ template_ids = parameters.values_list('template').distinct()
+ queryset = queryset.filter(pk__in=[el[0] for el in template_ids])
+ except (ValueError, PartCategory.DoesNotExist):
+ pass
+
+ return queryset
+
class PartParameterList(generics.ListCreateAPIView):
""" API endpoint for accessing a list of PartParameter objects
diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py
index 5d63448bb7..c46950adca 100644
--- a/InvenTree/part/serializers.py
+++ b/InvenTree/part/serializers.py
@@ -211,6 +211,34 @@ class PartThumbSerializerUpdate(InvenTreeModelSerializer):
]
+class PartParameterTemplateSerializer(InvenTreeModelSerializer):
+ """ JSON serializer for the PartParameterTemplate model """
+
+ class Meta:
+ model = PartParameterTemplate
+ fields = [
+ 'pk',
+ 'name',
+ 'units',
+ ]
+
+
+class PartParameterSerializer(InvenTreeModelSerializer):
+ """ JSON serializers for the PartParameter model """
+
+ template_detail = PartParameterTemplateSerializer(source='template', many=False, read_only=True)
+
+ class Meta:
+ model = PartParameter
+ fields = [
+ 'pk',
+ 'part',
+ 'template',
+ 'template_detail',
+ 'data'
+ ]
+
+
class PartBriefSerializer(InvenTreeModelSerializer):
""" Serializer for Part (brief detail) """
@@ -259,11 +287,16 @@ class PartSerializer(InvenTreeModelSerializer):
category_detail = kwargs.pop('category_detail', False)
+ parameters = kwargs.pop('parameters', False)
+
super().__init__(*args, **kwargs)
if category_detail is not True:
self.fields.pop('category_detail')
+ if parameters is not True:
+ self.fields.pop('parameters')
+
@staticmethod
def annotate_queryset(queryset):
"""
@@ -356,19 +389,18 @@ class PartSerializer(InvenTreeModelSerializer):
# PrimaryKeyRelated fields (Note: enforcing field type here results in much faster queries, somehow...)
category = serializers.PrimaryKeyRelatedField(queryset=PartCategory.objects.all())
- # TODO - Include annotation for the following fields:
- # allocated_stock = serializers.FloatField(source='allocation_count', read_only=True)
- # bom_items = serializers.IntegerField(source='bom_count', read_only=True)
- # used_in = serializers.IntegerField(source='used_in_count', read_only=True)
+ parameters = PartParameterSerializer(
+ many=True,
+ read_only=True,
+ )
class Meta:
model = Part
partial = True
fields = [
'active',
- # 'allocated_stock',
+
'assembly',
- # 'bom_items',
'category',
'category_detail',
'component',
@@ -388,6 +420,7 @@ class PartSerializer(InvenTreeModelSerializer):
'minimum_stock',
'name',
'notes',
+ 'parameters',
'pk',
'purchaseable',
'revision',
@@ -398,7 +431,6 @@ class PartSerializer(InvenTreeModelSerializer):
'thumbnail',
'trackable',
'units',
- # 'used_in',
'variant_of',
'virtual',
]
@@ -600,34 +632,6 @@ class BomItemSerializer(InvenTreeModelSerializer):
]
-class PartParameterTemplateSerializer(InvenTreeModelSerializer):
- """ JSON serializer for the PartParameterTemplate model """
-
- class Meta:
- model = PartParameterTemplate
- fields = [
- 'pk',
- 'name',
- 'units',
- ]
-
-
-class PartParameterSerializer(InvenTreeModelSerializer):
- """ JSON serializers for the PartParameter model """
-
- template_detail = PartParameterTemplateSerializer(source='template', many=False, read_only=True)
-
- class Meta:
- model = PartParameter
- fields = [
- 'pk',
- 'part',
- 'template',
- 'template_detail',
- 'data'
- ]
-
-
class CategoryParameterTemplateSerializer(InvenTreeModelSerializer):
""" Serializer for PartCategoryParameterTemplate """
diff --git a/InvenTree/part/templates/part/category.html b/InvenTree/part/templates/part/category.html
index acbd0b16f1..6a61ef2fbf 100644
--- a/InvenTree/part/templates/part/category.html
+++ b/InvenTree/part/templates/part/category.html
@@ -223,13 +223,14 @@
{{ block.super }}
{% if category %}
- loadParametricPartTable(
- "#parametric-part-table",
- {
- headers: {{ headers|safe }},
- data: {{ parameters|safe }},
- }
- );
+ onPanelLoad('parameters', function() {
+ loadParametricPartTable(
+ "#parametric-part-table",
+ {
+ category: {{ category.pk }},
+ }
+ );
+ });
$("#toggle-starred").click(function() {
toggleStar({
@@ -240,9 +241,6 @@
{% endif %}
- // Enable left-hand navigation sidebar
- enableSidebar('category');
-
// Enable breadcrumb tree view
enableBreadcrumbTree({
label: 'category',
@@ -258,18 +256,20 @@
}
});
- loadPartCategoryTable(
- $('#subcategory-table'), {
- params: {
- {% if category %}
- parent: {{ category.pk }},
- {% else %}
- parent: null,
- {% endif %}
- },
- allowTreeView: true,
- }
- );
+ onPanelLoad('subcategories', function() {
+ loadPartCategoryTable(
+ $('#subcategory-table'), {
+ params: {
+ {% if category %}
+ parent: {{ category.pk }},
+ {% else %}
+ parent: null,
+ {% endif %}
+ },
+ allowTreeView: true,
+ }
+ );
+ });
$("#cat-create").click(function() {
@@ -339,19 +339,24 @@
{% endif %}
- loadPartTable(
- "#part-table",
- "{% url 'api-part-list' %}",
- {
- params: {
- {% if category %}category: {{ category.id }},
- {% else %}category: "null",
- {% endif %}
+ onPanelLoad('parts', function() {
+ loadPartTable(
+ "#part-table",
+ "{% url 'api-part-list' %}",
+ {
+ params: {
+ {% if category %}category: {{ category.id }},
+ {% else %}category: "null",
+ {% endif %}
+ },
+ buttons: ['#part-options'],
+ checkbox: true,
+ gridView: true,
},
- buttons: ['#part-options'],
- checkbox: true,
- gridView: true,
- },
- );
+ );
+ });
+
+ // Enable left-hand navigation sidebar
+ enableSidebar('category');
{% endblock %}
diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py
index fb45db8f07..9f3cd07f7c 100644
--- a/InvenTree/part/views.py
+++ b/InvenTree/part/views.py
@@ -988,22 +988,6 @@ class CategoryDetail(InvenTreeRoleMixin, DetailView):
category = kwargs.get('object', None)
if category:
- cascade = kwargs.get('cascade', True)
-
- # Prefetch parts parameters
- parts_parameters = category.prefetch_parts_parameters(cascade=cascade)
-
- # Get table headers (unique parameters names)
- context['headers'] = category.get_unique_parameters(cascade=cascade,
- prefetch=parts_parameters)
-
- # Insert part information
- context['headers'].insert(0, 'description')
- context['headers'].insert(0, 'part')
-
- # Get parameters data
- context['parameters'] = category.get_parts_parameters(cascade=cascade,
- prefetch=parts_parameters)
# Insert "starred" information
context['starred'] = category.is_starred_by(self.request.user)
diff --git a/InvenTree/stock/templates/stock/location.html b/InvenTree/stock/templates/stock/location.html
index 1da0030bc6..45dcc0ba59 100644
--- a/InvenTree/stock/templates/stock/location.html
+++ b/InvenTree/stock/templates/stock/location.html
@@ -202,15 +202,17 @@
{% block js_ready %}
{{ block.super }}
- loadStockLocationTable($('#sublocation-table'), {
- params: {
- {% if location %}
- parent: {{ location.pk }},
- {% else %}
- parent: 'null',
- {% endif %}
- },
- allowTreeView: true,
+ onPanelLoad('sublocations', function() {
+ loadStockLocationTable($('#sublocation-table'), {
+ params: {
+ {% if location %}
+ parent: {{ location.pk }},
+ {% else %}
+ parent: 'null',
+ {% endif %}
+ },
+ allowTreeView: true,
+ });
});
linkButtonsToSelection(
@@ -325,19 +327,21 @@
});
});
- loadStockTable($("#stock-table"), {
- buttons: [
- '#stock-options',
- ],
- params: {
- {% if location %}
- location: {{ location.pk }},
- {% endif %}
- part_detail: true,
- location_detail: true,
- supplier_part_detail: true,
- },
- url: "{% url 'api-stock-list' %}",
+ onPanelLoad('stock', function() {
+ loadStockTable($("#stock-table"), {
+ buttons: [
+ '#stock-options',
+ ],
+ params: {
+ {% if location %}
+ location: {{ location.pk }},
+ {% endif %}
+ part_detail: true,
+ location_detail: true,
+ supplier_part_detail: true,
+ },
+ url: "{% url 'api-stock-list' %}",
+ });
});
enableSidebar('stocklocation');
diff --git a/InvenTree/templates/js/translated/model_renderers.js b/InvenTree/templates/js/translated/model_renderers.js
index 68ba496309..e3abe1186f 100644
--- a/InvenTree/templates/js/translated/model_renderers.js
+++ b/InvenTree/templates/js/translated/model_renderers.js
@@ -312,7 +312,13 @@ function renderPartCategory(name, data, parameters, options) {
// eslint-disable-next-line no-unused-vars
function renderPartParameterTemplate(name, data, parameters, options) {
- var html = `${data.name} - [${data.units}]`;
+ var units = '';
+
+ if (data.units) {
+ units = ` [${data.units}]`;
+ }
+
+ var html = `${data.name}${units}`;
return html;
}
diff --git a/InvenTree/templates/js/translated/part.js b/InvenTree/templates/js/translated/part.js
index dcdd3da7d6..b0283a1b35 100644
--- a/InvenTree/templates/js/translated/part.js
+++ b/InvenTree/templates/js/translated/part.js
@@ -1068,68 +1068,84 @@ function loadRelatedPartsTable(table, part_id, options={}) {
}
+/* Load parametric table for part parameters
+ */
function loadParametricPartTable(table, options={}) {
- /* Load parametric table for part parameters
- *
- * Args:
- * - table: HTML reference to the table
- * - table_headers: Unique parameters found in category
- * - table_data: Parameters data
- */
- var table_headers = options.headers;
- var table_data = options.data;
+ var columns = [
+ {
+ field: 'name',
+ title: '{% trans "Part" %}',
+ switchable: false,
+ sortable: true,
+ formatter: function(value, row) {
+ var name = row.full_name;
- var columns = [];
+ var display = imageHoverIcon(row.thumbnail) + renderLink(name, `/part/${row.pk}/`);
- for (var header of table_headers) {
- if (header === 'part') {
- columns.push({
- field: header,
- title: '{% trans "Part" %}',
- sortable: true,
- sortName: 'name',
- formatter: function(value, row) {
-
- var name = '';
-
- if (row.IPN) {
- name += row.IPN + ' | ' + row.name;
- } else {
- name += row.name;
- }
-
- return renderLink(name, '/part/' + row.pk + '/');
- }
- });
- } else if (header === 'description') {
- columns.push({
- field: header,
- title: '{% trans "Description" %}',
- sortable: true,
- });
- } else {
- columns.push({
- field: header,
- title: header,
- sortable: true,
- filterControl: 'input',
- });
+ return display;
+ }
}
- }
+ ];
+
+ // Request a list of parameters we are interested in for this category
+ inventreeGet(
+ '{% url "api-part-parameter-template-list" %}',
+ {
+ category: options.category,
+ },
+ {
+ async: false,
+ success: function(response) {
+ for (var template of response) {
+ columns.push({
+ field: `parameter_${template.pk}`,
+ title: template.name,
+ switchable: true,
+ sortable: true,
+ filterControl: 'input',
+ });
+ }
+ }
+ }
+ );
+
+ // TODO: Re-enable filter control for parameter values
$(table).inventreeTable({
- sortName: 'part',
- queryParams: table_headers,
+ url: '{% url "api-part-list" %}',
+ queryParams: {
+ category: options.category,
+ cascade: true,
+ parameters: true,
+ },
groupBy: false,
- name: options.name || 'parametric',
+ name: options.name || 'part-parameters',
formatNoMatches: function() {
return '{% trans "No parts found" %}';
},
columns: columns,
showColumns: true,
- data: table_data,
- filterControl: true,
+ // filterControl: true,
+ sidePagination: 'server',
+ idField: 'pk',
+ uniqueId: 'pk',
+ onLoadSuccess: function() {
+
+ var data = $(table).bootstrapTable('getData');
+
+ for (var idx = 0; idx < data.length; idx++) {
+ var row = data[idx];
+ var pk = row.pk;
+
+ // Make each parameter accessible, based on the "template" columns
+ row.parameters.forEach(function(parameter) {
+ row[`parameter_${parameter.template}`] = parameter.data;
+ });
+
+ $(table).bootstrapTable('updateRow', pk, row);
+ }
+ }
});
}