2010-02-14 12 views
15

Si hemos creado un perfil de cómo Django recomienda:Django ¿eliminar objeto extraño?

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

A continuación, cuando se elimina el objeto User de Django admin, borra sus too.This perfil es debido a que el perfil tiene una clave externa para el usuario y que quiere para proteger la integridad referencial. Sin embargo, quiero esta funcionalidad incluso si el puntero va por el otro lado. Por ejemplo, en mi clase Profile tengo:

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') 

Y lo quiero de manera que si se elimina el Profile que va a eliminar el expedidor/transportista/objetos de afiliados asociados (no me pregunte por qué Django hizo " afiliarse a "alguna palabra clave extraña". Debido a que los expedidores, transportistas y afiliados son tipos de usuarios, y no tiene sentido que existan sin el resto de los datos (nadie podría iniciar sesión como uno solo).

La razón por la que no he puesto las llaves en los otros objetos, es porque entonces Django tendría que unirse internamente todas esas tablas cada vez que quería comprobar qué tipo de usuario era ...

+0

"afiliado" ciertamente no es "algún tipo de palabra clave extraña" en Django. Puedo crear un modelo con un campo llamado "afiliado" y trabajar con él perfectamente en mi código. –

+0

Eso es muy raro. Todo funciona bien, pero se niega a aparecer en la sección de administración. ¿Lo revisaste? SVN checkout como hace una semana. – mpen

Respuesta

10

Si bien usar una señal post_delete como la descrita por bernardo arriba es un enfoque correcto, eso funcionará bien, trato de evitar usar señales tan poco como sea humanamente posible ya que siento que convoluciona innecesariamente su código al agregar comportamiento a la funcionalidad estándar en lugares que uno podría estar esperando.

Prefiero el método anterior anterior, sin embargo, el ejemplo dado por Felix tiene un defecto fatal; la función de borrado() que está anulando el siguiente aspecto:

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() 

Aviso el parámetro 'usando', en la mayoría de los casos que llamamos eliminar() con argumentos vacíos así que puede haber siquiera sabido que estaba allí. En el ejemplo anterior, este parámetro está oculto porque nosotros anulamos y no miramos la funcionalidad de la superclase; si alguien pasa el parámetro "usar" al eliminar el perfil, causará un comportamiento inesperado. Para evitar eso, nos aseguraríamos de preservar el argumento junto con su Lika defecto así:

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) 

Una trampa para el enfoque predominante, sin embargo, es que eliminar() no ser llamado explícitamente por registro db en mayor elimina, esto significa que si va a querer eliminar múltiples perfiles a la vez y mantener el comportamiento dominante (llamando a .delete() en un conjunto de comandos django, por ejemplo) necesitará aprovechar la señal de eliminación (como lo describe bernardo) o tendrá que repetir en cada registro borrándolos individualmente (caro y feo).

+0

no es necesario especificar por qué responde cuando la respuesta es buena, y usted también obtiene un crédito – dashesy

+0

Tenga en cuenta que la eliminación (El método para un objeto no necesariamente se llama cuando se eliminan objetos a granel utilizando un QuerySet. Para garantizar que se ejecute la lógica de eliminación personalizada, puede usar las señales pre_delete y/o post_delete. https://docs.djangoproject.com/en/1.9/topics/db/models/#overriding-model-methods – dnaranjo

+0

@dnaranjo Sí, todo esto está expuesto en las trampas enumeradas al final de mi respuesta. Sin embargo, gracias por comentar, es de esperar que alguien que no lea toda la respuesta se dé cuenta de las posibles dificultades leyendo sus comentarios y ayude a tomar una decisión educada para sus necesidades individuales. – krayzk

5

Puede override the delete() método de la clase de perfil y eliminar los otros objetos en este método antes de eliminar el perfil real.

Algo así como:

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

Se ve bien, pero el método 'delete()' nunca parece ser llamado cuando elimino cosas a través del administrador de Django ...? – mpen

+2

@Mark: Al menos debería funcionar cuando eliminas un solo objeto. Con la eliminación de varios objetos, parece haber un problema: http://code.djangoproject.com/ticket/10751 –

5

Una mejor manera de hacer esto y que trabaja con el método de eliminación de objeto y queryset de borrar método está utilizando la señal post_delete, como se puede ver en el documentation.

En su caso, el código sería bastante similar a esto:

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() 

Esto funciona sólo para Django 1.3 o mayor, porque la señal post_delete fue añadido en esta versión de Django.