2010-03-19 16 views
49

Django Modelle handhaben im Allgemeinen die ON DELETE CASCADE Verhalten durchaus angemessen (in einer Weise, die auf Datenbanken arbeitet, die es nicht nativ unterstützt.)Welche Möglichkeiten gibt es, um Djangos kaskadierendes Löschverhalten zu überschreiben?

Allerdings bin ich kämpfen, um zu entdecken, was der beste Weg ist, diese zu überschreiben Verhalten, wo es nicht angebracht, in den folgenden Szenarien zum Beispiel:

  • ON RESTRICT DELETE (dh verhindern das Löschen eines Objekts, wenn es Kind Aufzeichnungen hat)

  • ON DELETE SET NULL (dh nicht Löschen Sie einen untergeordneten Datensatz, aber setzen Sie den übergeordneten Schlüssel auf NULL inst EAD, um die Beziehung zu brechen)

  • Aktualisieren Sie andere verwandte Daten, wenn ein Datensatz gelöscht wird (z. ein hochgeladenes Bild-Datei)

Die folgende löschen die möglichen Wege sind diese, die ich bin mir dessen bewusst zu erreichen:

  • Aufschalten des Modells delete() Methode. Während diese Art von Arbeiten, wird es umgangen, wenn die Datensätze über eine QuerySet gelöscht werden. Außerdem muss jedes Modell delete() außer Kraft gesetzt werden, um sicherzustellen, dass der Django-Code nie aufgerufen wird und super() nicht aufgerufen werden kann, da er möglicherweise QuerySet verwendet, um untergeordnete Objekte zu löschen.

  • Signale verwenden. Dies scheint ideal zu sein, wie sie beim direkten Löschen des Modells oder Löschen über ein QuerySet genannt werden. Es gibt jedoch keine Möglichkeit zu verhindern, dass ein untergeordnetes Objekt gelöscht wird. Daher ist es nicht möglich, ON CASCADE RESTRICT oder SET NULL zu implementieren.

  • eine Datenbank-Engine verwenden, die diese richtig behandelt (was macht Django in diesem Fall tun?)

  • Warten Sie, bis Django unterstützt (und mit Fehlern leben, bis dann ...)

Es scheint, als wäre die erste Option die einzig mögliche, aber sie ist hässlich, wirft das Kind mit dem Bade aus und riskiert etwas zu verpassen, wenn ein neues Modell/eine neue Beziehung hinzugefügt wird.

Fehle ich etwas? Irgendwelche Empfehlungen?

Antwort

60

Nur eine Anmerkung für diejenigen, die auf dieses Problem auch stoßen, gibt es jetzt eine integrierte Lösung in Django 1.3.

Siehe die Details in der Dokumentation django.db.models.ForeignKey.on_delete Danke für den Herausgeber der Website Fragments of Code, um darauf hinzuweisen. in Django erforderlich sein wird, auch 2.0 die Dokumentation nach, `on_delete` für ForeignKey Felder Einstellung -

on_delete=models.SET_NULL 
+8

+1:

Die einfachste mögliche Szenario in Ihrem Modell FK Felddefinition fügen Sie einfach. – whusterj

6

Django emuliert nur CASCADE-Verhalten.

Nach discussion in Django Users Group die am besten geeigneten Lösungen sind:

  • ON DELETE SET NULL Szenario zu wiederholen - manuell obj.rel_set.clear() (für jedes verwandtes Modell) vor obj.delete().
  • Wiederholen Sie ON DELETE RESTRICT Szenario - manuell überprüfen obj.rel_set leer vor obj.delete().
4

Ok, das Folgende ist die Lösung, auf die ich mich festgelegt habe, obwohl es bei weitem nicht zufriedenstellend ist.

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

In jedem meiner Modelle:

für Subklassen dieses Modells
class MyModel(models.Model): 
    class Meta: 
     abstract = True 

    def pre_delete_handler(self): 
     pass 

Ein Signal-Handler erfasst alle pre_delete Ereignisse:

Ich habe eine abstrakte Basisklasse für alle meine Modelle hinzugefügt Ich simuliere alle "ON DELETE RESTRICT" Relationen, indem ich eine Exception von der pre_delete_handler Methode ausliege, wenn ein untergeordneter Datensatz existiert.

Dies bricht das Löschen ab, bevor Daten geändert werden.

Leider ist es nicht möglich, Daten im pre_delete-Signal zu aktualisieren (z. B. ON DELETE SET NULL zu emulieren), da die Liste der zu löschenden Objekte bereits von Django generiert wurde, bevor die Signale gesendet werden. Django tut dies, um zu vermeiden, dass man sich auf zirkuläre Referenzen festsetzt und verhindert, dass ein Objekt mehrmals unnötig signalisiert wird.

Sicherstellen, dass ein Löschvorgang ausgeführt werden kann, liegt jetzt in der Verantwortung des aufrufenden Codes. Zur Unterstützung dieser verfügt jedes Modell über eine prepare_delete() Methode, die kümmert Schlüssel zu NULL über self.related_set.clear() oder ähnlich der Einstellung: zu viel Code mit

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

Um zu vermeiden, zu ändern in meinem views.py und models.py, die delete() Methode überschrieben wird auf MyModelprepare_delete() nennen:

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

Dies bedeutet, dass alle, aber wenn explizit über obj.delete() wie erwartet funktionieren wird, genannt Löschungen ein Lösch aus einer Beziehung kaskadiert hat d Objekt oder erfolgt über eine queryset.delete() und der aufrufende Code hat nicht sichergestellt, dass alle Links wo nötig unterbrochen werden, dann wird pre_delete_handler eine Ausnahme auslösen.

Und schließlich, ich habe ein ähnliches post_delete_handler Verfahren zu den Modellen hinzugefügt, die auf das post_delete Signal aufgerufen werden und lassen das Modell alle anderen Daten aufklären (zB das Löschen von Dateien für ImageField s.)

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) 

Ich hoffe, dass es jemandem hilft und dass der Code ohne viel Aufwand wieder in etwas Brauchbareres verwandelt werden kann.

Vorschläge, wie Sie dies verbessern können, sind mehr als willkommen.