mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
commit
793b4f8063
@ -41,6 +41,17 @@ class InvenTreeTree(models.Model):
|
||||
null=True,
|
||||
related_name='children')
|
||||
|
||||
@property
|
||||
def item_count(self):
|
||||
""" Return the number of items which exist *under* this node in the tree.
|
||||
|
||||
Here an 'item' is considered to be the 'leaf' at the end of each branch,
|
||||
and the exact nature here will depend on the class implementation.
|
||||
|
||||
The default implementation returns zero
|
||||
"""
|
||||
return 0
|
||||
|
||||
def getUniqueParents(self, unique=None):
|
||||
""" Return a flat set of all parent items that exist above this node.
|
||||
If any parents are repeated (which would be very bad!), the process is halted
|
||||
|
@ -28,11 +28,22 @@ class TreeSerializer(views.APIView):
|
||||
Ref: https://github.com/jonmiles/bootstrap-treeview
|
||||
"""
|
||||
|
||||
@property
|
||||
def root_url(self):
|
||||
""" Return the root URL for the tree. Implementation is class dependent.
|
||||
|
||||
Default implementation returns #
|
||||
"""
|
||||
|
||||
return '#'
|
||||
|
||||
def itemToJson(self, item):
|
||||
|
||||
data = {
|
||||
'pk': item.id,
|
||||
'text': item.name,
|
||||
'href': item.get_absolute_url(),
|
||||
'tags': [item.item_count],
|
||||
}
|
||||
|
||||
if item.has_children:
|
||||
@ -51,12 +62,18 @@ class TreeSerializer(views.APIView):
|
||||
|
||||
nodes = []
|
||||
|
||||
top_count = 0
|
||||
|
||||
for item in top_items:
|
||||
nodes.append(self.itemToJson(item))
|
||||
top_count += item.item_count
|
||||
|
||||
top = {
|
||||
'pk': None,
|
||||
'text': self.title,
|
||||
'href': self.root_url,
|
||||
'nodes': nodes,
|
||||
'tags': [top_count],
|
||||
}
|
||||
|
||||
response = {
|
||||
|
@ -14,6 +14,7 @@ from rest_framework import generics, permissions
|
||||
|
||||
from django.db.models import Q
|
||||
from django.conf.urls import url, include
|
||||
from django.urls import reverse
|
||||
|
||||
from .models import Part, PartCategory, BomItem, PartStar
|
||||
from .models import SupplierPart, SupplierPriceBreak
|
||||
@ -30,6 +31,10 @@ class PartCategoryTree(TreeSerializer):
|
||||
|
||||
title = "Parts"
|
||||
model = PartCategory
|
||||
|
||||
@property
|
||||
def root_url(self):
|
||||
return reverse('part-index')
|
||||
|
||||
|
||||
class CategoryList(generics.ListCreateAPIView):
|
||||
|
@ -47,18 +47,18 @@ class PartCategory(InvenTreeTree):
|
||||
verbose_name = "Part Category"
|
||||
verbose_name_plural = "Part Categories"
|
||||
|
||||
@property
|
||||
def item_count(self):
|
||||
return self.partcount
|
||||
|
||||
@property
|
||||
def partcount(self):
|
||||
""" Return the total part count under this category
|
||||
(including children of child categories)
|
||||
"""
|
||||
|
||||
count = self.parts.count()
|
||||
|
||||
for child in self.children.all():
|
||||
count += child.partcount
|
||||
|
||||
return count
|
||||
return len(Part.objects.filter(category__in=self.getUniqueChildren(),
|
||||
active=True))
|
||||
|
||||
@property
|
||||
def has_parts(self):
|
||||
|
@ -1,7 +1,7 @@
|
||||
<div class='navigation'>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href='#' id='toggle-part-tree'><b>+</b></a></li>
|
||||
<li><a href='#' id='toggle-part-tree'><b><span class='glyphicon glyphicon-small glyphicon-th-list'></span></b></a></li>
|
||||
<li class="breadcrumb-item{% if category is None %} active" aria-current="page{% endif %}"><a href="/part/">Parts</a></li>
|
||||
{% if category %}
|
||||
{% for path_item in category.parentpath %}
|
||||
|
@ -34,7 +34,11 @@ InvenTree | Part List
|
||||
{% block js_ready %}
|
||||
{{ block.super }}
|
||||
loadTree("{% url 'api-part-tree' %}",
|
||||
"#part-tree");
|
||||
"#part-tree",
|
||||
{
|
||||
name: 'part',
|
||||
}
|
||||
);
|
||||
|
||||
$("#toggle-part-tree").click(function() {
|
||||
toggleSideNav("#sidenav");
|
||||
|
@ -19,6 +19,14 @@
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.treeview .badge {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.treeview .list-group-item {
|
||||
padding: 6px 12px;
|
||||
}
|
||||
|
||||
/* Force select2 elements in modal forms to be full width */
|
||||
.select-full-width {
|
||||
width: 100%;
|
||||
@ -113,13 +121,26 @@
|
||||
.navigation {
|
||||
}
|
||||
|
||||
.breadcrump {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.inventree-body {
|
||||
width: 100%;
|
||||
padding: 5px;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.inventree-pre-content {
|
||||
width: 100%;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.inventree-content {
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
padding-top: 15px;
|
||||
margin-right: 50px;
|
||||
margin-left: 50px;
|
||||
width: 100%;
|
||||
padding-top: 5px;
|
||||
width: auto;
|
||||
transition: 0.1s;
|
||||
}
|
||||
|
||||
@ -165,7 +186,7 @@
|
||||
position: fixed; /* Stay in place */
|
||||
background-color: #fff; /* Black*/
|
||||
overflow-x: hidden; /* Disable horizontal scroll */
|
||||
//transition: 0.1s; /* 0.5 second transition effect to slide in the sidenav */
|
||||
transition: 0.1s; /* 0.5 second transition effect to slide in the sidenav */
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
|
@ -1,4 +1,26 @@
|
||||
function loadTree(url, tree, data) {
|
||||
function loadTree(url, tree, options={}) {
|
||||
/* Load the side-nav tree view
|
||||
|
||||
Args:
|
||||
url: URL to request tree data
|
||||
tree: html ref to treeview
|
||||
options:
|
||||
data: data object to pass to the AJAX request
|
||||
selected: ID of currently selected item
|
||||
name: name of the tree
|
||||
*/
|
||||
|
||||
var data = {};
|
||||
|
||||
if (options.data) {
|
||||
data = options.data;
|
||||
}
|
||||
|
||||
var key = "inventree-sidenav-items-";
|
||||
|
||||
if (options.name) {
|
||||
key += options.name;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: url,
|
||||
@ -9,14 +31,17 @@ function loadTree(url, tree, data) {
|
||||
if (response.tree) {
|
||||
$(tree).treeview({
|
||||
data: response.tree,
|
||||
enableLinks: true
|
||||
enableLinks: true,
|
||||
showTags: true,
|
||||
});
|
||||
|
||||
var saved_exp = sessionStorage.getItem('inventree-sidenav-expanded-items').split(",");
|
||||
if (sessionStorage.getItem(key)) {
|
||||
var saved_exp = sessionStorage.getItem(key).split(",");
|
||||
|
||||
// Automatically expand the desired notes
|
||||
for (var q = 0; q < saved_exp.length; q++) {
|
||||
$(tree).treeview('expandNode', parseInt(saved_exp[q]));
|
||||
// Automatically expand the desired notes
|
||||
for (var q = 0; q < saved_exp.length; q++) {
|
||||
$(tree).treeview('expandNode', parseInt(saved_exp[q]));
|
||||
}
|
||||
}
|
||||
|
||||
// Setup a callback whenever a node is toggled
|
||||
@ -32,7 +57,7 @@ function loadTree(url, tree, data) {
|
||||
}
|
||||
|
||||
// Save the expanded nodes
|
||||
sessionStorage.setItem('inventree-sidenav-expanded-items', exp);
|
||||
sessionStorage.setItem(key, exp);
|
||||
});
|
||||
}
|
||||
},
|
||||
@ -43,6 +68,7 @@ function loadTree(url, tree, data) {
|
||||
}
|
||||
|
||||
function openSideNav() {
|
||||
document.getElementById("sidenav").style.display = "block";
|
||||
document.getElementById("sidenav").style.width = "250px";
|
||||
document.getElementById("inventree-content").style.marginLeft = "270px";
|
||||
|
||||
@ -52,8 +78,9 @@ function openSideNav() {
|
||||
}
|
||||
|
||||
function closeSideNav() {
|
||||
document.getElementById("sidenav").style.display = "none";
|
||||
document.getElementById("sidenav").style.width = "0";
|
||||
document.getElementById("inventree-content").style.marginLeft = "50px";
|
||||
document.getElementById("inventree-content").style.marginLeft = "0px";
|
||||
|
||||
sessionStorage.setItem('inventree-sidenav-state', 'closed');
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ from django_filters.rest_framework import FilterSet, DjangoFilterBackend
|
||||
from django_filters import NumberFilter
|
||||
|
||||
from django.conf.urls import url, include
|
||||
from django.urls import reverse
|
||||
from django.db.models import Q
|
||||
|
||||
from .models import StockLocation, StockItem
|
||||
@ -27,6 +28,10 @@ class StockCategoryTree(TreeSerializer):
|
||||
title = 'Stock'
|
||||
model = StockLocation
|
||||
|
||||
@property
|
||||
def root_url(self):
|
||||
return reverse('stock-index')
|
||||
|
||||
|
||||
class StockDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
""" API detail endpoint for Stock object
|
||||
|
@ -48,6 +48,18 @@ class StockLocation(InvenTreeTree):
|
||||
}
|
||||
)
|
||||
|
||||
@property
|
||||
def stock_item_count(self):
|
||||
""" Return the number of StockItem objects which live in or under this category
|
||||
"""
|
||||
|
||||
return len(StockItem.objects.filter(location__in=self.getUniqueChildren()))
|
||||
|
||||
@property
|
||||
def item_count(self):
|
||||
|
||||
return self.stock_item_count
|
||||
|
||||
|
||||
@receiver(pre_delete, sender=StockLocation, dispatch_uid='stocklocation_delete_log')
|
||||
def before_delete_stock_location(sender, instance, using, **kwargs):
|
||||
|
@ -1,7 +1,7 @@
|
||||
<div class="navigation">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href='#' id='toggle-stock-tree'><b>+</b></a></li>
|
||||
<li><a href='#' title='Toggle Stock Tree' id='toggle-stock-tree'><b><span class='glyphicon glyphicon-small glyphicon-th-list'></span></b></a></li>
|
||||
<li class="breadcrumb-item{% if location is None %} active" aria-current="page{% endif %}"><a href="/stock/">Stock</a></li>
|
||||
{% if location %}
|
||||
{% for path_item in location.parentpath %}
|
||||
|
@ -33,7 +33,12 @@ InvenTree | Stock
|
||||
initSideNav();
|
||||
{{ block.super }}
|
||||
loadTree("{% url 'api-stock-tree' %}",
|
||||
"#stock-tree");
|
||||
"#stock-tree",
|
||||
{
|
||||
name: 'stock',
|
||||
selected: 'elab',
|
||||
}
|
||||
);
|
||||
|
||||
$("#toggle-stock-tree").click(function() {
|
||||
toggleSideNav("#sidenav");
|
||||
|
@ -52,19 +52,26 @@ InvenTree
|
||||
|
||||
<div class='main body wrapper'>
|
||||
|
||||
<div class='inventree-body'>
|
||||
|
||||
<div class='containter-fluid inventree-pre-content'>
|
||||
{% block pre_content %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
|
||||
<div class='sidenav' id='sidenav'>
|
||||
{% block sidenav %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
|
||||
|
||||
<div class="container container-fluid inventree-content" id='inventree-content'>
|
||||
{% block pre_content %}
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<!-- Each view fills in here.. -->
|
||||
{% endblock %}
|
||||
|
||||
{% block post_content %}
|
||||
{% endblock %}
|
||||
|
||||
</div>
|
||||
|
||||
{% include 'modals.html' %}
|
||||
@ -72,6 +79,8 @@ InvenTree
|
||||
{% include 'notification.html' %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script type="text/javascript" src="{% static 'script/jquery_3.3.1_jquery.min.js' %}"></script>
|
||||
<script type='text/javascript' src="{% static 'script/jquery.form.min.js' %}"></script>
|
||||
|
Loading…
Reference in New Issue
Block a user