Opened 35 minutes ago
#37471 new Bug
No system check for GenericRelation on a model with DB_CASCADE, leaving orphaned generic rows
| Reported by: | Akshat Gupta | Owned by: | |
|---|---|---|---|
| Component: | Database layer (models, ORM) | Version: | 6.1 |
| Severity: | Normal | Keywords: | DB_CASCADE, GenericRelation, on_delete |
| Cc: | Akshat Gupta | Triage Stage: | Unreviewed |
| Has patch: | no | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description
The docs say that database-level and Python-level on_delete variants cannot be mixed in the same model or in related models. System checks enforce this with models.E050 and fields.E323.
GenericRelation is a Python-level cleanup, but it is not checked. If a model has a GenericRelation and a DB_CASCADE foreign key, the database deletes the model's rows and Django never deletes the generic related objects. They stay in the table with no error and no check warning.
class Tag(models.Model): content_type = models.ForeignKey(ContentType, models.CASCADE) object_id = models.PositiveIntegerField() content_object = GenericForeignKey() class Author(models.Model): pass class Book(models.Model): author = models.ForeignKey(Author, models.DB_CASCADE) tags = GenericRelation(Tag)
>>> author = Author.objects.create() >>> book = Book.objects.create(author=author) >>> book.tags.create() >>> author.delete() >>> Book.objects.count(), Tag.objects.count() (0, 1)
manage.py check reports no issues. With models.CASCADE instead of models.DB_CASCADE, the Tag row is deleted.
Why it happens: Collector.collect() skips DB_CASCADE relations, so Book instances are never collected. Their private fields are never visited, so GenericRelation.bulk_related_objects() never runs. GenericRelation.remote_field.on_delete is DO_NOTHING, so Model._check_related_fields() does not see it as a Python-level variant.
I verified this on 6.1.3 and on main with SQLite. I also ran random schemas with database and Python on_delete variants against the equivalent all-Python schemas. Among schemas that pass all checks, the only difference in the final data was orphaned generic rows.
Suggested fix: add a system check error for a model that has a GenericRelation (any private field with bulk_related_objects()) and a DB_CASCADE foreign key. It would not fire for DB_SET_NULL or DB_SET_DEFAULT, because those do not delete the model's rows. This is the same kind of gap as #37254 (implicit M2M through tables), which was fixed in 6.1.x.
I am happy to work on a patch once the approach is agreed.