Python 下 Flask 结合 Neo4j 构建管理系统(四)

数据的增删改功能引入了 bootstrap table 插件进行操作。 #### bootstrap table 使用 引入 Bootstrap 库(假如你的项目还没有使用)和 bootstrap-table.css 到 head 标签下。

1
2
<link rel="stylesheet" href="bootstrap.min.css">
<link rel="stylesheet" href="bootstrap-table.css">

引入 jQuery 库,bootstrap 库(假如你的项目还没有使用)和 bootstrap-table.js 到 head 标签下或者在 body 标签关闭之前(一般建议这么做)。

1
2
3
4
5
<script src="jquery.min.js"></script>
<script src="bootstrap.min.js"></script>
<script src="bootstrap-table.js"></script>
<-- put your locale files after bootstrap-table.js -->
<script src="bootstrap-table-zh-CN.js"></script>

表格初始化

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
var TableInit = function () {
var oTableInit = new Object();
//初始化Table

oTableInit.Init = function () {
$('#ta_info').bootstrapTable({
url: $SCRIPT_ROOT + "/search_table", //请求后台的URL(*)
method: 'get', //请求方式(*)
toolbar: '#toolbar', //工具按钮用哪个容器
striped: true, //是否显示行间隔色
cache: false, //是否使用缓存,默认为true,所以一般情况下需要设置一下这个属性(*)
pagination: true, //是否显示分页(*)
sortable: false, //是否启用排序
sortOrder: "asc", //排序方式
queryParams: oTableInit.queryParams,//传递参数(*)
sidePagination: "server", //分页方式:client客户端分页,server服务端分页(*)
pageNumber: 1, //初始化加载第一页,默认第一页
pageSize: 10, //每页的记录行数(*)
pageList: [10, 25, 50, 100], //可供选择的每页的行数(*)
search: false, //是否显示表格搜索,此搜索是客户端搜索,不会进服务端,所以,个人感觉意义不大
strictSearch: false,
showColumns: true, //是否显示所有的列
showRefresh: true, //是否显示刷新按钮
minimumCountColumns: 2, //最少允许的列数
clickToSelect: true, //是否启用点击选中行
height: 500, //行高,如果没有设置height属性,表格自动根据记录条数觉得表格高度
uniqueId: "ID", //每一行的唯一标识,一般为主键列
showToggle: true, //是否显示详细视图和列表视图的切换按钮
cardView: false, //是否显示详细视图
detailView: false, //是否显示父子表
columns: [{
checkbox: true
}, {
field: 'name',
title: '信息名'
}, {
field: 'label',
title: '类别'
}]
});
};

//得到查询的参数
oTableInit.queryParams = function (params) {
var temp = { //这里的键的名字和控制器的变量名必须一直,这边改动,控制器也需要改成一样的
limit: params.limit, //页面大小
offset: params.offset, //页码
q: $("#search_table").find("input[name=condition]").val()
};
return temp;
};
return oTableInit;

};

服务器分页

后端代码:

1
2
3
4
5
6
7
8
9
10
11
12
@app.route('/search_table')
def search_table():
try:
q = request.args.get('q', 0)
limit = request.args.get('limit', 1)
offset = request.args.get('offset', 2)
except KeyError:
return []
else:
user = User(session.get("username"))
results = user.search_table(q, limit, offset)
return results
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def search_table(self, condition, limit, offset):
nodes = []
act_nodes = []
query = "MATCH (m) WHERE m.name CONTAINS \'" + condition + \
"\' and (labels(m)='Sort' or labels(m)='GOODS' or labels(m)='Website') RETURN m, labels(m)"
results = graph.run(query)
for result, label in results:
node = {}
node["name"] = result.get("name")
node["label"] = label[0]
nodes.append(node)
total = len(nodes)
for i in range(int(offset), int(offset) + int(limit)):
if i >= len(nodes):
break
act_nodes.append(nodes[i])
return jsonify({'total': total, 'rows': act_nodes})
  • 服务器分页必须返回 totalrows 两个参数,前者代表查询数据的总长度,后者返回该页应该显示的数据。

增删改

以增加数据为例: 增加数据 由于 Neo4j 中的数据不是结构性数据,所以增加数据的基本思路是由用户选择父节点,然后向父节点增加子节点,增加子节点的过程中必须填入子节点的 名字类别以及和父节点的关系属性,如果子节点还有其它属性,则可以点击增加行进行额外属性的增加。 前端代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
$('#btn_add').click(function () {
var father_node = $("#ta_info").bootstrapTable('getSelections');
if (father_node == "") {
alert("请选择插入元素的父节点")
} else if (father_node.length > 1) {
alert("父节点只允许一个")
} else {
if ($('.added-row').length > 0)
$('.added-row').remove();
$('#insertModal_body').find("input[name=name]").val("");
$('#insertModal_body').find("input[name=label]").val("");
$('#insertModal_body').find("input[name=relation]").val("");
$('#insertModal').modal()
}
return false
});

$('#add_row').click(function () {
var modal_body = document.getElementById("insertModal_body");
var row_div = document.createElement("div");
var row_label = document.createElement("input");
var row_text = document.createElement("input");
row_div.setAttribute("class", "input-data form-inline added-row");
row_label.setAttribute("class", "extra-label form-control");
row_label.setAttribute("type", "text");
row_label.setAttribute("placeholder", "属性名");
row_label.setAttribute("name", "attr");
row_text.setAttribute("class", "extra-text form-control");
row_text.setAttribute("placeholder", "属性");
row_text.setAttribute("type", "text");
row_div.appendChild(row_label);
row_div.appendChild(row_text);
modal_body.appendChild(row_div);
});

$('#insert_btn').click(function () {
var data = {};
var father_node = $("#ta_info").bootstrapTable('getSelections');
var insertModal = $('#insertModal_body');
var fa_name = father_node[0]["name"];
var fa_label = father_node[0]['label'];
var child_name = insertModal.find("input[name=name]").val();
var child_label = insertModal.find("input[name=label]").val();
var relation = insertModal.find("input[name=relation]").val();
var child_attrs_label = $('.extra-label');
var child_attrs = $('.extra-text');
data['fa_name'] = fa_name;
data['fa_label'] = fa_label;
data['name'] = child_name;
data['label'] = child_label;
data['relation'] = relation;
if (child_name == "" || child_label == "" || relation == "") {
alert("请填写必要信息");
return false;
} else if (fa_name == child_name) {
alert("不可与父节点名称相同");
return false;
}
if (child_attrs.length > 0) {
console.log(1);
for (var i = 0; i < child_attrs.length; i++) {
data[child_attrs_label[i].value] = child_attrs[i].value;
}
}

data = JSON.stringify(data);
$.ajax({
url: "/insert",
type: 'POST',
data: data,
contentType: 'application/json; charset=UTF-8',
success: function (msg) {
if (msg == "插入成功") {
$('#insertModal').modal('hide');
$("#ta_info").bootstrapTable('refresh');
}
alert(msg);
}
})
});

后端代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@app.route('/insert', methods=['POST'])
def get_insert():
try:
data = request.get_json()
except KeyError:
return []
else:
user = User(session.get("username"))
start_node = {}
end_node = {}
start_node['name'] = data['fa_name']
start_node['label'] = data['fa_label']
relation = data['relation']
data.pop('fa_name')
data.pop('fa_label')
data.pop('relation')
for key in data.keys():
end_node[key] = data[key]
results = user.insert_table(start_node, relation, end_node)
return results
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def insert_table(self, start_node, relation, end_node):
end_node_label = end_node['label']
end_node.pop("label")
data = ""
for key in end_node.keys():
data += key + ":" + "\'" + end_node[key] + "\'" + ","
data = data.strip(",")
check_query = "MATCH (m) WHERE m.username=\'" + end_node['name'] \
+ "\' and labels(m)=\'" + end_node_label + "\' RETURN m"
insert_query = "MATCH (m) WHERE m.name=\'" + start_node['name'] \
+ "\' and labels(m)=\'" + start_node['label'] + \
"\' CREATE (n:" + end_node_label + "" + "{" + data + "})<-" + \
"[:" + relation + "]-(m) RETURN n"
check_result = graph.run(check_query)
if check_result.forward():
return "已经存在此节点"
print(insert_query)
insert_result = graph.run(insert_query)
if insert_result.forward():
return "插入成功"
return "插入失败"

总结

利用 Neo4j 结合 Flask 构建 Web 应用的关键在于如何前端和后端如何进行数据交互,Flask 可以很方便的通过 jsonify() 方法返回 Json 数据,所以在构建应用的过程中可以尽可能使用 ajax 向后端发送 Json格式数据。


参考资料

Bootstrap Table