Merge pull request #952 from SchrodingersGat/variant-table

Add ability to filter part list by 'ancestor'
This commit is contained in:
Oliver 2020-09-03 00:09:53 +10:00 committed by GitHub
commit 81b50312e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 1995 additions and 1617 deletions

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

View File

@ -275,6 +275,7 @@ class PartList(generics.ListCreateAPIView):
- purchaseable: Filter by purcahseable field
- salable: Filter by salable field
- active: Filter by active field
- ancestor: Filter parts by 'ancestor' (template / variant tree)
"""
serializer_class = part_serializers.PartSerializer
@ -387,10 +388,24 @@ class PartList(generics.ListCreateAPIView):
We overide the DRF filter_fields here because
"""
params = self.request.query_params
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?
starred = self.request.query_params.get('starred', None)
starred = params.get('starred', None)
if starred is not None:
starred = str2bool(starred)
@ -402,10 +417,10 @@ class PartList(generics.ListCreateAPIView):
queryset = queryset.exclude(pk__in=starred_parts)
# Cascade?
cascade = str2bool(self.request.query_params.get('cascade', None))
cascade = str2bool(params.get('cascade', None))
# 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:
# No category filtering if category is not specified
@ -437,7 +452,7 @@ class PartList(generics.ListCreateAPIView):
queryset = part_serializers.PartSerializer.annotate_queryset(queryset)
# 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:
has_stock = str2bool(has_stock)
@ -447,7 +462,7 @@ class PartList(generics.ListCreateAPIView):
queryset = queryset.filter(Q(in_stock__lte=0))
# 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:
low_stock = str2bool(low_stock)

View File

@ -1,8 +1,10 @@
{% load i18n %}
<div class='navigation'>
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<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 %}
{% for path_item in category.parentpath %}
<li class="breadcrumb-item"><a href="{% url 'category-detail' path_item.id %}">{{ path_item.name }}</a></li>

View File

@ -18,7 +18,7 @@
{% endif %}
{% if part.variant_of %}
<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>
{% endif %}

View File

@ -1,5 +1,6 @@
{% extends "part/part_base.html" %}
{% load static %}
{% load i18n %}
{% load inventree_extras %}
{% block details %}
@ -7,7 +8,7 @@
<div class='row'>
<div class='col-sm-6'>
<h4>Part Variants</h4>
<h4>{% trans "Part Variants" %}</h4>
</div>
<div class='col-sm-6'>
</div>
@ -17,45 +18,32 @@
<div id='button-toolbar'>
<div class='btn-group'>
{% 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 %}
</div>
</div>
<table class='table table-striped table-condensed' id='variant-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 class='table table-striped table-condensed' id='variants-table' data-toolbar='#button-toolbar'>
</table>
{% 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.super }}
$('#variant-table').inventreeTable({
});
loadPartVariantTable($('#variants-table'), {{ part.pk }});
$('#new-variant').click(function() {
launchModalForm(

View File

@ -301,7 +301,7 @@ class MakePartVariant(AjaxCreateView):
form = super(AjaxCreateView, self).get_form()
# Hide some variant-related fields
form.fields['variant_of'].widget = HiddenInput()
# form.fields['variant_of'].widget = HiddenInput()
return form

View File

@ -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={}) {
/* Load part listing data into specified table.
*

View File

@ -162,7 +162,7 @@ def translate(c):
or after adding translations for existing strings.
"""
manage(c, "makemigrations")
manage(c, "makemessages")
manage(c, "compilemessages")
@task