Django ignore ondelete=models.CASCADE

I created custom auth user model which using foreign key from company model. I try to set rule that in case if company deleted have to be deleted all users who belong to company.
When i do makemigration i see inside migration file for Users model :

(‘company’, models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=‘myuserauth.company’)),

So everything looks fine. But in moment when i do migrate Table created with this DDL :

CREATE TABLE `myuserauth_myuser` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `password` varchar(128) NOT NULL,
  `last_login` datetime(6) DEFAULT NULL,
  `is_superuser` tinyint(1) NOT NULL,
  `first_name` varchar(150) NOT NULL,
  `last_name` varchar(150) NOT NULL,
  `date_joined` datetime(6) NOT NULL,
  `email` varchar(255) NOT NULL,
  `username` varchar(150) NOT NULL,
  `is_active` tinyint(1) NOT NULL,
  `is_admin` tinyint(1) NOT NULL,
  `company_id` bigint(20) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `email` (`email`),
  KEY `myuserauth_myuser_company_id_389f550c_fk_myuserauth_company_id` (`company_id`),
  CONSTRAINT `myuserauth_myuser_company_id_389f550c_fk_myuserauth_company_id` FOREIGN KEY (`company_id`) REFERENCES `myuserauth_company` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

I.e . migrate don`t set on delete CASCADE and due to it it have on delete RESTRICT by default. Is it bug or i do something wrong ?
This is model file :

from django.db import models
from django.contrib.auth.models import (
    AbstractUser, BaseUserManager
)

# Create your models here.
class MyUserManager(BaseUserManager):
    def create_company(self):
        company = Company()
        company.save()
        return company

    def create_user(self, email, password=None, company=None):
        """
        Creates and saves a User with the given email, date of
        birth and password.
        """
        if not email:
            raise ValueError('Users must have an email address')

        if company is None:
            company = self.create_company()

        user = self.model(
            email=self.normalize_email(email),
        )

        user.set_password(password)
        user.company = company
        user.save(using=self._db)
        return user

    def create_superuser(self, email, password=None, company=None):
        """
        Creates and saves a superuser with the given email, date of
        birth and password.
        """
        user = self.create_user(
            email,
            password=password,
        )
        user.is_admin = True
        user.is_superuser = True
        user.is_active = True
        user.save(using=self._db)
        return user

class Company(models.Model):
    name = models.CharField(max_length=150, null=True, blank=True)


class MyUser(AbstractUser):
    email = models.EmailField(
        verbose_name='email address',
        max_length=255,
        unique=True,
    )
    username = models.CharField(
        verbose_name='not unique username',
        max_length=150,
        unique=False,
    )
    is_active = models.BooleanField(default=False)
    is_admin = models.BooleanField(default=False)
    company = models.ForeignKey(Company, on_delete=models.CASCADE)

    objects = MyUserManager()

    USERNAME_FIELD = 'email'
    EMAIL_FIELD = 'email'
    REQUIRED_FIELDS = []

    def __str__(self):
        return self.email

    def has_perm(self, perm, obj=None):
        "Does the user have a specific permission?"
        # Simplest possible answer: Yes, always
        return True

    def has_module_perms(self, app_label):
        "Does the user have permissions to view the app `app_label`?"
        # Simplest possible answer: Yes, always
        return True

    @property
    def is_staff(self):
        "Is the user a member of staff?"
        # Simplest possible answer: All admins are staff
        return self.is_admin

From the docs on on_delete:

on_delete doesn’t create an SQL constraint in the database. Support for database-level cascade options may be implemented later.

This leads me to believe it’s handled only by the ORM and not within the database itself.

Hi. Thanks for your answer. It is look like 7 years people wait this feature without success. I have only some questions left. If to use ORM only cascade implementation is it possible to trust to it. I mean : do i need to do extrta checks after deletion to be sure is cascade really worked. may be you know what behavior in cases if cascade deletion interrupted in the middle of process is all data will be rolled back ? What will happen if lost database connection in this moment. I understand that this questions is more about deep internals but may be you know.

I’ve never seen a reported bug about CASCADE not working, or any questions on SO about it either. Pretty sure the functionality here is solid.

If you’re going to use a framework like Django, you really have to just trust it. Of course, if you find a bug, then report it and put code in to mitigate it, but don’t try and mitigate bugs that you have no real reason to think exist …

The issue here - and I can see his point - isn’t that there may or may not be a bug in the code itself, but that there may be transactional implications involved in the cascade processing. (The old “what happens if something fundamental fails mid-transaction” situation.)

And the answer is “Yes, Django processes these cascades in a transaction” - From the docs on Database Transactions:

Django uses transactions or savepoints automatically to guarantee the integrity of ORM operations that require multiple queries, especially delete() and update() queries.

1 Like