2010-02-14 5 views
15

Wenn wir ein Profil einrichten, wie Django empfiehlt:Django Fremdkörper löschen?

class Profile(models.Model): 
    user = models.ForeignKey(User, unique=True) 

Dann, wenn Sie das Objekt User von Django Admin löschen, sein Profil löscht too.This ist, weil das Profil einen Fremdschlüssel Benutzer hat und es will um referentielle Integrität zu schützen. Allerdings möchte ich diese Funktionalität, auch wenn der Zeiger in die andere Richtung geht. Zum Beispiel auf meiner Profile Klasse I haben:

shipper = models.ForeignKey(Shipper, unique=True, blank=True, null=True) 
carrier = models.ForeignKey(Carrier, unique=True, blank=True, null=True) 
affiliat = models.ForeignKey(Affiliate, unique=True, blank=True, null=True, verbose_name='Affiliate') 

Und ich will es so, dass, wenn Sie die Profile löschen werde es die zugehörigen Absender/Träger/Affiliate-Objekte löschen (fragen Sie mich nicht warum Django gemacht " Affiliate "irgendein seltsames Schlüsselwort". Weil Versender, Spediteure und Affiliates Arten von Benutzern sind und es keinen Sinn ergibt, dass sie ohne den Rest der Daten existieren (niemand könnte sich als ein Benutzer anmelden).

Der Grund, warum ich nicht die Schlüssel auf den anderen Objekten gelegt hat, denn dann würde Django intern haben kommen alle diese Tabellen jedes Mal, wenn ich welche Art der Benutzer war ...

+0

"Affiliate" ist sicherlich nicht "irgendeine Art von seltsamen Schlüsselwort" in Django. Ich kann ein Modell mit einem Feld namens "affiliate" erstellen und in meinem Code gut damit arbeiten. –

+0

Das ist sehr seltsam. Alles funktioniert gut, aber es weigert sich, im Admin-Bereich zu erscheinen. Hast du das überprüft? SVN Checkout wie vor einer Woche. – mpen

Antwort

10

Während eines post_delete Signal unter Verwendung eines von bernardo oben beschrieben ist ok Ansatz, die gut funktionieren werden, versuche ich, wie nur irgend unter Verwendung von Signalen so wenig möglich zu vermeiden, wie ich fühle mich wie es durch das Hinzufügen Verhalten Standardfunktionalität Code unnötig faltet an Orten, die man erwarten könnte.

Ich bevorzuge die oben genannte Methode, aber das Beispiel von Felix hat einen schwerwiegenden Fehler; die delete() Funktion ist zwingende sieht wie folgt aus:

def delete(self, using=None): 
    using = using or router.db_for_write(self.__class__, instance=self) 
    assert self._get_pk_val() is not None, "%s object can't be deleted because its %s attribute is set to None." % (self._meta.object_name, self._meta.pk.attname) 

    collector = Collector(using=using) 
    collector.collect([self]) 
    collector.delete() 

Hinweis den Parameter ‚mit‘, in den meisten Fällen wir löschen() aufrufen, mit leeren Argumente, so dass wir auch dort war bekannt haben. Im obigen Beispiel wird dieser Parameter von uns überschrieben und überschreibt nicht die Superklassenfunktionalität. Wenn jemand den Parameter 'using' beim Löschen des Profils übergibt, führt dies zu unerwartetem Verhalten. Um zu vermeiden, dass, würden wir das Argument erhalten zusammen mit ihrem Standard lika stellen Sie sicher, so:

class Profile(models.Model): 
# ... 

def delete(self, using=None): 
    if self.shipper: 
     self.shipper.delete() 
    if self.carrier: 
     self.carrier.delete() 
    if self.affiliat: 
     self.affiliat.delete() 
    super(Profile, self).delete(using) 

Einen pitfall den übergeordneten Ansatz ist jedoch, dass löschen() nicht explizit pro db Rekord auf Masse gecallt dies bedeutet, löscht, dass, wenn Sie das Löschsignal nutzt entweder mehrere Profile auf einmal und halten Sie das übergeordnete Verhalten (Aufruf .delete() auf einem django queryset zum Beispiel) löschen gehen zu wollen, musst (wie von bernardo beschrieben) oder Sie müssen jeden Datensatz einzeln durchlaufen (sie sind teuer und hässlich).

+0

keine Notwendigkeit zu spezifizieren, warum Sie antworten, wenn die Antwort gut ist, und Sie erhalten Kredit dafür – dashesy

+0

Beachten Sie, dass das Löschen () Methode für ein Objekt wird nicht unbedingt beim Löschen von Objekten in großen Mengen mit einem QuerySet aufgerufen. Um sicherzustellen, dass eine benutzerdefinierte Löschlogik ausgeführt wird, können Sie die Signale pre_delete und/oder post_delete verwenden. https://docs.djangoproject.com/de/1.9/topics/db/models/#overriding-model-methods – dnaranjo

+0

@dnaranjo Ja, das ist alles in den Fallstricken am Ende meiner Antwort aufgeführt. Vielen Dank für Ihre Kommentare, hoffentlich wird jemand, der die ganze Antwort nicht durchliest, die möglichen Fallstricke durch das Lesen Ihrer Kommentare bemerken und wird dabei helfen, eine fundierte Entscheidung für ihre individuellen Bedürfnisse zu treffen. – krayzk

5

Sie können prüfen wollten override the delete() Methode der Profilklasse und löschen Sie die anderen Objekte in dieser Methode, bevor Sie das aktuelle Profil löschen.

Etwas wie:

class Profile(models.Model): 
    # ... 

    def delete(self): 
     if self.shipper: 
      self.shipper.delete() 
     if self.carrier: 
      self.carrier.delete() 
     if self.affiliat: 
      self.affiliat.delete() 
     super(Profile, self).delete() 
+0

Sieht gut aus, aber die Methode 'delete()' scheint nie aufgerufen zu werden, wenn ich Sachen über den Django-Admin lösche ...? – mpen

+2

@Mark: Es sollte mindestens funktionieren, wenn Sie ein einzelnes Objekt löschen. Beim Löschen mehrerer Objekte scheint es ein Problem zu geben: http://code.djangoproject.com/ticket/10751 –

5

Ein besserer Weg, dies zu tun und das funktioniert mit Löschmethode des Objekts und QuerySet die Methode löschen das post_delete Signal verwendet, wie Sie in den documentation sehen können.

In Ihrem Fall würde Ihr Code dies ganz ähnlich sein:

from django.db import models 
from django.dispatch import receiver 

@receiver(models.signals.post_delete, sender=Profile) 
def handle_deleted_profile(sender, instance, **kwargs): 
    if instance.shipper: 
     instance.shipper.delete() 
    if instance.carrier: 
     instance.carrier.delete() 
    if instance.affiliat: 
     instance.affiliat.delete() 

Dies funktioniert nur für Django 1.3 oder höher, weil das post_delete Signal in dieser Django Version hinzugefügt wurde.

Verwandte Themen