From f578f680017467f88699f7733d1ede145d26f70d Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 20 Apr 2021 19:30:43 +1000 Subject: [PATCH 1/8] Split "part category" view into separate pages --- InvenTree/part/templates/part/category.html | 28 +++----- .../part/templates/part/category_navbar.html | 17 +++++ .../templates/part/category_subcategory.html | 42 +++++++++++ .../part/templates/part/subcategories.html | 22 ------ InvenTree/part/urls.py | 29 +++++--- InvenTree/templates/js/part.js | 70 +++++++++++++++++++ 6 files changed, 156 insertions(+), 52 deletions(-) create mode 100644 InvenTree/part/templates/part/category_subcategory.html delete mode 100644 InvenTree/part/templates/part/subcategories.html diff --git a/InvenTree/part/templates/part/category.html b/InvenTree/part/templates/part/category.html index 0d1d11e7fd..04594afb3b 100644 --- a/InvenTree/part/templates/part/category.html +++ b/InvenTree/part/templates/part/category.html @@ -2,6 +2,10 @@ {% load static %} {% load i18n %} +{% block menubar %} +{% include 'part/category_navbar.html' with tab='parts' %} +{% endblock %} + {% block content %}
@@ -100,14 +104,10 @@
- {% if category and category.children.all|length > 0 %} - {% include "part/subcategories.html" with children=category.children.all collapse_id="categories" %} - {% elif children|length > 0 %} - {% include "part/subcategories.html" with children=children collapse_id="categories" %} - {% endif %} - +{% block category_content %} +
+{% endblock %} + {% block category_tables %} {% endblock category_tables %} @@ -162,24 +164,10 @@ {% block js_ready %} {{ block.super }} - {% if category %} enableNavbar({ label: 'category', toggleId: '#category-menu-toggle', }); - {% endif %} - - if (inventreeLoadInt("show-part-cats") == 1) { - $("#collapse-item-categories").collapse('show'); - } - - $("#collapse-item-categories").on('shown.bs.collapse', function() { - inventreeSave('show-part-cats', 1); - }); - - $("#collapse-item-categories").on('hidden.bs.collapse', function() { - inventreeDel('show-part-cats'); - }); $("#cat-create").click(function() { launchModalForm( diff --git a/InvenTree/part/templates/part/category_navbar.html b/InvenTree/part/templates/part/category_navbar.html index 9374ecaaf1..e723db358d 100644 --- a/InvenTree/part/templates/part/category_navbar.html +++ b/InvenTree/part/templates/part/category_navbar.html @@ -8,17 +8,34 @@ +
  • + {% if category %} + + {% else %} + + {% endif %} + + {% trans "Subcategories" %} + +
  • +
  • + {% if category %} + {% else %} + + {% endif %} {% trans "Parts" %}
  • + {% if category %}
  • {% trans "Parameters" %}
  • + {% endif %} \ No newline at end of file diff --git a/InvenTree/part/templates/part/category_subcategory.html b/InvenTree/part/templates/part/category_subcategory.html new file mode 100644 index 0000000000..e45e01499a --- /dev/null +++ b/InvenTree/part/templates/part/category_subcategory.html @@ -0,0 +1,42 @@ +{% extends "part/category.html" %} + +{% load i18n %} +{% load inventree_extras %} +{% load static %} + +{% block menubar %} +{% include 'part/category_navbar.html' with tab='subcategories' %} +{% endblock %} + +{% block category_content %} + +
    + +
    +

    {% trans "Subcategories" %}

    +
    + +
    + +
    +{% endblock %} + +{% block js_ready %} +{{ block.super }} + + enableNavbar({ + label: 'category', + toggleId: '#category-menu-toggle', + }); + + loadPartCategoryTable($('#subcategory-table'), { + params: { + {% if category %} + parent: {{ category.pk }} + {% else %} + parent: 'null' + {% endif %} + } + }); + +{% endblock %} diff --git a/InvenTree/part/templates/part/subcategories.html b/InvenTree/part/templates/part/subcategories.html deleted file mode 100644 index 5e6b570217..0000000000 --- a/InvenTree/part/templates/part/subcategories.html +++ /dev/null @@ -1,22 +0,0 @@ -{% extends "collapse.html" %} -{% load i18n %} - -{% block collapse_title %} -{{ children | length }} {% trans 'Child Categories' %} -{% endblock %} - -{% block collapse_content %} - -{% endblock %} \ No newline at end of file diff --git a/InvenTree/part/urls.py b/InvenTree/part/urls.py index f1185fbe8c..b8f80d4845 100644 --- a/InvenTree/part/urls.py +++ b/InvenTree/part/urls.py @@ -88,14 +88,26 @@ category_parameter_urls = [ url(r'^(?P\d+)/delete/', views.CategoryParameterTemplateDelete.as_view(), name='category-param-template-delete'), ] -part_category_urls = [ - url(r'^edit/?', views.CategoryEdit.as_view(), name='category-edit'), - url(r'^delete/?', views.CategoryDelete.as_view(), name='category-delete'), +category_urls = [ - url(r'^parameters/', include(category_parameter_urls)), + # Create a new category + url(r'^new/', views.CategoryCreate.as_view(), name='category-create'), - url(r'^parametric/?', views.CategoryParametric.as_view(), name='category-parametric'), - url(r'^.*$', views.CategoryDetail.as_view(), name='category-detail'), + # Top level subcategory display + url(r'^subcategory/', views.PartIndex.as_view(template_name='part/category_subcategory.html'), name='category-index-subcategory'), + + # Category detail views + url(r'(?P\d+)/', include([ + url(r'^edit/', views.CategoryEdit.as_view(), name='category-edit'), + url(r'^delete/', views.CategoryDelete.as_view(), name='category-delete'), + url(r'^parameters/', include(category_parameter_urls)), + + url(r'^subcategory/', views.CategoryDetail.as_view(template_name='part/category_subcategory.html'), name='category-subcategory'), + url(r'^parametric/', views.CategoryParametric.as_view(), name='category-parametric'), + + # Anything else + url(r'^.*$', views.CategoryDetail.as_view(), name='category-detail'), + ])) ] part_bom_urls = [ @@ -106,9 +118,6 @@ part_bom_urls = [ # URL list for part web interface part_urls = [ - # Create a new category - url(r'^category/new/?', views.CategoryCreate.as_view(), name='category-create'), - # Create a new part url(r'^new/?', views.PartCreate.as_view(), name='part-create'), @@ -125,7 +134,7 @@ part_urls = [ url(r'^(?P\d+)/', include(part_detail_urls)), # Part category - url(r'^category/(?P\d+)/', include(part_category_urls)), + url(r'^category/', include(category_urls)), # Part related url(r'^related-parts/', include(part_related_urls)), diff --git a/InvenTree/templates/js/part.js b/InvenTree/templates/js/part.js index cefc2af8a7..b7e052ce72 100644 --- a/InvenTree/templates/js/part.js +++ b/InvenTree/templates/js/part.js @@ -1,4 +1,5 @@ {% load i18n %} +{% load inventree_extras %} /* Part API functions * Requires api.js to be loaded first @@ -506,6 +507,75 @@ function loadPartTable(table, url, options={}) { }); } + +function loadPartCategoryTable(table, options) { + /* Display a table of part categories */ + + var params = options.params || {}; + + var filterListElement = options.filterList || '#filter-list-category'; + + var filters = {}; + + var filterKey = options.filterKey || options.name || 'category'; + + if (!options.disableFilters) { + filters = loadTableFilters(filterKey); + } + + var original = {}; + + for (var key in params) { + original[key] = params[key]; + filters[key] = params[key]; + } + + setupFilterList(filterKey, table, filterListElement); + + table.inventreeTable({ + method: 'get', + url: options.url || '{% url "api-part-category-list" %}', + queryParams: filters, + sidePagination: 'server', + name: 'category', + original: original, + showColumns: true, + columns: [ + { + checkbox: true, + title: '{% trans "Select" %}', + searchable: false, + switchable: false, + }, + { + field: 'name', + title: '{% trans "Name" %}', + switchable: true, + sortable: true, + formatter: function(value, row) { + return renderLink( + value, + `/part/category/${row.pk}/` + ); + } + }, + { + field: 'description', + title: '{% trans "Description" %}', + switchable: true, + sortable: false, + }, + { + field: 'parts', + title: '{% trans "Parts" %}', + switchable: true, + sortable: false, + } + ] + }); +} + + function yesNoLabel(value) { if (value) { return `{% trans "YES" %}`; From 6986709fb899f43724647f9ea891ce6b578c2444 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 20 Apr 2021 19:49:07 +1000 Subject: [PATCH 2/8] Reorganized stock location view --- InvenTree/stock/api.py | 5 ++ InvenTree/stock/serializers.py | 1 + InvenTree/stock/templates/stock/location.html | 42 +++++------ .../stock/templates/stock/location_list.html | 24 ------- .../templates/stock/location_navbar.html | 33 +++++++++ .../stock/templates/stock/sublocation.html | 36 ++++++++++ InvenTree/stock/urls.py | 27 ++++--- InvenTree/templates/js/stock.js | 71 +++++++++++++++++++ 8 files changed, 184 insertions(+), 55 deletions(-) delete mode 100644 InvenTree/stock/templates/stock/location_list.html create mode 100644 InvenTree/stock/templates/stock/location_navbar.html create mode 100644 InvenTree/stock/templates/stock/sublocation.html diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py index 6da3962224..2667352515 100644 --- a/InvenTree/stock/api.py +++ b/InvenTree/stock/api.py @@ -320,6 +320,11 @@ class StockLocationList(generics.ListCreateAPIView): 'description', ] + ordering_fields = [ + 'name', + 'items', + ] + class StockList(generics.ListCreateAPIView): """ API endpoint for list view of Stock objects diff --git a/InvenTree/stock/serializers.py b/InvenTree/stock/serializers.py index 5b00c1dd17..2439bb9330 100644 --- a/InvenTree/stock/serializers.py +++ b/InvenTree/stock/serializers.py @@ -244,6 +244,7 @@ class LocationSerializer(InvenTreeModelSerializer): items = serializers.IntegerField(source='item_count', read_only=True) + class Meta: model = StockLocation fields = [ diff --git a/InvenTree/stock/templates/stock/location.html b/InvenTree/stock/templates/stock/location.html index 74e43f88bb..396a433566 100644 --- a/InvenTree/stock/templates/stock/location.html +++ b/InvenTree/stock/templates/stock/location.html @@ -2,8 +2,15 @@ {% load static %} {% load inventree_extras %} {% load i18n %} + +{% block menubar %} +{% include "stock/location_navbar.html" with tab="stock" %} +{% endblock %} + {% block content %} +
    + {% setting_object 'STOCK_OWNERSHIP_CONTROL' as owner_control %} {% if owner_control.value == "True" %} {% authorized_owners location.owner as owners %} @@ -120,36 +127,29 @@
    + -{% if location and location.children.all|length > 0 %} -{% include 'stock/location_list.html' with children=location.children.all collapse_id="locations" %} -{% elif locations|length > 0 %} -{% include 'stock/location_list.html' with children=locations collapse_id="locations" %} -{% endif %} +{% block location_content %} -
    - -{% include "stock_table.html" %} +
    +
    +

    {% trans "Stock Items" %}

    +
    + {% include "stock_table.html" %}
    {% endblock %} -{% block js_load %} -{{ block.super }} + + {% endblock %} + {% block js_ready %} {{ block.super }} - if (inventreeLoadInt("show-part-locs") == 1) { - $("#collapse-item-locations").collapse('show'); - } - - $("#collapse-item-locations").on('shown.bs.collapse', function() { - inventreeSave('show-part-locs', 1); - }); - - $("#collapse-item-locations").on('hidden.bs.collapse', function() { - inventreeDel('show-part-locs'); + enableNavbar({ + label: 'location', + toggleId: '#location-menu-toggle' }); {% if location %} @@ -261,7 +261,7 @@ ], params: { {% if location %} - location: {{ location.id }}, + location: {{ location.pk }}, {% endif %} part_detail: true, location_detail: true, diff --git a/InvenTree/stock/templates/stock/location_list.html b/InvenTree/stock/templates/stock/location_list.html deleted file mode 100644 index f9464c5fa3..0000000000 --- a/InvenTree/stock/templates/stock/location_list.html +++ /dev/null @@ -1,24 +0,0 @@ -{% extends "collapse.html" %} -{% load i18n %} - -{% if roles.stock_location.view or roles.stock.view %} -{% block collapse_title %} -{% trans 'Sub-Locations' %}{{ children|length }} -{% endblock %} - -{% block collapse_content %} -
      -{% for child in children %} -
    • {{ child.name }} - {{ child.description }} - {% if child.item_count > 0 %} - - - {% comment %}Translators: pluralize with counter{% endcomment %} - {% blocktrans count counter=child.item_count %}{{ counter }} Item{% plural %}{{ counter }} Items{% endblocktrans %} - - {% endif %} -
    • -{% endfor %} -
    -{% endblock %} -{% endif %} \ No newline at end of file diff --git a/InvenTree/stock/templates/stock/location_navbar.html b/InvenTree/stock/templates/stock/location_navbar.html new file mode 100644 index 0000000000..0cb0c9d1eb --- /dev/null +++ b/InvenTree/stock/templates/stock/location_navbar.html @@ -0,0 +1,33 @@ +{% load i18n %} + + \ No newline at end of file diff --git a/InvenTree/stock/templates/stock/sublocation.html b/InvenTree/stock/templates/stock/sublocation.html new file mode 100644 index 0000000000..b8b32328a2 --- /dev/null +++ b/InvenTree/stock/templates/stock/sublocation.html @@ -0,0 +1,36 @@ +{% extends "stock/location.html" %} + +{% load static %} +{% load i18n %} +{% load inventree_extras %} + +{% block menubar %} +{% include "stock/location_navbar.html" with tab="sublocations" %} +{% endblock %} + + +{% block location_content %} + +
    +
    +

    {% trans "Sublocations" %}

    +
    +
    +
    + +{% endblock %} + +{% block js_ready %} +{{ block.super }} + +loadStockLocationTable($('#sublocation-table'), { + params: { + {% if location %} + parent: {{ location.pk }}, + {% else %} + parent: 'null', + {% endif %} + } +}); + +{% endblock %} \ No newline at end of file diff --git a/InvenTree/stock/urls.py b/InvenTree/stock/urls.py index 5c6c678978..24e609fa4f 100644 --- a/InvenTree/stock/urls.py +++ b/InvenTree/stock/urls.py @@ -6,14 +6,21 @@ from django.conf.urls import url, include from . import views -# URL list for web interface -stock_location_detail_urls = [ - url(r'^edit/?', views.StockLocationEdit.as_view(), name='stock-location-edit'), - url(r'^delete/?', views.StockLocationDelete.as_view(), name='stock-location-delete'), - url(r'^qr_code/?', views.StockLocationQRCode.as_view(), name='stock-location-qr'), +location_urls = [ + + url(r'^new/', views.StockLocationCreate.as_view(), name='stock-location-create'), + + url(r'^(?P\d+)/', include([ + url(r'^edit/?', views.StockLocationEdit.as_view(), name='stock-location-edit'), + url(r'^delete/?', views.StockLocationDelete.as_view(), name='stock-location-delete'), + url(r'^qr_code/?', views.StockLocationQRCode.as_view(), name='stock-location-qr'), + + url(r'sublocation/', views.StockLocationDetail.as_view(template_name='stock/sublocation.html'), name='stock-location-sublocation'), + + # Anything else + url('^.*$', views.StockLocationDetail.as_view(), name='stock-location-detail'), + ])), - # Anything else - url('^.*$', views.StockLocationDetail.as_view(), name='stock-location-detail'), ] stock_item_detail_urls = [ @@ -49,9 +56,7 @@ stock_tracking_urls = [ stock_urls = [ # Stock location - url(r'^location/(?P\d+)/', include(stock_location_detail_urls)), - - url(r'^location/new/', views.StockLocationCreate.as_view(), name='stock-location-create'), + url(r'^location/', include(location_urls)), url(r'^item/new/?', views.StockItemCreate.as_view(), name='stock-item-create'), @@ -81,5 +86,7 @@ stock_urls = [ # Individual stock items url(r'^item/(?P\d+)/', include(stock_item_detail_urls)), + url(r'^sublocations/', views.StockIndex.as_view(template_name='stock/sublocation.html'), name='stock-sublocations'), + url(r'^.*$', views.StockIndex.as_view(), name='stock-index'), ] diff --git a/InvenTree/templates/js/stock.js b/InvenTree/templates/js/stock.js index 33f2dae8d6..990acf65ba 100644 --- a/InvenTree/templates/js/stock.js +++ b/InvenTree/templates/js/stock.js @@ -897,6 +897,77 @@ function loadStockTable(table, options) { }); } +function loadStockLocationTable(table, options) { + /* Display a table of stock locations */ + + var params = options.params || {}; + + var filterListElement = options.filterList || '#filter-list-location'; + + var filters = {}; + + var filterKey = options.filterKey || options.name || 'location'; + + if (!options.disableFilters) { + filters = loadTableFilters(filterKey); + } + + var original = {}; + + for (var key in params) { + original[key] = params[key]; + } + + setupFilterList(filterKey, table, filterListElement); + + for (var key in params) { + filters[key] = params[key]; + } + + table.inventreeTable({ + method: 'get', + url: options.url || '{% url "api-location-list" %}', + queryParams: filters, + sidePagination: 'server', + name: 'location', + original: original, + showColumns: true, + columns: [ + { + checkbox: true, + title: '{% trans "Select" %}', + searchable: false, + switchable: false, + }, + { + field: 'name', + title: '{% trans "Name" %}', + switchable: true, + sortable: true, + formatter: function(value, row) { + return renderLink( + value, + `/stock/location/${row.pk}/` + ); + }, + }, + { + field: 'description', + title: '{% trans "Description" %}', + switchable: true, + sortable: false, + }, + { + field: 'items', + title: '{% trans "Stock Items" %}', + switchable: true, + sortable: false, + sortName: 'item_count', + } + ] + }); +} + function loadStockTrackingTable(table, options) { var cols = [ From 412b05d76c1b4b426b3c020c583bad4937c9ea4c Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 20 Apr 2021 20:00:15 +1000 Subject: [PATCH 3/8] Allow API filtering by "cascading" stock locations --- InvenTree/stock/api.py | 37 +++++++++++++------ .../stock/templates/stock/sublocation.html | 11 +++++- InvenTree/templates/js/stock.js | 6 +++ InvenTree/templates/js/table_filters.js | 22 +++++++++++ 4 files changed, 64 insertions(+), 12 deletions(-) diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py index 2667352515..0238a62c63 100644 --- a/InvenTree/stock/api.py +++ b/InvenTree/stock/api.py @@ -289,20 +289,35 @@ class StockLocationList(generics.ListCreateAPIView): queryset = super().get_queryset() - loc_id = self.request.query_params.get('parent', None) + params = self.request.query_params - if loc_id is not None: + cascade = str2bool(params.get('cascade', False)) - # Look for top-level locations - if isNull(loc_id): + loc_id = params.get('parent', None) + + # Look for top-level locations + if isNull(loc_id): + + # If we allow "cascade" at the top-level, this essentially means *all* locations + if not cascade: queryset = queryset.filter(parent=None) - - else: - try: - loc_id = int(loc_id) - queryset = queryset.filter(parent=loc_id) - except ValueError: - pass + + else: + + try: + location = StockLocation.objects.get(pk=loc_id) + + # All sub-locations to be returned too? + if cascade: + parents = location.get_descendants(include_self=True) + parent_ids = [p.id for p in parents] + queryset = queryset.filter(parent__in=parent_ids) + + else: + queryset = queryset.filter(parent=location) + + except (ValueError, StockLocation.DoesNotExist): + pass return queryset diff --git a/InvenTree/stock/templates/stock/sublocation.html b/InvenTree/stock/templates/stock/sublocation.html index b8b32328a2..c5a6062064 100644 --- a/InvenTree/stock/templates/stock/sublocation.html +++ b/InvenTree/stock/templates/stock/sublocation.html @@ -15,7 +15,16 @@

    {% trans "Sublocations" %}

    -
    + +
    +
    + +
    +
    + +
    +
    +
    {% endblock %} diff --git a/InvenTree/templates/js/stock.js b/InvenTree/templates/js/stock.js index 990acf65ba..c10e432f5e 100644 --- a/InvenTree/templates/js/stock.js +++ b/InvenTree/templates/js/stock.js @@ -957,6 +957,12 @@ function loadStockLocationTable(table, options) { switchable: true, sortable: false, }, + { + field: 'pathstring', + title: '{% trans "Path" %}', + switchable: true, + sortable: false, + }, { field: 'items', title: '{% trans "Stock Items" %}', diff --git a/InvenTree/templates/js/table_filters.js b/InvenTree/templates/js/table_filters.js index ba73244c74..775f0d9803 100644 --- a/InvenTree/templates/js/table_filters.js +++ b/InvenTree/templates/js/table_filters.js @@ -62,6 +62,28 @@ function getAvailableTableFilters(tableKey) { }; } + // Filters for "stock location" table + if (tableKey == "location") { + return { + cascade: { + type: 'bool', + title: '{% trans "Include sublocations" %}', + description: '{% trans "Include locations" %}', + } + }; + } + + // Filters for "part category" table + if (tableKey == "category") { + return { + cascade: { + type: 'bool', + title: '{% trans "Include subcategories" %}', + description: '{% trans "Include subcategories" %}', + } + }; + } + // Filters for the "customer stock" table (really a subset of "stock") if (tableKey == "customerstock") { return { From 38eea21f4f9bcfab23993d5999899a42f56f6a62 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 20 Apr 2021 20:06:13 +1000 Subject: [PATCH 4/8] Enable printing of multiple stock location labels --- .../stock/templates/stock/sublocation.html | 36 ++++++++++++++++--- InvenTree/templates/stock_table.html | 1 + 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/InvenTree/stock/templates/stock/sublocation.html b/InvenTree/stock/templates/stock/sublocation.html index c5a6062064..d38539f842 100644 --- a/InvenTree/stock/templates/stock/sublocation.html +++ b/InvenTree/stock/templates/stock/sublocation.html @@ -18,10 +18,18 @@
    - -
    -
    - + + +
    + +
    @@ -42,4 +50,24 @@ loadStockLocationTable($('#sublocation-table'), { } }); +linkButtonsToSelection( + $('#sublocation-table'), + [ + '#location-print-options', + ] +); + +$('#multi-location-print-label').click(function() { + + var selections = $('#sublocation-table').bootstrapTable('getSelections'); + + var locations = []; + + selections.forEach(function(loc) { + locations.push(loc.pk); + }); + + printStockLocationLabels(locations); +}) + {% endblock %} \ No newline at end of file diff --git a/InvenTree/templates/stock_table.html b/InvenTree/templates/stock_table.html index 8eff0f4aed..1f54767f02 100644 --- a/InvenTree/templates/stock_table.html +++ b/InvenTree/templates/stock_table.html @@ -32,6 +32,7 @@ {% endif %} +
    +
    diff --git a/InvenTree/templates/js/part.js b/InvenTree/templates/js/part.js index b7e052ce72..e3e7190952 100644 --- a/InvenTree/templates/js/part.js +++ b/InvenTree/templates/js/part.js @@ -546,6 +546,7 @@ function loadPartCategoryTable(table, options) { title: '{% trans "Select" %}', searchable: false, switchable: false, + visible: false, }, { field: 'name', @@ -565,6 +566,12 @@ function loadPartCategoryTable(table, options) { switchable: true, sortable: false, }, + { + field: 'pathstring', + title: '{% trans "Path" %}', + switchable: true, + sortable: false, + }, { field: 'parts', title: '{% trans "Parts" %}', From fd3e59650a84a8f61c21ade53fca685547632ccd Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 20 Apr 2021 20:15:51 +1000 Subject: [PATCH 6/8] Style fixes --- InvenTree/stock/api.py | 4 ++-- InvenTree/stock/serializers.py | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py index 08599ea858..a76fd801bd 100644 --- a/InvenTree/stock/api.py +++ b/InvenTree/stock/api.py @@ -291,9 +291,9 @@ class StockLocationList(generics.ListCreateAPIView): params = self.request.query_params - cascade = str2bool(params.get('cascade', False)) - loc_id = params.get('parent', None) + + cascade = str2bool(params.get('cascade', False)) # Look for top-level locations if isNull(loc_id): diff --git a/InvenTree/stock/serializers.py b/InvenTree/stock/serializers.py index 2439bb9330..5b00c1dd17 100644 --- a/InvenTree/stock/serializers.py +++ b/InvenTree/stock/serializers.py @@ -244,7 +244,6 @@ class LocationSerializer(InvenTreeModelSerializer): items = serializers.IntegerField(source='item_count', read_only=True) - class Meta: model = StockLocation fields = [ From 2bb86ff22be3fc3d39b78bcdbed000926e0d9bf7 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 20 Apr 2021 20:20:00 +1000 Subject: [PATCH 7/8] Adds missing template file --- .../part/templates/part/subcategory.html | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 InvenTree/part/templates/part/subcategory.html diff --git a/InvenTree/part/templates/part/subcategory.html b/InvenTree/part/templates/part/subcategory.html new file mode 100644 index 0000000000..9bb8fe98b7 --- /dev/null +++ b/InvenTree/part/templates/part/subcategory.html @@ -0,0 +1,51 @@ +{% extends "part/category.html" %} + +{% load i18n %} +{% load inventree_extras %} +{% load static %} + +{% block menubar %} +{% include 'part/category_navbar.html' with tab='subcategories' %} +{% endblock %} + +{% block category_content %} + +
    + +
    +

    {% trans "Subcategories" %}

    +
    + +
    +
    + +
    + +
    +
    +
    + +
    + +
    +{% endblock %} + +{% block js_ready %} +{{ block.super }} + + enableNavbar({ + label: 'category', + toggleId: '#category-menu-toggle', + }); + + loadPartCategoryTable($('#subcategory-table'), { + params: { + {% if category %} + parent: {{ category.pk }} + {% else %} + parent: 'null' + {% endif %} + } + }); + +{% endblock %} From 4d1eb51bc429a50110ae5a4779fa6809472bc282 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 20 Apr 2021 20:42:55 +1000 Subject: [PATCH 8/8] Fixes --- InvenTree/part/api.py | 5 ++++- InvenTree/part/test_api.py | 44 +++++++++++++++++++++++++++++++++++++- InvenTree/stock/api.py | 5 ++++- 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/InvenTree/part/api.py b/InvenTree/part/api.py index 0ce7c7d5a6..a2d7609bed 100644 --- a/InvenTree/part/api.py +++ b/InvenTree/part/api.py @@ -74,8 +74,11 @@ class CategoryList(generics.ListCreateAPIView): cascade = str2bool(params.get('cascade', False)) + # Do not filter by category + if cat_id is None: + pass # Look for top-level categories - if isNull(cat_id): + elif isNull(cat_id): if not cascade: queryset = queryset.filter(parent=None) diff --git a/InvenTree/part/test_api.py b/InvenTree/part/test_api.py index ed88e1dd55..4389003544 100644 --- a/InvenTree/part/test_api.py +++ b/InvenTree/part/test_api.py @@ -37,12 +37,54 @@ class PartAPITest(InvenTreeAPITestCase): super().setUp() def test_get_categories(self): - """ Test that we can retrieve list of part categories """ + """ + Test that we can retrieve list of part categories, + with various filtering options. + """ + url = reverse('api-part-category-list') + + # Request *all* part categories response = self.client.get(url, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(len(response.data), 8) + # Request top-level part categories only + response = self.client.get( + url, + { + 'parent': 'null', + }, + format='json' + ) + + self.assertEqual(len(response.data), 2) + + # Children of PartCategory<1>, cascade + response = self.client.get( + url, + { + 'parent': 1, + 'cascade': 'true', + }, + format='json', + ) + + self.assertEqual(len(response.data), 5) + + # Children of PartCategory<1>, do not cascade + response = self.client.get( + url, + { + 'parent': 1, + 'cascade': 'false', + }, + format='json', + ) + + self.assertEqual(len(response.data), 3) + def test_add_categories(self): """ Check that we can add categories """ data = { diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py index a76fd801bd..0e64cecdbd 100644 --- a/InvenTree/stock/api.py +++ b/InvenTree/stock/api.py @@ -295,8 +295,11 @@ class StockLocationList(generics.ListCreateAPIView): cascade = str2bool(params.get('cascade', False)) + # Do not filter by location + if loc_id is None: + pass # Look for top-level locations - if isNull(loc_id): + elif isNull(loc_id): # If we allow "cascade" at the top-level, this essentially means *all* locations if not cascade: