¿Qué hace on_delete en los modelos Django?

348

Estoy bastante familiarizado con Django, pero recientemente noté que existe una on_delete=models.CASCADEopción con los modelos, he buscado la documentación para el mismo pero no pude encontrar nada más que:

Cambiado en Django 1.9:

on_deleteahora se puede usar como el segundo argumento posicional (anteriormente, por lo general, solo se pasaba como argumento de palabra clave). Será un argumento obligatorio en Django 2.0.

un ejemplo de caso de uso es

from django.db import models

class Car(models.Model):
    manufacturer = models.ForeignKey(
        'Manufacturer',
        on_delete=models.CASCADE,
    )
    # ...

class Manufacturer(models.Model):
    # ...
    pass

¿Qué hace on_delete? ( Supongo que las acciones a realizar si se elimina el modelo )

¿Qué models.CASCADEhacer? ( cualquier pista en la documentación )

¿Qué otras opciones están disponibles ( si mi suposición es correcta )?

¿Dónde reside la documentación para esto?

Todo es variedad
fuente
También hay una respuesta a una pregunta similar en stackoverflow.com/questions/47914325/…
HelenM
1
El texto de esta pregunta similar aparece ahora, a continuación, en esta respuesta. Comienza "FYI, el parámetro on_delete en los modelos está al revés de lo que parece". Proporciona muchos más detalles que las respuestas originales.
HelenM

Respuestas:

780

Este es el comportamiento a adoptar cuando se elimina el objeto referenciado . No es específico de django, este es un estándar SQL.

Hay 6 posibles acciones a tomar cuando se produce dicho evento:

  • CASCADE: Cuando se elimina el objeto al que se hace referencia, también elimine los objetos que tienen referencias a él (cuando elimina una publicación de blog, por ejemplo, es posible que también desee eliminar los comentarios). Equivalente SQL: CASCADE.
  • PROTECT: Prohibir la eliminación del objeto referenciado. Para eliminarlo, deberá eliminar todos los objetos que lo referencian manualmente. Equivalente SQL: RESTRICT.
  • SET_NULL: Establezca la referencia en NULL (requiere que el campo sea anulable). Por ejemplo, cuando elimina un usuario, es posible que desee conservar los comentarios que publicó en las publicaciones de blog, pero digamos que fue publicado por un usuario anónimo (o eliminado). Equivalente SQL: SET NULL.
  • SET_DEFAULT: Establece el valor predeterminado. Equivalente SQL: SET DEFAULT.
  • SET(...): Establece un valor dado. Este no es parte del estándar SQL y es manejado completamente por Django.
  • DO_NOTHING: Probablemente sea una muy mala idea ya que esto crearía problemas de integridad en su base de datos (haciendo referencia a un objeto que en realidad no existe). Equivalente SQL: NO ACTION.

Fuente: documentación de Django

Consulte también la documentación de PostGreSQL por ejemplo.

En la mayoría de los casos, CASCADEes el comportamiento esperado, pero para cada ForeignKey, siempre debe preguntarse cuál es el comportamiento esperado en esta situación. PROTECTy a SET_NULLmenudo son útiles Establecer CASCADEdonde no debería, potencialmente puede eliminar toda su base de datos en cascada, simplemente eliminando un solo usuario.


Nota adicional para aclarar la dirección de la cascada

Es divertido notar que la dirección de la CASCADEacción no está clara para muchas personas. En realidad, es divertido notar que solo la CASCADEacción no está clara. Entiendo que el comportamiento en cascada puede ser confuso, sin embargo, debe pensar que es la misma dirección que cualquier otra acción . Por lo tanto, si siente que la CASCADEdirección no es clara para usted, en realidad significa que el on_deletecomportamiento no es claro para usted.

En su base de datos, una clave foránea está representada básicamente por un campo entero cuyo valor es la clave primaria del objeto foráneo. Supongamos que tiene una entrada comment_A , que tiene una clave foránea para una entrada article_B . Si elimina la entrada comment_A , todo está bien, article_B utiliza para vivir sin comment_A y no se molestan si se elimina. Sin embargo, si se elimina article_B , entonces comment_A pánico! Nunca vivió sin article_B y lo necesita, es parte de sus atributos ( article=article_Bpero ¿qué es * article_B ** ???). Aquí es donde on_deleteinterviene, para determinar cómo resolver este error de integridadya sea diciendo:

  • "¡No! ¡Por favor! ¡No! ¡No puedo vivir sin ti!" (que se dice PROTECTen lenguaje SQL)
  • "Muy bien, si no soy tuyo, entonces no soy de nadie" (que se dice SET_NULL)
  • "Adiós mundo, no puedo vivir sin el artículo B" y suicidarme (este es el CASCADEcomportamiento).
  • "Está bien, tengo un amante extra, voy a hacer referencia a article_C a partir de ahora" ( SET_DEFAULTo incluso SET(...)).
  • "No puedo enfrentar la realidad, ¡seguiré llamándote incluso si eso es lo único que me queda!" ( DO_NOTHING)

Espero que aclare la dirección de la cascada. :)

Antoine Pinsard
fuente
19
Una pregunta tonta, pero la cascada siempre debe ser unidireccional, ¿verdad? Es decir, si Comment tiene una clave externa para BlogPosteliminar BlogPost, debería eliminar Comentario, pero eliminar Comentario no debería eliminar BlogPost, independientemente del RDMS.
Anthony Manning-Franklin
20
@AnthonyManningFranklin Claro. Al eliminar solo se activa cuando una referencia está "rota". Lo cual no es el caso cuando elimina un comentario, ya que elimina la referencia al mismo tiempo.
Antoine Pinsard
66
La pregunta no es tonta; Necesito esa explicación también. Entonces, aquí asumimos que la relación es unilateral, el dueño de la relación es Commentquien tiene el campo FK en su tabla, mientras que BlogPost"posee" Commentsi hablamos del modelo de la vida real. Bueno.
WesternGun
3
Una cosa importante a tener en cuenta es que establecer un on_delete en Django NO crea una cláusula ON DELETE en la base de datos. El comportamiento especificado (como CASCADE) solo afectará las eliminaciones realizadas a través de Django, y no las eliminaciones sin procesar realizadas directamente en la base de datos.
JoeMjr2
2
Gran explicación ¡Obtiene un voto positivo de mi parte!
Homúnculo Reticulli
42

El on_deletemétodo se usa para decirle a Django qué hacer con las instancias del modelo que dependen de la instancia del modelo que elimine. (por ejemplo, una ForeignKeyrelación). El on_delete=models.CASCADEdice Django en cascada el efecto de borrado es decir, seguir borrando los modelos dependientes también.

Aquí hay un ejemplo más concreto. Suponga que tiene un Authormodelo que es ForeignKeyun Bookmodelo. Ahora, si elimina una instancia del Authormodelo, Django no sabría qué hacer con las instancias del Bookmodelo que dependen de esa instancia del Authormodelo. El on_deletemétodo le dice a Django qué hacer en ese caso. La configuración le on_delete=models.CASCADEindicará a Django que ponga en cascada el efecto de eliminación, es decir, elimine todas las Bookinstancias del modelo que dependen de la Authorinstancia del modelo que eliminó.

Nota: on_deletese convertirá en un argumento obligatorio en Django 2.0. En versiones anteriores, su valor predeterminado es CASCADE.

Aquí está toda la documentación oficial.

him229
fuente
37

Para su información, el on_deleteparámetro en los modelos es al revés de lo que parece. Pones on_deleteuna clave externa (FK) en un modelo para decirle a django qué hacer si se elimina la entrada FK a la que estás apuntando en tu registro. La opciones de nuestra tienda han utilizado la mayoría son PROTECT, CASCADEy SET_NULL. Aquí están las reglas básicas que he descubierto:

  1. Úselo PROTECTcuando su FK esté apuntando a una tabla de búsqueda que realmente no debería estar cambiando y que ciertamente no debería causar que su tabla cambie. Si alguien intenta eliminar una entrada en esa tabla de búsqueda, PROTECTevita que la elimine si está vinculada a algún registro. También evita que django elimine su registro solo porque eliminó una entrada en una tabla de búsqueda. Esta última parte es crítica. Si alguien eliminara el género "Femenino" de mi tabla de Género, CIERTAMENTE NO querría que eso elimine instantáneamente a todas las personas que tenía en mi tabla de Persona que tenían ese género.
  2. Úselo CASCADEcuando su FK esté apuntando a un registro "padre". Por lo tanto, si una persona puede tener muchas entradas PersonEthnicity (él / ella puede ser indio americano, negro y blanco), y esa persona se elimina, realmente me gustaría que se eliminen las entradas "child" PersonEthnicity. Son irrelevantes sin la Persona.
  3. Utilizar SET_NULLcuando no quiere que la gente se les permitirá borrar una entrada en una tabla de consulta, pero aún desea conservar su registro. Por ejemplo, si una persona puede tener una escuela secundaria, pero realmente no me importa si esa escuela secundaria desaparece en mi mesa de consulta, diría on_delete=SET_NULL. Esto dejaría mi registro de Persona ahí afuera; solo establecería el FK de la escuela secundaria en mi Persona como nulo. Obviamente, tendrás que permitir null=Trueese FK.

Aquí hay un ejemplo de un modelo que hace las tres cosas:

class PurchPurchaseAccount(models.Model):
    id = models.AutoField(primary_key=True)
    purchase = models.ForeignKey(PurchPurchase, null=True, db_column='purchase', blank=True, on_delete=models.CASCADE) # If "parent" rec gone, delete "child" rec!!!
    paid_from_acct = models.ForeignKey(PurchPaidFromAcct, null=True, db_column='paid_from_acct', blank=True, on_delete=models.PROTECT) # Disallow lookup deletion & do not delete this rec.
    _updated = models.DateTimeField()
    _updatedby = models.ForeignKey(Person, null=True, db_column='_updatedby', blank=True, related_name='acctupdated_by', on_delete=models.SET_NULL) # Person records shouldn't be deleted, but if they are, preserve this PurchPurchaseAccount entry, and just set this person to null.

    def __unicode__(self):
        return str(self.paid_from_acct.display)
    class Meta:
        db_table = u'purch_purchase_account'

Como último dato, ¿sabía que si no especifica on_delete(o no lo hizo), el comportamiento predeterminado es CASCADE? Esto significa que si alguien eliminó una entrada de género en su tabla de Género, ¡cualquier registro de Persona con ese género también se eliminará!

Yo diría: "En caso de duda, listo on_delete=models.PROTECT". Luego prueba tu aplicación. Rápidamente descubrirá qué FK deben etiquetarse como otros valores sin poner en peligro ninguno de sus datos.

Además, vale la pena señalar que en on_delete=CASCADErealidad no se agrega a ninguna de sus migraciones, si ese es el comportamiento que está seleccionando. Supongo que esto se debe a que es el valor predeterminado, por lo que poner on_delete=CASCADEes lo mismo que no poner nada.

HelenM
fuente
12

Como se mencionó anteriormente, CASCADE eliminará el registro que tenga una clave foránea y haga referencia a otro objeto que se eliminó. Entonces, por ejemplo, si tiene un sitio web de bienes raíces y tiene una Propiedad que hace referencia a una Ciudad

class City(models.Model):
    # define model fields for a city

class Property(models.Model):
    city = models.ForeignKey(City, on_delete = models.CASCADE)
    # define model fields for a property

y ahora cuando la Ciudad se elimina de la base de datos, todas las Propiedades asociadas (por ejemplo, bienes inmuebles ubicados en esa ciudad) también se eliminarán de la base de datos

Ahora también quiero mencionar el mérito de otras opciones, como SET_NULL o SET_DEFAULT o incluso DO_NOTHING. Básicamente, desde la perspectiva de la administración, desea "eliminar" esos registros. Pero realmente no quieres que desaparezcan. Por muchos motivos. Alguien podría haberlo eliminado accidentalmente, o para auditar y monitorear. Y simple presentación de informes. Por lo tanto, puede ser una forma de "desconectar" la propiedad de una ciudad. Nuevamente, dependerá de cómo se escriba su solicitud.

Por ejemplo, algunas aplicaciones tienen un campo "eliminado" que es 0 o 1. Y todas sus búsquedas y vistas de lista, etc., cualquier cosa que pueda aparecer en informes o en cualquier lugar donde el usuario pueda acceder desde el front-end, excluye cualquier cosa que sea deleted == 1. Sin embargo, si crea un informe personalizado o una consulta personalizada para desplegar una lista de registros que se eliminaron y aún más para ver cuándo se modificó por última vez (otro campo) y quién (es decir, quién lo eliminó y cuándo). eso es muy ventajoso desde el punto de vista ejecutivo.

Y no olvide que puede revertir eliminaciones accidentales tan simples como deleted = 0para esos registros.

Mi punto es, si hay una funcionalidad, siempre hay una razón detrás de esto. No siempre es una buena razón. Pero una razón. Y a menudo uno bueno también.

George Mogilevsky
fuente
3
Esto fue útil porque aclaró en qué dirección ocurre la CASCADA. La respuesta aceptada no está clara si no está familiarizado con las cascadas SQL.
codescribblr
¡Gracias, muy agradecido!
George Mogilevsky
2
Me upvote esta respuesta porque responde mi duda acerca de la dirección en el modelo de relación
edepe
6

Aquí está la respuesta a su pregunta que dice: ¿por qué usamos on_delete?

Cuando se elimina un objeto al que hace referencia una ForeignKey, Django emula de manera predeterminada el comportamiento de la restricción de SQL ON DELETE CASCADE y también elimina el objeto que contiene la ForeignKey. Este comportamiento se puede anular especificando el argumento on_delete. Por ejemplo, si tiene una ForeignKey anulable y desea que se establezca como nula cuando se elimina el objeto al que se hace referencia:

user = models.ForeignKey(User, blank=True, null=True, on_delete=models.SET_NULL)

Los valores posibles para on_delete se encuentran en django.db.models:

CASCADA: elimina en cascada; el valor por defecto.

PROTEGER: Evita la eliminación del objeto al que se hace referencia al proteger ProtectedError, una subclase de django.db.IntegrityError.

SET_NULL: establece el ForeignKey nulo; esto solo es posible si null es True.

SET_DEFAULT: establece ForeignKey en su valor predeterminado; se debe establecer un valor predeterminado para ForeignKey.

Sonia Rani
fuente
Las palabras simples me dejan claro ya que no soy maduro con SQL y Django también. Gracias.
wm.p1us
2

Digamos que tiene dos modelos, uno llamado Persona y otro llamado Empresas .

Por definición, una persona puede crear más de una empresa.

Teniendo en cuenta que una empresa puede tener una sola persona, queremos que cuando se elimine a una persona, también se eliminen todas las empresas asociadas con esa persona.

Entonces, comenzamos creando un modelo de Persona, como este

class Person(models.Model):
    id = models.IntegerField(primary_key=True)
    name = models.CharField(max_length=20)

    def __str__(self):
        return self.id+self.name

Entonces, el modelo de Empresas puede verse así

class Companies(models.Model):
    title = models.CharField(max_length=20)
    description=models.CharField(max_length=10)
    person= models.ForeignKey(Person,related_name='persons',on_delete=models.CASCADE)

Observe el uso de on_delete=models.CASCADEen las empresas modelo. Es decir, eliminar todas las empresas cuando se elimina la persona que lo posee (instancia de la clase Persona).

Tiago Martins Peres 李大仁
fuente
1

Reoriente su modelo mental de la funcionalidad de "CASCADA" pensando en agregar un FK a una cascada ya existente (es decir, una cascada). La fuente de esta cascada es una clave primaria. Los borrados fluyen hacia abajo.

Por lo tanto, si define on_delete de un FK como "CASCADE", está agregando el registro de este FK a una cascada de eliminaciones que se originan en el PK. El registro de FK puede participar en esta cascada o no ("SET_NULL"). De hecho, un registro con un FK puede incluso evitar el flujo de las eliminaciones. Construye una presa con "PROTEGER".

Gregory
fuente
0

Usar CASCADE significa en realidad decirle a Django que elimine el registro referenciado. En el ejemplo de la aplicación de encuesta a continuación: Cuando se elimina una 'Pregunta', también se eliminarán las Opciones que tiene esta Pregunta.

p. ej. Pregunta: ¿Cómo se enteró de nosotros? (Opciones: 1. Amigos 2. Anuncio de TV 3. Motor de búsqueda 4. Promoción de correo electrónico)

Cuando elimine esta pregunta, también eliminará todas estas cuatro opciones de la tabla. Tenga en cuenta en qué dirección fluye. No tiene que poner on_delete = models.CASCADE en Question Model póngalo en la opción.

from django.db import models



class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.dateTimeField('date_published')

class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    choice_text = models.CharField(max_legth=200)
    votes = models.IntegerField(default=0)
Kunal
fuente