mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge pull request #718 from SchrodingersGat/starred-filter
Starred filter
This commit is contained in:
commit
2cbf7d578b
@ -87,7 +87,7 @@ function loadPartTable(table, url, options={}) {
|
|||||||
* buttons: If provided, link buttons to selection status of this table
|
* buttons: If provided, link buttons to selection status of this table
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var params = options.parms || {};
|
var params = options.params || {};
|
||||||
|
|
||||||
var filters = loadTableFilters("parts");
|
var filters = loadTableFilters("parts");
|
||||||
|
|
||||||
@ -147,6 +147,10 @@ function loadPartTable(table, url, options={}) {
|
|||||||
display += `<span class='fas fa-tools label-right' title='Assembled part'></span>`;
|
display += `<span class='fas fa-tools label-right' title='Assembled part'></span>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (row.starred) {
|
||||||
|
display += `<span class='fas fa-star label-right' title='Starred part'></span>`;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
if (row.component) {
|
if (row.component) {
|
||||||
display = display + `<span class='fas fa-cogs label-right' title='Component part'></span>`;
|
display = display + `<span class='fas fa-cogs label-right' title='Component part'></span>`;
|
||||||
|
@ -494,15 +494,16 @@ class IndexView(TemplateView):
|
|||||||
|
|
||||||
context = super(TemplateView, self).get_context_data(**kwargs)
|
context = super(TemplateView, self).get_context_data(**kwargs)
|
||||||
|
|
||||||
context['starred'] = [star.part for star in self.request.user.starred_parts.all()]
|
# TODO - Re-implement this when a less expensive method is worked out
|
||||||
|
# context['starred'] = [star.part for star in self.request.user.starred_parts.all()]
|
||||||
|
|
||||||
# Generate a list of orderable parts which have stock below their minimum values
|
# Generate a list of orderable parts which have stock below their minimum values
|
||||||
# TODO - Is there a less expensive way to get these from the database
|
# TODO - Is there a less expensive way to get these from the database
|
||||||
context['to_order'] = [part for part in Part.objects.filter(purchaseable=True) if part.need_to_restock()]
|
# context['to_order'] = [part for part in Part.objects.filter(purchaseable=True) if part.need_to_restock()]
|
||||||
|
|
||||||
# Generate a list of assembly parts which have stock below their minimum values
|
# Generate a list of assembly parts which have stock below their minimum values
|
||||||
# TODO - Is there a less expensive way to get these from the database
|
# TODO - Is there a less expensive way to get these from the database
|
||||||
context['to_build'] = [part for part in Part.objects.filter(assembly=True) if part.need_to_restock()]
|
# context['to_build'] = [part for part in Part.objects.filter(assembly=True) if part.need_to_restock()]
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
@ -33,16 +33,29 @@
|
|||||||
"{% url 'supplier-part-create' %}",
|
"{% url 'supplier-part-create' %}",
|
||||||
{
|
{
|
||||||
data: {
|
data: {
|
||||||
supplier: {{ company.id }}
|
{% if company.is_supplier %}supplier: {{ company.id }},{% endif %}
|
||||||
|
{% if company.is_manufacturer %}manufacturer: {{ company.id }},{% endif %}
|
||||||
},
|
},
|
||||||
reload: true,
|
reload: true,
|
||||||
secondary: [
|
secondary: [
|
||||||
{
|
{
|
||||||
field: 'part',
|
field: 'part',
|
||||||
label: 'New Part',
|
label: '{% trans "New Part" %}',
|
||||||
title: 'Create New Part',
|
title: '{% trans "Create new Part" %}',
|
||||||
url: "{% url 'part-create' %}"
|
url: "{% url 'part-create' %}"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
field: 'supplier',
|
||||||
|
label: "{% trans 'New Supplier' %}",
|
||||||
|
title: "{% trans 'Create new Supplier' %}",
|
||||||
|
url: "{% url 'supplier-create' %}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'manufacturer',
|
||||||
|
label: '{% trans "New Manufacturer" %}',
|
||||||
|
title: '{% trans "Create new Manufacturer" %}',
|
||||||
|
url: "{% url 'manufacturer-create' %}",
|
||||||
|
},
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -273,10 +273,6 @@ class SupplierPartCreate(AjaxCreateView):
|
|||||||
Hide some fields if they are not appropriate in context
|
Hide some fields if they are not appropriate in context
|
||||||
"""
|
"""
|
||||||
form = super(AjaxCreateView, self).get_form()
|
form = super(AjaxCreateView, self).get_form()
|
||||||
|
|
||||||
if form.initial.get('supplier', None):
|
|
||||||
# Hide the supplier field
|
|
||||||
form.fields['supplier'].widget = HiddenInput()
|
|
||||||
|
|
||||||
if form.initial.get('part', None):
|
if form.initial.get('part', None):
|
||||||
# Hide the part field
|
# Hide the part field
|
||||||
@ -292,20 +288,27 @@ class SupplierPartCreate(AjaxCreateView):
|
|||||||
"""
|
"""
|
||||||
initials = super(SupplierPartCreate, self).get_initial().copy()
|
initials = super(SupplierPartCreate, self).get_initial().copy()
|
||||||
|
|
||||||
|
manufacturer_id = self.get_param('manufacturer')
|
||||||
supplier_id = self.get_param('supplier')
|
supplier_id = self.get_param('supplier')
|
||||||
part_id = self.get_param('part')
|
part_id = self.get_param('part')
|
||||||
|
|
||||||
if supplier_id:
|
if supplier_id:
|
||||||
try:
|
try:
|
||||||
initials['supplier'] = Company.objects.get(pk=supplier_id)
|
initials['supplier'] = Company.objects.get(pk=supplier_id)
|
||||||
except Company.DoesNotExist:
|
except (ValueError, Company.DoesNotExist):
|
||||||
initials['supplier'] = None
|
pass
|
||||||
|
|
||||||
|
if manufacturer_id:
|
||||||
|
try:
|
||||||
|
initials['manufacturer'] = Company.objects.get(pk=manufacturer_id)
|
||||||
|
except (ValueError, Company.DoesNotExist):
|
||||||
|
pass
|
||||||
|
|
||||||
if part_id:
|
if part_id:
|
||||||
try:
|
try:
|
||||||
initials['part'] = Part.objects.get(pk=part_id)
|
initials['part'] = Part.objects.get(pk=part_id)
|
||||||
except Part.DoesNotExist:
|
except (ValueError, Part.DoesNotExist):
|
||||||
initials['part'] = None
|
pass
|
||||||
|
|
||||||
return initials
|
return initials
|
||||||
|
|
||||||
|
@ -153,6 +153,7 @@ class PartList(generics.ListCreateAPIView):
|
|||||||
The Part object list can be filtered by:
|
The Part object list can be filtered by:
|
||||||
- category: Filter by PartCategory reference
|
- category: Filter by PartCategory reference
|
||||||
- cascade: If true, include parts from sub-categories
|
- cascade: If true, include parts from sub-categories
|
||||||
|
- starred: Is the part "starred" by the current user?
|
||||||
- is_template: Is the part a template part?
|
- is_template: Is the part a template part?
|
||||||
- variant_of: Filter by variant_of Part reference
|
- variant_of: Filter by variant_of Part reference
|
||||||
- assembly: Filter by assembly field
|
- assembly: Filter by assembly field
|
||||||
@ -257,12 +258,18 @@ class PartList(generics.ListCreateAPIView):
|
|||||||
# Filter items which have an 'in_stock' level higher than 'minimum_stock'
|
# Filter items which have an 'in_stock' level higher than 'minimum_stock'
|
||||||
data = data.filter(Q(in_stock__gte=F('minimum_stock')))
|
data = data.filter(Q(in_stock__gte=F('minimum_stock')))
|
||||||
|
|
||||||
|
# Get a list of the parts that this user has starred
|
||||||
|
starred_parts = [star.part.pk for star in self.request.user.starred_parts.all()]
|
||||||
|
|
||||||
# Reduce the number of lookups we need to do for the part categories
|
# Reduce the number of lookups we need to do for the part categories
|
||||||
categories = {}
|
categories = {}
|
||||||
|
|
||||||
for item in data:
|
for item in data:
|
||||||
|
|
||||||
if item['image']:
|
if item['image']:
|
||||||
|
# Is this part 'starred' for the current user?
|
||||||
|
item['starred'] = item['pk'] in starred_parts
|
||||||
|
|
||||||
img = item['image']
|
img = item['image']
|
||||||
|
|
||||||
# Use the 'thumbnail' image here instead of the full-size image
|
# Use the 'thumbnail' image here instead of the full-size image
|
||||||
@ -294,32 +301,53 @@ class PartList(generics.ListCreateAPIView):
|
|||||||
return Response(data)
|
return Response(data)
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
"""
|
||||||
# Does the user wish to filter by category?
|
Implement custom filtering for the Part list API
|
||||||
cat_id = self.request.query_params.get('category', None)
|
"""
|
||||||
|
|
||||||
# Start with all objects
|
# Start with all objects
|
||||||
parts_list = Part.objects.all()
|
parts_list = Part.objects.all()
|
||||||
|
|
||||||
cascade = str2bool(self.request.query_params.get('cascade', False))
|
# Filter by 'starred' parts?
|
||||||
|
starred = str2bool(self.request.query_params.get('starred', None))
|
||||||
|
|
||||||
|
if starred is not None:
|
||||||
|
starred_parts = [star.part.pk for star in self.request.user.starred_parts.all()]
|
||||||
|
|
||||||
|
if starred:
|
||||||
|
parts_list = parts_list.filter(pk__in=starred_parts)
|
||||||
|
else:
|
||||||
|
parts_list = parts_list.exclude(pk__in=starred_parts)
|
||||||
|
|
||||||
|
cascade = str2bool(self.request.query_params.get('cascade', None))
|
||||||
|
|
||||||
|
# Does the user wish to filter by category?
|
||||||
|
cat_id = self.request.query_params.get('category', None)
|
||||||
|
|
||||||
if cat_id is None:
|
if cat_id is None:
|
||||||
# Top-level parts
|
# No category filtering if category is not specified
|
||||||
if not cascade:
|
pass
|
||||||
parts_list = parts_list.filter(category=None)
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
try:
|
# Category has been specified!
|
||||||
category = PartCategory.objects.get(pk=cat_id)
|
if isNull(cat_id):
|
||||||
|
# A 'null' category is the top-level category
|
||||||
|
if cascade is False:
|
||||||
|
# Do not cascade, only list parts in the top-level category
|
||||||
|
parts_list = parts_list.filter(category=None)
|
||||||
|
|
||||||
# If '?cascade=true' then include parts which exist in sub-categories
|
else:
|
||||||
if cascade:
|
try:
|
||||||
parts_list = parts_list.filter(category__in=category.getUniqueChildren())
|
category = PartCategory.objects.get(pk=cat_id)
|
||||||
# Just return parts directly in the requested category
|
|
||||||
else:
|
# If '?cascade=true' then include parts which exist in sub-categories
|
||||||
parts_list = parts_list.filter(category=cat_id)
|
if cascade:
|
||||||
except (ValueError, PartCategory.DoesNotExist):
|
parts_list = parts_list.filter(category__in=category.getUniqueChildren())
|
||||||
pass
|
# Just return parts directly in the requested category
|
||||||
|
else:
|
||||||
|
parts_list = parts_list.filter(category=cat_id)
|
||||||
|
except (ValueError, PartCategory.DoesNotExist):
|
||||||
|
pass
|
||||||
|
|
||||||
# Ensure that related models are pre-loaded to reduce DB trips
|
# Ensure that related models are pre-loaded to reduce DB trips
|
||||||
parts_list = self.get_serializer_class().setup_eager_loading(parts_list)
|
parts_list = self.get_serializer_class().setup_eager_loading(parts_list)
|
||||||
|
@ -200,11 +200,11 @@
|
|||||||
{% if category %}
|
{% if category %}
|
||||||
$("#cat-edit").click(function () {
|
$("#cat-edit").click(function () {
|
||||||
launchModalForm(
|
launchModalForm(
|
||||||
"{% url 'category-edit' category.id %}",
|
"{% url 'category-edit' category.id %}",
|
||||||
{
|
{
|
||||||
reload: true
|
reload: true
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -227,9 +227,9 @@
|
|||||||
"#part-table",
|
"#part-table",
|
||||||
"{% url 'api-part-list' %}",
|
"{% url 'api-part-list' %}",
|
||||||
{
|
{
|
||||||
query: {
|
params: {
|
||||||
{% if category %}
|
{% if category %}category: {{ category.id }},
|
||||||
category: {{ category.id }},
|
{% else %}category: "null",
|
||||||
{% endif %}
|
{% endif %}
|
||||||
},
|
},
|
||||||
buttons: ['#part-options'],
|
buttons: ['#part-options'],
|
||||||
|
@ -9,13 +9,7 @@ InvenTree | Index
|
|||||||
<hr>
|
<hr>
|
||||||
{% include "InvenTree/starred_parts.html" with collapse_id="starred" %}
|
{% include "InvenTree/starred_parts.html" with collapse_id="starred" %}
|
||||||
|
|
||||||
{% if to_order %}
|
{% include "InvenTree/low_stock.html" with collapse_id="order" %}
|
||||||
{% include "InvenTree/parts_to_order.html" with collapse_id="order" %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if to_build %}
|
|
||||||
{% include "InvenTree/parts_to_build.html" with collapse_id="build" %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
@ -25,15 +19,31 @@ InvenTree | Index
|
|||||||
|
|
||||||
{% block js_ready %}
|
{% block js_ready %}
|
||||||
|
|
||||||
console.log("abcde?");
|
|
||||||
|
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
|
|
||||||
//TODO: These calls to bootstrapTable() are failing, for some reason?
|
loadPartTable("#starred-parts-table", "{% url 'api-part-list' %}", {
|
||||||
//$("#to-build-table").bootstrapTable();
|
params: {
|
||||||
//$("#to-order-table").bootstrapTable();
|
"starred": true,
|
||||||
//$("#starred-parts-table").bootstrapTable();
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
loadPartTable("#low-stock-table", "{% url 'api-part-list' %}", {
|
||||||
|
params: {
|
||||||
|
"low_stock": true,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#starred-parts-table").on('load-success.bs.table', function() {
|
||||||
|
var count = $("#starred-parts-table").bootstrapTable('getData').length;
|
||||||
|
|
||||||
|
$("#starred-parts-count").html(count);
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#low-stock-table").on('load-success.bs.table', function() {
|
||||||
|
var count = $("#low-stock-table").bootstrapTable('getData').length;
|
||||||
|
|
||||||
|
$("#low-stock-count").html(count);
|
||||||
|
});
|
||||||
|
|
||||||
console.log("Got to here...");
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
15
InvenTree/templates/InvenTree/low_stock.html
Normal file
15
InvenTree/templates/InvenTree/low_stock.html
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{% extends "collapse.html" %}
|
||||||
|
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block collapse_title %}
|
||||||
|
<span class='fas fa-shopping-cart icon-header'></span>
|
||||||
|
{% trans "Low Stock" %}<span class='badge' id='low-stock-count'>0</span>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block collapse_content %}
|
||||||
|
|
||||||
|
<table class='table table-striped table-condensed' id='low-stock-table'>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{% endblock %}
|
@ -1,15 +0,0 @@
|
|||||||
{% extends "collapse.html" %}
|
|
||||||
{% block collapse_title %}
|
|
||||||
<span class='fas fa-shopping-cart icon-header'></span>
|
|
||||||
Parts to Order<span class='badge'>{{ to_order | length }}</span>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block collapse_heading %}
|
|
||||||
There are {{ to_order | length }} parts which need to be ordered.
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block collapse_content %}
|
|
||||||
|
|
||||||
{% include "required_part_table.html" with parts=to_order table_id="to-order-table" %}
|
|
||||||
|
|
||||||
{% endblock %}
|
|
@ -1,15 +1,15 @@
|
|||||||
{% extends "collapse.html" %}
|
{% extends "collapse.html" %}
|
||||||
|
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
{% block collapse_title %}
|
{% block collapse_title %}
|
||||||
<span class='fas fa-star icon-header'></span>
|
<span class='fas fa-star icon-header'></span>
|
||||||
Starred Parts<span class='badge'>{{ starred | length }}</span>
|
{% trans "Starred Parts" %}<span class='badge' id='starred-parts-count'>0</span>
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block collapse_heading %}
|
|
||||||
You have {{ starred | length }} favourite parts
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block collapse_content %}
|
{% block collapse_content %}
|
||||||
|
|
||||||
{% include "required_part_table.html" with parts=starred table_id="starred-parts-table" %}
|
<table class='table tabe-striped table-condensed' id='starred-parts-table'>
|
||||||
|
</table>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -102,6 +102,7 @@ InvenTree
|
|||||||
|
|
||||||
<script type='text/javascript' src="{% static 'script/inventree/inventree.js' %}"></script>
|
<script type='text/javascript' src="{% static 'script/inventree/inventree.js' %}"></script>
|
||||||
<script type='text/javascript' src="{% static 'script/inventree/api.js' %}"></script>
|
<script type='text/javascript' src="{% static 'script/inventree/api.js' %}"></script>
|
||||||
|
<script type='text/javascript' src="{% static 'script/inventree/part.js' %}"></script>
|
||||||
<script type='text/javascript' src="{% static 'script/inventree/bom.js' %}"></script>
|
<script type='text/javascript' src="{% static 'script/inventree/bom.js' %}"></script>
|
||||||
<script type='text/javascript' src="{% static 'script/inventree/filters.js' %}"></script>
|
<script type='text/javascript' src="{% static 'script/inventree/filters.js' %}"></script>
|
||||||
<script type='text/javascript' src="{% static 'script/inventree/tables.js' %}"></script>
|
<script type='text/javascript' src="{% static 'script/inventree/tables.js' %}"></script>
|
||||||
|
@ -89,6 +89,10 @@ function getAvailableTableFilters(tableKey) {
|
|||||||
type: 'bool',
|
type: 'bool',
|
||||||
title: '{% trans "Component" %}',
|
title: '{% trans "Component" %}',
|
||||||
},
|
},
|
||||||
|
starred: {
|
||||||
|
type: 'bool',
|
||||||
|
title: '{% trans "Starred" %}',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user