Opened 2 years ago

Closed 14 months ago

#23076 closed Bug (fixed)

Cascaded deletion of polymorphic models fails

Reported by: jernej@… Owned by: nobody
Component: Database layer (models, ORM) Version: master
Severity: Normal Keywords:
Cc: mmitar@…, james@…, tzanke@… Triage Stage: Accepted
Has patch: yes Needs documentation: no
Needs tests: yes Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Currently, when deleting polymorphic models (for example by using django-polymorphic), the django.db.models.deletion.Collector incorrectly assumes that all models are of the same type as the first model in the list. Then, it generates incorrect delete statements when polymorphic models are used which then result in an IntegrityError when the transaction is committed.

This is a patch that applies to 1.6.x and master (see pull request 2940) that fixes this issue and it is directly based on a patch submitted by Kronuz at the end of ticket #16128.

Attachments (1)

delete_fails_with_m2m_to_proxy_model.patch (3.4 KB) - added by James Murty 14 months ago.
Patch file for Django PR #5399

Download all attachments as: .zip

Change History (16)

comment:1 Changed 2 years ago by Mitar

Cc: mmitar@… added

comment:2 Changed 2 years ago by Nick Sandford

Needs tests: set

comment:3 Changed 2 years ago by Tim Graham

The related issue in django-polymorphic seems to be https://github.com/chrisglass/django_polymorphic/issues/34.

I'd like someone with more knowledge of the ORM to review this and comment on the solution. It's difficult for me to tell from the patch, but I guess there may be a performance penalty if we don't assume that all objects are of the same type.

The patch obviously requires tests in order to be merged.

comment:4 Changed 2 years ago by Tim Graham

Triage Stage: UnreviewedAccepted

Accepting on the basis of the problem. Not sure about the solution and the patch still needs tests.

comment:5 Changed 2 years ago by Carl Meyer

Resolution: wontfix
Status: newclosed

I don't think this is a bug in Django, or Django's problem to fix. The patch adds ~40 new lines of code to the deletion collector and makes it significantly more complicated (and almost certainly introduces performance regressions; one of them is even called out with a FIXME in the patch), all to support something which violates one of the basic assumptions of the Django ORM (that querysets are homogenous).

Closing as wontfix.

comment:6 Changed 2 years ago by Jernej Kos

Sorry, but the FIXME you mention is also present in current Django master and is not introduced by this patch. The patch simply augments current code and leaves existing stuff as it is (including the comment). The only part that it adds is the one that iterates over actual models. Please see:

https://github.com/django/django/blob/master/django/db/models/deletion.py#L191

As for the fix, please advise how would one then properly handle cascaded deletion of polymorphic models? Or are you saying that polymorphic models should not be supported and/or used with the Django's ORM?

comment:7 Changed 2 years ago by Mitar

I don't think it introduces performance regressions, for non-polymorphic models the code behaves exactly the same as it is currently, concrete_model_objs containing only one element, so for each loops doing only one iteration.

Last edited 2 years ago by Mitar (previous) (diff)

comment:8 in reply to:  6 Changed 2 years ago by Carl Meyer

Replying to kostko:

Sorry, but the FIXME you mention is also present in current Django master and is not introduced by this patch. The patch simply augments current code and leaves existing stuff as it is (including the comment). The only part that it adds is the one that iterates over actual models. Please see:

https://github.com/django/django/blob/master/django/db/models/deletion.py#L191

Yes, you're right, sorry about that. The diff showed it in green (probably because of indentation changes) and I didn't notice that it had already been there previously. My review of the patch was cursory, since I don't think this is a bug in Django regardless.

As for the fix, please advise how would one then properly handle cascaded deletion of polymorphic models? Or are you saying that polymorphic models should not be supported and/or used with the Django's ORM?

I'm saying that until/unless polymorphic models are part of Django and fully supported by the ORM, Django's code should not be made more complex in order to accommodate them (unless the changes are otherwise generally beneficial, for instance providing additional customization points), and it is the responsibility of a third-party add-on that implements them to make it work.

For an illustration of the problem, see the fact that the pull request had no tests, and it would be impossible to implement tests for it without deep poking into ORM internals. If you can't reproduce the problem using supported APIs, it's a good sign that the fix for the problem may not belong in Django.

I'm not familiar enough with django-polymorphic to say how it should fix the problem. It seems to me that an overridden queryset.delete() might be able to adjust things such that the existing collector could handle it. If an additional hook or customization point in Django would allow django-polymorphic to insert the delete-cascade behavior it needs, I'd be more inclined to support a patch like that, rather than a patch that adds support to Django's delete-collector for a scenario that Django doesn't support.

comment:9 Changed 14 months ago by James Murty

Resolution: wontfix
Status: closednew

Please consider re-opening this issue as it is related to Django core's handling of proxy models in general, not just django-polymorphic.

The root cause of this issue is that Django's deletion collector does not include M2M reverse references to proxy parents/ancestors of a model when deleting it; it only finds these relationships for concrete parent models. This means that deleting a proxy-based model object with extant M2M relationships targeting a proxy parent will fail with integrity errors, if you are using a database like PostgreSQL that enforces foreign key constraints.

I have created a Pull Requests based on the current master branch that adds unit tests demonstrating the issue when run against PostgreSQL: https://github.com/django/django/pull/5404

The same issue affects 1.8, and I can follow up with a PR for that branch if it would be useful?

We have done some work investigating this issue and have the rough beginnings of patches for the 1.7 and 1.8 branches, using a different approach to the patches earlier in this ticket – namely, adjusting the get_deleted_objects and related code path to find reverse M2M relationships on proxy models.

We could proceed with these patches, but only if the issue is considered relevant and serious enough to fix in Django core.

[Edit: Updated PR link to latest version based on master branch]

Last edited 14 months ago by James Murty (previous) (diff)

Changed 14 months ago by James Murty

Patch file for Django PR #5399

comment:10 Changed 14 months ago by James Murty

Cc: james@… added

comment:11 Changed 14 months ago by Anssi Kääriäinen

I suggest you do the work against master. It is unlikely we will fix this in 1.8 or earlier, as this isn't a regression or severe enough failure.

This might be related to #25505 and #18012.

comment:12 Changed 14 months ago by TZanke

Cc: tzanke@… added

comment:13 Changed 14 months ago by James Murty

Thanks for the feedback. I have submitted PR 5404 with the tests rebased onto master instead of the 1.7.x branch, and will do any related work against master.

Last edited 14 months ago by James Murty (previous) (diff)

comment:14 Changed 14 months ago by James Murty

Please see the new ticket #25520 which is based on this issue but is more specific to the new proposed tests (and potential patch)

comment:15 Changed 14 months ago by Simon Charette <charette.s@…>

Resolution: fixed
Status: newclosed

In 211486f3:

Fixed #23076, #25505 -- Fixed deletion of intermediate proxy models.

Thanks to James Murty for his work on an alternate patch.

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