mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-30 04:26:44 +00:00
Merge pull request #952 from SchrodingersGat/variant-table
Add ability to filter part list by 'ancestor'
This commit is contained in:
commit
81b50312e2
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -275,6 +275,7 @@ class PartList(generics.ListCreateAPIView):
|
|||||||
- purchaseable: Filter by purcahseable field
|
- purchaseable: Filter by purcahseable field
|
||||||
- salable: Filter by salable field
|
- salable: Filter by salable field
|
||||||
- active: Filter by active field
|
- active: Filter by active field
|
||||||
|
- ancestor: Filter parts by 'ancestor' (template / variant tree)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
serializer_class = part_serializers.PartSerializer
|
serializer_class = part_serializers.PartSerializer
|
||||||
@ -387,10 +388,24 @@ class PartList(generics.ListCreateAPIView):
|
|||||||
We overide the DRF filter_fields here because
|
We overide the DRF filter_fields here because
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
params = self.request.query_params
|
||||||
|
|
||||||
queryset = super().filter_queryset(queryset)
|
queryset = super().filter_queryset(queryset)
|
||||||
|
|
||||||
|
# Filter by 'ancestor'?
|
||||||
|
ancestor = params.get('ancestor', None)
|
||||||
|
|
||||||
|
if ancestor is not None:
|
||||||
|
# If an 'ancestor' part is provided, filter to match only children
|
||||||
|
try:
|
||||||
|
ancestor = Part.objects.get(pk=ancestor)
|
||||||
|
descendants = ancestor.get_descendants(include_self=False)
|
||||||
|
queryset = queryset.filter(pk__in=[d.pk for d in descendants])
|
||||||
|
except (ValueError, Part.DoesNotExist):
|
||||||
|
pass
|
||||||
|
|
||||||
# Filter by 'starred' parts?
|
# Filter by 'starred' parts?
|
||||||
starred = self.request.query_params.get('starred', None)
|
starred = params.get('starred', None)
|
||||||
|
|
||||||
if starred is not None:
|
if starred is not None:
|
||||||
starred = str2bool(starred)
|
starred = str2bool(starred)
|
||||||
@ -402,10 +417,10 @@ class PartList(generics.ListCreateAPIView):
|
|||||||
queryset = queryset.exclude(pk__in=starred_parts)
|
queryset = queryset.exclude(pk__in=starred_parts)
|
||||||
|
|
||||||
# Cascade?
|
# Cascade?
|
||||||
cascade = str2bool(self.request.query_params.get('cascade', None))
|
cascade = str2bool(params.get('cascade', None))
|
||||||
|
|
||||||
# Does the user wish to filter by category?
|
# Does the user wish to filter by category?
|
||||||
cat_id = self.request.query_params.get('category', None)
|
cat_id = params.get('category', None)
|
||||||
|
|
||||||
if cat_id is None:
|
if cat_id is None:
|
||||||
# No category filtering if category is not specified
|
# No category filtering if category is not specified
|
||||||
@ -437,7 +452,7 @@ class PartList(generics.ListCreateAPIView):
|
|||||||
queryset = part_serializers.PartSerializer.annotate_queryset(queryset)
|
queryset = part_serializers.PartSerializer.annotate_queryset(queryset)
|
||||||
|
|
||||||
# Filter by whether the part has stock
|
# Filter by whether the part has stock
|
||||||
has_stock = self.request.query_params.get("has_stock", None)
|
has_stock = params.get("has_stock", None)
|
||||||
if has_stock is not None:
|
if has_stock is not None:
|
||||||
has_stock = str2bool(has_stock)
|
has_stock = str2bool(has_stock)
|
||||||
|
|
||||||
@ -447,7 +462,7 @@ class PartList(generics.ListCreateAPIView):
|
|||||||
queryset = queryset.filter(Q(in_stock__lte=0))
|
queryset = queryset.filter(Q(in_stock__lte=0))
|
||||||
|
|
||||||
# If we are filtering by 'low_stock' status
|
# If we are filtering by 'low_stock' status
|
||||||
low_stock = self.request.query_params.get('low_stock', None)
|
low_stock = params.get('low_stock', None)
|
||||||
|
|
||||||
if low_stock is not None:
|
if low_stock is not None:
|
||||||
low_stock = str2bool(low_stock)
|
low_stock = str2bool(low_stock)
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
|
||||||
<div class='navigation'>
|
<div class='navigation'>
|
||||||
<nav aria-label="breadcrumb">
|
<nav aria-label="breadcrumb">
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li><a href='#' id='toggle-part-tree'><b><span class='fas fa-stream'></span></b></a></li>
|
<li><a href='#' id='toggle-part-tree'><b><span class='fas fa-stream'></span></b></a></li>
|
||||||
<li class="breadcrumb-item{% if category is None %} active" aria-current="page{% endif %}"><a href="/part/">Parts</a></li>
|
<li class="breadcrumb-item{% if category is None %} active" aria-current="page{% endif %}"><a href="/part/">{% trans "Parts" %}</a></li>
|
||||||
{% if category %}
|
{% if category %}
|
||||||
{% for path_item in category.parentpath %}
|
{% for path_item in category.parentpath %}
|
||||||
<li class="breadcrumb-item"><a href="{% url 'category-detail' path_item.id %}">{{ path_item.name }}</a></li>
|
<li class="breadcrumb-item"><a href="{% url 'category-detail' path_item.id %}">{{ path_item.name }}</a></li>
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% if part.variant_of %}
|
{% if part.variant_of %}
|
||||||
<div class='alert alert-info alert-block'>
|
<div class='alert alert-info alert-block'>
|
||||||
{% trans "This part is a variant of" %} <b><a href="{% url 'part-detail' part.variant_of.id %}">{{ part.variant_of.full_name }}</a></b>
|
{% trans "This part is a variant of" %} <b><a href="{% url 'part-variants' part.variant_of.id %}">{{ part.variant_of.full_name }}</a></b>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
{% extends "part/part_base.html" %}
|
{% extends "part/part_base.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
{% load i18n %}
|
||||||
{% load inventree_extras %}
|
{% load inventree_extras %}
|
||||||
|
|
||||||
{% block details %}
|
{% block details %}
|
||||||
@ -7,7 +8,7 @@
|
|||||||
|
|
||||||
<div class='row'>
|
<div class='row'>
|
||||||
<div class='col-sm-6'>
|
<div class='col-sm-6'>
|
||||||
<h4>Part Variants</h4>
|
<h4>{% trans "Part Variants" %}</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class='col-sm-6'>
|
<div class='col-sm-6'>
|
||||||
</div>
|
</div>
|
||||||
@ -17,45 +18,32 @@
|
|||||||
<div id='button-toolbar'>
|
<div id='button-toolbar'>
|
||||||
<div class='btn-group'>
|
<div class='btn-group'>
|
||||||
{% if part.is_template and part.active %}
|
{% if part.is_template and part.active %}
|
||||||
<button class='btn btn-success' id='new-variant' title='Create new variant'>New Variant</button>
|
<button class='btn btn-success' id='new-variant' title='{% trans "Create new variant" %}'>{% trans "New Variant" %}</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table class='table table-striped table-condensed' id='variant-table' data-toolbar='#button-toolbar'>
|
<table class='table table-striped table-condensed' id='variants-table' data-toolbar='#button-toolbar'>
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th data-sortable='true'>Variant</th>
|
|
||||||
<th data-sortable='true'>Description</th>
|
|
||||||
<th data-sortable='true'>Stock</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for variant in part.variants.all %}
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
{% include "hover_image.html" with image=variant.image hover=True %}
|
|
||||||
<a href="{% url 'part-detail' variant.id %}">{{ variant.full_name }}</a>
|
|
||||||
{% if not variant.active %}
|
|
||||||
<span class='label label-warning' style='float: right;'>INACTIVE</span>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td>{{ variant.description }}</td>
|
|
||||||
<td>{% decimal variant.total_stock %}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js_load %}
|
||||||
|
{{ block.super }}
|
||||||
|
|
||||||
|
<!-- jquery-treegrid -->
|
||||||
|
<script type='text/javascript' src='{% static "treegrid/js/jquery.treegrid.js" %}'></script>
|
||||||
|
<script type='text/javascript' src='{% static "treegrid/js/jquery.treegrid.bootstrap3.js" %}'></script>
|
||||||
|
|
||||||
|
<!-- boostrap-table-treegrid -->
|
||||||
|
<script type='text/javascript' src='{% static "bootstrap-table/extensions/treegrid/bootstrap-table-treegrid.js" %}'></script>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block js_ready %}
|
{% block js_ready %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
|
|
||||||
|
loadPartVariantTable($('#variants-table'), {{ part.pk }});
|
||||||
$('#variant-table').inventreeTable({
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#new-variant').click(function() {
|
$('#new-variant').click(function() {
|
||||||
launchModalForm(
|
launchModalForm(
|
||||||
|
@ -301,7 +301,7 @@ class MakePartVariant(AjaxCreateView):
|
|||||||
form = super(AjaxCreateView, self).get_form()
|
form = super(AjaxCreateView, self).get_form()
|
||||||
|
|
||||||
# Hide some variant-related fields
|
# Hide some variant-related fields
|
||||||
form.fields['variant_of'].widget = HiddenInput()
|
# form.fields['variant_of'].widget = HiddenInput()
|
||||||
|
|
||||||
return form
|
return form
|
||||||
|
|
||||||
|
@ -60,6 +60,98 @@ function toggleStar(options) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function loadPartVariantTable(table, partId, options) {
|
||||||
|
/* Load part variant table
|
||||||
|
*/
|
||||||
|
|
||||||
|
var params = {
|
||||||
|
ancestor: partId,
|
||||||
|
};
|
||||||
|
|
||||||
|
var cols = [
|
||||||
|
{
|
||||||
|
field: 'pk',
|
||||||
|
title: 'ID',
|
||||||
|
visible: false,
|
||||||
|
switchable: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'name',
|
||||||
|
title: '{% trans "Name" %}',
|
||||||
|
switchable: false,
|
||||||
|
formatter: function(value, row, index, field) {
|
||||||
|
var html = '';
|
||||||
|
|
||||||
|
var name = '';
|
||||||
|
|
||||||
|
if (row.IPN) {
|
||||||
|
name += row.IPN;
|
||||||
|
name += ' | ';
|
||||||
|
}
|
||||||
|
|
||||||
|
name += value;
|
||||||
|
|
||||||
|
if (row.revision) {
|
||||||
|
name += ' | ';
|
||||||
|
name += row.revision;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row.is_template) {
|
||||||
|
name = '<i>' + name + '</i>';
|
||||||
|
}
|
||||||
|
|
||||||
|
html += imageHoverIcon(row.thumbnail);
|
||||||
|
html += renderLink(name, `/part/${row.pk}/`);
|
||||||
|
|
||||||
|
return html;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'IPN',
|
||||||
|
title: '{% trans 'IPN' %}',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'revision',
|
||||||
|
title: '{% trans 'Revision' %}',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'description',
|
||||||
|
title: '{% trans "Description" %}',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'in_stock',
|
||||||
|
title: '{% trans "Stock" %}',
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
table.inventreeTable({
|
||||||
|
url: "{% url 'api-part-list' %}",
|
||||||
|
name: 'partvariants',
|
||||||
|
showColumns: true,
|
||||||
|
original: params,
|
||||||
|
queryParams: params,
|
||||||
|
formatNoMatches: function() { return "{% trans "No variants found" %}"; },
|
||||||
|
columns: cols,
|
||||||
|
treeEnable: true,
|
||||||
|
rootParentId: partId,
|
||||||
|
parentIdField: 'variant_of',
|
||||||
|
idField: 'pk',
|
||||||
|
uniqueId: 'pk',
|
||||||
|
treeShowField: 'name',
|
||||||
|
sortable: true,
|
||||||
|
search: true,
|
||||||
|
onPostBody: function() {
|
||||||
|
table.treegrid({
|
||||||
|
treeColumn: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
table.treegrid('collapseAll');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function loadPartTable(table, url, options={}) {
|
function loadPartTable(table, url, options={}) {
|
||||||
/* Load part listing data into specified table.
|
/* Load part listing data into specified table.
|
||||||
*
|
*
|
||||||
|
Loading…
x
Reference in New Issue
Block a user