Merge pull request #718 from SchrodingersGat/starred-filter

Starred filter
This commit is contained in:
Oliver 2020-04-13 22:41:35 +10:00 committed by GitHub
commit 2cbf7d578b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 140 additions and 76 deletions

View File

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

View File

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

View File

@ -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' %}",
},
] ]
}); });
}); });

View File

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

View File

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

View File

@ -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'],

View File

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

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

View File

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

View File

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

View File

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

View File

@ -89,6 +89,10 @@ function getAvailableTableFilters(tableKey) {
type: 'bool', type: 'bool',
title: '{% trans "Component" %}', title: '{% trans "Component" %}',
}, },
starred: {
type: 'bool',
title: '{% trans "Starred" %}',
},
}; };
} }