#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.

Change History (0)

Note: See TracTickets for help on using tickets.
Back to Top