我的第一個python web開發框架(28)——定制ORM(四)


  在數據庫操作時,新增記錄也是必不可少的,接下來我們應用字典的特性來組合sql語句

  先上產品新增接口代碼

 1 @post('/api/product/')
 2 def callback():
 3     """
 4     新增記錄
 5     """
 6     name = web_helper.get_form('name', '產品名稱')
 7     code = web_helper.get_form('code', '產品編碼')
 8     product_class_id = convert_helper.to_int0(web_helper.get_form('product_class_id', '產品分類'))
 9     standard = web_helper.get_form('standard', '產品規格')
10     quality_guarantee_period = web_helper.get_form('quality_guarantee_period', '保質期')
11     place_of_origin = web_helper.get_form('place_of_origin', '產地')
12     front_cover_img = web_helper.get_form('front_cover_img', '封面圖片')
13     content = web_helper.get_form('content', '產品描述', is_check_special_char=False)
14     # 防sql注入攻擊處理
15     content = string_helper.filter_str(content, "'")
16     # 防xss攻擊處理
17     content = string_helper.clear_xss(content)
18     is_enable = convert_helper.to_int0(web_helper.get_form('is_enable', '是否啟用'))
19 
20     # 添加記錄(使用returning這個函數能返回指定的字段值,這里要求返回新添加記錄的自增id值)
21     sql = """insert into product (name, code, product_class_id, standard, quality_guarantee_period,
22                 place_of_origin, front_cover_img, content, is_enable)
23               values (%s, %s, %s, %s, %s, %s, %s, %s, %s) returning id"""
24     vars = (name, code, product_class_id, standard, quality_guarantee_period, place_of_origin, front_cover_img, content, is_enable)
25     # 寫入數據庫
26     result = db_helper.write(sql, vars)
27     # 判斷是否提交成功
28     if result and result[0].get('id'):
29         return web_helper.return_msg(0, '成功')
30     else:
31         return web_helper.return_msg(-1, "提交失敗")

  在21行到24行就是sql語句的拼接,使用這種方法,我們經常會因為多寫或少寫%s和變量,導致sql執行出錯。

  在python中,我們最常用的就是字典,從新增記錄的語句中,可以看到字段與值是一一對應的,那么我們就可以考慮使用字典的方式來進行存儲字段與值,在ORM中對其進行處理,然后拼接生成對應的sql語句。

  操作步驟如下幾步:

  1.將新增記錄時的字段名與值,使用字典方式存儲起來

  2.將字典做為參數傳給ORM新增記錄方法

  3.在進行下一步之前,大家首先要了解python字符串替的有多種方法,例如:方法一:’Hello %s' % ('world',)  ;方法二:'Hello %(name)s' % {'name': 'world'} ;等,還有很多其他方法這里不一一列舉。第一種只能按照指定的順序進行替換,數量必須一致,不然會出錯;而第二種字典類型替換時,字典位置不需要考慮,字典值超出替換內容,會自動忽略,它最大的好處,我覺得是可讀性,讓這個字符串變的更加容易理解。所以下面我們將會使用字典替換方法。

  4.新增記錄方法接收到參數以后,使用for循環,將字段名提取出來,生成sql插入字段名數組和字典替換數組,即:insert into table_name (字段名,字段名...) values (值,值...),這里需要將字典中的字段名提取出來組合成“字段名,字段名...”這樣的串,值也是一樣,為了讓組合后的字符串更容易調試與理解,我們將它生成字典替換格式%(name1)s,%(name2)s...,最好再使用字典直接替換(當然你直接將值連接起來也行,你可以根據自己的需要進行調整)。


  例如,我們需要添加產品名稱、產品編碼、產品分類id,可以它們用字典存儲起來做為參數

fields = {
    'name': "'名稱'",
    'code': "'201808031245678'",
    'product_class_id': 1,
}

  然后可以通過for循環,將字典參數進行處理,提取出來存儲到list中

key_list = []
value_list = []
for key in fields.keys():
    key_list.append(key)
    value_list.append('%(' + key + ')s')

  執行后,key_list與value_list的值分別為:

key_list = ['name', 'product_class_id', 'code']
value_list = ['%(name)s', '%(product_class_id)s', '%(code)s']

  然后我們設置一個sql字符串拼接字典,將表名、字段名字符串與值字符串存儲進去,在存儲前使用join方式進行拼接,生成用逗號分隔的字符串

parameter = {
    'table_name': self.__table_name,
    'key_list': ','.join(key_list),
    'value_list': ','.join(value_list)
}

  執行后生成的值為:

parameter = {'key_list': 'name,product_class_id,code', 'value_list': '%(name)s,%(product_class_id)s,%(code)s', 'table_name': 'product'}

  然后將它們與新增sql合成

sql = "insert into %(table_name)s (%(key_list)s) values (%(value_list)s) returning id " % parameter

  執行后sql值為:

sql = 'insert into product (name,product_class_id,code) values (%(name)s,%(product_class_id)s,%(code)s) '

  最后將它與最開始提交的字典參數進行合成

sql = sql % fields

  生成最終可執行的sql語句

sql = "insert into product (name,product_class_id,code) values ('名稱',1,'201808031245678') "

 

  完整代碼

 1     def add_model(self, fields, returning=''):
 2         """新增數據庫記錄"""
 3         ### 拼接sql語句 ###
 4         # 初始化變量
 5         key_list = []
 6         value_list = []
 7         # 將傳入的字典參數進行處理,把字段名生成sql插入字段名數組和字典替換數組
 8         # PS:字符串使用字典替換參數時,格式是%(name)s,這里會生成對應的字串
 9         # 比如:
10         #   傳入的字典為: {'id': 1, 'name': '名稱'}
11         #   那么生成的key_list為:'id','name'
12         #   而value_list為:'%(id)s,%(name)s'
13         #   最終而value_list為字符串對應名稱位置會被替換成相應的值
14         for key in fields.keys():
15             key_list.append(key)
16             value_list.append('%(' + key + ')s')
17         # 設置sql拼接字典,並將數組(lit)使用join方式進行拼接,生成用逗號分隔的字符串
18         parameter = {
19             'table_name': self.__table_name,
20             'pk_name': self.__pk_name,
21             'key_list': ','.join(key_list),
22             'value_list': ','.join(value_list)
23         }
24         # 如果有指定返回參數,則添加
25         if returning:
26             parameter['returning'] = ', ' + returning
27         else:
28             parameter['returning'] = ''
29 
30         # 生成可以使用字典替換的字符串
31         sql = "insert into %(table_name)s (%(key_list)s) values (%(value_list)s) returning %(pk_name)s %(returning)s" % parameter
32         # 將生成好的字符串替字典參數值,生成最終可執行的sql語句
33         sql = sql % fields
34 
35         with db_helper.PgHelper(self.__db, self.__is_output_sql) as db:
36             # 執行sql語句
37             result = db.execute(sql)
38             if result:
39                 db.commit()
40                 return result[0]
41             return {}

  代碼大家多debug一下,就可以查看到每一行代碼的執行情況以及sql拼接生成情況了。

  這里的sql語句額外添加了postgresql的returning函數,在默認情況下會返回數據插入成功后的主鍵值給主程序。我們在進行新增操作時,經常要獲取新增記錄的自增id,通過return id就可以將它返回給我們,如果你還需要返回其他字段值,可以在這個新增方式的returning參數中添加想要返回的字段名,如果想返回整條記錄,輸入*號就可以了。

 

  寫到這里,可以看到查詢與執行,都使用類似的with方法,我們可以將它們提取出來重構成獨立的函數,為后續的修改和其他查詢去除重復代碼,改造后代碼如下:

  1 #!/usr/bin/env python
  2 # coding=utf-8
  3 
  4 from common import db_helper
  5 
  6 
  7 class LogicBase():
  8     """邏輯層基礎類"""
  9 
 10     def __init__(self, db, is_output_sql, table_name, column_name_list='*', pk_name='id'):
 11         """類初始化"""
 12         # 數據庫參數
 13         self.__db = db
 14         # 是否輸出執行的Sql語句到日志中
 15         self.__is_output_sql = is_output_sql
 16         # 表名稱
 17         self.__table_name = str(table_name).lower()
 18         # 查詢的列字段名稱,*表示查詢全部字段,多於1個字段時用逗號進行分隔,除了字段名外,也可以是表達式
 19         self.__column_name_list = str(column_name_list).lower()
 20         # 主健名稱
 21         self.__pk_name = str(pk_name).lower()
 22 
 23     #####################################################################
 24     ### 執行Sql ###
 25 
 26     def get_model(self, wheres):
 27         """通過條件獲取一條記錄"""
 28         # 如果有條件,則自動添加where
 29         if wheres:
 30             wheres = ' where ' + wheres
 31 
 32         # 合成sql語句
 33         sql = "select %(column_name_list)s from %(table_name)s %(wheres)s" % \
 34               {'column_name_list': self.__column_name_list, 'table_name': self.__table_name, 'wheres': wheres}
 35         # 初化化數據庫鏈接
 36         result = self.select(sql)
 37         if result:
 38             return result[0]
 39         return {}
 40 
 41     def get_model_for_pk(self, pk, wheres=''):
 42         """通過主鍵值獲取數據庫記錄實體"""
 43         if not pk:
 44             return {}
 45         # 組裝查詢條件
 46         wheres = '%s = %s' % (self.__pk_name, str(pk))
 47 
 48         return self.get_model(wheres)
 49 
 50     def add_model(self, fields, returning=''):
 51         """新增數據庫記錄"""
 52         ### 拼接sql語句 ###
 53         # 初始化變量
 54         key_list = []
 55         value_list = []
 56         # 將傳入的字典參數進行處理,把字段名生成sql插入字段名數組和字典替換數組
 57         # PS:字符串使用字典替換參數時,格式是%(name)s,這里會生成對應的字串
 58         # 比如:
 59         #   傳入的字典為: {'id': 1, 'name': '名稱'}
 60         #   那么生成的key_list為:'id','name'
 61         #   而value_list為:'%(id)s,%(name)s'
 62         #   最終而value_list為字符串對應名稱位置會被替換成相應的值
 63         for key in fields.keys():
 64             key_list.append(key)
 65             value_list.append('%(' + key + ')s')
 66         # 設置sql拼接字典,並將數組(lit)使用join方式進行拼接,生成用逗號分隔的字符串
 67         parameter = {
 68             'table_name': self.__table_name,
 69             'pk_name': self.__pk_name,
 70             'key_list': ','.join(key_list),
 71             'value_list': ','.join(value_list)
 72         }
 73         # 如果有指定返回參數,則添加
 74         if returning:
 75             parameter['returning'] = ', ' + returning
 76         else:
 77             parameter['returning'] = ''
 78 
 79         # 生成可以使用字典替換的字符串
 80         sql = "insert into %(table_name)s (%(key_list)s) values (%(value_list)s) returning %(pk_name)s %(returning)s" % parameter
 81         # 將生成好的字符串替字典參數值,生成最終可執行的sql語句
 82         sql = sql % fields
 83 
 84         result = self.execute(sql)
 85         if result:
 86             return result[0]
 87         return {}
 88 
 89     def select(self, sql):
 90         """執行sql查詢語句(select)"""
 91         with db_helper.PgHelper(self.__db, self.__is_output_sql) as db:
 92             # 執行sql語句
 93             result = db.execute(sql)
 94             if not result:
 95                 result = []
 96         return result
 97 
 98     def execute(self, sql):
 99         """執行sql語句,並提交事務"""
100         with db_helper.PgHelper(self.__db, self.__is_output_sql) as db:
101             # 執行sql語句
102             result = db.execute(sql)
103             if result:
104                 db.commit()
105             else:
106                 result = []
107         return result
108 
109     #####################################################################

 

  上單元測試代碼:

#!/usr/bin/evn python
# coding=utf-8

import unittest
from common.string_helper import string
from logic import product_logic

class DbHelperTest(unittest.TestCase):
    """數據庫操作包測試類"""

    def setUp(self):
        """初始化測試環境"""
        print('------ini------')

    def tearDown(self):
        """清理測試環境"""
        print('------clear------')

    def test(self):
        ##############################################
        # 只需要看這里,其他代碼是測試用例的模板代碼 #
        ##############################################
        # 實例化product表操作類ProductLogic
        _product_logic = product_logic.ProductLogic()
        # 執行get_model_for_pk()方法,獲取記錄實體
        model = _product_logic.get_model_for_pk(2)
        print(model)

        # 測試新增記錄
        fields = {
            'name': string('名稱'),
            'code': string('201808031245678'),
            'product_class_id': 1,
        }
        result = _product_logic.add_model(fields, '*')
        print(result)

        ##############################################

if __name__ == '__main__':
    unittest.main()

  PS:單元測試中,通過from common.string_helper import string導入了string這個方法,它是用來給字符串添加單撇號字符(')用的,因為在更新數據庫時,字符串類型需要用單撇號括起來的,而Python中直接使用單撇字符只是標識它為字符串而已,需要用\'或雙引號+單撇字符+雙引號方式,才能得到我們想要的結果,為了方便操作,所以增加了string這個方法,幫我們進行轉換。

  輸出結果:

------ini------
{'content': '產品描述', 'name': '餅干', 'place_of_origin': '廣東廣州', 'add_time': datetime.datetime(2018, 7, 25, 23, 10, 4), 'code': '20180212321211', 'is_enable': 1, 'front_cover_img': 'http://xxx.com/xxx.jpg', 'product_class_id': 1, 'quality_guarantee_period': '2018年12月', 'id': 2, 'standard': '500g'}
{'content': '', 'name': '名稱', 'place_of_origin': '', 'add_time': datetime.datetime(2018, 8, 3, 16, 51, 3), 'code': '201808031245678', 'is_enable': 0, 'front_cover_img': '', 'product_class_id': 1, 'quality_guarantee_period': '', 'id': 15, 'standard': ''}
------clear------

 

  前面的接口也可以改造為

 1 @post('/api/product/')
 2 def callback():
 3     """
 4     新增記錄
 5     """
 6     name = web_helper.get_form('name', '產品名稱')
 7     code = web_helper.get_form('code', '產品編碼')
 8     product_class_id = convert_helper.to_int0(web_helper.get_form('product_class_id', '產品分類'))
 9     standard = web_helper.get_form('standard', '產品規格')
10     quality_guarantee_period = web_helper.get_form('quality_guarantee_period', '保質期')
11     place_of_origin = web_helper.get_form('place_of_origin', '產地')
12     front_cover_img = web_helper.get_form('front_cover_img', '封面圖片')
13     content = web_helper.get_form('content', '產品描述', is_check_special_char=False)
14     # 防sql注入攻擊處理
15     content = string_helper.filter_str(content, "'")
16     # 防xss攻擊處理
17     content = string_helper.clear_xss(content)
18     is_enable = convert_helper.to_int0(web_helper.get_form('is_enable', '是否啟用'))
19 
20     # 設置新增參數
21     fields = {
22         'name': string(name),
23         'code': string(code),
24         'product_class_id': product_class_id,
25         'standard': string(standard),
26         'quality_guarantee_period': string(quality_guarantee_period),
27         'place_of_origin': string(place_of_origin),
28         'front_cover_img': string(front_cover_img),
29         'content': string(content),
30         'is_enable': is_enable,
31     }
32     # 實例化product表操作類ProductLogic
33     _product_logic = product_logic.ProductLogic()
34     # 新增記錄
35     result = _product_logic.add_model(fields)
36     # 判斷是否提交成功
37     if result:
38         return web_helper.return_msg(0, '成功')
39     else:
40         return web_helper.return_msg(-1, "提交失敗")

  代碼行數比之前是增加了,但可讀性好了很多

 

 

版權聲明:本文原創發表於 博客園,作者為 AllEmpty 本文歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則視為侵權。

python開發QQ群:669058475(本群已滿)、733466321(可以加2群)    作者博客:http://www.cnblogs.com/EmptyFS/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM