Commit 2f06a2b1 authored by ibuler's avatar ibuler

[Feature] tree增删功能

parent 1ac30ed0
...@@ -13,7 +13,7 @@ ...@@ -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 ...@@ -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): ...@@ -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
def get(self, request): serializer.save()
data = []
for node in self.get_queryset():
parent = ":".join(node.id.split(":")[:-1]) class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
d = { queryset = Node.objects.all()
"id": node.id, permission_classes = (IsSuperUser,)
"pId": parent, serializer_class = serializers.NodeSerializer
"name": node.name instance = None
}
if node.id == "0": def post(self, request, *args, **kwargs):
d["open"] = True if not request.data.get("name"):
data.append(d) request.data["name"] = _("New node {}").format(
return Response(data) Node.get_root_node().get_next_child_id().split(":")[-1]
)
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)
...@@ -4,8 +4,8 @@ from rest_framework import serializers ...@@ -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): ...@@ -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
...@@ -13,8 +13,26 @@ ...@@ -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 @@ ...@@ -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,16 +102,25 @@ ...@@ -83,16 +102,25 @@
</div> </div>
<div class="row"> <div class="row">
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div id="rMenu">
<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>
{% endblock %} {% endblock %}
{% block custom_foot_js %} {% block custom_foot_js %}
<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 @@ ...@@ -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"});
function onRemove(e, treeId, treeNode) { $("body").bind("mousedown", onBodyMouseDown);
showLog("Remove node")
} }
function beforeRename(treeId, treeNode, newName, isCancel) { function beforeClick(treeId, treeNode, clickFlag) {
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) { function showLog(str) {
showLog("Show remove btn"); console.log(str)
return !treeNode.isFirstNode;
} }
function showRenameBtn(treeId, treeNode) { function hideRMenu() {
showLog("Show rename btn"); if (rMenu) rMenu.css({"visibility": "hidden"});
return !treeNode.isLastNode; $("body").unbind("mousedown", onBodyMouseDown);
} }
function showLog(str) { function onBodyMouseDown(event){
console.log(str) if (!(event.target.id === "rMenu" || $(event.target).parents("#rMenu").length>0)) {
rMenu.css({"visibility" : "hidden"});
} }
function getTime() {
var now = new Date(),
h = now.getHours(),
m = now.getMinutes(),
s = now.getSeconds(),
ms = now.getMilliseconds();
return (h + ":" + m + ":" + s + " " + ms);
} }
var newCount = 1; function addTreeNode() {
hideRMenu();
var parentNode = zTree.getSelectedNodes()[0];
if (!parentNode){
return
}
var url = "{% url 'api-assets:node-children' pk='0:0' %}".replace("0:0", parentNode.id );
function addHoverDom(treeId, treeNode) { $.post(url, {}, function (data, status){
var sObj = $("#" + treeNode.tId + "_span"); if (status === "success") {
if (treeNode.editNameFlag || $("#addBtn_" + treeNode.tId).length > 0) return; var newNode = { name:data["name"], id:data["id"], pId: parentNode.id };
var addStr = "<span class='button add' id='addBtn_" + treeNode.tId newNode.checked = zTree.getSelectedNodes()[0].checked;
+ "' title='add node' onfocus='this.blur();'></span>"; zTree.addNodes(parentNode, newNode);
sObj.after(addStr); } else {
var btn = $("#addBtn_" + treeNode.tId); alert("{% trans 'Create node failed' %}")
if (btn) btn.bind("click", function () { }
var zTree = $.fn.zTree.getZTreeObj("treeDemo");
zTree.addNodes(treeNode, {
id: (100 + newCount),
pId: treeNode.id,
name: "new node" + (newCount++)
});
return false;
}); });
}; }
function removeHoverDom(treeId, treeNode) { function removeTreeNode() {
showLog("Remove hove dom"); hideRMenu();
$("#addBtn_" + treeNode.tId).unbind().remove(); var current_node = zTree.getSelectedNodes()[0];
}; if (!current_node){
return
}
function selectAll() { if (current_node.children && current_node.children.length > 0) {
var zTree = $.fn.zTree.getZTreeObj("treeDemo"); alert("{% trans 'Have child node, cancel' %}")
zTree.setting.edit.editNameSelectAll = $("#selectAll").attr("checked"); } 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 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 @@ ...@@ -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){
$.each(data, function (index, value) {
value["pId"] = value["parent"];
if (value["id"] === "0") {
value["open"] = true;
}
});
zNodes = data; zNodes = data;
console.log(data); $.fn.zTree.init($("#assetTree"), setting, zNodes);
console.log(status); zTree = $.fn.zTree.getZTreeObj("assetTree");
$.fn.zTree.init($("#treeDemo"), setting, zNodes); rMenu = $("#rMenu");
}); });
$("#selectAll").bind("click", selectAll);
} }
$(document).ready(function(){ $(document).ready(function(){
initTable(); initTable();
......
...@@ -13,6 +13,7 @@ router.register(r'v1/clusters', api.ClusterViewSet, 'cluster') ...@@ -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 = [ ...@@ -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
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment