【tornado】系列項目(二)基於領域驅動模型的區域後臺管理+前端easyui實現 Python操做 RabbitMQ、Redis、Memcache、SQLAlchemy 【tornado】系列項目

【tornado】系列項目(二)基於領域驅動模型的區域後臺管理+前端easyui實現

 

本項目是一個系列項目,最終的目的是開發出一個相似京東商城的網站。本文主要介紹後臺管理中的區域管理,以及前端基於easyui插件的使用。本次增刪改查因數據量少,所以採用模態對話框方式進行,關於數據量大采用跳轉方式修改,詳見博主後續博文。javascript

後臺界面展現:css

地區管理包含省市縣的管理。詳見下文。html

1、數據庫設計前端

?
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
class Province(Base):
     """
    
     """
     __tablename__ = 'province'
     nid = Column(Integer, primary_key = True )
     caption = Column(VARCHAR( 16 ), index = True )
 
 
class City(Base):
     """
    
     """
     __tablename__ = 'city'
     nid = Column(Integer, primary_key = True )
     caption = Column(VARCHAR( 16 ), index = True )
     province_id = Column(Integer, ForeignKey( 'province.nid' ))
 
 
class County(Base):
     """
     縣(區)
     """
     __tablename__ = 'county'
     nid = Column(Integer, primary_key = True )
     caption = Column(VARCHAR( 16 ), index = True )
     city_id = Column(Integer, ForeignKey( 'city.nid' ))

  本次採用的是sqlAlchemy模塊建立數據庫,關於sqlAlchemy的數據庫連接以及數據庫建立本文不作介紹,詳細見Python操做 RabbitMQ、Redis、Memcache、SQLAlchemy(點擊進入詳細介紹)java

表關係分析:上述表關係比較簡單,市中有外鍵,表明這是市是屬於哪一個省;同理縣中也有外鍵,表明這個縣是屬於哪一個市。python

2、目錄結構

該目錄結構在前面博文【tornado】系列項目(一)之基於領域驅動模型架構設計的京東用戶管理後臺 中有詳細介紹,本博文再也不贅述。jquery

3、路由映射

?
1
2
3
4
5
6
(r "/ProvinceManager.html$" , Region.ProvinceManagerHandler),  #省份模板展現
   (r "/province.html$" , Region.ProvinceHandler), #省份的增刪改查
   (r "/CityManager.html$" , Region.CityManagerHandler), #市模板展現
   (r "/City.html$" , Region.CityHandler),   #市的增刪改查
   (r "/CountyManager.html$" , Region.CountyManagerHandler), #縣的模板展現
   (r "/County.html$" , Region.CountyHandler), #縣的增刪改查

4、後臺模板展現Handler

#以省份爲例進行介紹(市縣相似):ajax

數據獲取Handler:sql

?
1
2
3
4
5
class ProvinceManagerHandler(AdminRequestHandler):
 
     def get( self , * args, * * kwargs):
         # 打開頁面,顯示全部的省
         self .render( 'Region/ProvinceManager.html' )

 本Handler主要用於從模板展現,默認若是隻有這一個handler,用戶看到的將是空頁面。關於數據的增刪改查,詳見下文。數據庫

5、核心增刪改查操做

再介紹增刪改查以前,咱們先介紹母板文件layout的前端html和繼承該模板的ProvinceManager.html部分JavaScript代碼:

母版layout html:

?
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
<! DOCTYPE html>
< html >
< head lang="en">
     < meta charset="UTF-8">
     < title ></ title >
     < link rel="stylesheet" type="text/css" href="/Statics/Admin/Plugins/jquery-easyui/themes/default/easyui.css">  #導入easyui的css
     < link rel="stylesheet" type="text/css" href="/Statics/Admin/Plugins/jquery-easyui/themes/icon.css">  #導入easyui的圖標
     < link rel="stylesheet" type="text/css" href="/Statics/Admin/Css/Common.css"> #自定義css
 
     < script type="text/javascript" src="/Statics/Admin/Plugins/jquery-easyui/jquery.min.js"></ script >   #導入jQuery
     < script type="text/javascript" src="/Statics/Admin/Plugins/jquery-easyui/jquery.easyui.min.js"></ script >  #導入easyui的js
 
</ head >
< body class="easyui-layout">
 
     < div data-options="region:'north'" style="height:50px">
 
     </ div >
     < div data-options="region:'south',split:true" style="height:30px;"></ div >
     < div data-options="region:'west',split:true" title="後臺管理" style="width:200px;">
         < div id="aa" class="easyui-accordion" data-options="fit:true,border:false">
             < div title="地區管理">  #easyui訂製的左側菜單
                 < a id="jd_menu_province" class="jd-menu" href="/ProvinceManager.html">省</ a >
                 < a id="jd_menu_city" class="jd-menu" href="/CityManager.html">市</ a >
                 < a id="jd_menu_county" class="jd-menu" href="/CountyManager.html">縣(區)</ a >
             </ div >
             < div title="用戶管理">
                 < a id="user" class="jd-menu" href="#">用戶管理</ a >
                 < a id="jd_menu_merchant" class="jd-menu" href="/MerchantManager.html">商戶管理</ a >
             </ div >
             < div title="JD自營">
                 < a id="jd_menu_product" class="jd-menu" href="/ProductManager.html">產品管理</ a >
             </ div >
         </ div >
     </ div >
     < div data-options="region:'center'" title="{% block crumbs %} {% end %}">  #內容顯示區
         {% block content %} {% end %}
     </ div >
 
</ body >
</ html >

  省分內容展現區html:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
< div >
     < table id="dg"></ table >   #easyui訂製table
 
     < div id="dlg" class="easyui-dialog" style="width:400px;height:200px;padding:10px 20px" closed="true" buttons="#dlg-buttons"> #easyui訂製模態對話框,默認關閉狀態
         < form id="fm1">
             < div class="input-group clearfix">
                 < div class="group-label" style="width: 80px;">
                     < span >省份:</ span >
                 </ div >
                 < div class="group-input" style="width: 300px;">
                     < input id="dlg_nid" style="width: 200px;display: none"  name="nid"/>
                     < input id="dlg_province" style="width: 200px" class="easyui-textbox" type="text" name="caption" data-options="required:true,missingMessage:'省份不能爲空'" />  #easyui訂製form驗證+錯誤信息提示
                 </ div >
             </ div >
         </ form >
     </ div >
     < div id="dlg-buttons">  #easyui訂製按鈕
         < span id="dlg_summary" style="color: red"></ span >
         < a href="#" class="easyui-linkbutton" iconCls="icon-ok" onclick="Save()">保存</ a >
         < a href="#" class="easyui-linkbutton" iconCls="icon-cancel" onclick="javascript:$('#dlg').dialog('close')">取消</ a >
     </ div >
</ div >

  JavaScript代碼:

?
1
2
3
4
5
6
7
$( function () {
           // 加載表格數據
           InitTable();  #初始化表格內容(即查詢)
           InitPagination();  #初始化分頁
           InitMenu(); #初始化左側菜單
 
       });

  首先介紹兩個簡單的js:

?
1
2
3
4
5
6
7
/*
         初始化左側菜單
         */
         function InitMenu(){
             $( '#aa' ).accordion( 'select' ,0);  #easyui語法:選擇左側第0個標籤
             $( '#jd_menu_province' ).addClass( 'active' ); #讓省份默認選中
         }
?
1
2
3
4
5
6
7
8
9
10
11
12
/*
       初始化分頁
        */
       function InitPagination(){
           var pager = $( '#dg' ).datagrid( 'getPager' );
           $(pager).pagination({
               beforePageText: '第' ,
               afterPageText: '頁 共{pages}頁' ,
               displayMsg: '當前顯示{from}-{to}條記錄 共{total}條數據'
 
           })
       }

關鍵的表格數據初始化js(查詢js):

?
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
function InitTable(){
     $( '#dg' ).datagrid({
         title: '省份列表' ,
         iconCls: 'icon-save' ,   #省份圖標
         url: '/province.html' ,  #獲取數據的url
         method: 'get' ,   #獲取方式
         //fitColumns: true,
         idField: 'nid' , 
         singleSelect: true , #默認單選
         rownumbers: true , #顯示行號
         striped: true , #奇數行與偶數行顏色有區別
         columns:[[     #每一列標題(easyui默認根據field將後端傳來的數據按表格進行顯示)
             {
                 field: 'ck' ,
                 checkbox: true  #顯示checkbox
             },
             {
                 field: 'nid' ,   #從數據庫獲取的nid
                 title: 'ID' , #顯示名稱爲ID
                 width:80, #寬度80px
                 align: 'center'   #居中顯示
             },
             {
                 field: 'caption' ,
                 title: '標題' ,
                 width:180,
                 align: 'center' }
         ]],
         toolbar: [   #顯示的按鈕
             {
                 text: '添加' ,  #按鈕名稱
                 iconCls: 'icon-add' ,  #按鈕圖標
                 handler: AddRow  #點擊按鈕後執行的返回函數
             },{
                 text: '刪除' ,
                 iconCls: 'icon-remove' ,
                 handler: RemoveRow
             },{
                 text: '修改' ,
                 iconCls: 'icon-edit' ,
                 handler: EditRow
             }
         ],
         pagePosition: 'both' ,   #上下均顯示分頁
         pagination: true , #顯示分頁
         pageSize:10, #默認每頁顯示的數據總數
         pageNumber: 1,   #默認第一頁
         pageList: [10,20,50], #分頁可選每頁顯示數量
         loadFilter: function (data){ #過濾函數
             return data;
 
             }
         });
}

  上述js代碼即查詢時的js代碼,接下來咱們先看查詢的後端業務處理類:

?
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
def get( self , * args, * * kwargs):
      """
      獲取
      :param args:
      :param kwargs:
      :return:
      """
      if self .get_argument( 'type' , None ) = = 'all' : #若是是獲取全部數據
          ret = { 'status' : True , 'rows' : "", 'summary' :''}   #未來要返回給前端的字典,包含是否獲取成功的狀態、獲取的數據、錯誤信息
          try :
              region_service = RegionService(RegionRepository())   #將數據庫處理類的對象傳入數據庫業務協調類
              all_province_list = region_service.get_province() #獲取全部省份
              ret[ 'rows' ] = all_province_list #將省份數據添加進返回前端的字典
          except Exception as e:
              ret[ 'status' ] = False
              ret[ 'summary' ] = str (e)
          self .write(json.dumps(ret)) #返回給前端
      else :   #若是是獲取分頁數據
          ret = { 'status' : True , 'total' : 0 , 'rows' : [], 'summary' : ''}
          try :
              rows = int ( self .get_argument( 'rows' , 10 )) #每頁顯示10條
              page = int ( self .get_argument( 'page' , 1 )) #顯示第一頁
              start = (page - 1 ) * rows 開始條數
 
              region_service = RegionService(RegionRepository())
              row_list = region_service.get_province_by_page(start, rows) #根據分頁獲取省份數據
              row_count = region_service.get_province_count()   #獲取省份總數
 
              ret[ 'total' ] = row_count
              ret[ 'rows' ] = row_list
          except Exception as e:
              ret[ 'status' ] = False
              ret[ 'summary' ] = str (e)
 
          self .write(json.dumps(ret))  #返回給前端

數據庫業務協調處理類的對應操做:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class RegionService:
     def __init__( self , region_repository):
         self .regionRepository = region_repository
 
     def get_province_count( self ):
         count = self .regionRepository.fetch_province_count() #獲取省份總數
         return count
 
     def get_province_by_page( self , start, offset): #根據分頁獲取省份
 
         result = self .regionRepository.fetch_province_by_page(start, offset)
         return result
 
     def get_province( self ): #獲取全部省份
         return self .regionRepository.fetch_province()

 數據庫操做類相關操做:

?
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
class RegionRepository(IRegionRepository):
 
     def __init__( self ):
         self .db_conn = DbConnection() #實例化數據庫連接對象(只需建立一次對象,下面全部方法都不須要再建立)
 
     def fetch_province( self ): #獲取全部省份
         cursor = self .db_conn.connect()
         sql = """select nid,caption from province order by nid desc """
         cursor.execute(sql)
         db_result = cursor.fetchall()
         self .db_conn.close()
         return db_result
 
 
     def fetch_province_by_page( self , start, offset): #根據分頁獲取省份
         ret = None
         cursor = self .db_conn.connect()
         sql = """select nid,caption from province order by nid desc limit %s offset %s """
         cursor.execute(sql, (offset, start))
         db_result = cursor.fetchall()
         self .db_conn.close()
         return db_result
     def fetch_province_count( self ): #獲取省份總數
         cursor = self .db_conn.connect()
         sql = """select count(1) as count from province """
         cursor.execute(sql)
         db_result = cursor.fetchone()
         self .db_conn.close()
         return db_result[ 'count' ]

  以上就是查詢操做的全部過程。

增長:

js:

?
1
2
3
4
5
6
7
8
9
10
/*
         添加
         */
         function AddRow(){
             // 顯示對話框,因爲但願添加則將方法設置爲POST
             $( '#fm1' ).form( 'clear' ); #清空上次form的內容
             $( '#dlg' ).dialog( 'open' ).dialog( 'setTitle' , '建立省份' ); #設置模態對話框標籤是建立省份
             $( '#dlg_summary' ).empty(); #清空錯誤信息
             METHOD = 'post' ;   #設置提交方式爲post
         }

  增長操做實際上就作了一個操做:打開模態對話框。

前端頁面展現:

 

當用戶輸入須要添加的省份,接下來點擊保存按鈕,數據將被寫入數據庫並在前端顯示:

保存的js代碼:

?
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
/*
     保存按鈕
      */
     function Save(){
         var isValid = $( '#fm1' ).form( 'validate' );前端form驗證
         if (isValid){
             $.ajax({
                 url: '/province.html' , #提交的url
                 type: METHOD,   #根據以前定義的方法進行提交
                 data: {caption: $( '#dlg_province' ).val(),nid:  $( '#dlg_nid' ).val()}, #提交的數據
                 dataType: 'json' , #數據格式
                 success: function (data){ #若是後端成功返回數據
                     if (data.status){ #後端操做成功
                         $( '#fm1' ).form( 'clear' ); #清空form內容
                         $( '#dlg' ).dialog( 'close' ); #關閉模態對話框
                         $( '#dg' ).datagrid( 'reload' ); #從新加載數據
                     } else {
                         $( '#dlg_summary' ).text(data.summary); #不然顯示錯誤信息
                     }
                 }
             })
         } else {
             // 前端驗證經過
         }
         // $('#fm').form('clear');
     }

  增長對應的後端業務處理方法:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def post( self , * args, * * kwargs):
        """
        添加
        :param args:
        :param kwargs:
        :return:
        """
        ret = { 'status' : False , 'summary' : ''}
        caption = self .get_argument( 'caption' , None )
        if not caption:
            ret[ 'summary' ] = '省份不能爲空'
        else :
            try :
 
                region_service = RegionService(RegionRepository())
                result = region_service.create_province(caption) #建立省份,若是省份已存在,返回None
                if not result:
                    ret[ 'summary' ] = '省份已經存在'
                else :
                    ret[ 'status' ] = True #操做成功
            except Exception as e:
                ret[ 'summary' ] = str (e)
 
        self .write(json.dumps(ret)) #返回給前端

 數據庫協調處理類對應的方法:

?
1
2
3
4
5
def create_province( self , caption):
     exist = self .regionRepository.exist_province(caption) #先判斷省份是否存在,若是存在,該方法返回值爲None
     if not exist:
          self .regionRepository.add_province(caption) #建立省份
          return True

 數據庫對應操做:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def exist_province( self , caption): #省份是否存在
      cursor = self .db_conn.connect()
      sql = """select count(1) as count from province where caption=%s """
      cursor.execute(sql, (caption,))
      db_result = cursor.fetchone()
      self .db_conn.close()
 
      return db_result[ 'count' ]
 
  def add_province( self , caption):   #建立省份
      cursor = self .db_conn.connect()
      sql = """insert into province (caption) values(%s)"""
      effect_rows = cursor.execute(sql, (caption,))
      self .db_conn.close()
      return effect_rows

  以上就是省份添加的所有過程。

修改:

實際上修改和添加基本上是同樣的,接下來只介紹與添加不一樣的地方:

js:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
       修改
       */
       function EditRow(){
           // 顯示對話框,因爲但願修改則將方法設置爲PUT
 
           // 獲取選中的值,將其賦值到頁面上,而後ajax提交
           var row = $( '#dg' ).datagrid( 'getSelected' );
           $( '#dlg_summary' ).empty();
           if (row){
               METHOD = 'put' ;
               $( '#fm1' ).form( 'clear' );
               $( '#fm1' ).form( 'load' ,row);
               $( '#dlg' ).dialog( 'open' ).dialog( 'setTitle' , '修改省份' );
 
           } else {
               $.messager.alert( '警告' , '請選擇要修改的行' , 'warning' );
           }
 
       }

  這裏彈出模態對話框,與添加不一樣的是,修改須要將用戶原有數據存放在input標籤中,方便用戶進行修改。同時,將提交方法修改成put。

修改模態對話框示例截圖:

接下來用戶修改完成後的點擊保存,關於保存的js代碼詳見上文添加部分。

保存的後臺業務處理handler方法:

?
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
def put( self , * args, * * kwargs):
        """
        更新
        :param args:
        :param kwargs:
        :return:
        """
        ret = { 'status' : False , 'summary' : ''}
        nid = self .get_argument( 'nid' , None )
        caption = self .get_argument( 'caption' , None )
        if not caption or not nid:
            ret[ 'summary' ] = '省份不能爲空'
        else :
            try :
 
                region_service = RegionService(RegionRepository())
                result = region_service.modify_province(nid, caption)
 
                if not result:
                    ret[ 'summary' ] = '省份已經存在'
                else :
                    ret[ 'status' ] = True
            except Exception as e:
                ret[ 'summary' ] = str (e)
        self .write(json.dumps(ret))

  該方法與添加時的方法基本一致,這裏不作過多介紹。

數據庫協調處理類對應的方法:

?
1
2
3
4
5
def modify_province( self , nid, caption):
        exist = self .regionRepository.exist_province(caption)
        if not exist:
             self .regionRepository.update_province(nid, caption) #更新省份
             return True

數據庫操做對應類的方法:

?
1
2
3
4
5
6
def update_province( self , nid, caption): #更新省份
       cursor = self .db_conn.connect()
       sql = """update province set caption=%s where nid=%s """
       effect_rows = cursor.execute(sql, (caption, nid,))
       self .db_conn.close()
       return effect_rows

  以上就是省份數據修改的所有過程。

刪除:

js:

?
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
/*
        刪除
        */
        function RemoveRow(){
            // 獲取已經選中的行
            var rows = $( '#dg' ).datagrid( 'getSelections' );
            console.log(rows);
            if (rows.length<=0){
                // 警告框
                $.messager.alert( '警告' , '請選擇要刪除的行' , 'warning' );
            } else if (rows.length>1){
                $.messager.alert( '警告' , '不支持批量刪除' );
            } else {
                // 確認框
                $.messager.confirm( '肯定' , '您肯定要刪除嗎?' , function (status) { #easyui訂製的確認框
                    if (status){
                        // 點擊肯定
                        // 獲取當前選中行的值,Ajax發送到後臺
                        var row = rows[0];
                        $.ajax({
                            url: 'province.html' ,
                            type: 'delete' ,
                            data: {nid: row.nid},
                            dataType: 'json' ,
                            success: function (data) {
                                if (data.status){
                                    //刪除成功
                                    $.messager.show({  #easyui訂製的messager框
                                        msg: '刪除成功' ,
                                        showType: 'slide' , #淡出
                                        showSpeed: 500, #速度
                                        timeout: 5,  #顯示5秒
                                        style:{
                                            right: '' ,
                                            top:document.body.scrollTop+document.documentElement.scrollTop, #在屏幕上方顯示
                                            bottom: ''
                                        }
                                    });
                                    // 從新加載表格
                                    var rowIndex = $( '#dg' ).datagrid( 'getRowIndex' , row);
                                    $( '#dg' ).datagrid( 'deleteRow' ,rowIndex);
                                    $( '#dg' ).datagrid( 'reload' );
 
                                    // 刪除指定行
                                    //var rowIndex = dt.datagrid('getRowIndex', row);
                                    //dt.datagrid('deleteRow',rowIndex);
 
                                } else {
                                    //刪除失敗
                                    // $.messager.alert('錯誤信息', data.summary ,'error');
                                    $.messager.show({  #顯示錯誤信息
                                        icon: 'error' ,
                                        title: '錯誤信息' ,
                                        msg:data.summary,
                                        showType: 'slide' ,
                                        timeout: 0,
                                        style:{
                                            right: '' ,
                                            top:document.body.scrollTop+document.documentElement.scrollTop,
                                            bottom: ''
                                        }
                                    });
                                }
                            }
                        });
                    }
                })
            }
        }

  後臺handler類對應方法:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def delete( self , * args, * * kwargs):
        """
        刪除
        :param args:
        :param kwargs:
        :return:
        """
        ret = { 'status' : False , 'summary' : ''}
 
        nid = self .get_argument( 'nid' , None )
 
        if not nid:
            ret[ 'summary' ] = '請選擇要刪除的省份'
        else :
            # 調用service去刪除吧...
            # 若是刪除失敗,則顯示錯誤信息
            try :
                region_service = RegionService(RegionRepository())
                region_service.delete_province(nid) #根據nid刪除省份
                ret[ 'status' ] = True
            except Exception as e:
                ret[ 'summary' ] = str (e)
        self .write(json.dumps(ret))

  數據庫業務協調處理類對應的方法:

?
1
2
3
def delete_province( self , nid):
 
        self .regionRepository.remove_province(nid)

  數據庫操做類對應方法:

?
1
2
3
4
5
6
def remove_province( self , nid):
        cursor = self .db_conn.connect()
        sql = """delete from province where nid=%s """
        effect_rows = cursor.execute(sql, (nid,))
        self .db_conn.close()
        return effect_rows

  以上就是刪除的所有過程。

總結:本文主要以省份的增刪改查爲例介紹了前端easyui的使用,後端handler、數據庫業務協調處理類、數據庫操做類的整個流程。

相關文章
相關標籤/搜索