68

Djangoモデルは通常、ON DELETE CASCADEの動作を非常に適切に処理します(ネイティブでサポートされていないデータベースで機能する方法で)。

ただし、次のシナリオなど、適切でない場合にこの動作をオーバーライドするための最良の方法を見つけるのに苦労しています。

  • ON DELETE RESTRICT(つまり、子レコードがある場合はオブジェクトの削除を防止します)

  • ON DELETE SET NULL(つまり、子レコードを削除せずに、関係を解除する代わりに、その親キーをNULLに設定します)

  • レコードが削除されたときに他の関連データを更新する(アップロードされた画像ファイルの削除など)

以下は、私が知っているこれらを達成するための潜在的な方法です。

  • モデルのdelete()メソッドをオーバーライドします。この種の作業は機能しますが、レコードが。を介して削除されると回避されQuerySetます。また、すべてのモデルdelete()をオーバーライドして、Djangoのコードがsuper()呼び出されないようにし、QuerySet子オブジェクトを削除するためにを使用する可能性があるため、呼び出すことができないようにする必要があります。

  • 信号を使用します。モデルを直接削除するとき、またはQuerySetを介して削除するときに呼び出されるため、これは理想的なようです。ただし、子オブジェクトの削除を防ぐことはできないため、ONCASCADERESTRICTまたはSETNULLを実装することはできません。

  • これを適切に処理するデータベースエンジンを使用します(この場合、Djangoは何をしますか?)

  • Djangoがそれをサポートするまで待ちます(そしてそれまでバグを抱えて生きます...)

最初のオプションが唯一の実行可能なオプションのようですが、それは醜く、お風呂の水で赤ちゃんを捨て、新しいモデル/関係が追加されたときに何かを見逃すリスクがあります。

私は何かが足りないのですか?何かお勧めはありますか?

4

3 に答える 3

97

この問題に遭遇した人へのメモとして、Django1.3に組み込みのソリューションがあります。

ドキュメントの詳細を参照してくださいdjango.db.models.ForeignKey.on_deleteFragmentsofCodeサイトの編集者に指摘していただきありがとうございます。

最も単純なシナリオでは、モデルのFKフィールド定義を追加するだけです。

on_delete=models.SET_NULL
于 2010-12-08T21:04:08.143 に答える
7

DjangoはCASCADEの動作のみをエミュレートします。

Django Users Groupでの議論によると、最も適切なソリューションは次のとおりです。

  • ON DELETE SET NULLシナリオを繰り返すには、obj.delete()の前に(関連するすべてのモデルに対して)obj.rel_set.clear()を手動で実行します。
  • ON DELETE RESTRICTシナリオを繰り返すには、obj.delete()の前にobj.rel_setが空であることを手動で確認します。
于 2010-03-19T06:35:39.413 に答える
6

さて、以下は私が解決した解決策ですが、それは満足のいくものではありません。

すべてのモデルに抽象基本クラスを追加しました。

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)

各モデルで、子レコードが存在する場合ON DELETE RESTRICTにメソッドから例外をスローすることにより、任意の""関係をシミュレートします。pre_delete_handler

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()キーをNULLviaself.related_set.clear()または同様のものに設定する方法があります。

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

views.pymyとのコードをあまり変更する必要がないようmodels.pyに、delete()メソッドはオーバーライドされて次のコマンドMyModelを呼び出しますprepare_delete()

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

これは、明示的にviaobj.delete()を呼び出した削除は期待どおりに機能することを意味しますが、削除が関連オブジェクトからカスケードされているか、を介して行われqueryset.delete()、呼び出し元のコードが必要な場所ですべてのリンクが切断されていることを確認していない場合、はpre_delete_handler例外をスローします。

そして最後にpost_delete_handler、シグナルで呼び出され、post_deleteモデルが他のデータをクリアできるようにする同様のメソッドをモデルに追加しました(たとえば、ImageFieldsのファイルを削除します)。

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)

それが誰かの助けになり、コードをあまり問題なくより使いやすいものに再スレッド化できることを願っています。

これを改善する方法についての提案は大歓迎です。

于 2010-04-01T02:24:29.337 に答える