From eeeb04c9f45cfb1703110b6268d52ca6cb95ca6d Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 10 Apr 2020 08:02:51 +1000 Subject: [PATCH 01/46] Unit testing for Order app API --- InvenTree/order/test_api.py | 44 +++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 InvenTree/order/test_api.py diff --git a/InvenTree/order/test_api.py b/InvenTree/order/test_api.py new file mode 100644 index 0000000000..cb0ffa2566 --- /dev/null +++ b/InvenTree/order/test_api.py @@ -0,0 +1,44 @@ +""" +Tests for the Order API +""" + +from rest_framework.test import APITestCase +from rest_framework import status + +from django.urls import reverse +from django.contrib.auth import get_user_model + + +class OrderTest(APITestCase): + + fixtures = [ + 'category', + 'part', + 'company', + 'location', + 'supplier_part', + 'stock', + ] + + def setUp(self): + + # Create a user for auth + User = get_user_model() + User.objects.create_user('testuser', 'test@testing.com', 'password') + self.client.login(username='testuser', password='password') + + def doGet(self, url, options=''): + + return self.client.get(url + "?" + options, format='json') + + def test_po_list(self,): + + url = reverse('api-po-list') + + # List all order items + response = self.doGet(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + # Filter by stuff + response = self.doGet(url, 'status=10&part=1&supplier_part=1') + self.assertEqual(response.status_code, status.HTTP_200_OK) From 5aa43a5a18e9d5a384e767e2eed79581812a28d6 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 10 Apr 2020 12:54:54 +1000 Subject: [PATCH 02/46] Load / save stock table filters in session storage --- .../static/script/inventree/stock.js | 56 +++++++++++++++++-- 1 file changed, 50 insertions(+), 6 deletions(-) diff --git a/InvenTree/InvenTree/static/script/inventree/stock.js b/InvenTree/InvenTree/static/script/inventree/stock.js index 996f566fb6..27d720c410 100644 --- a/InvenTree/InvenTree/static/script/inventree/stock.js +++ b/InvenTree/InvenTree/static/script/inventree/stock.js @@ -14,6 +14,48 @@ function getStockLocations(filters={}, options={}) { return inventreeGet('/api/stock/location/', filters, options) } +function loadStockFilters() { + // Load the stock table filters from session-storage + var filterstring = inventreeLoad("stockfilters", "cascade=true&loc=1"); + + var split = filterstring.split("&"); + + var filters = {}; + + console.log("Loaded stock filters: " + filterstring); + + split.forEach(function(item, index) { + var f = item.split('='); + + if (f.length == 2) { + filters[f[0]] = f[1]; + } else { + console.log("Improperly formatted filter: " + item); + } + }); + + return filters; +} + + +function saveStockFilters(filters) { + // Save the stock table filters to session storage + + var strings = []; + + for (var key in filters) { + strings.push(key + "=" + filters[key]); + } + + var filterstring = strings.join('&'); + + console.log("Saving stock filters: " + filterstring); + + inventreeSave("stockfilters", filterstring); +} + + + /* Functions for interacting with stock management forms */ @@ -40,19 +82,23 @@ function loadStockTable(table, options) { * buttons - Which buttons to link to stock selection callbacks */ + // List of user-params which override the default filters var params = options.params || {}; - // Enforce 'cascade' option - // TODO - Make this user-configurable? - params.cascade = true; + var filters = loadStockFilters(); - console.log('load stock table'); + // Override the default values, or add new ones + for (var key in params) { + filters[key] = params[key]; + } table.inventreeTable({ method: 'get', formatNoMatches: function() { return 'No stock items matching query'; }, + url: options.url, + queryParams: filters, customSort: customGroupSorter, groupBy: true, groupByField: options.groupByField || 'part', @@ -241,8 +287,6 @@ function loadStockTable(table, options) { title: 'Notes', } ], - url: options.url, - queryParams: params, }); if (options.buttons) { From 5d141a0b98302de8ea6735bc4f579c779e9e6838 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 10 Apr 2020 16:46:34 +1000 Subject: [PATCH 03/46] Display a list of filters for Stock table - Delete a filter by pressing "X" button --- InvenTree/InvenTree/static/css/inventree.css | 56 +++++++++++++ .../static/script/inventree/stock.js | 79 +++++++++++++++++-- InvenTree/templates/stock_table.html | 25 +++--- 3 files changed, 145 insertions(+), 15 deletions(-) diff --git a/InvenTree/InvenTree/static/css/inventree.css b/InvenTree/InvenTree/static/css/inventree.css index 0725c9d1a6..e8a98476db 100644 --- a/InvenTree/InvenTree/static/css/inventree.css +++ b/InvenTree/InvenTree/static/css/inventree.css @@ -157,6 +157,62 @@ font-style: italic; } +.dropdown { + padding-left: 1px; + margin-left: 1px; +} + +/* Styles for table buttons and filtering */ +.button-toolbar .btn { + margin-left: 1px; + margin-right: 1px; +} + +.filters { + display: inline-block; + *display: inline; + border: 1px solid #555; + border-radius: 3px; + padding: 3px; + margin-bottom: 1px; + margin-top: 1px; + vertical-align: middle; +} + +.filter-list { + margin: 1px; + padding: 1px; +} + +.filter-list .close { + cursor: pointer; + right: 0%; + padding-right: 2px; + padding-left: 2px; + transform: translate(0%, -25%); +} + +.filter-list .close:hover {background: #bbb;} + +.filter-list > li { + display: inline-block; + *display: inline; + zoom: 1; + padding-left: 3px; + padding-right: 3px; + padding-top: 2px; + padding-bottom: 2px; + border: 1px solid; + border-color: #aaa; + border-radius: 3px; + background: #eee; + margin: 1px; +} + +.filter-list > li:hover { + background: #ddd; +} + /* Part image icons with full-display on mouse hover */ .hover-img-thumb { diff --git a/InvenTree/InvenTree/static/script/inventree/stock.js b/InvenTree/InvenTree/static/script/inventree/stock.js index 27d720c410..f79a4bf217 100644 --- a/InvenTree/InvenTree/static/script/inventree/stock.js +++ b/InvenTree/InvenTree/static/script/inventree/stock.js @@ -14,10 +14,33 @@ function getStockLocations(filters={}, options={}) { return inventreeGet('/api/stock/location/', filters, options) } +// A map of available filters for the stock table +function getStockFilterOptions() { + return { + 'cascade': { + 'type': 'bool', + }, + 'status': { + 'options': { + 'OK': 10, + 'ATTENTION': 50, + 'DAMAGED': 55, + 'DESTROYED': 60, + 'LOST': 70 + }, + } + }; +} + + function loadStockFilters() { // Load the stock table filters from session-storage var filterstring = inventreeLoad("stockfilters", "cascade=true&loc=1"); + if (filterstring.length == 0) { + filterstring = 'cascade=true&test=1&location=10&status=50'; + } + var split = filterstring.split("&"); var filters = {}; @@ -25,12 +48,15 @@ function loadStockFilters() { console.log("Loaded stock filters: " + filterstring); split.forEach(function(item, index) { - var f = item.split('='); - if (f.length == 2) { - filters[f[0]] = f[1]; - } else { - console.log("Improperly formatted filter: " + item); + if (item.length > 0) { + var f = item.split('='); + + if (f.length == 2) { + filters[f[0]] = f[1]; + } else { + console.log("Improperly formatted filter: " + item); + } } }); @@ -54,6 +80,41 @@ function saveStockFilters(filters) { inventreeSave("stockfilters", filterstring); } +function removeStockFilter(key) { + + var filters = loadStockFilters(); + + delete filters[key]; + + saveStockFilters(filters); + + return filters; +} + + +function updateStockFilterList(filterListElement, filters, table, params) { + + for (var key in filters) { + $(filterListElement).append(`
  • ${key} = ${filters[key]}x
  • ` ); + } + + $(filterListElement).find(".close").click(function() { + var element = $(this); + + var tag = element.attr('filter-tag'); + + // Clear out any existing elements + $(filterListElement).empty(); + + var filters = removeStockFilter(tag); + + updateStockFilterList(filterListElement, filters); + + // TODO - Reload data in table? + }); + + console.log("done"); +} /* Functions for interacting with stock management forms @@ -80,13 +141,18 @@ function loadStockTable(table, options) { * params - query params for augmenting stock data request * groupByField - Column for grouping stock items * buttons - Which buttons to link to stock selection callbacks + * filterList -
      element where filters are displayed */ // List of user-params which override the default filters var params = options.params || {}; + var filterListElement = options.filterList || "#stock-filter-list"; + var filters = loadStockFilters(); + updateStockFilterList(filterListElement, filters, table, params); + // Override the default values, or add new ones for (var key in params) { filters[key] = params[key]; @@ -293,6 +359,9 @@ function loadStockTable(table, options) { linkButtonsToSelection(table, options.buttons); } + // Display the filters + updateStockFilterList(filterListElement); + function stockAdjustment(action) { var items = $("#stock-table").bootstrapTable("getSelections"); diff --git a/InvenTree/templates/stock_table.html b/InvenTree/templates/stock_table.html index 2d823c9510..cd0c7a52cf 100644 --- a/InvenTree/templates/stock_table.html +++ b/InvenTree/templates/stock_table.html @@ -2,22 +2,27 @@
      - - {% if read_only %} - {% else %} - -
      From 4256d09e80163dc90f58406210b996f1f9dbe254 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 10 Apr 2020 23:54:10 +1000 Subject: [PATCH 04/46] Bugfix for bootstrap-table.j "Refreshing" the table options did not work proper good --- .../InvenTree/static/script/bootstrap/bootstrap-table.js | 5 +---- InvenTree/InvenTree/static/script/inventree/stock.js | 3 --- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/InvenTree/InvenTree/static/script/bootstrap/bootstrap-table.js b/InvenTree/InvenTree/static/script/bootstrap/bootstrap-table.js index 4567f29ac9..5fdfd2ee58 100644 --- a/InvenTree/InvenTree/static/script/bootstrap/bootstrap-table.js +++ b/InvenTree/InvenTree/static/script/bootstrap/bootstrap-table.js @@ -3272,10 +3272,7 @@ }, { key: 'getOptions', value: function getOptions() { - // deep copy and remove data - var options = JSON.parse(JSON.stringify(this.options)); - delete options.data; - return options; + return this.options; } }, { key: 'getSelections', diff --git a/InvenTree/InvenTree/static/script/inventree/stock.js b/InvenTree/InvenTree/static/script/inventree/stock.js index f79a4bf217..0f5936a5ed 100644 --- a/InvenTree/InvenTree/static/script/inventree/stock.js +++ b/InvenTree/InvenTree/static/script/inventree/stock.js @@ -359,9 +359,6 @@ function loadStockTable(table, options) { linkButtonsToSelection(table, options.buttons); } - // Display the filters - updateStockFilterList(filterListElement); - function stockAdjustment(action) { var items = $("#stock-table").bootstrapTable("getSelections"); From b2565270a53524276e808207efd5ef75094df18c Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sat, 11 Apr 2020 00:20:46 +1000 Subject: [PATCH 05/46] Cleanup logic for refreshing table with original filters --- .../static/script/inventree/stock.js | 80 +++++++++++++++++-- 1 file changed, 73 insertions(+), 7 deletions(-) diff --git a/InvenTree/InvenTree/static/script/inventree/stock.js b/InvenTree/InvenTree/static/script/inventree/stock.js index 0f5936a5ed..e5892843f3 100644 --- a/InvenTree/InvenTree/static/script/inventree/stock.js +++ b/InvenTree/InvenTree/static/script/inventree/stock.js @@ -91,14 +91,27 @@ function removeStockFilter(key) { return filters; } +function createStockFilter() { + // TODO + console.log("create stock filter"); +} -function updateStockFilterList(filterListElement, filters, table, params) { +function clearStockFilters() { + // TODO + console.log("clear stock filters"); +} + +function updateStockFilterList(filterListElement, table) { + + var filters = loadStockFilters(); for (var key in filters) { $(filterListElement).append(`
    • ${key} = ${filters[key]}x
    • ` ); } - $(filterListElement).find(".close").click(function() { + // Whenever the callback is called, pass the original parameters through + + $(filterListElement).find(".close").click(function(event) { var element = $(this); var tag = element.attr('filter-tag'); @@ -108,12 +121,12 @@ function updateStockFilterList(filterListElement, filters, table, params) { var filters = removeStockFilter(tag); - updateStockFilterList(filterListElement, filters); + reloadStockTable(table, filters); + + // Call this function again to re-update the filterss + updateStockFilterList(filterListElement, table); - // TODO - Reload data in table? }); - - console.log("done"); } @@ -131,6 +144,40 @@ function removeStockRow(e) { $('#' + row).remove(); } + +function reloadStockTable(table, filters) { + /* Reload the stock table. + * + * 'original' is the original query params provided to the + * 'loadStockTable' function. + * These override any user-configured filters. + */ + + + // Override the queryParams for the table + var options = table.bootstrapTable('getOptions'); + + var params = {}; + + var filters = loadStockFilters(); + + for (var key in filters) { + params[key] = filters[key]; + } + + // Original parameters will override + for (var key in options.original) { + params[key] = options.original[key]; + } + + options.queryParams = params; + + table.bootstrapTable('refreshOptions', options); + table.bootstrapTable('refresh'); +} + + + function loadStockTable(table, options) { /* Load data into a stock table with adjustable options. * Fetches data (via AJAX) and loads into a bootstrap table. @@ -151,7 +198,25 @@ function loadStockTable(table, options) { var filters = loadStockFilters(); - updateStockFilterList(filterListElement, filters, table, params); + var original = {}; + + for (var key in params) { + original[key] = params[key]; + } + + // Record a copy of the original query params + // It will be required if the table is refreshed + //options.original = original; + + updateStockFilterList(filterListElement, table); + + $("#filter-add").click(function() { + createStockFilter(); + }); + + $("#filter-clear").click(function() { + clearStockFilters(); + }); // Override the default values, or add new ones for (var key in params) { @@ -167,6 +232,7 @@ function loadStockTable(table, options) { queryParams: filters, customSort: customGroupSorter, groupBy: true, + original: original, groupByField: options.groupByField || 'part', groupByFormatter: function(field, id, data) { From 613dd9d4717874ec208447b63bda0ada5ec040c6 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sat, 11 Apr 2020 00:45:18 +1000 Subject: [PATCH 06/46] Add (very rough) function to add new custom table filters - The javascript needs a LOT of work! --- .../static/script/inventree/stock.js | 85 ++++++++++++++++++- InvenTree/templates/stock_table.html | 25 +++--- 2 files changed, 97 insertions(+), 13 deletions(-) diff --git a/InvenTree/InvenTree/static/script/inventree/stock.js b/InvenTree/InvenTree/static/script/inventree/stock.js index e5892843f3..c8d3f35cd6 100644 --- a/InvenTree/InvenTree/static/script/inventree/stock.js +++ b/InvenTree/InvenTree/static/script/inventree/stock.js @@ -15,10 +15,17 @@ function getStockLocations(filters={}, options={}) { } // A map of available filters for the stock table -function getStockFilterOptions() { +function getAvailableStockFilters() { return { 'cascade': { 'type': 'bool', + 'description': 'Include stock in sublocations', + 'title': 'sublocations', + }, + 'active': { + 'type': 'bool', + 'title': 'part active', + 'description': 'Show stock for active parts', }, 'status': { 'options': { @@ -28,6 +35,7 @@ function getStockFilterOptions() { 'DESTROYED': 60, 'LOST': 70 }, + 'description': 'Stock status', } }; } @@ -35,10 +43,10 @@ function getStockFilterOptions() { function loadStockFilters() { // Load the stock table filters from session-storage - var filterstring = inventreeLoad("stockfilters", "cascade=true&loc=1"); + var filterstring = inventreeLoad("stockfilters", "cascade=true"); if (filterstring.length == 0) { - filterstring = 'cascade=true&test=1&location=10&status=50'; + filterstring = 'cascade=true&status=60'; } var split = filterstring.split("&"); @@ -91,9 +99,80 @@ function removeStockFilter(key) { return filters; } +function addStockFilter(key, value) { + var filters = loadStockFilters(); + + filters[key] = value; + + saveStockFilters(filters); + + return filters; +} + function createStockFilter() { // TODO console.log("create stock filter"); + + var html = ``; + + // Add in a (blank) selection for filter value + html += ``; + + html += ``; + + var div = $("#add-new-filter"); + + div.html(html); + + div.find("#filter-make").click(function() { + var tag = div.find("#filter-tag").val(); + var val = div.find("#filter-value").val(); + + console.log(tag + " -> " + val); + + addStockFilter(tag, val); + }); + + div.find('#filter-tag').on('change', function() { + console.log(this.value); + + // Select the filter + var filter = available[this.value]; + + var list = div.find('#filter-value'); + + list.empty(); + + if ('type' in filter) { + if (filter.type == 'bool') { + + list.append(``); + list.append(``); + } + } else if ('options' in filter) { + for (var opt in filter.options) { + + list.append(``); + } + } + + console.log("..."); + }); + + console.log('done'); } function clearStockFilters() { diff --git a/InvenTree/templates/stock_table.html b/InvenTree/templates/stock_table.html index cd0c7a52cf..db40da2d87 100644 --- a/InvenTree/templates/stock_table.html +++ b/InvenTree/templates/stock_table.html @@ -2,26 +2,31 @@
      - {% if read_only %} - {% else %} - -
      From 58636139af0a50dcb5af5b97dcf3aade4a168391 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sat, 11 Apr 2020 10:14:31 +1000 Subject: [PATCH 07/46] Refactoring filtering code --- .../static/script/inventree/filters.js | 110 ++++++++++++++++++ .../static/script/inventree/stock.js | 67 +---------- InvenTree/templates/base.html | 1 + 3 files changed, 114 insertions(+), 64 deletions(-) create mode 100644 InvenTree/InvenTree/static/script/inventree/filters.js diff --git a/InvenTree/InvenTree/static/script/inventree/filters.js b/InvenTree/InvenTree/static/script/inventree/filters.js new file mode 100644 index 0000000000..26799a6560 --- /dev/null +++ b/InvenTree/InvenTree/static/script/inventree/filters.js @@ -0,0 +1,110 @@ +/** + * Code for managing query filters / table options. + * + * Optional query filters are available to the user for various + * tables display in the web interface. + * These filters are saved to the web session, and should be + * persistent for a given table type. + * + * This makes use of the 'inventreeSave' and 'inventreeLoad' functions + * for writing to and reading from session storage. + * + */ + + +/** + * Return the custom filtering options available for a particular table + * + * @param {*} tableKey - string key lookup for the table + */ +function getFilterOptions(tableKey) { + + tableKey = tableKey.toLowerCase(); + + // Filters for the "Stock" table + if (tableKey == 'stock') { + return { + 'cascade': { + 'type': 'bool', + 'description': 'Include stock in sublocations', + 'title': 'sublocations', + }, + 'active': { + 'type': 'bool', + 'title': 'part active', + 'description': 'Show stock for active parts', + }, + 'status': { + 'options': { + 'OK': 10, + 'ATTENTION': 50, + 'DAMAGED': 55, + 'DESTROYED': 60, + 'LOST': 70 + }, + 'description': 'Stock status', + } + }; + } + + + // Finally, no matching key + return {}; +} + + +/** + * Load table filters for the given table from session storage + * + * @param tableKey - String key for the particular table + * @param defaults - Default filters for this table e.g. 'cascade=1&location=5' + */ +function loadTableFilters(tableKey, defaults) { + + var lookup = "table-filters-" + tableKey.toLowerCase(); + + var filterstring = inventreeLoad(lookup, defaults); + + var filters = {}; + + console.log(`Loaded filters for table '${tableKey}' - ${filterstring}`); + + filterstring.split("&").forEach(function(item, index) { + item = item.trim(); + + if (item.length > 0) { + var f = item.split('='); + + if (f.length == 2) { + filters[f[0]] = f[1]; + } else { + console.log(`Improperly formatted filter: ${item}`); + } + } + }); + + return filters; +} + + +/** + * Save table filters to session storage + * + * @param {*} tableKey - string key for the given table + * @param {*} filters - object of string:string pairs + */ +function saveTableFilters(tableKey, filters) { + var lookup = "table-filters-" + tableKey.toLowerCase(); + + var strings = []; + + for (var key in filters) { + strings.push(`${key.trim()}=${String(filters[key]).trim()}`); + } + + var filterstring = strings.join('&'); + + console.log(`Saving filters for table '${tableKey}' - ${filterstring}`); + + inventreeSave(lookup, filterstring); +} \ No newline at end of file diff --git a/InvenTree/InvenTree/static/script/inventree/stock.js b/InvenTree/InvenTree/static/script/inventree/stock.js index c8d3f35cd6..d619ff52ee 100644 --- a/InvenTree/InvenTree/static/script/inventree/stock.js +++ b/InvenTree/InvenTree/static/script/inventree/stock.js @@ -14,78 +14,17 @@ function getStockLocations(filters={}, options={}) { return inventreeGet('/api/stock/location/', filters, options) } -// A map of available filters for the stock table -function getAvailableStockFilters() { - return { - 'cascade': { - 'type': 'bool', - 'description': 'Include stock in sublocations', - 'title': 'sublocations', - }, - 'active': { - 'type': 'bool', - 'title': 'part active', - 'description': 'Show stock for active parts', - }, - 'status': { - 'options': { - 'OK': 10, - 'ATTENTION': 50, - 'DAMAGED': 55, - 'DESTROYED': 60, - 'LOST': 70 - }, - 'description': 'Stock status', - } - }; -} - function loadStockFilters() { - // Load the stock table filters from session-storage - var filterstring = inventreeLoad("stockfilters", "cascade=true"); - if (filterstring.length == 0) { - filterstring = 'cascade=true&status=60'; - } - - var split = filterstring.split("&"); - - var filters = {}; - - console.log("Loaded stock filters: " + filterstring); - - split.forEach(function(item, index) { - - if (item.length > 0) { - var f = item.split('='); - - if (f.length == 2) { - filters[f[0]] = f[1]; - } else { - console.log("Improperly formatted filter: " + item); - } - } - }); - - return filters; + return loadTableFilters("stock", "cascade=true"); } function saveStockFilters(filters) { // Save the stock table filters to session storage - var strings = []; - - for (var key in filters) { - strings.push(key + "=" + filters[key]); - } - - var filterstring = strings.join('&'); - - console.log("Saving stock filters: " + filterstring); - - inventreeSave("stockfilters", filterstring); + saveTableFilters("stock", filters); } function removeStockFilter(key) { @@ -115,7 +54,7 @@ function createStockFilter() { var html = ``; - var available = getFilterOptions("stock"); - - var filters = loadStockFilters(); + var available = getRemainingTableFilters("stock"); for (var key in available) { - // Ignore any keys that are already used.. - if (key in filters) continue; html += ``; } @@ -80,13 +43,10 @@ function createStockFilter() { var tag = div.find("#filter-tag").val(); var val = div.find("#filter-value").val(); - console.log(tag + " -> " + val); - - addStockFilter(tag, val); + addTableFilter("stock", tag, val); }); div.find('#filter-tag').on('change', function() { - console.log(this.value); // Select the filter var filter = available[this.value]; @@ -95,23 +55,13 @@ function createStockFilter() { list.empty(); - if ('type' in filter) { - if (filter.type == 'bool') { + // Make options + var options = getFilterOptionList("stock", this.value); - list.append(``); - list.append(``); - } - } else if ('options' in filter) { - for (var opt in filter.options) { - - list.append(``); - } + for (var option in options) { + list.append(``); } - - console.log("..."); }); - - console.log('done'); } function clearStockFilters() { @@ -121,7 +71,7 @@ function clearStockFilters() { function updateStockFilterList(filterListElement, table) { - var filters = loadStockFilters(); + var filters = loadTableFilters("stock"); for (var key in filters) { $(filterListElement).append(`
    • ${key} = ${filters[key]}x
    • ` ); @@ -137,7 +87,7 @@ function updateStockFilterList(filterListElement, table) { // Clear out any existing elements $(filterListElement).empty(); - var filters = removeStockFilter(tag); + var filters = removeTableFilter("stock", tag); reloadStockTable(table, filters); @@ -177,7 +127,7 @@ function reloadStockTable(table, filters) { var params = {}; - var filters = loadStockFilters(); + var filters = loadTableFilters("stock"); for (var key in filters) { params[key] = filters[key]; @@ -214,7 +164,7 @@ function loadStockTable(table, options) { var filterListElement = options.filterList || "#stock-filter-list"; - var filters = loadStockFilters(); + var filters = loadTableFilters("stock"); var original = {}; From 41b208992c1b6d7f6a2a17cce0db40afa78917ef Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sat, 11 Apr 2020 11:01:11 +1000 Subject: [PATCH 10/46] cleanup existing code --- .../static/script/inventree/filters.js | 168 +++++++++--------- .../static/script/inventree/stock.js | 4 +- 2 files changed, 88 insertions(+), 84 deletions(-) diff --git a/InvenTree/InvenTree/static/script/inventree/filters.js b/InvenTree/InvenTree/static/script/inventree/filters.js index d26c7ef0ca..69b8ceae45 100644 --- a/InvenTree/InvenTree/static/script/inventree/filters.js +++ b/InvenTree/InvenTree/static/script/inventree/filters.js @@ -11,6 +11,91 @@ * */ +/** + * Load table filters for the given table from session storage + * + * @param tableKey - String key for the particular table + * @param defaults - Default filters for this table e.g. 'cascade=1&location=5' + */ +function loadTableFilters(tableKey, defaults) { + + var lookup = "table-filters-" + tableKey.toLowerCase(); + + var filterstring = inventreeLoad(lookup, defaults); + + var filters = {}; + + console.log(`Loaded filters for table '${tableKey}' - ${filterstring}`); + + filterstring.split("&").forEach(function(item, index) { + item = item.trim(); + + if (item.length > 0) { + var f = item.split('='); + + if (f.length == 2) { + filters[f[0]] = f[1]; + } else { + console.log(`Improperly formatted filter: ${item}`); + } + } + }); + + return filters; +} + + +/** + * Save table filters to session storage + * + * @param {*} tableKey - string key for the given table + * @param {*} filters - object of string:string pairs + */ +function saveTableFilters(tableKey, filters) { + var lookup = "table-filters-" + tableKey.toLowerCase(); + + var strings = []; + + for (var key in filters) { + strings.push(`${key.trim()}=${String(filters[key]).trim()}`); + } + + var filterstring = strings.join('&'); + + console.log(`Saving filters for table '${tableKey}' - ${filterstring}`); + + inventreeSave(lookup, filterstring); +} + + +/* + * Remove a named filter parameter + */ +function removeTableFilter(tableKey, filterKey) { + + var filters = loadTableFilters(tableKey, ''); + + delete filters[filterKey]; + + saveTableFilters(tableKey, filters); + + // Return a copy of the updated filters + return filters; +} + + +function addTableFilter(tableKey, filterKey, filterValue) { + + var filters = loadTableFilters(tableKey, ''); + + filters[filterKey] = filterValue; + + saveTableFilters(tableKey, filters); + + // Return a copy of the updated filters + return filters; +} + /** * Return the custom filtering options available for a particular table @@ -106,90 +191,7 @@ function getFilterOptionList(tableKey, filterKey) { } -/** - * Load table filters for the given table from session storage - * - * @param tableKey - String key for the particular table - * @param defaults - Default filters for this table e.g. 'cascade=1&location=5' - */ -function loadTableFilters(tableKey, defaults) { - var lookup = "table-filters-" + tableKey.toLowerCase(); - - var filterstring = inventreeLoad(lookup, defaults); - - var filters = {}; - - console.log(`Loaded filters for table '${tableKey}' - ${filterstring}`); - - filterstring.split("&").forEach(function(item, index) { - item = item.trim(); - - if (item.length > 0) { - var f = item.split('='); - - if (f.length == 2) { - filters[f[0]] = f[1]; - } else { - console.log(`Improperly formatted filter: ${item}`); - } - } - }); - - return filters; -} - - -/** - * Save table filters to session storage - * - * @param {*} tableKey - string key for the given table - * @param {*} filters - object of string:string pairs - */ -function saveTableFilters(tableKey, filters) { - var lookup = "table-filters-" + tableKey.toLowerCase(); - - var strings = []; - - for (var key in filters) { - strings.push(`${key.trim()}=${String(filters[key]).trim()}`); - } - - var filterstring = strings.join('&'); - - console.log(`Saving filters for table '${tableKey}' - ${filterstring}`); - - inventreeSave(lookup, filterstring); -} - - -/* - * Remove a named filter parameter - */ -function removeTableFilter(tableKey, filterKey) { - - var filters = loadTableFilters(tableKey, ''); - - delete filters[filterKey]; - - saveTableFilters(tableKey, filters); - - // Return a copy of the updated filters - return filters; -} - - -function addTableFilter(tableKey, filterKey, filterValue) { - - var filters = loadTableFilters(tableKey, ''); - - filters[filterKey] = filterValue; - - saveTableFilters(tableKey, filters); - - // Return a copy of the updated filters - return filters; -} /** diff --git a/InvenTree/InvenTree/static/script/inventree/stock.js b/InvenTree/InvenTree/static/script/inventree/stock.js index 1123ecb9a8..bdf8e52ead 100644 --- a/InvenTree/InvenTree/static/script/inventree/stock.js +++ b/InvenTree/InvenTree/static/script/inventree/stock.js @@ -25,7 +25,9 @@ function createStockFilter() { for (var key in available) { - html += ``; + var title = getFilterTitle("stock", key); + + html += ``; } html += ``; From 33ac34cc4094fbef30e325157ce59dd46d45e294 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sat, 11 Apr 2020 11:02:14 +1000 Subject: [PATCH 11/46] Add blank option to filter selection --- InvenTree/InvenTree/static/script/inventree/stock.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/InvenTree/InvenTree/static/script/inventree/stock.js b/InvenTree/InvenTree/static/script/inventree/stock.js index bdf8e52ead..dd1ad0f868 100644 --- a/InvenTree/InvenTree/static/script/inventree/stock.js +++ b/InvenTree/InvenTree/static/script/inventree/stock.js @@ -23,6 +23,8 @@ function createStockFilter() { var available = getRemainingTableFilters("stock"); + html += ``; + for (var key in available) { var title = getFilterTitle("stock", key); From 57c5d6c97a80e407e9693c2fc2898601a58717d2 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sat, 11 Apr 2020 12:30:24 +1000 Subject: [PATCH 12/46] Moar refactoring --- .../static/script/inventree/filters.js | 65 ++++++++++++++++++- .../static/script/inventree/stock.js | 45 ++++--------- 2 files changed, 73 insertions(+), 37 deletions(-) diff --git a/InvenTree/InvenTree/static/script/inventree/filters.js b/InvenTree/InvenTree/static/script/inventree/filters.js index 69b8ceae45..0ae09b29ad 100644 --- a/InvenTree/InvenTree/static/script/inventree/filters.js +++ b/InvenTree/InvenTree/static/script/inventree/filters.js @@ -112,11 +112,11 @@ function getAvailableTableFilters(tableKey) { 'cascade': { 'type': 'bool', 'description': 'Include stock in sublocations', - 'title': 'sublocations', + 'title': 'Include sublocations', }, 'active': { 'type': 'bool', - 'title': 'part active', + 'title': 'Acitve parts', 'description': 'Show stock for active parts', }, 'status': { @@ -127,8 +127,12 @@ function getAvailableTableFilters(tableKey) { 'DESTROYED': 60, 'LOST': 70 }, + 'title': 'Stock status', 'description': 'Stock status', - } + }, + 'test': { + title: 'A test parameter', + }, }; } @@ -191,7 +195,62 @@ function getFilterOptionList(tableKey, filterKey) { } +/* + * Generate a list of
    {% endif %} +
    + +
    - -
    diff --git a/InvenTree/templates/table_filters.html b/InvenTree/templates/table_filters.html index 5ab0aefec8..ca1f741fa6 100644 --- a/InvenTree/templates/table_filters.html +++ b/InvenTree/templates/table_filters.html @@ -1,10 +1,10 @@ {% load i18n %} {% load inventree_extras %} - + From f0ffb0f8c06a989704f4fedc8ef5ca8d80c43705 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sat, 11 Apr 2020 19:59:16 +1000 Subject: [PATCH 21/46] Ability to include part_detail in build API - Build list now uses bootstrapTable --- .../static/script/inventree/build.js | 14 ++++ .../static/script/inventree/filters.js | 4 +- InvenTree/build/api.py | 13 ++++ InvenTree/build/serializers.py | 12 ++++ .../build/templates/build/build_list.html | 38 ----------- InvenTree/build/templates/build/index.html | 65 ++++++------------- 6 files changed, 61 insertions(+), 85 deletions(-) delete mode 100644 InvenTree/build/templates/build/build_list.html diff --git a/InvenTree/InvenTree/static/script/inventree/build.js b/InvenTree/InvenTree/static/script/inventree/build.js index 5ed035be86..03c33f7b57 100644 --- a/InvenTree/InvenTree/static/script/inventree/build.js +++ b/InvenTree/InvenTree/static/script/inventree/build.js @@ -8,6 +8,8 @@ function loadBuildTable(table, options) { filters[key] = params[key]; } + setupFilterList("build", table); + table.inventreeTable({ method: 'get', formatNoMatches: function() { @@ -26,14 +28,26 @@ function loadBuildTable(table, options) { { field: 'title', title: 'Build', + sortable: true, + formatter: function(value, row, index, field) { + return renderLink(value, '/build/' + row.pk + '/'); + } }, { field: 'part', title: 'Part', + sortable: true, + formatter: function(value, row, index, field) { + + var name = row.part_detail.full_name; + + return imageHoverIcon(row.part_detail.thumbnail) + renderLink(name, '/part/' + row.part + '/'); + } }, { field: 'quantity', title: 'Quantity', + sortable: true, }, { field: 'status_text', diff --git a/InvenTree/InvenTree/static/script/inventree/filters.js b/InvenTree/InvenTree/static/script/inventree/filters.js index b543df4730..de9f987911 100644 --- a/InvenTree/InvenTree/static/script/inventree/filters.js +++ b/InvenTree/InvenTree/static/script/inventree/filters.js @@ -238,8 +238,8 @@ function setupFilterList(tableKey, table, target) { var addClicked = false; - if (!target || target.length == 0) { - target = '#filter-list-" + tableKey'; + if (target == null || target.length == 0) { + target = `#filter-list-${tableKey}`; } var tag = `filter-tag-${tableKey}`; diff --git a/InvenTree/build/api.py b/InvenTree/build/api.py index dc6e484ec0..3be81f7b59 100644 --- a/InvenTree/build/api.py +++ b/InvenTree/build/api.py @@ -11,6 +11,8 @@ from rest_framework import generics, permissions from django.conf.urls import url, include +from InvenTree.helpers import str2bool + from .models import Build, BuildItem from .serializers import BuildSerializer, BuildItemSerializer @@ -39,6 +41,17 @@ class BuildList(generics.ListCreateAPIView): 'part', ] + def get_serializer(self, *args, **kwargs): + + try: + part_detail = str2bool(self.request.GET.get('part_detail', None)) + except AttributeError: + part_detail = None + + kwargs['part_detail'] = part_detail + + return self.serializer_class(*args, **kwargs) + class BuildDetail(generics.RetrieveUpdateAPIView): """ API endpoint for detail view of a Build object """ diff --git a/InvenTree/build/serializers.py b/InvenTree/build/serializers.py index 3eb3aee5be..19073d1e2d 100644 --- a/InvenTree/build/serializers.py +++ b/InvenTree/build/serializers.py @@ -10,6 +10,7 @@ from InvenTree.serializers import InvenTreeModelSerializer from stock.serializers import StockItemSerializerBrief from .models import Build, BuildItem +from part.serializers import PartBriefSerializer class BuildSerializer(InvenTreeModelSerializer): @@ -18,6 +19,16 @@ class BuildSerializer(InvenTreeModelSerializer): url = serializers.CharField(source='get_absolute_url', read_only=True) status_text = serializers.CharField(source='get_status_display', read_only=True) + part_detail = PartBriefSerializer(source='part', many=False, read_only=True) + + def __init__(self, *args, **kwargs): + part_detail = kwargs.pop('part_detail', False) + + super().__init__(*args, **kwargs) + + if part_detail is not True: + self.fields.pop('part_detail') + class Meta: model = Build fields = [ @@ -27,6 +38,7 @@ class BuildSerializer(InvenTreeModelSerializer): 'creation_date', 'completion_date', 'part', + 'part_detail', 'quantity', 'status', 'status_text', diff --git a/InvenTree/build/templates/build/build_list.html b/InvenTree/build/templates/build/build_list.html deleted file mode 100644 index 6b04ee6c8e..0000000000 --- a/InvenTree/build/templates/build/build_list.html +++ /dev/null @@ -1,38 +0,0 @@ -{% extends "collapse.html" %} - -{% block collapse_title %} -{{ title }} - {{ builds | length }} -{% endblock %} - -{% block collapse_content %} - - - - - - - - {% if completed %} - - {% else %} - - {% endif %} - - - -{% for build in builds %} - - - - - - {% else %} - - {% endif %} - -{% endfor %} - -
    BuildPartQuantityStatusCompletedCreated
    {{ build.title }}{{ build.part.full_name }}{{ build.quantity }}{% include "build_status.html" with build=build %} - {% if completed %} - {{ build.completion_date }}{{ build.completed_by.username }}{{ build.creation_date }}
    -{% endblock %} \ No newline at end of file diff --git a/InvenTree/build/templates/build/index.html b/InvenTree/build/templates/build/index.html index bd20717f5f..9edfecfc45 100644 --- a/InvenTree/build/templates/build/index.html +++ b/InvenTree/build/templates/build/index.html @@ -13,25 +13,24 @@ InvenTree | Build List

    Part Builds

    -
    -
    - + +
    + +
    + +
    +
    + +
    +
    -
    - - +
    -{% include "build/build_list.html" with builds=active title="Active Builds" completed=False collapse_id='active' %} - -{% include "build/build_list.html" with builds=completed completed=True title="Completed Builds" collapse_id="complete" %} - -{% include "build/build_list.html" with builds=cancelled title="Cancelled Builds" completed=False collapse_id="cancelled" %} - {% endblock %} {% block js_ready %} @@ -41,42 +40,18 @@ InvenTree | Build List $("#new-build").click(function() { launchModalForm( - "{% url 'build-create' %}", - { - follow: true - }); + "{% url 'build-create' %}", + { + follow: true + } + ); }); loadBuildTable($("#build-table"), { - url: "{% url 'api-build-list' %}" - }); - - $(".build-table").inventreeTable({ - formatNoMatches: function() { return 'No builds found'; }, - columns: [ - { - field: 'name', - title: 'Build', - sortable: true, - }, - { - field: 'part', - title: 'Part', - sortable: true, - }, - { - title: 'Quantity', - sortable: true, - searchable: false - }, - { - title: 'Status', - sortable: true, - }, - { - sortable: true, - }, - ] + url: "{% url 'api-build-list' %}", + params: { + part_detail: "true", + }, }); {% endblock %} \ No newline at end of file From 5d70f496a5ad6d4d3043e676fa4e47dc644c54a2 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sat, 11 Apr 2020 20:03:31 +1000 Subject: [PATCH 22/46] Ability to filter build list by status --- InvenTree/build/api.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/InvenTree/build/api.py b/InvenTree/build/api.py index 3be81f7b59..1476af3dcd 100644 --- a/InvenTree/build/api.py +++ b/InvenTree/build/api.py @@ -41,6 +41,22 @@ class BuildList(generics.ListCreateAPIView): 'part', ] + def get_queryset(self): + """ + Override the queryset filtering, + as some of the fields don't natively play nicely with DRF + """ + + build_list = super().get_queryset() + + # Filter by build status? + status = self.request.query_params.get('status', None) + + if status is not None: + build_list = build_list.filter(status=status) + + return build_list + def get_serializer(self, *args, **kwargs): try: From ba7c0bdea082810f2b3ab7ccf622817835d66680 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sat, 11 Apr 2020 20:48:02 +1000 Subject: [PATCH 23/46] Improvements for status code generation - Now includes labels - Python template generates javascript which is then rendered? I don't even follow it any more --- .../static/script/inventree/build.js | 7 +++- .../static/script/inventree/filters.js | 13 ++++--- .../static/script/inventree/order.js | 2 -- InvenTree/InvenTree/status_codes.py | 36 +++++++++++++++++-- InvenTree/templates/status_codes.html | 27 ++++++++++++++ InvenTree/templates/table_filters.html | 20 +++++------ 6 files changed, 81 insertions(+), 24 deletions(-) create mode 100644 InvenTree/templates/status_codes.html diff --git a/InvenTree/InvenTree/static/script/inventree/build.js b/InvenTree/InvenTree/static/script/inventree/build.js index 03c33f7b57..c559f773d8 100644 --- a/InvenTree/InvenTree/static/script/inventree/build.js +++ b/InvenTree/InvenTree/static/script/inventree/build.js @@ -50,12 +50,17 @@ function loadBuildTable(table, options) { sortable: true, }, { - field: 'status_text', + field: 'status', title: 'Status', + sortable: true, + formatter: function(value, row, index, field) { + return buildStatusDisplay(value); + }, }, { field: 'creation_date', title: 'Created', + sortable: true, }, ], }); diff --git a/InvenTree/InvenTree/static/script/inventree/filters.js b/InvenTree/InvenTree/static/script/inventree/filters.js index de9f987911..1c42ddf46b 100644 --- a/InvenTree/InvenTree/static/script/inventree/filters.js +++ b/InvenTree/InvenTree/static/script/inventree/filters.js @@ -216,8 +216,9 @@ function generateFilterInput(tableKey, filterKey) { // Return a 'select' input with the available values html = ``; @@ -368,7 +369,6 @@ function getFilterOptionValue(tableKey, filterKey, valueKey) { var filter = getFilterSettings(tableKey, filterKey); - var value = String(valueKey); // Lookup for boolean options @@ -381,11 +381,10 @@ function getFilterOptionValue(tableKey, filterKey, valueKey) { // Iterate through a list of options if ('options' in filter) { - for (var option in filter.options) { - var v = String(filter.options[option]); + for (var key in filter.options) { - if (v == valueKey) { - return option; + if (key == valueKey) { + return filter.options[key].value; } } diff --git a/InvenTree/InvenTree/static/script/inventree/order.js b/InvenTree/InvenTree/static/script/inventree/order.js index a54a464b88..babdf34f83 100644 --- a/InvenTree/InvenTree/static/script/inventree/order.js +++ b/InvenTree/InvenTree/static/script/inventree/order.js @@ -187,7 +187,5 @@ function orderStatusLabel(code, label) { html += label; html += ""; - console.log(html); - return html; } \ No newline at end of file diff --git a/InvenTree/InvenTree/status_codes.py b/InvenTree/InvenTree/status_codes.py index 7037ccde65..3c68dceb75 100644 --- a/InvenTree/InvenTree/status_codes.py +++ b/InvenTree/InvenTree/status_codes.py @@ -7,6 +7,8 @@ class StatusCode: This is used to map a set of integer values to text. """ + labels = {} + @classmethod def list(cls): """ @@ -16,10 +18,18 @@ class StatusCode: codes = [] for key in cls.options.keys(): - codes.append({ + + opt = { 'key': key, 'value': cls.options[key] - }) + } + + label = cls.labels.get(key) + + if label: + opt['label'] = label + + codes.append(opt) return codes @@ -61,6 +71,15 @@ class OrderStatus(StatusCode): RETURNED: _("Returned"), } + labels = { + PENDING: "primary", + PLACED: "primary", + COMPLETE: "success", + CANCELLED: "danger", + LOST: "warning", + RETURNED: "warning", + } + # Open orders OPEN = [ PENDING, @@ -91,6 +110,12 @@ class StockStatus(StatusCode): LOST: _("Lost"), } + labels = { + OK: 'success', + ATTENTION: 'warning', + DAMAGED: 'danger', + } + # The following codes correspond to parts that are 'available' or 'in stock' AVAILABLE_CODES = [ OK, @@ -120,6 +145,13 @@ class BuildStatus(StatusCode): COMPLETE: _("Complete"), } + labels = { + PENDING: 'primary', + ALLOCATED: 'info', + COMPLETE: 'success', + CANCELLED: 'danger', + } + ACTIVE_CODES = [ PENDING, ALLOCATED diff --git a/InvenTree/templates/status_codes.html b/InvenTree/templates/status_codes.html new file mode 100644 index 0000000000..0d92b6f467 --- /dev/null +++ b/InvenTree/templates/status_codes.html @@ -0,0 +1,27 @@ +var {{ label }}Codes = { + {% for opt in options %}'{{ opt.key }}': { + key: '{{ opt.key }}', + value: '{{ opt.value }}',{% if opt.label %} + label: '{{ opt.label }}',{% endif %} + },{% endfor %} +}; + +function {{ label }}StatusDisplay(key) { + + key = String(key); + + var label = {{ label }}Codes[key].label; + + var value = {{ label }}Codes[key].value; + + if (value == null || value.length == 0) { + value = key; + } + + // Label not found, return the original string + if (label == null || label.length == 0) { + return value; + } + + return `${value}`; +} diff --git a/InvenTree/templates/table_filters.html b/InvenTree/templates/table_filters.html index ca1f741fa6..0cefc2d9f5 100644 --- a/InvenTree/templates/table_filters.html +++ b/InvenTree/templates/table_filters.html @@ -5,6 +5,11 @@