Merge pull request #2445 from SchrodingersGat/issue2250

Issue2250
This commit is contained in:
Oliver 2021-12-11 09:15:37 +11:00 committed by GitHub
commit dfd7a439a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 263 additions and 18 deletions

View File

@ -215,7 +215,7 @@
}
.treeview .list-group-item {
padding: 10px 5px;
padding: 3px 5px;
}
.treeview .list-group-item .indent {
@ -539,7 +539,7 @@
padding: 0px 10px;
}
.breadcrump {
.breadcrumb {
margin-bottom: 5px;
margin-left: 5px;
margin-right: 10px;

View File

@ -239,6 +239,23 @@ class CategoryParameterList(generics.ListAPIView):
return queryset
class CategoryTree(generics.ListAPIView):
"""
API endpoint for accessing a list of PartCategory objects ready for rendering a tree.
"""
queryset = PartCategory.objects.all()
serializer_class = part_serializers.CategoryTree
filter_backends = [
DjangoFilterBackend,
filters.OrderingFilter,
]
# Order by tree level (top levels first) and then name
ordering = ['level', 'name']
class PartSalePriceList(generics.ListCreateAPIView):
"""
API endpoint for list view of PartSalePriceBreak model
@ -1515,6 +1532,7 @@ part_api_urls = [
# Base URL for PartCategory API endpoints
url(r'^category/', include([
url(r'^tree/', CategoryTree.as_view(), name='api-part-category-tree'),
url(r'^parameters/', CategoryParameterList.as_view(), name='api-part-category-parameter-list'),
url(r'^(?P<pk>\d+)/?', CategoryDetail.as_view(), name='api-part-category-detail'),

View File

@ -70,6 +70,20 @@ class CategorySerializer(InvenTreeModelSerializer):
]
class CategoryTree(InvenTreeModelSerializer):
"""
Serializer for PartCategory tree
"""
class Meta:
model = PartCategory
fields = [
'pk',
'name',
'parent',
]
class PartAttachmentSerializer(InvenTreeAttachmentSerializer):
"""
Serializer for the PartAttachment class

View File

@ -6,6 +6,10 @@
{% include 'part/category_sidebar.html' %}
{% endblock %}
{% block breadcrumb_tree %}
<div id="breadcrumb-tree"></div>
{% endblock breadcrumb_tree %}
{% block heading %}
{% if category %}
{% trans "Part Category" %}: {{ category.name }}
@ -239,8 +243,24 @@
{% endif %}
// Enable left-hand navigation sidebar
enableSidebar('category');
// Enable breadcrumb tree view
enableBreadcrumbTree({
label: 'category',
url: '{% url "api-part-category-tree" %}',
{% if category %}
selected: {{ category.pk }},
{% endif %}
processNode: function(node) {
node.text = node.name;
node.href = `/part/category/${node.pk}/`;
return node;
}
});
loadPartCategoryTable(
$('#subcategory-table'), {
params: {

View File

@ -9,6 +9,10 @@
{% include 'part/part_sidebar.html' %}
{% endblock %}
{% block breadcrumb_tree %}
<div id="breadcrumb-tree"></div>
{% endblock breadcrumb_tree %}
{% block page_content %}
<div class='panel panel-hidden' id='panel-part-stock'>
@ -132,10 +136,6 @@
</div>
</div>
<div class='panel panel-hidden' id='panel-pricing'>
<!-- TODO -->
</div>
<div class='panel panel-hidden' id='panel-variants'>
<div class='panel-heading'>
<div class='d-flex flex-wrap'>
@ -1066,4 +1066,18 @@
enableSidebar('part');
enableBreadcrumbTree({
label: 'part',
url: '{% url "api-part-category-tree" %}',
{% if part.category %}
selected: {{ part.category.pk }},
{% endif %}
processNode: function(node) {
node.text = node.name;
node.href = `/part/category/${node.pk}/`;
return node;
}
});
{% endblock %}

View File

@ -14,9 +14,10 @@
{% endblock %}
{% block breadcrumbs %}
<a href='#' id='breadcrumb-tree-toggle' class="breadcrumb-item"><i class="fas fa-bars"></i></a>
{% if part %}
{% include "part/cat_link.html" with category=part.category part=part %}
{% else %}
{% include 'part/cat_link.html' with category=category %}
{% endif %}
{% endblock %}
{% endblock breadcrumbs %}

View File

@ -277,6 +277,24 @@ class StockLocationList(generics.ListCreateAPIView):
]
class StockLocationTree(generics.ListAPIView):
"""
API endpoint for accessing a list of StockLocation objects,
ready for rendering as a tree
"""
queryset = StockLocation.objects.all()
serializer_class = StockSerializers.LocationTreeSerializer
filter_backends = [
DjangoFilterBackend,
filters.OrderingFilter,
]
# Order by tree level (top levels first) and then name
ordering = ['level', 'name']
class StockFilter(rest_filters.FilterSet):
"""
FilterSet for StockItem LIST API
@ -1182,6 +1200,9 @@ class LocationDetail(generics.RetrieveUpdateDestroyAPIView):
stock_api_urls = [
url(r'^location/', include([
url(r'^tree/', StockLocationTree.as_view(), name='api-location-tree'),
url(r'^(?P<pk>\d+)/', LocationDetail.as_view(), name='api-location-detail'),
url(r'^.*$', StockLocationList.as_view(), name='api-location-list'),
])),

View File

@ -390,6 +390,20 @@ class SerializeStockItemSerializer(serializers.Serializer):
)
class LocationTreeSerializer(InvenTree.serializers.InvenTreeModelSerializer):
"""
Serializer for a simple tree view
"""
class Meta:
model = StockLocation
fields = [
'pk',
'name',
'parent',
]
class LocationSerializer(InvenTree.serializers.InvenTreeModelSerializer):
""" Detailed information about a stock location
"""

View File

@ -9,9 +9,15 @@
{% endblock %}
{% block breadcrumbs %}
<a href='#' id='breadcrumb-tree-toggle' class="breadcrumb-item"><i class="fas fa-bars"></i></a>
{% include 'stock/loc_link.html' with location=item.location %}
{% endblock %}
{% block breadcrumb_tree %}
<div id="breadcrumb-tree"></div>
{% endblock breadcrumb_tree %}
{% block heading %}
{% trans "Stock Item" %}: {{ item.part.full_name}}
{% endblock heading %}
@ -611,4 +617,18 @@ $('#serial-number-search').click(function() {
findStockItemBySerialNumber({{ item.part.pk }});
});
enableBreadcrumbTree({
label: 'stockitem',
url: '{% url "api-location-tree" %}',
{% if item.location %}
selected: {{ item.location.pk }},
{% endif %}
processNode: function(node) {
node.text = node.name;
node.href = `/stock/item/${node.pk}/`;
return node;
}
});
{% endblock %}

View File

@ -7,6 +7,10 @@
{% include "stock/location_sidebar.html" %}
{% endblock %}
{% block breadcrumb_tree %}
<div id="breadcrumb-tree"></div>
{% endblock breadcrumb_tree %}
{% block heading %}
{% if location %}
{% trans "Stock Location" %}: {{ location.name }}
@ -348,4 +352,19 @@
enableSidebar('stocklocation');
// Enable breadcrumb tree view
enableBreadcrumbTree({
label: 'location',
url: '{% url "api-location-tree" %}',
{% if location %}
selected: {{ location.pk }},
{% endif %}
processNode: function(node) {
node.text = node.name;
node.href = `/stock/location/${node.pk}/`;
return node;
}
});
{% endblock %}

View File

@ -18,9 +18,10 @@
{% endblock %}
{% block breadcrumbs %}
<a href='#' id='breadcrumb-tree-toggle' class="breadcrumb-item"><i class="fas fa-bars"></i></a>
{% if item %}
{% include 'stock/loc_link.html' with location=item.location %}
{% else %}
{% include 'stock/loc_link.html' with location=location %}
{% endif %}
{% endblock %}
{% endblock breadcrumbs %}

View File

@ -74,13 +74,13 @@
<div class='row flex-nowrap inventree-body'>
<div class='col-auto px-1 sidebar-wrapper'>
<div id='sidebar' class='collapse collapse-horizontal show border-end' style='display: none;'>
<div id='sidebar' class='collapse collapse-horizontal show' style='display: none;'>
<div id='sidebar-nav' class='list-group text-sm-start'>
<ul id='sidebar-list-group' class='list-group sidebar-list-group'>
{% block sidebar %}
<!-- Sidebar goes here -->
{% endblock %}
{% include "sidebar_toggle.html" %}
{% include "sidebar_toggle.html" with target='sidebar' %}
</ul>
</div>
</div>
@ -104,14 +104,20 @@
{% endblock %}
{% block breadcrumb_list %}
<div class='container-fluid navigation'>
<div class='container-fluid navigation' id='breadcrumb-div'>
<nav aria-label='breadcrumb'>
<ol class='breadcrumb'>
<ol class='breadcrumb' id='breadcrumb-list'>
{% block breadcrumbs %}
{% endblock %}
</ol>
</nav>
<div id='breadcrumb-tree-collapse' class='collapse collapse-horizontal show border' style='display: none;'>
{% block breadcrumb_tree %}
{% endblock %}
</div>
</div>
{% endblock %}
{% block content %}

View File

@ -6,6 +6,7 @@
addSidebarHeader,
addSidebarItem,
addSidebarLink,
enableBreadcrumbTree,
enableSidebar,
onPanelLoad,
*/
@ -145,6 +146,101 @@ function enableSidebar(label, options={}) {
}
/**
* Enable support for breadcrumb tree navigation on this page
*/
function enableBreadcrumbTree(options) {
var label = options.label;
if (!label) {
console.log('ERROR: enableBreadcrumbTree called without supplying label');
return;
}
var filters = options.filters || {};
inventreeGet(
options.url,
filters,
{
success: function(data) {
// Data are returned from the InvenTree server as a flattened list;
// We need to convert this into a tree structure
var nodes = {};
var roots = [];
var node = null;
for (var i = 0; i < data.length; i++) {
node = data[i];
node.nodes = [];
nodes[node.pk] = node;
node.selectable = false;
if (options.processNode) {
node = options.processNode(node);
}
node.state = {
expanded: node.pk == options.selected,
selected: node.pk == options.selected,
};
}
for (var i = 0; i < data.length; i++) {
node = data[i];
if (node.parent != null) {
nodes[node.parent].nodes.push(node);
if (node.state.expanded) {
nodes[node.parent].state.expanded = true;
}
} else {
roots.push(node);
}
}
$('#breadcrumb-tree').treeview({
data: roots,
showTags: true,
enableLinks: true,
expandIcon: 'fas fa-chevron-right',
collapseIcon: 'fa fa-chevron-down',
});
setBreadcrumbTreeState(label, state);
}
}
);
$('#breadcrumb-tree-toggle').click(function() {
// Add callback to "collapse" and "expand" the sidebar
// By default, the menu is "expanded"
var state = localStorage.getItem(`inventree-tree-state-${label}`) || 'expanded';
// We wish to "toggle" the state!
setBreadcrumbTreeState(label, state == 'expanded' ? 'collapsed' : 'expanded');
});
// Set the initial state (default = expanded)
var state = localStorage.getItem(`inventree-tree-state-${label}`) || 'expanded';
function setBreadcrumbTreeState(label, state) {
if (state == 'collapsed') {
$('#breadcrumb-tree-collapse').hide(100);
} else {
$('#breadcrumb-tree-collapse').show(100);
}
localStorage.setItem(`inventree-tree-state-${label}`, state);
}
}
/*
* Set the "toggle" state of the sidebar
@ -180,7 +276,7 @@ function setSidebarState(label, state) {
function addSidebarItem(options={}) {
var html = `
<a href='#' id='select-${options.label}' title='${options.text}' class='list-group-item sidebar-list-group-item border-end-0 d-inline-block text-truncate sidebar-selector' data-bs-parent='#sidebar'>
<a href='#' id='select-${options.label}' title='${options.text}' class='list-group-item sidebar-list-group-item border-end d-inline-block text-truncate sidebar-selector' data-bs-parent='#sidebar'>
<i class='bi bi-bootstrap'></i>
${options.content_before || ''}
<span class='sidebar-item-icon fas ${options.icon}'></span>
@ -199,7 +295,7 @@ function addSidebarItem(options={}) {
function addSidebarHeader(options={}) {
var html = `
<span title='${options.text}' class="list-group-item sidebar-list-group-item border-end-0 d-inline-block text-truncate" data-bs-parent="#sidebar">
<span title='${options.text}' class="list-group-item sidebar-list-group-item border-end d-inline-block text-truncate" data-bs-parent="#sidebar">
<h6>
<i class="bi bi-bootstrap"></i>
<span class='sidebar-item-text' style='display: none;'>${options.text}</span>

View File

@ -1,5 +1,5 @@
{% load i18n %}
<span title='{{ text }}' class="list-group-item sidebar-list-group-item border-end-0 d-inline-block text-truncate bg-light" data-bs-parent="#sidebar">
<span title='{{ text }}' class="list-group-item sidebar-list-group-item border-end d-inline-block text-truncate bg-light" data-bs-parent="#sidebar">
<h6>
<i class="bi bi-bootstrap"></i>
{% if icon %}<span class='sidebar-item-icon fas {{ icon }}'></span>{% endif %}

View File

@ -1,5 +1,5 @@
{% load i18n %}
<a href="#" id='select-{{ label }}' title='{{ text }}' class="list-group-item sidebar-list-group-item border-end-0 d-inline-block text-truncate sidebar-selector" data-bs-parent="#sidebar">
<a href="#" id='select-{{ label }}' title='{{ text }}' class="list-group-item sidebar-list-group-item border-end d-inline-block text-truncate sidebar-selector" data-bs-parent="#sidebar">
<i class="bi bi-bootstrap"></i>
<span class='sidebar-item-icon fas {{ icon|default:"fa-circle" }}'></span>
<span class='sidebar-item-text' style='display: none;'>{{ text }}</span>

View File

@ -1,4 +1,4 @@
{% load i18n %}
<a href="{{ url }}" class="list-group-item sidebar-list-group-item border-end-0 d-inline-block text-truncate" data-bs-parent="#sidebar">
<a href="{{ url }}" class="list-group-item sidebar-list-group-item border-end d-inline-block text-truncate" data-bs-parent="#sidebar">
<i class="bi bi-bootstrap"></i><span class='sidebar-item-icon fas {{ icon }}'></span><span class='sidebar-item-text' style='display: none;'>{{ text }}</span>
</a>

View File

@ -1,3 +1,4 @@
<a href="#" id='sidebar-toggle' class="list-group-item sidebar-list-group-item border-end-0 d-inline-block text-truncate sidebar-toggle" data-bs-parent="#sidebar" style='display: none;'>
<a href="#" id='{{ target }}-toggle' class="list-group-item sidebar-list-group-item border-end d-inline-block text-truncate sidebar-toggle" data-bs-parent="#sidebar" style='display: none;'>
<i class="bi bi-bootstrap"></i><span id='sidebar-toggle-icon' class='sidebar-item-icon fas fa-chevron-left'></span>
{% if text %}<span class='sidebar-item-text' style='display: none;'>{{ text }}</span>{% endif %}
</a>