What does on_delete do on Django models?
This is the behaviour to adopt when the referenced object is deleted. It is not specific to Django; this is an SQL standard. Although Django has its own implementation on top of SQL. (1)
There are seven possible actions to take when such event occurs:
CASCADE: When the referenced object is deleted, also delete the objects that have references to it (when you remove a blog post for instance, you might want to delete comments as well). SQL equivalent:
PROTECT: Forbid the deletion of the referenced object. To delete it you will have to delete all objects that reference it manually. SQL equivalent:
RESTRICT: (introduced in Django 3.1) Similar behavior as
PROTECTthat matches SQL's
RESTRICTmore accurately. (See django documentation example)
SET_NULL: Set the reference to NULL (requires the field to be nullable). For instance, when you delete a User, you might want to keep the comments he posted on blog posts, but say it was posted by an anonymous (or deleted) user. SQL equivalent:
SET_DEFAULT: Set the default value. SQL equivalent:
SET(...): Set a given value. This one is not part of the SQL standard and is entirely handled by Django.
DO_NOTHING: Probably a very bad idea since this would create integrity issues in your database (referencing an object that actually doesn't exist). SQL equivalent:
NO ACTION. (2)
Source: Django documentation
See also the documentation of PostgreSQL for instance.
In most cases,
CASCADE is the expected behaviour, but for every ForeignKey, you should always ask yourself what is the expected behaviour in this situation.
SET_NULL are often useful. Setting
CASCADE where it should not, can potentially delete all of your database in cascade, by simply deleting a single user.
Additional note to clarify cascade direction
It's funny to notice that the direction of the
CASCADE action is not clear to many people. Actually, it's funny to notice that only the
CASCADE action is not clear. I understand the cascade behavior might be confusing, however you must think that it is the same direction as any other action. Thus, if you feel that
CASCADE direction is not clear to you, it actually means that
on_delete behavior is not clear to you.
In your database, a foreign key is basically represented by an integer field which value is the primary key of the foreign object. Let's say you have an entry comment_A, which has a foreign key to an entry article_B. If you delete the entry comment_A, everything is fine. article_B used to live without comment_A and don't bother if it's deleted. However, if you delete article_B, then comment_A panics! It never lived without article_B and needs it, and it's part of its attributes (
article=article_B, but what is article_B???). This is where
on_delete steps in, to determine how to resolve this integrity error, either by saying:
- "No! Please! Don't! I can't live without you!" (which is said
- "All right, if I'm not yours, then I'm nobody's" (which is said
- "Good bye world, I can't live without article_B" and commit suicide (this is the
- "It's OK, I've got spare lover, and I'll reference article_C from now" (
SET_DEFAULT, or even
- "I can't face reality, and I'll keep calling your name even if that's the only thing left to me!" (
I hope it makes cascade direction clearer. :)
(1) Django has its own implementation on top of SQL. And, as mentioned by @JoeMjr2 in the comments below, Django will not create the SQL constraints. If you want the constraints to be ensured by your database (for instance, if your database is used by another application, or if you hang in the database console from time to time), you might want to set the related constraints manually yourself. There is an open ticket to add support for database-level on delete constrains in Django.
(2) Actually, there is one case where
DO_NOTHINGcan be useful: If you want to skip Django's implementation and implement the constraint yourself at the database-level.
on_delete method is used to tell Django what to do with model instances that depend on the model instance you delete. (e.g. a
ForeignKey relationship). The
on_delete=models.CASCADE tells Django to cascade the deleting effect i.e. continue deleting the dependent models as well.
Here's a more concrete example. Assume you have an
Author model that is a
ForeignKey in a
Book model. Now, if you delete an instance of the
Author model, Django would not know what to do with instances of the
Book model that depend on that instance of
Author model. The
on_delete method tells Django what to do in that case. Setting
on_delete=models.CASCADE will instruct Django to cascade the deleting effect i.e. delete all the
Book model instances that depend on the
Author model instance you deleted.
on_delete will become a required argument in Django 2.0. In older versions it defaults to
on_delete parameter in models is backwards from what it sounds like. You put
on_delete on a foreign key (FK) on a model to tell Django what to do if the FK entry that you are pointing to on your record is deleted. The options our shop have used the most are
SET_NULL. Here are the basic rules I have figured out:
PROTECTwhen your FK is pointing to a look-up table that really shouldn't be changing and that certainly should not cause your table to change. If anyone tries to delete an entry on that look-up table,
PROTECTprevents them from deleting it if it is tied to any records. It also prevents Django from deleting your record just because it deleted an entry on a look-up table. This last part is critical. If someone were to delete the gender "Female" from my Gender table, I CERTAINLY would NOT want that to instantly delete any and all people I had in my Person table who had that gender.
CASCADEwhen your FK is pointing to a "parent" record. So, if a Person can have many PersonEthnicity entries (he/she can be American Indian, Black, and White), and that Person is deleted, I really would want any "child" PersonEthnicity entries to be deleted. They are irrelevant without the Person.
SET_NULLwhen you do want people to be allowed to delete an entry on a look-up table, but you still want to preserve your record. For example, if a Person can have a HighSchool, but it doesn't really matter to me if that high-school goes away on my look-up table, I would say
on_delete=SET_NULL. This would leave my Person record out there; it just would just set the high-school FK on my Person to null. Obviously, you will have to allow
null=Trueon that FK.
Here is an example of a model that does all three things:
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'
As a last tidbit, did you know that if you don't specify
on_delete (or didn't), the default behavior is
CASCADE? This means that if someone deleted a gender entry on your Gender table, any Person records with that gender were also deleted!
I would say, "If in doubt, set
on_delete=models.PROTECT." Then go test your application. You will quickly figure out which FKs should be labeled the other values without endangering any of your data.
Also, it is worth noting that
on_delete=CASCADE is actually not added to any of your migrations, if that is the behavior you are selecting. I guess this is because it is the default, so putting
on_delete=CASCADE is the same thing as putting nothing.