2010-03-19 9 views
49

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.)¿Cuáles son las opciones para anular el comportamiento de eliminación en cascada de Django?

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

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

  • eN NULL SET DELETE (es decir, no hacer eliminar un registro secundario, pero establecer su clave principal para NULL inst ead para romper la relación)

  • Actualizar otros datos relacionados cuando se elimina un registro (p. la eliminación de un archivo de imagen cargada)

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

  • reemplazar el método del modelo delete(). Si bien este tipo de trabajo funciona, se evita cuando los registros se eliminan a través de QuerySet. Además, se debe anular el delete() de todos los modelos para asegurarse de que nunca se llame al código de Django y no se pueda llamar al super(), ya que puede usar un QuerySet para eliminar objetos secundarios.

  • Señales de uso. Esto parece ser ideal, ya que se llaman cuando se elimina directamente el modelo o se elimina a través de un QuerySet. Sin embargo, no hay posibilidad de evitar que un objeto secundario se elimine, por lo que no se puede implementar ON CASCADE RESTRICT o SET NULL.

  • utilizar 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 fea, arroja al bebé con el agua del baño y se arriesga a perder algo cuando se agrega una nueva modelo/relación.

¿Echo de menos algo? ¿Alguna recomendación?

Respuesta

60

Solo una nota para aquellos que se encuentran con este problema, ahora hay una solución incorporada en Django 1.3.

Consulte los detalles en la documentación django.db.models.ForeignKey.on_delete Gracias al editor del sitio Fragments of Code 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 
+8

+1 - de acuerdo a los documentos, la configuración 'También se requerirá on_delete' para los campos ForeignKey en Django 2.0. – whusterj

6

Django solo emula el comportamiento de CASCADE.

Según discussion en Django Grupo de Usuarios de las soluciones más adecuadas son:

  • se repita el escenario SET NULL Eliminar - hacer manualmente obj.rel_set.clear() (para todos los modelos relacionados) antes obj.delete().
  • Para repetir el escenario ON DELETE RESTRICT, compruebe manualmente que obj.rel_set está vacío antes de obj.delete().
4

Ok, la siguiente es la solución que he establecido, 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 pre_delete eventos 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, Simulo cualquier relación "ON DELETE RESTRICT" lanzando una excepción desde el método pre_delete_handler si existe un registro secundario.

class RelatedRecordsExist(Exception): pass 

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

Esto anula la eliminación antes de que se modifiquen los datos.

Desafortunadamente, no es posible actualizar ningún dato en la señal pre_delete (por ejemplo, para emular ON DELETE SET NULL) ya que Django ya ha generado la lista de objetos para eliminar antes de enviar las señales. Django hace esto para evitar atascarse en las referencias circulares y para evitar señalar un objeto varias veces innecesariamente.

Asegurar que se pueda realizar una eliminación 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 claves para NULL través 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 desde una relacionarse d objeto o se realiza 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, entonces el pre_delete_handler lanzará una excepción.

Y por último, he añadido un post_delete_handler método 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 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) 

Espero que ayude a alguien y que el código se pueda volver a enhebrar para convertirlo en algo más utilizable sin demasiados problemas.

Cualquier sugerencia sobre cómo mejorar esto es más que bienvenida.

Cuestiones relacionadas