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