Pregunta

Los modelos de Django en general, manejan el comportamiento ON DELETE CASCADE muy adecuadamente (de una manera que trabaja en las bases de datos que no son compatibles de forma nativa.)

Sin embargo, estoy luchando para descubrir cuál es la mejor manera de anular este comportamiento en los que no es el caso, en los siguientes escenarios, por ejemplo:

  • ON DELETE restringir (es decir, evitar la eliminación de un objeto si tiene registros secundarios)

  • ON DELETE SET NULL (es decir, no elimine un registro hijo, pero se especificó que es clave primaria a NULL en lugar de romper la relación)

  • Actualización otros datos relacionados cuando se elimina un registro (por ejemplo, la eliminación de un archivo de imagen cargada)

Las siguientes son las formas posibles para lograr estos que yo sepa:

  • Anular método delete() del modelo. Si bien este tipo de obras, es dejado de lado cuando los registros se eliminan a través de un QuerySet. Además, delete() de cada modelo debe ser anulado a hacer que el código seguro de Django nunca es llamado y super() no se puede llamar, ya que puede utilizar un QuerySet eliminar objetos secundarios.

  • Utilice las señales. Esto parece ser ideal como se les llama al eliminar directamente el modelo o eliminar a través de un QuerySet. Sin embargo, no hay ninguna posibilidad de evitar que un objeto secundario que se borre lo que no se puede utilizar para poner en práctica sobre el Cascade restringir o SET NULL.

  • Utiliza un motor de base de datos que maneja esto correctamente (lo que hace Django puede hacer en este caso?)

  • Esperar a que Django soporta (y vivir con los insectos hasta entonces ...)

Parece que la primera opción es la única viable, pero es feo, lanza al bebé junto con el agua del baño, y los riesgos que falta algo cuando se añade un nuevo modelo / relación.

Me estoy perdiendo algo? ¿Alguna recomendación?

¿Fue útil?

Solución

Sólo una nota para los que se encuentra con este tema, así, ahora hay una solución incorporada en Django 1.3.

Vea los detalles en la documentación django.db.models.ForeignKey.on_delete Gracias por editor del sitio de fragmentos de código para señalarlo.

El escenario más simple posible, sólo tiene que añadir en su modelo FK definición de campo:

on_delete=models.SET_NULL

Otros consejos

Ok, la siguiente es la solución que yo he decidido por, aunque está lejos de ser satisfactoria.

He añadido una clase base abstracta para todos mis modelos:

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

    def pre_delete_handler(self):
        pass

Un manejador de señales atrapa cualquier evento pre_delete de subclases de este modelo:

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

En cada una de mis modelos, que simulan las relaciones "ON DELETE RESTRICT" lanzando una excepción a partir del método pre_delete_handler si existe un registro hijo.

class RelatedRecordsExist(Exception): pass

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

Esto aborta el borrado antes de modificar los datos.

Por desgracia, no es posible actualizar los datos en la señal pre_delete (por ejemplo para emular ON DELETE SET NULL) como la lista de objetos para eliminar ya ha sido generada por Django antes de enviar las señales. Django hace esto para evitar quedarse atascado en las referencias circulares y señalización para evitar un objeto varias veces de forma innecesaria.

Asegurar una eliminación se puede realizar es ahora responsabilidad del código de llamada. Para ayudar con esto, cada modelo tiene un método prepare_delete() que se encarga de establecer las claves a través de NULL self.related_set.clear() o similar:

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

Para evitar tener que cambiar demasiado código en mi views.py y models.py, el método delete() se anula en MyModel llamar prepare_delete():

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

Esto significa que cualquier eliminaciones llamados explícitamente a través de obj.delete() funcionarán como se espera, pero si una de eliminar tiene en cascada de un objeto relacionado o se hace a través de un queryset.delete() y el código de llamada no se ha asegurado de que todos los enlaces se rompen cuando sea necesario, a continuación, la pre_delete_handler lanzará una excepción.

Y por último, he añadido un método post_delete_handler similar a los modelos que se llama a la señal post_delete y permite que el modelo de aclarar cualquier otros datos (por ejemplo, la eliminación de archivos de 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)

Espero que ayude a alguien y que el código puede ser re-hilo de vuelta en algo más utilizable sin demasiados problemas.

¿Alguna sugerencia sobre cómo mejorar este son más que bienvenidos.

scroll top