Python 全栈系列64 - 使用Flask+DataTables+Mongo搭建交互式表格

说明

差不多凑齐7颗龙珠,可以在前端做一个交互式的表格页面了。主要用于快速的检索和录入数据。

  • 1 datatables: 提供了自动的分页、检索,以及渲染单元格样式
  • 2 icheck: 提供了行的checkbox
  • 3 awesomefont: 提供小图标,看起来方便
  • 4 jquery: 控制前后端的数据交互以及前端页面的修改
  • 5 flask: 提供后端的数据处理
  • 6 mongo: 提供非结构化的字典存储,方便修改
  • 7 modal: 提供更友好的编辑提示

使用场景:提供快速的录入构建图结构的入口。

A 内容

整体上处理的步骤大约是这样
Python 全栈系列64 - 使用Flask+DataTables+Mongo搭建交互式表格

  • 1 H5页面:访问页面
  • 2 Flask读取:页面载入是请求数据,此时从Mongo里读取数据
  • 3 Flask处理:将读取的非结构化数据转化为表格,以及准备datatables格式化需要的表头
  • 4 Ajax,DataTables:获取到数据后将数据灌入,使用DataTables格式化
  • 5 JQuery格式化:使用Jquery对某些列的格式进行处理,展示起来更方便
  • 6 Datatables:用户使用表格快速搜索,浏览
  • 7 模态框:用户通过模态框进行表格内容的修改
  • 8 Ajax异步提交:使用Ajax提交更改(选定行)
  • 9 Flask处理:Flask获取更改的数据,并处理为合适的格式
  • 10 Flask写入进行写入后返回更改的数据,将这部分数据送回(而不是整个表格)进行异步刷新

约定:每行数据都有一列可以起到主键的作用(唯一),在插入数据时只采用主键匹配的方式。

1 H5页面:访问页面

基于jinja搭建的基础模板(base.html)可以参考这篇文章,该模板提供了基础的样式和js组件导入,不再细说。
现在的页面view10.html,整个的h5页面的布局以行为基本单位,其他的暂时不用去管。

{% extends './main/index.html'%}

{%block pagecontent%}
<!-- 空白行作为间隔 -->
<div class="m-5"></div>

<!-- row1 -->
<div class="row">
    <div class="col">
        <button id="btn" class="btn btn-primary">test</button><br><br>
        <!-- 步骤一:表头 -->
        <table id="table_id_example" class="display">
            <thead>
                <tr>
                    <th width="20px"><input type="checkbox" class="icheckbox_minimal" id="all_checked"></th>
                    <th>姓名</th>
                    <th>年龄</th>
                    <th>称呼</th>
                    <th>状态</th>
                    <th>操作</th>
                </tr>
            </thead>
        </table>

    </div>

</div>

{%endblock%}

2 Flask读取:页面载入是请求数据,此时从Mongo里读取数据

先实现功能,但是之后也应该将Mongo的数据库操作封装为一个类。

# 返回所有测试记录
def get_test_db_recs(**mongo_cfg):
    with MongoClient(mongo_cfg['host'], mongo_cfg['port']) as conn:
        db = conn.admin
        db.authenticate(mongo_cfg['user'], mongo_cfg['pwd'])
        db = conn[mongo_cfg['source_db_name']]
        collection = db[mongo_cfg['source_collect_name']]
        some_one = collection.find({}, {'_id': 0, 'name': 1, 'age': 1, 'title': 1,'status':1})
        # 连接关闭前把游标里的数拿出来
        some_one = list(some_one)
    return some_one
---
[{'name': 'andy', 'age': 11, 'title': 'mr', 'status': 1}, {'name': 'billy', 'age': 12, 'title': 'mr', 'status': 1}, {'name': 'cindy', 'age': 12, 'title': 'ms', 'status': 1}]

这个函数获取了mongo库里的所有数据(不带筛选),并且挑选了name, age, title, status四列数据。由于mongo的_id是一个对象,因此就不拿了,这需要在数据中自己保留一个主键字段。

# 获取数据的视图函数,返回json
@app1.route('/view13/', methods=['GET'])
def view13():
    res = get_test_db_recs(**mongo_cfg)
    print(res)
    res_df = pd.DataFrame(res, columns=['name', 'age', 'title','status'])
    res_json = res_df.to_json(orient='records')
    res_dict = {}
    res_dict['data'] = json.loads(res_json)
    return jsonify(res_dict)

获取的mongo数据是一个列表,每个元素对应一行的数据。将其转换为DataFrame,再利用DataFrame自带的转json功能将数据转为json格式。

{
  "data": [
    {
      "age": 11, 
      "name": "andy", 
      "status": 1, 
      "title": "mr"
    }, 
    {
      "age": 12, 
      "name": "billy", 
      "status": 1, 
      "title": "mr"
    }, 
    {
      "age": 12, 
      "name": "cindy", 
      "status": 1, 
      "title": "ms"
    }
  ]
}

3 Flask处理:将读取的非结构化数据转化为表格,以及准备datatables格式化需要的表头

这一步可以是直接从数据库获取表格再进行数据格式的转换;也可以是从第二步数据获(接口方式)取得到的非结构化(json)数据中还原为表格再处理。
我们假设为第二种(更有通用性)
从view13中获取数据,再转为df。df是可以直接转为h5元素的。

@app1.route('/view14/', methods=['GET', 'POST'])
def view14():
    # 1 获取url数据
    url = 'http://localhost:5000' + url_for('app1.view13')
    resp = req.get(url)
    input_dict = json.loads(resp.text)
    input_df = pd.DataFrame(input_dict['data'])
    return input_df.to_html()

Python 全栈系列64 - 使用Flask+DataTables+Mongo搭建交互式表格
我们使用函数将df默认的表格加上我们制定的元素id和样式。将上面的视图函数略作修改

# 替换h5元素属性
def a_replace_h5attr(para_dict):
    res = ''
    for k in para_dict.keys():
        res += ''' %s="%s" ''' %(k, para_dict[k])
    return res 

# 获取接口数据
@app1.route('/view14/', methods=['GET', 'POST'])
def view14():
    # 1 获取url数据
    url = 'http://localhost:5000' + url_for('app1.view13')
    resp = req.get(url)
    input_dict = json.loads(resp.text)
    input_df = pd.DataFrame(input_dict['data'])
    print(input_df)
    # return jsonify(input_dict)
    para_dict = {'id':'table1', 'class':'display'}
    html1 = input_df.to_html().replace('''class="dataframe"''', a_replace_h5attr(para_dict))
    return html1

在浏览器中可以看到table元素的id和样式已经改过来了。


<table border="1"  id="table1"  class="display" >
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>age</th>
      <th>name</th>
      <th>status</th>
      <th>title</th>
...

4 Ajax,DataTables:获取到数据后将数据灌入,使用DataTables格式化

ajax成功后页面有了一个table元素,使用其元素id进行datatables转换就可以了。同时将第一列空元素用checkbox填充,以便后续的选择。

$(function(){

// 1 载入后的请求数据
data_url = '/read_mongo/view14/'
para_data = {}
para_data['a'] = 'test'
para_data_str = JSON.stringify(para_data)
$.ajax({
 type:'POST',
 url: data_url,
 data: para_data_str,
 timeout:3000, //超时毫秒
 processData:false,
contentType:'application/json',
 // 这种要用get_data加json.loads去获取contentType:'application/x-www-form-urlencoded',
 //datatype:'json',
 success:function(res_data){
    table_h5 = res_data.data
    $('#row1col1').empty();
    $('#row1col1').html(table_h5)
    $('#table1').DataTable();

    // 2 格式化
    $('#table1').find('tbody').find('tr').each(function(){
    // 2.1给第一列增加checkbox项
    $(this).children('td:eq(0)').html('<input type="checkbox">')
    // 2.2将状态数字用h5元素代替
    td4_val = $(this).children('td:eq(4)').text()
    $(this).children('td:eq(4)').html(vmap(td4_val))
    })

 },
 error:function(){}
 })

5 JQuery格式化:使用Jquery对某些列的格式进行处理,展示起来更方便

需要对哪些列进行格式化,就在外面自定义好函数

// 根据不同的值,使用不同的h5替代
function vmap(val){
    // val1 = Number(val)
    // alert(val1)
    val1 = val
    if(val1==1){
        res = '<span class="text-success"><i class="fa fa-check-circle" aria-hidden="true"></i></span>'
    }
    else{
        res = '<span class="text-danger"><i  class="fa fa-info-circle" aria-hidden="true"></i></span>'
    }
    return res
}

Python 全栈系列64 - 使用Flask+DataTables+Mongo搭建交互式表格

6 Datatables:用户使用表格快速搜索,浏览

主要利用datatables自带的内容模糊搜索,排序以及分页。

7 模态框:用户通过模态框进行表格内容的修改

有部分js函数看起来比较碎,我就不放上面了。表格的操作分为增、删、改三块,再加上批量提交给后台,因此一共有四个操作。以下代码增加了4个控制按钮:

...
<!-- 空白行作为间隔 -->
<div class="m-5"></div>
<!-- row1 -->
<div class="row" >
    <div class="col" id='row1col1'>

        <!-- ======================> 按钮组 -->
        <div class="btn-group operation">
            <button id="btn_add" type="button" class="btn btn-primary  mx-2" data-toggle="modal">
                <!-- bootstrap4已经不默认提供图标了,而是分开了,需要在别的地方下载,并使用方法也有所变化 -->
                <i class="fa fa-plus" aria-hidden="true">新增</i>
            </button>

            <button id="btn_edit" type="button" class="btn btn-info mx-2">
                <i class="fa fa-pencil" aria-hidden="true">修改</i>
            </button>

            <button id="btn_delete" type="button" class="btn btn-danger  mx-2">
                <i class="fa fa-remove" aria-hidden="true">删除</i>
            </button>


            <button id="btn_submmit" type="button" class="btn btn-success mx-2">
                <i class="fa fa-paper-plane" aria-hidden="true">批量提交</i>
            </button>
        </div>

        <!-- ======================> 新增模态框 -->
        {{add_modal|safe}}
    </div>
</div>
...

新增行的模态框没有直接放在里面,一方面是因为代码比较多,另一方面具体的表单区域可以增加不同的行。
新增部分的模态框如下,使用其中的一段jinja语句来动态增加表单区域:

<!-- 点击添加按钮的时候弹出的就是模态框,其本身看起来像一个网页  modal fade -> modal-dialog -> modal-content -> modal-header | ->addBookModal - footer -->
<div class="modal fade" id="addRow" role="dialog">

    <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title">
                <!-- 直接用css来控制关闭模态框 -->
                <button type="button" class="close" data-dismiss="modal"><i class="fa fa-window-close" aria-hidden="true"> 添加数据</i></button>
                </h5>
            </div>

            <div id="addRowModal" class="modal-body">
                <div class="form-horizontal">

                    {%for k,v in var_dict.items()%}
                    <div class="form-group">
                        <label for="{{k|e}}" class="col-sm-2 control-label">{{v|e}}:*</label>
                        <div class="col-sm-10">
                            <input class="form-control" id="{{k|e}}" type="text">
                        </div>
                    </div>

                    {% endfor%}
                </div>
            </div>

            <div class="modal-footer">
                <div class="center-block">
                    <button id="cancelAdd" type="button" class="btn btn-warning" data-dismiss="modal">取消</button>
                    <button id="addRowsInfo" type="button" class="btn btn-success" data-dismiss="modal">保存</button>
                </div>
            </div>
        </div>
    </div>
</div>

Python 全栈系列64 - 使用Flask+DataTables+Mongo搭建交互式表格

8 Ajax异步提交:使用Ajax提交更改(选定行)

点击时使用ajax将数据提交,同时可以声明在接受到数据后如何处理(数据库接受更改后会返回数据,由js再渲染到表格里)

function submit_checked(table_id){
    var submit_list = []
    $(table_id).find('input[type="checkbox"]:checked').each(function(){
        var curTr = $(this).closest('tr')
        tem_list = []
        for(i=1;i<curTr.children('td').length;i++){
            tem_list.push($(curTr).children('td').eq(i).text())
        }
        submit_list.push(tem_list)
        
        // alert($(curTr).children('td:eq(3)').text())
    })

    // 准备提交
    summit_url = '/read_mongo/view16/'
    para_dict = {}
    para_dict['data'] = submit_list
    para_dict_str = JSON.stringify(para_dict)
    $.ajax({
        type:'POST',
        url: summit_url,
        data: para_dict_str,
        timeout:3000, //超时毫秒
        processData:false,
        contentType:'application/json',
        success:function(){},
        error:function(){}
    })
    
}

9 Flask处理:Flask获取更改的数据,并处理为合适的格式

一种是新增,一种是更新,两种方法。对于存mongo来说,这样应该就ok了。(不设置删的操作,而是改为使用is_enable字段,所以删也是一种更新)

@app1.route('/view16/', methods=['GET', 'POST'])
def view16():
    para_dict  = request.get_json()
    print(para_dict)

    # ===============>新增方法 
    # 将其塑造为df
    data_df = pd.DataFrame(para_dict['data'], columns  = ['tem_row_id','age', 'name', 'status','title'])
    print(data_df)
    # 选取必要的部分转为json
    # data_json = data_df[['tem_row_id','name','age','title']].to_json(orient='records')
    # data_json1 = json.loads(data_json)
    # print(data_json1)
    # save_many_recs(data_json1)
    # ===============>键值匹配的更新方法
    match_key_list = data_df[['name']].to_json(orient='records')
    match_val_list = data_df[['tem_row_id', 'age', 'status','title']].to_json(orient='records')
    match_key_list1 = json.loads(match_key_list)
    match_val_list1 = json.loads(match_val_list)
    print(match_key_list1)
    print(match_val_list1)

    update_many_recs_with_key(match_key_list1, match_val_list1)
    return jsonify(para_dict)

以下是存库的两个函数

# 存数据库
def save_many_recs(data_dict_list):
    with MongoClient(mongo_cfg['host'], mongo_cfg['port']) as conn:
        db = conn.admin
        db.authenticate(mongo_cfg['user'], mongo_cfg['pwd'])
        db = conn[mongo_cfg['source_db_name']]
        collection = db[mongo_cfg['source_collect_name']]
        collection.insert_many(data_dict_list)

# 根据键值更新多个
def update_many_recs_with_key(key_list, val_list):
    with MongoClient(mongo_cfg['host'], mongo_cfg['port']) as conn:
        db = conn.admin
        db.authenticate(mongo_cfg['user'], mongo_cfg['pwd'])
        db = conn[mongo_cfg['source_db_name']]
        collection = db[mongo_cfg['source_collect_name']]
        for i in range(len(key_list)):
            collection.find_one_and_update(key_list[i],{'$set':val_list[i]})

10 Flask写入进行写入后返回更改的数据,将这部分数据送回(而不是整个表格)进行异步刷新

这部分略,因为要结合实际的应用去写,没有固定的格式。

Next

  • 1 构建服务,以表编辑向mongo传递数据,这部分数据需要符合构建图的规范。
上一篇:Scrapy(4)spider 帮助你寻找最美小姐姐


下一篇:mongo中的索引