Django model重寫save方法及update踩坑記錄

一個很是實用的小方法數據庫

試想一下,Django中若是咱們想對保存進數據庫的數據作校驗,有哪些實現的方法?bash

咱們能夠在view中去處理,每當view接收請求,就對提交的數據作校驗,校驗不經過直接返回錯誤,不寫數據庫,校驗經過再調用createupdate方法寫入數據庫函數

以上方式比較簡單,容易理解,但隨之又帶來了麻煩,咱們需在全部接收數據的地方都要去校驗,那麼有沒有更加優雅的方式呢?若是你看過我以前的文章『Django使用Signals監測model字段變化發送通知』]就能想到能夠經過signals信號來處理,添加一個pre_save的信號,每當數據庫數據變動前都會觸發pre_save方法,能夠在這裏進行校驗,免去了view中多個地方校驗的麻煩post

而今天要說的並非signals,而是另外一種比較經常使用的作法:重寫model的save方法spa

重寫save方法

save方法的主要做用就是將一個對象保存到數據庫。若是咱們想在數據入庫以前作一些處理,除了上邊提到的signals以外,還能夠經過重寫save方法來實現。具體實現方式看下面這個例子code

假如咱們定義了model以下:cdn

class TempTask(models.Model):
    ...
    
    exechost = models.CharField(max_length=64, default='localhost', verbose_name='執行主機')
    execuser = models.ForeignKey(ExecUser, null=True, on_delete=models.PROTECT, db_constraint=False)
複製代碼

exechost默認爲Localhost,execuser默認爲空,現有需求:當exechost不爲localhost時,他必須符合ip:port的格式,且execuser不能爲空。這是一個比較複雜的校驗方式,咱們能夠經過重寫save方法來處理對象

class TempTask(models.Model):
    ...

    def save(self, *args, **kwargs):
        if self.exechost and (self.exechost.strip() != 'localhost'):
            if len(self.exechost.split(':')) != 2:
                raise ValidationError('執行主機格式錯誤,應爲ip:port格式')

            if not self.execuser:
                raise ValidationError('當執行主機存在時執行用戶不能爲空')

        super().save(*args, **kwargs)
複製代碼

咱們能夠在save函數內執行各類自定義邏輯,但須要注意的是,最後必需要調用super().save()方法來保證執行了父類的save(),這樣才能保證數據寫入了數據庫。blog

這樣在當咱們執行create語句插入數據的時候就會先去執行save中的校驗方法進行校驗了ip

TempTask.objects.create(**postdata)
複製代碼

update踩坑

就當我覺得一切都要結束準備起身衝杯咖啡的時候,我發現新加數據能夠正常進行校驗,但更新數據卻不行,更新的代碼以下:

TempTask.objects.filter(id=pk).update(**postdata)
複製代碼

通過一番查找發現了問題所在,官方文檔中有這麼一句話

Unfortunately, there isn’t a workaround when creating or updating objects in bulk, since none of save(), pre_save, and post_save are called.
複製代碼

也就是說,當使用查詢集批量更新對象時,將不會爲每一個對象調用save()方法,連pre_savepost_save也不會被調用。與save()相似的還有model的delete()方法,當批量刪除的時候,一樣不會調用model的delete()方法,但delete是可使用pre_deletepost_delete信號的

解決這個問題的方法很簡單,那就是將更新的代碼換成下邊這種,保證調用到save方法

_t = TempTask.objects.get(id=pk)
_t.__dict__.update(**postdata)
_t.save()
複製代碼

掃碼關注公衆號查看更多實用文章
相關文章
相關標籤/搜索