摘要:最近開發(fā)一個(gè)內(nèi)部的記錄系統(tǒng)其中有一個(gè)需求要求將所有數(shù)據(jù)庫(kù)操作記錄下來(lái)為此想了一些方案記錄一下思路演化這個(gè)需求出來(lái)的一瞬間我就否定了在業(yè)務(wù)邏輯層保存操作記錄的方案我認(rèn)為這樣耦合度比較高成本也太高代碼也會(huì)大量重復(fù)的操作中刪除操作會(huì)調(diào)用的方法增改
最近開發(fā)一個(gè)內(nèi)部的記錄系統(tǒng),其中有一個(gè)需求要求將所有數(shù)據(jù)庫(kù)操作記錄下來(lái),為此想了一些方案.記錄一下.
思路演化這個(gè)需求出來(lái)的一瞬間我就否定了在業(yè)務(wù)邏輯層保存操作記錄的方案,我認(rèn)為這樣耦合度比較高,成本也太高. 代碼也會(huì)大量重復(fù).
Django的ORM操作中,刪除操作會(huì)調(diào)用models.Model的delete方法,增改會(huì)調(diào)用save方法,修改這些方法能夠覆蓋除了查詢以外的所有ORM操作(查詢暫時(shí)跳過),修改save和delete的方法無(wú)外乎就是類繼承,裝飾器.
我也考慮了使用signal系統(tǒng),但是這樣依然要在業(yè)務(wù)邏輯層處理發(fā)送信號(hào)的問題,感覺更復(fù)雜一些(比如對(duì)照修改前后的數(shù)據(jù)不如直接在Model中操作方便,Model的save方法在存盤之前,self是待存數(shù)據(jù),根據(jù)self.pk從db中取出數(shù)據(jù)是舊數(shù)據(jù)方便對(duì)比.如果要在signal中拿到兩份數(shù)據(jù)比較麻煩,可能需要在業(yè)務(wù)層做更多的斟酌).
繼承我首先嘗試了類繼承的方法
class TopSecret(models.Model): class Meta: db_table = "絕密文件" name = models.CharField(max_length=32) content = models.TextField()
這是原始的model,我改寫成了如下
class Logger(models.Model): def save(self, *args, **kwargs): print("Do some log") super(Logger, self).save(*args, **kwargs) class TopSecret(Logger): class Meta: db_table = "絕密文件" name = models.CharField(max_length=32) content = models.TextField()
我覺得這樣應(yīng)該可以的.然而在調(diào)用save()方法時(shí)出現(xiàn)錯(cuò)誤OperationalError: no such table: logged_logger,可以看出,我在原始model定義的Meta信息失效了,框架轉(zhuǎn)而在Logger類中尋找Meta,未找到的情況下使用了框架的默認(rèn)值.
我嘗試將super(Logger, self).save(*args, **kwargs)改成super(self.__class__, self).save(*args, **kwargs),這樣super又成了調(diào)用TopSecret父類Logger的save(),如此反復(fù)形成了循環(huán)調(diào)用報(bào)錯(cuò).
我仔細(xì)想了一下,Model類尋找Meta的邏輯是肯定不去修改的,修改這個(gè)顯得不劃算,也違反了不隨便改框架的基本原則,當(dāng)時(shí)在此我轉(zhuǎn)向了裝飾器方法,而放棄了類繼承.
今天我寫這篇文章的時(shí)候隱約想起一件事情,Model好像有一個(gè)abstract的屬性,果然如此.定義這個(gè)Meta信息之后,框架會(huì)認(rèn)為這是一個(gè)抽象類,而不是數(shù)據(jù)模型,完美解決了問題.
class Logger(models.Model): class Meta: abstract = True def save(self, *args, **kwargs): print("Do some log") super(Logger, self).save(*args, **kwargs) class TopSecret(Logger): class Meta: db_table = "絕密文件" name = models.CharField(max_length=32) content = models.TextField()
In [1]: from logged.models import TopSecret In [2]: obj = TopSecret(name="123",content="測(cè)試內(nèi)容") In [3]: obj.save() Do some log In [4]:
在想起abstract之前我還想過其他的方案,比如多帶帶增加log類.這樣可以避免在Model父類和子類之間增加一層,解決了Meta信息的問題.
class Logger(object): def save(self, *args, **kwargs): print("Do some log") models.Model.save(self, *args, **kwargs) class TopSecret(Logger, models.Model): class Meta: db_table = "絕密文件" name = models.CharField(max_length=32) content = models.TextField()
這樣做有好處也有壞處,好處是Logger不再繼承Model,算是解耦合增加了代碼的可讀性,壞處是我看Logger那里調(diào)用save方法的方式比較別扭,將實(shí)例方法當(dāng)做靜態(tài)方法調(diào)用手動(dòng)傳入實(shí)例有一種很違和的感覺,不過總算是能工作了.
裝飾器裝飾器是當(dāng)時(shí)類繼承沒有成功,我走的另一條路.
首先因?yàn)槲覀兊难b飾器不可能裝到框架代碼里去,只能在我們定義的Model模型上使用類裝飾器.當(dāng)時(shí)我的實(shí)現(xiàn)是使用django自帶的method_decorator.這個(gè)函數(shù)可以將函數(shù)裝飾器變成方法裝飾器,裝飾到一個(gè)類的方法上,比較常見的用法是為dispatch方法去除csrf保護(hù).
但是使用這個(gè)方法會(huì)有一個(gè)問題,那就是寫一個(gè)函數(shù)裝飾器本身是不會(huì)取到類的實(shí)例本身的.還需要為save方法傳入類的實(shí)例本身才能取到類數(shù)據(jù)進(jìn)行日志操作,不行,不夠優(yōu)雅.
怎么辦?只能直接寫類裝飾器了.
之前沒寫過類裝飾器,其實(shí)類裝飾器和普通函數(shù)裝飾器一樣,思路和繼承的寫法也是一樣的.
def cbd_logger(obj): if hasattr(obj, "save"): save = obj.save def _save(self, *args, **kwargs): print "do some log %s" % self.name return save(self, *args, **kwargs) setattr(obj, "save", _save) return obj @cbd_logger class TopSecret(models.Model): class Meta: db_table = "絕密文件" name = models.CharField(max_length=32) content = models.TextField()
值得注意的是,_save中不能直接return obj.save(self,*args,**kwargs),這么做會(huì)導(dǎo)致運(yùn)行時(shí)調(diào)用當(dāng)前實(shí)例的save方法,也就是_save本身,搞成無(wú)限遞歸
我們分別打印一下cbd_logger下這些方法的id看一下
>>>obj.save() save 90220624 obj.save 106285376 self.save 106285376
在裝飾后的save方法中,obj.save的地址和self.save的地址是一樣的,這個(gè)save已經(jīng)被裝飾器修改過了.
和下面這個(gè)閉包的原理差不多.
>>>fs = [lambda i:i*2 for i in range(3)] >>>for f in fs: ... print(f(1)) 2 2 2總結(jié)
類的繼承方法和裝飾器方法實(shí)際上都在做同一件事,就是在框架本身的save和delete方法外層增加日志操作.但是需求還沒有實(shí)現(xiàn),我們保存日志的時(shí)候,不只要知道數(shù)據(jù)變動(dòng),還要知道這些操作是誰(shuí)做的,如何優(yōu)雅的將這些信息傳遞給負(fù)責(zé)記錄的代碼?
目前我們選擇的是在操作Model時(shí),約定不使用objects,只使用TopSecret(name="",content="").save(request)這種方法,將request傳遞給save,再由之前實(shí)現(xiàn)的logger從request取出必要的信息進(jìn)行記錄,比如IP,User,甚至UA等等.這么做業(yè)務(wù)層需要多傳一個(gè)參數(shù),還是有了感知,但是也是沒辦法的事.對(duì)現(xiàn)有代碼的改動(dòng)也是我知道的辦法中最小的.
這套下來(lái)感覺django文檔中的給出的信息很充分,實(shí)現(xiàn)這個(gè)需求并不難.一開始出現(xiàn)的問題還是因?yàn)閷?duì)文檔印象不夠深,沒有第一時(shí)間解決問題.
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/41766.html
摘要:另外,項(xiàng)目在單元測(cè)試中使用的是的內(nèi)存數(shù)據(jù)庫(kù),這樣開發(fā)者運(yùn)行單元測(cè)試的時(shí)候不需要安裝和配置復(fù)雜的數(shù)據(jù)庫(kù),只要安裝好就可以了。而且,數(shù)據(jù)庫(kù)是保存在內(nèi)存中的,會(huì)提高單元測(cè)試的速度。是實(shí)現(xiàn)層的基礎(chǔ)。項(xiàng)目一般會(huì)使用數(shù)據(jù)庫(kù)來(lái)運(yùn)行單元測(cè)試。 OpenStack中的關(guān)系型數(shù)據(jù)庫(kù)應(yīng)用 OpenStack中的數(shù)據(jù)庫(kù)應(yīng)用主要是關(guān)系型數(shù)據(jù)庫(kù),主要使用的是MySQL數(shù)據(jù)庫(kù)。當(dāng)然也有一些NoSQL的應(yīng)用,比如Ce...
摘要:當(dāng)然還有其他高級(jí)的使用,日后再說完整的用戶名郵箱聯(lián)系地址留言信息用戶留言信息使用之前已經(jīng)定義好了數(shù)據(jù)模型的字段元數(shù)據(jù)方法等。 前言 接續(xù)前文,上一篇文章主要涉及了 Django 項(xiàng)目的基礎(chǔ)配置等,這篇主要涉及數(shù)據(jù)庫(kù)相關(guān)的 ORM ,也就是 Django 中的 Model 的使用,MVT 三層之間的交互 教程基本都是東拼西湊的,防止有些東西表述不準(zhǔn)確,因?yàn)槲抑皩?JavaScript ...
摘要:對(duì)象關(guān)系映射,簡(jiǎn)稱模式是一種為了解決面向?qū)ο笈c關(guān)系數(shù)據(jù)庫(kù)存在的互不匹配的現(xiàn)象的技術(shù)。在業(yè)務(wù)邏輯層和數(shù)據(jù)庫(kù)層之間充當(dāng)了橋梁的作用。每個(gè)字段被指定為一個(gè)類屬性,每個(gè)屬性映射到一個(gè)數(shù)據(jù)庫(kù)列。字符類型,必須提供參數(shù),表示字符長(zhǎng)度。 對(duì)象關(guān)系映射(Object Relational Mapping,簡(jiǎn)稱ORM)模式是一種為了解決面向?qū)ο笈c關(guān)系數(shù)據(jù)庫(kù)存在的互不匹配的現(xiàn)象的技術(shù)。 簡(jiǎn)單的說,ORM是...
閱讀 1087·2021-11-22 13:52
閱讀 999·2019-08-30 15:44
閱讀 619·2019-08-30 15:43
閱讀 2485·2019-08-30 12:52
閱讀 3529·2019-08-29 16:16
閱讀 707·2019-08-29 13:05
閱讀 2995·2019-08-26 18:36
閱讀 2072·2019-08-26 13:46