[Feature] tree增删功能

pull/1044/head
ibuler 2018-01-31 17:01:21 +08:00
parent 1ac30ed09c
commit 2f06a2b1d3
4 changed files with 192 additions and 135 deletions

View File

@ -13,7 +13,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from rest_framework import generics from rest_framework import generics, mixins
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework_bulk import BulkModelViewSet from rest_framework_bulk import BulkModelViewSet
@ -21,6 +21,7 @@ from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView
from rest_framework.pagination import LimitOffsetPagination from rest_framework.pagination import LimitOffsetPagination
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.db.models import Q, Count from django.db.models import Q, Count
from django.utils.translation import ugettext_lazy as _
from common.mixins import CustomFilterMixin from common.mixins import CustomFilterMixin
from common.utils import get_logger from common.utils import get_logger
@ -311,21 +312,41 @@ class LabelViewSet(BulkModelViewSet):
return super().list(request, *args, **kwargs) return super().list(request, *args, **kwargs)
class TreeViewApi(APIView): class NodeViewSet(BulkModelViewSet):
queryset = Node.objects.all()
permission_classes = (IsSuperUser,)
serializer_class = serializers.NodeSerializer
def get_queryset(self): def perform_create(self, serializer):
return Node.objects.all() child_id = Node.get_root_node().get_next_child_id()
serializer.validated_data["id"] = child_id
serializer.save()
def get(self, request):
data = [] class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
for node in self.get_queryset(): queryset = Node.objects.all()
parent = ":".join(node.id.split(":")[:-1]) permission_classes = (IsSuperUser,)
d = { serializer_class = serializers.NodeSerializer
"id": node.id, instance = None
"pId": parent,
"name": node.name def post(self, request, *args, **kwargs):
} if not request.data.get("name"):
if node.id == "0": request.data["name"] = _("New node {}").format(
d["open"] = True Node.get_root_node().get_next_child_id().split(":")[-1]
data.append(d) )
return Response(data) return super().post(request, *args, **kwargs)
def create(self, request, *args, **kwargs):
instance = self.get_object()
name = request.data.get("name")
node = instance.create_child(name=name)
return Response({"id": node.id, "name": node.name}, status=201)
def get(self, request, *args, **kwargs):
instance = self.get_object()
if self.request.query_params.get("all"):
children = instance.get_all_children()
else:
children = instance.get_children()
response = [{"id": node.id, "name": node.name} for node in children]
return Response(response, status=200)

View File

@ -4,8 +4,8 @@ from rest_framework import serializers
from rest_framework_bulk.serializers import BulkListSerializer from rest_framework_bulk.serializers import BulkListSerializer
from common.mixins import BulkSerializerMixin from common.mixins import BulkSerializerMixin
from .models import AssetGroup, Asset, Cluster, AdminUser, SystemUser, Label from .models import AssetGroup, Asset, Cluster, AdminUser, SystemUser, Label, Node
from .const import ADMIN_USER_CONN_CACHE_KEY, SYSTEM_USER_CONN_CACHE_KEY from .const import ADMIN_USER_CONN_CACHE_KEY
class AssetGroupSerializer(BulkSerializerMixin, serializers.ModelSerializer): class AssetGroupSerializer(BulkSerializerMixin, serializers.ModelSerializer):
@ -316,3 +316,26 @@ class LabelDistinctSerializer(serializers.ModelSerializer):
def get_value(obj): def get_value(obj):
labels = Label.objects.filter(name=obj["name"]) labels = Label.objects.filter(name=obj["name"])
return ', '.join([label.value for label in labels]) return ', '.join([label.value for label in labels])
class NodeSerializer(serializers.ModelSerializer):
parent = serializers.SerializerMethodField()
class Meta:
model = Node
fields = ['id', 'name', 'parent']
list_serializer_class = BulkListSerializer
@staticmethod
def get_parent(obj):
if obj.id == "0":
return "#"
if not obj.id.startswith("0"):
return "0"
return ":".join(obj.id.split(":")[:-1])
def get_fields(self):
fields = super().get_fields()
field = fields["id"]
field.required = False
return fields

View File

@ -13,8 +13,26 @@
}); });
</script> </script>
<style type="text/css"> <style type="text/css">
.ztree li span.button.add { div#rMenu {
margin-left:2px; margin-right: -1px; background-position:-144px 0; vertical-align:top; *vertical-align:middle position:absolute;
visibility:hidden;
text-align: left;
top: 100%;
left: 0;
z-index: 1000;
float: left;
padding: 5px 0;
margin: 2px 0 0;
list-style: none;
background-clip: padding-box;
}
div#rMenu li{
margin: 1px 0;
cursor: pointer;
{#list-style: none outside none;#}
}
.dropdown a:hover {
background-color: #f1f1f1
} }
</style> </style>
@ -28,8 +46,9 @@
<div class="ibox-content mailbox-content"> <div class="ibox-content mailbox-content">
<div class="file-manager"> <div class="file-manager">
<h5>Tree View</h5> <h5>Tree View</h5>
<div id="treeDemo" class="ztree"> <div id="assetTree" class="ztree">
</div> </div>
<div class="clearfix"></div> <div class="clearfix"></div>
</div> </div>
</div> </div>
@ -83,9 +102,17 @@
</div> </div>
<div class="row"> <div class="row">
</div> </div>
</div>
</div>
</div>
</div> <div id="rMenu">
</div> <ul class="dropdown-menu">
<li id="m_del" tabindex="-1" onclick="removeTreeNode();"><a>重命名</a></li>
<li id="m_add" tabindex="-1" onclick="addTreeNode();"><a>添加节点</a></li>
<li class="divider"></li>
<li id="m_del" tabindex="-1" onclick="removeTreeNode();"><a>删除节点</a></li>
</ul>
</div> </div>
{% endblock %} {% endblock %}
@ -93,6 +120,7 @@
<script src="{% static 'js/plugins/jstree/jstree.min.js' %}"></script> <script src="{% static 'js/plugins/jstree/jstree.min.js' %}"></script>
<script> <script>
var zTree, rMenu;
function initTable() { function initTable() {
var options = { var options = {
ele: $('#asset_list_table'), ele: $('#asset_list_table'),
@ -142,123 +170,105 @@
return jumpserver.initServerSideDataTable(options); return jumpserver.initServerSideDataTable(options);
} }
function beforeDrag(treeId, treeNodes) { function OnRightClick(event, treeId, treeNode) {
showLog("Before drag"); if (!treeNode && event.target.tagName.toLowerCase() !== "button" && $(event.target).parents("a").length == 0) {
return false; zTree.cancelSelectedNode();
} showRMenu("root", event.clientX, event.clientY);
} else if (treeNode && !treeNode.noR) {
function beforeEditName(treeId, treeNode) {
showLog("Before edit name");
className = (className === "dark" ? "" : "dark");
showLog("[ " + getTime() + " beforeEditName ]&nbsp;&nbsp;&nbsp;&nbsp; " + treeNode.name);
var zTree = $.fn.zTree.getZTreeObj("treeDemo");
zTree.selectNode(treeNode); zTree.selectNode(treeNode);
setTimeout(function () { showRMenu("node", event.clientX, event.clientY);
if (confirm("进入节点 -- " + treeNode.name + " 的编辑状态吗?")) {
setTimeout(function () {
zTree.editName(treeNode);
}, 0);
} }
}, 0);
return false;
} }
function beforeRemove(treeId, treeNode) { function showRMenu(type, x, y) {
showLog("[ " + getTime() + " beforeRemove ]&nbsp;&nbsp;&nbsp;&nbsp; " + treeNode.name); $("#rMenu ul").show();
className = (className === "dark" ? "" : "dark"); if (type === "root") {
var zTree = $.fn.zTree.getZTreeObj("treeDemo"); $("#m_del").hide();
zTree.selectNode(treeNode); $("#m_check").hide();
return confirm("确认删除 节点 -- " + treeNode.name + " 吗?"); $("#m_unCheck").hide();
} else {
$("#m_del").show();
$("#m_check").show();
$("#m_unCheck").show();
}
{#y += $("#page-wrapper")[0].scrollTop;#}
{#x += $("#page-wrapper")[0].scrollLeft;#}
x -= 220;
{#y -= 100;#}
{#y += document.body.scrollTop;#}
{#x += document.body.scrollLeft;#}
rMenu.css({"top":y+"px", "left":x+"px", "visibility":"visible"});
$("body").bind("mousedown", onBodyMouseDown);
} }
function onRemove(e, treeId, treeNode) { function beforeClick(treeId, treeNode, clickFlag) {
showLog("Remove node")
}
function beforeRename(treeId, treeNode, newName, isCancel) {
showLog((isCancel ? "<span style='color:red'>" : "") + "[ " + getTime() + " beforeRename ]&nbsp;&nbsp;&nbsp;&nbsp; " + treeNode.name + (isCancel ? "</span>" : ""));
className = (className === "dark" ? "" : "dark");
if (newName.length == 0) {
setTimeout(function () {
var zTree = $.fn.zTree.getZTreeObj("treeDemo");
zTree.cancelEditName();
alert("节点名称不能为空.");
}, 0);
return false;
}
return true; return true;
} }
function onRename(e, treeId, treeNode, isCancel) { function onClick(event, treeId, treeNode, clickFlag) {
console.log("On remname"); showLog("On click");
{#showLog((isCancel ? "<span style='color:red'>" : "") + "[ " + getTime() + " onRename ]&nbsp;&nbsp;&nbsp;&nbsp; " + treeNode.name + (isCancel ? "</span>" : ""));#}
}
function showRemoveBtn(treeId, treeNode) {
showLog("Show remove btn");
return !treeNode.isFirstNode;
}
function showRenameBtn(treeId, treeNode) {
showLog("Show rename btn");
return !treeNode.isLastNode;
} }
function showLog(str) { function showLog(str) {
console.log(str) console.log(str)
} }
function getTime() { function hideRMenu() {
var now = new Date(), if (rMenu) rMenu.css({"visibility": "hidden"});
h = now.getHours(), $("body").unbind("mousedown", onBodyMouseDown);
m = now.getMinutes(),
s = now.getSeconds(),
ms = now.getMilliseconds();
return (h + ":" + m + ":" + s + " " + ms);
} }
var newCount = 1; function onBodyMouseDown(event){
if (!(event.target.id === "rMenu" || $(event.target).parents("#rMenu").length>0)) {
rMenu.css({"visibility" : "hidden"});
}
}
function addHoverDom(treeId, treeNode) { function addTreeNode() {
var sObj = $("#" + treeNode.tId + "_span"); hideRMenu();
if (treeNode.editNameFlag || $("#addBtn_" + treeNode.tId).length > 0) return; var parentNode = zTree.getSelectedNodes()[0];
var addStr = "<span class='button add' id='addBtn_" + treeNode.tId if (!parentNode){
+ "' title='add node' onfocus='this.blur();'></span>"; return
sObj.after(addStr); }
var btn = $("#addBtn_" + treeNode.tId); var url = "{% url 'api-assets:node-children' pk='0:0' %}".replace("0:0", parentNode.id );
if (btn) btn.bind("click", function () {
var zTree = $.fn.zTree.getZTreeObj("treeDemo"); $.post(url, {}, function (data, status){
zTree.addNodes(treeNode, { if (status === "success") {
id: (100 + newCount), var newNode = { name:data["name"], id:data["id"], pId: parentNode.id };
pId: treeNode.id, newNode.checked = zTree.getSelectedNodes()[0].checked;
name: "new node" + (newCount++) zTree.addNodes(parentNode, newNode);
} else {
alert("{% trans 'Create node failed' %}")
}
}); });
return false; }
function removeTreeNode() {
hideRMenu();
var current_node = zTree.getSelectedNodes()[0];
if (!current_node){
return
}
if (current_node.children && current_node.children.length > 0) {
alert("{% trans 'Have child node, cancel' %}")
} else {
var url = "{% url 'api-assets:node-detail' pk='0:0' %}".replace("0:0", current_node.id );
$.ajax({
url: url,
method: "DELETE",
success: function () {
zTree.removeNode(current_node);
}
}); });
}; }
function removeHoverDom(treeId, treeNode) {
showLog("Remove hove dom");
$("#addBtn_" + treeNode.tId).unbind().remove();
};
function selectAll() {
var zTree = $.fn.zTree.getZTreeObj("treeDemo");
zTree.setting.edit.editNameSelectAll = $("#selectAll").attr("checked");
} }
function initTree() { function initTree() {
var setting = { var setting = {
view: { view: {
addHoverDom: addHoverDom, dblClickExpand: false
removeHoverDom: removeHoverDom,
selectedMulti: false
},
edit: {
enable: true,
editNameSelectAll: true,
showRemoveBtn: showRemoveBtn,
showRenameBtn: showRenameBtn
}, },
data: { data: {
simpleData: { simpleData: {
@ -266,23 +276,25 @@
} }
}, },
callback: { callback: {
beforeDrag: beforeDrag, onRightClick: OnRightClick,
beforeEditName: beforeEditName, beforeClick: beforeClick,
beforeRemove: beforeRemove, onClick: onClick
beforeRename: beforeRename,
onRemove: onRemove,
onRename: onRename
} }
}; };
var zNodes = []; var zNodes = [];
$.get("{% url 'api-assets:tree-view' %}", function(data, status){ $.get("{% url 'api-assets:node-list' %}", function(data, status){
zNodes = data; $.each(data, function (index, value) {
console.log(data); value["pId"] = value["parent"];
console.log(status); if (value["id"] === "0") {
$.fn.zTree.init($("#treeDemo"), setting, zNodes); value["open"] = true;
}
});
zNodes = data;
$.fn.zTree.init($("#assetTree"), setting, zNodes);
zTree = $.fn.zTree.getZTreeObj("assetTree");
rMenu = $("#rMenu");
}); });
$("#selectAll").bind("click", selectAll);
} }
$(document).ready(function(){ $(document).ready(function(){
initTable(); initTable();

View File

@ -13,6 +13,7 @@ router.register(r'v1/clusters', api.ClusterViewSet, 'cluster')
router.register(r'v1/admin-user', api.AdminUserViewSet, 'admin-user') router.register(r'v1/admin-user', api.AdminUserViewSet, 'admin-user')
router.register(r'v1/system-user', api.SystemUserViewSet, 'system-user') router.register(r'v1/system-user', api.SystemUserViewSet, 'system-user')
router.register(r'v1/labels', api.LabelViewSet, 'label') router.register(r'v1/labels', api.LabelViewSet, 'label')
router.register(r'v1/nodes', api.NodeViewSet, 'node')
urlpatterns = [ urlpatterns = [
url(r'^v1/assets-bulk/$', api.AssetListUpdateApi.as_view(), name='asset-bulk-update'), url(r'^v1/assets-bulk/$', api.AssetListUpdateApi.as_view(), name='asset-bulk-update'),
@ -42,7 +43,7 @@ urlpatterns = [
api.SystemUserPushApi.as_view(), name='system-user-push'), api.SystemUserPushApi.as_view(), name='system-user-push'),
url(r'^v1/system-user/(?P<pk>[0-9a-zA-Z\-]{36})/connective/$', url(r'^v1/system-user/(?P<pk>[0-9a-zA-Z\-]{36})/connective/$',
api.SystemUserTestConnectiveApi.as_view(), name='system-user-connective'), api.SystemUserTestConnectiveApi.as_view(), name='system-user-connective'),
url(r'^v1/tree/$', api.TreeViewApi.as_view(), name='tree-view') url(r'^v1/nodes/(?P<pk>[0-9:]+)/children/$', api.NodeChildrenApi.as_view(), name='node-children'),
] ]
urlpatterns += router.urls urlpatterns += router.urls