Django中使用Bootstrap展示树形结构
概述
在企业管理系统中,经常会有树形结构需求,例如:组织结构、权限等等,本文使用Django和Bootstrap Tree View来展示企业组织结构和对应组织的人员。
实现
模型类(models.py)
class Department(models.Model):
name = models.CharField(u‘单位名称‘, max_length=30)
pri = models.IntegerField(u‘序号‘)
desc = models.CharField(u‘备注‘, max_length=100)
parent = models.ForeignKey(‘self‘, blank=True, null=True, related_name=‘children‘, verbose_name=‘上级单位‘)
full_path = models.CharField(u‘全路径‘, max_length=20, blank=True, null=True)
deep_level = models.IntegerField(u‘深度‘, default=0)
def __str__(self):
return self.name
class Meta:
verbose_name = u‘组织结构‘
verbose_name_plural = u‘组织结构‘
class People(models.Model):
PEOPLE_TYPE_CHOICES = (
(‘A‘, u‘公务员‘),
(‘B‘, u‘事业编‘),
(‘C‘, u‘合同工‘),
(‘D‘, u‘临时工‘),
)
user = models.OneToOneField(User)
nickname = models.CharField(u‘昵称‘, max_length=50)
job = models.CharField(u‘职务‘, max_length=50)
job_type = models.CharField(u‘类别‘, choices=PEOPLE_TYPE_CHOICES, max_length=2, default=‘A‘)
department = models.ForeignKey(Department, related_name=‘peoples‘, verbose_name=‘单位‘)
def __str__(self):
return self.nickname
class Meta:
verbose_name = u‘人员‘
verbose_name_plural = u‘人员‘
class TreeNode():
def __init__(self):
self.id = 0
self.text = "Node 1"
self.href = "#node-1"
self.selectable = True
self.state = {
‘checked‘: True,
‘disabled‘: True,
‘expanded‘: True,
‘selected‘: True,
},
self.tags = [‘available‘],
self.nodes = []
def to_dict(self):
icon = (len(self.nodes) > 0) and ‘glyphicon glyphicon-list-alt‘ or ‘glyphicon glyphicon-user‘
return {
‘id‘: self.id,
‘text‘: self.text,
‘icon‘: icon,
‘href‘: self.href,
‘tags‘: [‘1‘],
‘nodes‘: self.nodes,
}
Department为组织结构,为表示上下级关系,使用了自关联,ForeignKey的第一个参数为self
,为方便上级查找所有的下级,添加related_name=’children’,这样在查询指定单位的下级时候,可以使用:
p = Department.objects.get(parent=None)
children = p.children.all()
来查询所有的下级。
People类为单位人员,我将它关联到Django内建的User,实现User和People的一对一关联,可以在后台管理页面直接编辑People,People类有一个外键,指向Department,同样添加了related_name=’peoples’方便查询。
为显示树状结构,我建立了一个TreeNode类,这个类不需要保存到数据库,它的属性主要供Bootstrap Tree View使用,因为Python的json模块不能直接序列化TreeNode类,所以添加一个to_dict方法,先将TreeNode转化为一个dict。
URL映射(urls.py)
from django.conf.urls import patterns, url
from dept import views as dept_vies
urlpatterns = [
url(r‘^show/$‘, dept_vies.show, name=‘dept_show‘),
url(r‘^tree/$‘, dept_vies.tree, name=‘dept_tree‘),
url(r‘^people/(\d+)$‘, dept_vies.people, name=‘dept_people‘),
]
视图(views.py)
def get_dept_tree(parents):
‘‘‘
根据提供的父节点,迭代出所有的子节点,并用一个dict的列表来表示
:param parents 父节点列表:
:return 返回dict列表:
‘‘‘
display_tree = []
for p in parents:
node = TreeNode()
node.id = p.id
node.text = p.name
children = p.children.all()
if len(children) > 0:
node.nodes = get_dept_tree(children)
display_tree.append(node.to_dict())
return display_tree
def show(request):
return render(request, "dept/show.html")
def tree(request):
root = Department.objects.get(parent=None)
tree = get_dept_tree([root])
return JsonResponse(tree, safe=False)
def people(request, pk):
dept = Department.objects.get(pk=pk)
peoples = dept.peoples.all()
return render(request, "dept/peoples.html", {‘peoples‘: peoples})
模板类
show.html
{% extends ‘base.html‘ %}
{% block title %}组织结构{% endblock %}
{% block head %}
<link type="text/css" href="/static/css/bootstrap-treeview.min.css">
{% endblock %}
{% block script %}
<script type="text/javascript" src="/static/js/bootstrap-treeview.min.js"></script>
<script type="text/javascript">
$(function () {
$.ajaxSetup({
data: {csrfmiddlewaretoken: ‘{{ csrf_token }}‘ },
});
var tree = $.getJSON("{% url ‘dept_tree‘ %}", ‘‘, function (data) {
$(‘#tree‘).treeview({
data: data,
level: 2,
showTags: true,
onNodeSelected: function (event, node) {
$.post("../people/"+node.id, {}, function(data){
console.log(data);
$("#result").html(data);
})
}
});
});
});
</script>
{% endblock %}
{% block content %}
<hr>
<div class="row">
<div id="tree" class="col-md-3"></div>
<div id="result" class="col-md-9">
sss
</div>
</div>
{% endblock %}
Django 中自带了防止CSRF攻击的功能,GET请求不需要 CSRF 认证,POST 请求需要正确认证才能得到正确的返回结果,如果没有认证,常常会出现下面django csrf token missing or incorrect的错误。最简洁的方法时设置JQuery的ajax属性:
$.ajaxSetup({
data: {csrfmiddlewaretoken: ‘{{ csrf_token }}‘ },
});
Bootstrap Tree View的使用文档见GitHub
people.html
<table class="table table-bordered">
<tr>
<th>姓名</th><th>职务</th><th>类型</th>
</tr>
{% for people in peoples %}
<tr>
<th>{{ people.nickname }}</th>
<th>{{ people.job }}</th>
<th>{{ people.get_job_type_display }}</th>
</tr>
{% empty %}
<tr><th colspan="3">没有人员</th> </tr>
{% endfor %}
</table>
在show.html页面,当点击不同的节点时候,会发出形如“people/1”的ajax请求,返回结果是一段HTML代码,通过JQuery的html()方法动态加载到show.html页面上。
people有一个job_type属性,为一个元组,如果直接使用people.job_type,仅仅会显示A、B、C等结构,如果想显示对应的描述信息,需要使用get_job_type_display。
总结
本文基本实现树形结构的展示、一对多关联查询、数据异步加载等功能,但具体细节还不够完善。
树形结构每个单位右边的角标没有实现,仅仅用了一个固定数字1来占位,设想可以显示单位人数,待完善。
树形结构的查询需要进一步封装,如果要查询根节点下的所有单位,目前只能够多次查询,查询效率不高,考虑使用full_path和deep_level两个冗余字段存储单位的全路径和节点深度,通过startswith来查询所有子节点,如果使用SQL SERVER数据库,可以通过触发器的方式动态修改这两个字段你,使用Django目前还没有较好的办法。