2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-30 12:36:45 +00:00

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 - 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)

View File

@ -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>

View File

@ -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 %}

View File

@ -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(

View File

@ -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

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={}) { function loadPartTable(table, url, options={}) {
/* Load part listing data into specified table. /* Load part listing data into specified table.
* *

View File

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