Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't my model's "on_delete=models.CASCADE," generate a cascading foreign key constraint?

I am using Django, Python 3.7, and PostgreSQL 9.5. How do I mark up my model such taht a cascading foreign key constraint gets generated? I currently have this in my models.py file ...

class ArticleStat(models.Model):
    article = models.ForeignKey(Article, on_delete=models.CASCADE, )

When I run make migrations my_project in the management console, it produces a file that contains this ...

    migrations.CreateModel(
        name='ArticleStat',
        fields=[
            ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
            ...
            ('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='my_project.Article')),
        ],
    ),

However, when I run the migration (using migrate my_project 0001), the resulting foreign key does not contain a cascade delete constraint. This is what the description looks like in PostgreSQL ...

"my_project_articlesta_article_id_a02a5add_fk_my_project" FOREIGN KEY (article_id) REFERENCES my_project_article(id) DEFERRABLE INITIALLY DEFERRED

How else can I get my models.py file to output a cascading delete foreign key constraint?

like image 412
Dave Avatar asked Jan 15 '19 20:01

Dave


People also ask

What does On_delete models Cascade do in Django?

When we set the on_delete parameter as CASCADE, deleting the reference object will also delete the referred object. This option is most useful in many relationships. Suppose a post has comments; when the Post is deleted, all the comments on that Post will automatically delete.

What does On_delete cascade mean?

ON DELETE CASCADE constraint is used in MySQL to delete the rows from the child table automatically, when the rows from the parent table are deleted. For example when a student registers in an online learning platform, then all the details of the student are recorded with their unique number/id.

What is Cascade option in ForeignKey?

A foreign key with cascade delete means that if a record in the parent table is deleted, then the corresponding records in the child table will automatically be deleted. This is called a cascade delete in SQL Server.

What does Django DB models model ForeignKey On_delete protect do?

The PROTECT argument of the ForeignKey on_delete option prevents the referenced object from being deleted if it already has an object referencing it in the database. Put simply, Django will prevent a post from deletion if it already has comments.


1 Answers

As described in the django documentation of ForeignKey django merely emulates this behaviour instead of deferring it to the database.

Django emulates the behavior of the SQL constraint ON DELETE CASCADE and also deletes the object containing the ForeignKey.

So to answer your question: This is expected behavior and will still work just like you would expect it to work on the database level.

How else can I get my models.py file to output a cascading delete foreign key constraint?

You can't as django currently does not support this feature. However there is a ticket discussing to add it: https://code.djangoproject.com/ticket/21961


Edit for further clarification on how to enforce this at database-level

While I highly recommend to just let django handle this for you there might be reasons not to do so.

To opt out of database table creation or deletion operations you can set Options.managed to False in the Meta class of ArticleStat. That would however also mean that you are now responsible to do the migrations manually e.g. writing the CREATE TABLE statement to define the table including the foreign key constraint (which you thus have full control over now). Another consideration to take into account is that you should instruct django to not do anything on deletion of a referenced Article object anymore (as your database is now responsible for that). That can be ensured by setting on_delete to models.DO_NOTHING.

Put together your ArticleStat would now look like this:

class ArticleStat(models.Model):
    article = models.ForeignKey(Article, on_delete=models.DO_NOTHING)

    class Meta:
        managed = False

A comment about signals prompted me to revisit this and list some pitfalls to consider.

  • opting out means opting out of django signals as well. In particular pre_delete and post_delete won't be fired for cascaded objects anymore.

  • As mentioned in the ticket description mixing database- and django-cascading won't play nicely together.

    If a model A references a model B using CASCADE_DB, but model B references model C using regular CASCADE, a deletion of A won't cascade all the way to C.

That being said I couldn't find any definite proof of why django is handling this the way it does currently.

like image 118
Fynn Becker Avatar answered Sep 19 '22 00:09

Fynn Becker