#36428 closed Bug (invalid)
Collector.delete() calls sort() but does not order deletions correctly when nullable FK is present with non-null value
Reported by: | Andréas Kühne | Owned by: | |
---|---|---|---|
Component: | Database layer (models, ORM) | Version: | 5.1 |
Severity: | Normal | Keywords: | |
Cc: | Andréas Kühne | Triage Stage: | Unreviewed |
Has patch: | no | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
When using DeleteView or Model.delete() in Django 5.1.8, I encountered a IntegrityError from PostgreSQL. The issue stems from the deletion order of models: Django attempts to delete a parent (ActivityLocation) before its dependent child (BookableItem), despite on_delete=models.CASCADE being set.
This occurs even though the Collector calls .sort(), which is supposed to reorder models in dependency-safe order. The BookableItem.location FK is nullable (null=True), but the actual instance has a non-null FK set.
Models to reproduce:
from django.db import models class ActivityLocation(models.Model): name = models.CharField(max_length=100) class BookableItem(models.Model): name = models.CharField(max_length=100) location = models.ForeignKey( ActivityLocation, on_delete=models.CASCADE, related_name='bookable_items', null=True, blank=True, )
Steps to reproduce:
# Create parent and child location = ActivityLocation.objects.create(name="Test Location") item = BookableItem.objects.create(name="Test Item", location=location) # Try to delete the location location.delete()
What I think should happen:
BookableItem should be deleted before ActivityLocation, as Django is responsible for enforcing deletion order (Postgres won’t do it automatically with deferred constraints).
What actually happens:
Django runs Collector.delete() which internally calls sort(), but the model deletion order remains:
[ActivityLocation, BookableItem]
If I however change to this:
from django.db import models class BookableItem(models.Model): name = models.CharField(max_length=100) location = models.ForeignKey( ActivityLocation, on_delete=models.CASCADE, related_name='bookable_items', )
Then the code works as expected.
Hello Andréas, I unfortunately cannot reproduce against
main
,5.2.x
, or5.1.6
with the following test against SQLite and Postgrestests/delete/models.py
tests/delete/tests.py
In all cases the issued SQL is correct and of the form
It would be quite surprising if cascade deletion dependency resolving was broken since 5.1.x given the way it's extensively tested and used in the wild so I highly suspect something else is at play here.
Perhaps you have model signal receivers connected (
pre_delete
,post_delete
) that might be interfering?