2010-03-19 74 views
49

Django模型一般處理ON DELETE CASCADE的行爲相當充分(在數據庫上工作的方式,不支持它本身。)重寫Django的級聯刪除行爲有什麼選擇?

不過,我掙扎,發現什麼是覆蓋最好的辦法行爲它是不恰當的,在下列情況下,例如:

  • ON DELETE限制功能(即防止刪除對象,如果它有子記錄)

  • ON DELETE SET NULL(即不刪除一個子記錄,但將其父鍵設置爲NULL inst ead打破關係)

  • 刪除記錄時更新其他相關數據刪除上傳的圖片文件)

以下是實現這些,我知道的潛在途徑:

  • 覆蓋模型的delete()方法。雖然這類作品在通過QuerySet刪除記錄時會被迴避。此外,必須重寫每個模型的delete()以確保從未調用Django的代碼,並且不能調用super(),因爲它可能使用QuerySet刪除子對象。

  • 使用信號。這似乎是理想的,因爲它們在直接刪除模型或通過QuerySet刪除時被調用。但是,不可能阻止子對象被刪除,因此無法實現ON CASCADE RESTRICT或SET NULL。

  • 使用,妥善處理這個數據庫引擎(這是什麼的Django在這種情況下怎麼辦?)

  • 等待,直到Django支持它(與錯誤,直到接活...)

看起來第一種選擇是唯一可行的選擇,但它很醜,會將嬰兒拋出洗澡水,並且在添加新的模型/關係時會冒險丟失某些東西。

我錯過了什麼嗎?任何建議?

回答

60

對於那些遇到這個問題的人來說,現在Django 1.3中已經有了一個內置的解決方案。

查看文檔中的詳細信息django.db.models.ForeignKey.on_delete感謝代碼片段的編輯指出。

最簡單的情況下只需添加你的模型FK字段定義:

on_delete=models.SET_NULL 
+8

+1 - 根據文檔,設置爲'ForeignKey的領域on_delete'也將在Django 2.0要求。 – whusterj 2016-03-29 15:04:06

6

Django只模仿CASCADE行爲。

根據Django的用戶組discussion最適當的解決辦法是:

  • 上重複DELETE SET NULL場景 - obj.delete之前手工做obj.rel_set.clear()(每一個相關的模型) ()。
  • 重複ON DELETE RESTRICT場景 - 手動檢查obj.rel_set是否在obj.delete()之前爲空。
4

好吧,以下是我已經解決的解決方案,但它遠沒有令人滿意。

我已經添加了一個抽象基類,我的所有車型:

class MyModel(models.Model): 
    class Meta: 
     abstract = True 

    def pre_delete_handler(self): 
     pass 

信號處理程序捕獲任何pre_delete事件這一模式的子類:

def pre_delete_handler(sender, instance, **kwargs): 
    if isinstance(instance, MyModel): 
     instance.pre_delete_handler() 
models.signals.pre_delete.connect(pre_delete_handler) 

在我的每一個車型,如果存在子記錄,我通過從pre_delete_handler方法拋出異常來模擬任何「ON DELETE RESTRICT」關係。

class RelatedRecordsExist(Exception): pass 

class SomeModel(MyModel): 
    ... 
    def pre_delete_handler(self): 
     if children.count(): 
      raise RelatedRecordsExist("SomeModel has child records!") 

這會在任何數據被修改之前中止刪除。

不幸的是,不可能更新pre_delete信號中的任何數據(例如模擬ON DELETE SET NULL),因爲在發送信號之前,Django已經生成了要刪除的對象列表。 Django這樣做是爲了避免陷入循環引用,並阻止多次不必要地發送對象信號。

確保可以執行刪除現在是調用代碼的責任。爲了幫助這一點,每個模型都有一個prepare_delete()方法,該方法通過self.related_set.clear()或類似的照顧設置鍵NULL的:

class MyModel(models.Model): 
    ... 
    def prepare_delete(self): 
     pass 

爲了避免改變太多的代碼在我views.pymodels.py,該delete()方法被重寫在MyModel調用prepare_delete()

class MyModel(models.Model): 
    ... 
    def delete(self): 
     self.prepare_delete() 
     super(MyModel, self).delete() 

這意味着,通過預期明確obj.delete()調用的任何刪除將工作,但如果刪除已經從級聯一個涉及d對象或通過queryset.delete()完成,並且調用代碼未確保在必要時所有鏈接都被中斷,則pre_delete_handler將引發異常。

最後,我添加了一個類似post_delete_handler方法被調用的post_delete信號,讓模型清理任何其它數據模型(比如刪除文件ImageField秒)

class MyModel(models.Model): 
    ... 

    def post_delete_handler(self): 
     pass 

def post_delete_handler(sender, instance, **kwargs): 
    if isinstance(instance, MyModel): 
     instance.post_delete_handler() 
models.signals.post_delete.connect(post_delete_handler) 

我希望能夠幫助某人,並且可以將代碼重新轉換回更有用的東西,而不會有太多麻煩。

任何有關如何改善此問題的建議都非常值得歡迎。