¿Qué hace on_delete en los modelos Django?
Estoy bastante familiarizado con Django, pero recientemente noté que existe una on_delete=models.CASCADE
opción con los modelos. He buscado la documentación sobre el mismo, pero no pude encontrar nada más que:
Modificado en Django 1.9:
on_delete
ahora se puede utilizar como segundo argumento posicional (anteriormente, normalmente solo se pasaba como argumento de palabra clave). Será un argumento obligatorio en Django 2.0.
Un caso de ejemplo 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 ).
¿Que es lo que models.CASCADE
hace? ( cualquier sugerencia en la documentación )
¿Qué otras opciones están disponibles ( si mi suposición es correcta? )?
¿Dónde reside la documentación para esto?
Este es el comportamiento a adoptar cuando se elimina el objeto referenciado . No es específico de Django; este es un estándar SQL. Aunque Django tiene su propia implementación además de SQL. (1)
Hay siete acciones posibles a tomar cuando ocurre tal evento:
CASCADE
: Cuando se elimina el objeto al que se hace referencia, elimine también los objetos que tengan referencias a él (cuando elimina una publicación de blog, por ejemplo, es posible que desee eliminar también los comentarios). Equivalente SQL:CASCADE
.PROTECT
: Prohibir la eliminación del objeto referenciado. Para eliminarlo tendrás que eliminar todos los objetos que hacen referencia a él manualmente. Equivalente SQL:RESTRICT
.RESTRICT
: (introducido en Django 3.1) Comportamiento similar alPROTECT
que coincide con SQLRESTRICT
con mayor precisión. (Ver ejemplo de documentación de Django )SET_NULL
: establece la referencia en NULL (requiere que el campo admita valores NULL). Por ejemplo, cuando elimina un usuario, es posible que desee conservar los comentarios que publicó en las publicaciones del blog, pero decir que fueron publicados 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 determinado. Éste no forma parte del estándar SQL y lo maneja enteramente 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
. (2)
Fuente: documentación de Django
Consulte también la documentación de PostgreSQL, por ejemplo.
En la mayoría de los casos, CASCADE
es el comportamiento esperado, pero para cada ForeignKey, siempre debes preguntarte cuál es el comportamiento esperado en esta situación. PROTECT
y SET_NULL
suelen ser útiles. Configurarlo CASCADE
donde no debería hacerlo puede potencialmente eliminar toda su base de datos en cascada, simplemente eliminando un solo usuario.
Nota adicional para aclarar la dirección de la cascada
Es curioso notar que la dirección de la CASCADE
acción no está clara para muchas personas. En realidad, es curioso notar que sólo la CASCADE
acción no está clara. Entiendo que el comportamiento en cascada puede resultar confuso, sin embargo debes pensar que es la misma dirección que cualquier otra acción . Por lo tanto, si siente que CASCADE
la dirección no está clara para usted, en realidad significa que on_delete
el comportamiento no está claro para usted.
En su base de datos, una clave externa está básicamente representada por un campo entero cuyo valor es la clave principal del objeto externo. Digamos que tiene una entrada comment_A , que tiene una clave externa para una entrada Article_B . Si eliminas la entrada comment_A , todo está bien. El artículo_B solía vivir sin el comentario_A y no se molesta si se elimina. Sin embargo, si eliminas el artículo_B , entonces el comentario_A entra en pánico. Nunca vivió sin artículo_B y lo necesita, es parte de sus atributos ( article=article_B
, pero ¿qué es artículo_B ???). Aquí es donde on_delete
interviene para determinar cómo resolver este error de integridad , ya sea diciendo:
- "¡No! ¡Por favor! ¡No lo hagas! ¡No puedo vivir sin ti!" (que se dice
PROTECT
oRESTRICT
en Django/SQL) - "Está 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
CASCADE
comportamiento). - "Está bien, tengo un amante de repuesto, haré referencia al artículo_C a partir de ahora" (
SET_DEFAULT
, o inclusoSET(...)
). - "No puedo afrontar la realidad, ¡seguiré gritando tu nombre incluso si eso es lo único que me queda!" (
DO_NOTHING
)
Espero que aclare la dirección de la cascada. :)
Notas a pie de página
(1) Django tiene su propia implementación además de SQL. Y, como lo menciona @JoeMjr2 en los comentarios a continuación , Django no creará las restricciones SQL. Si desea que su base de datos garantice las restricciones (por ejemplo, si su base de datos es utilizada por otra aplicación, o si se cuelga en la consola de la base de datos de vez en cuando), es posible que desee establecer las restricciones relacionadas manualmente usted mismo. Hay un ticket abierto para agregar soporte para restricciones de eliminación a nivel de base de datos en Django.
(2) En realidad, hay un caso en el que
DO_NOTHING
puede resultar útil: si desea omitir la implementación de Django e implementar la restricción usted mismo a nivel de base de datos.
El on_delete
método se utiliza para decirle a Django qué hacer con las instancias del modelo que dependen de la instancia del modelo que elimine. (por ejemplo, una ForeignKey
relación). Le on_delete=models.CASCADE
dice a Django que ponga en cascada el efecto de eliminación, es decir, que continúe eliminando también los modelos dependientes.
He aquí un ejemplo más concreto. Suponga que tiene un Author
modelo que está ForeignKey
en un Book
modelo. Ahora, si eliminas una instancia del Author
modelo, Django no sabrá qué hacer con las instancias del Book
modelo que dependen de esa instancia del Author
modelo. El on_delete
método le dice a Django qué hacer en ese caso. La configuración on_delete=models.CASCADE
le indicará a Django que ponga en cascada el efecto de eliminación, es decir, elimine todas las Book
instancias del modelo que dependen de la Author
instancia del modelo que eliminó.
Nota: on_delete
se convertirá en un argumento obligatorio en Django 2.0. En versiones anteriores el valor predeterminado es CASCADE
.
Aquí está toda la documentación oficial.
Para su información, el on_delete
parámetro en los modelos está al revés de lo que parece. Colocas on_delete
una 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. Las opciones que más ha utilizado nuestra tienda son PROTECT
, CASCADE
y SET_NULL
. Estas son las reglas básicas que he descubierto:
- Úselo
PROTECT
cuando su FK apunte a una tabla de búsqueda que realmente no debería cambiar y que ciertamente no debería causar que su tabla cambie. Si alguien intenta eliminar una entrada en esa tabla de búsqueda,PROTECT
le impide eliminarla si está vinculada a algún registro. También evita que Django borre su registro sólo porque eliminó una entrada en una tabla de búsqueda. Esta última parte es crítica. Si alguien eliminara el género "Mujer" de mi tabla Género, CIERTAMENTE NO quisiera que eso eliminara instantáneamente a todas y cada una de las personas que tenía en mi tabla Persona que tenían ese género. - Úselo
CASCADE
cuando su FK apunte a un registro "principal". Por lo tanto, si una persona puede tener muchas entradas de PersonEthnicity (puede ser indio americano, negro y blanco) y esa persona se elimina, realmente me gustaría que se eliminen todas las entradas de PersonEthnicity "secundarias". Son irrelevantes sin la Persona. - Úselo
SET_NULL
cuando desee que las personas puedan eliminar una entrada en una tabla de búsqueda, pero aún desee 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 tabla de consulta, diríaon_delete=SET_NULL
. Esto dejaría mi registro de Persona ahí fuera; simplemente establecería el FK de la escuela secundaria en mi Persona como nulo. Obviamente, tendrás que permitirnull=True
ese FK.
A continuación se muestra 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ías que si no especificas on_delete
(o no lo hiciste), el comportamiento predeterminado es CASCADE
? Esto significa que si alguien eliminó una entrada de género en su tabla Género, ¡también se eliminaron todos los registros de Persona con ese género!
Yo diría: "En caso de duda, configure on_delete=models.PROTECT
". Luego prueba tu aplicación. Descubrirá rápidamente qué FK deben etiquetarse como otros valores sin poner en peligro ninguno de sus datos.
Además, vale la pena señalar que on_delete=CASCADE
en realidad 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=CASCADE
es lo mismo que no poner nada.
Como se mencionó anteriormente, CASCADE eliminará el registro que tiene una clave externa y hace referencia a otro objeto que fue eliminado. 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 elimine de la base de datos, todas las propiedades asociadas (por ejemplo, bienes raíces ubicadas 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 muchas razones. Es posible que alguien lo haya eliminado accidentalmente o con fines de auditoría y seguimiento. Y reportajes sencillos. Por lo que puede ser una manera de “desconectar” la propiedad de una Ciudad. Nuevamente, dependerá de cómo esté escrita 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 los informes o en cualquier lugar al que el usuario pueda acceder desde la interfaz, excluye todo lo 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 por 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 de forma tan sencilla como deleted = 0
la de esos registros.
Lo que quiero decir es que si hay una funcionalidad, siempre hay una razón detrás de ella. No siempre es una buena razón. Pero una razón. Y muchas veces también una buena.
Usar CASCADE significa en realidad decirle a Django que elimine el registro al que se hace referencia. En el siguiente ejemplo de la aplicación de encuesta: Cuando se elimina una 'Pregunta', también se eliminarán las opciones que tiene esa pregunta.
Por ejemplo, Pregunta: ¿Cómo se enteró de nosotros? (Opciones: 1. Amigos 2. Anuncio de televisión 3. Motor de búsqueda 4. Promoción por correo electrónico)
Cuando elimine esta pregunta, también eliminará estas cuatro opciones de la tabla. Tenga en cuenta en qué dirección fluye. No tienes que poner on_delete=models.CASCADE en el Modelo de pregunta, ponlo en la Elecció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)