django-polymorphic icon indicating copy to clipboard operation
django-polymorphic copied to clipboard

Cannot delete or update a parent row: a foreign key constraint fails

Open thenewguy opened this issue 11 years ago • 42 comments

Using the delete_selected admin action can cause an error similar to the following:

IntegrityError: (1451, 'Cannot delete or update a parent row: a foreign key constraint fails (foo2.bar, CONSTRAINT format_ptr_id_refs_id_270cb9d612063f8e FOREIGN KEY (format_ptr_id) REFERENCES foo1 (id))')

thenewguy avatar Apr 23 '13 23:04 thenewguy

Hi, can you tell me how to reproduce this issue? I've tried everything in the example project, and didn't get this issue.

vdboor avatar Jul 05 '13 09:07 vdboor

Hello,

We have the same issue when deleting list of polymorphic elements. What is strange is that, when running tests, we are unable to reproduce the problem. ( no constraint violations )

Here is the example

  1. we have an "Group" object containing Question objects of 3 types ( "Rating" "FreeText" and "Choice" all subclass of "Question").
  2. "Question" holds the ForeignKey on "Group". 3a) If we create a "Group" with all questions of the same type, deleting the "Group" works fine. 3b) If we create mixed "Question" types ( one FreeText and one Rating in this case ), deleting the "Group" will fail with the following error "update or delete on table "sample_question" violates foreign key constraint "questionptr_id_refs_id_b59c6fec" on table "sample_rating""

We are using postgresql and it seems that the delete statements generated during the delete will depend on the FIRST type found in the deleted list.

  • DELETE FROM "sample_freetext" WHERE "question_ptr_id" IN (3, 2, 1)
  • DELETE FROM "sample_group" WHERE "id" IN (1)
  • DELETE FROM "sample_question" WHERE "id" IN (3, 2, 1)
  • COMMIT --> ERROR ( show above )

"sample_rating" elements are never deleted. Indeed, if we create a test, the result of Rating.objects.count(), will be 1 NOT 0.

If we create Rating first, then FreeText, it is the "sample_freetext" that is never delete...

As soon as we have time, we will investigating the code, to see where ( and how ) the delete query set is "generate".

dsaradini avatar Aug 05 '13 14:08 dsaradini

Hum that is very strange. I will look as well.

chrisglass avatar Aug 05 '13 14:08 chrisglass

@dsaradini you mention writing a test for it - would you mind sharing it in a gist or as a PR? What version of the software are you running? Can you try using trunk if you're using a released version?

chrisglass avatar Aug 05 '13 15:08 chrisglass

@chrisglass We're using the Pypi version 0.5.1. I will try with the trunk as soon as I have a moment, and also provide a running code that demonstrate the problem.

dsaradini avatar Aug 05 '13 16:08 dsaradini

We tracked down the error to a problem in the deletion collector of the Django ORM.

The first problem is django/db/models/deletion.py:102. This method assumes that all objects in the list are the same model class (which is correct, if you use the normal Django manager). django-polymorphic is different. Therefore the code adds all objects with the first model class it finds in the list of objects.

The objects which get passed to the add method are fetched in the related_objects method (line 240, same file). This method uses _base_manager of the related model (which is the base model of the multitable model construction).

We fixed this bug by manually adding a _base_manager = models.Manager() to the PolymorhpicModel subclass, but this doesn't feel right.

Another note: this only happens with foreignkeys/relations on the polymorphic base model. If you have foreignkeys in one of the submodels, everything works just fine. Hope the remarks help to find a clean solution for this issue.

stephrdev avatar Sep 05 '13 12:09 stephrdev

Thanks @stephrdev for the great help here. Can you post some example code (or gist) to demonstrate this issue? I'd love to fire up a project in the debugger to figure this out, but am keenly looking for model code.

vdboor avatar Sep 05 '13 17:09 vdboor

I also ran into this issue today, and it was caused by a GenericRelation object on the model. The collector has special code for that. I fixed it in my code by returning the non-polymorphic query:

class ContentItemRelation(GenericRelation):
    def __init__(self, **kwargs):
        super(ContentItemRelation, self).__init__(to=ContentItem,
            object_id_field='parent_id', content_type_field='parent_type', **kwargs)

    def bulk_related_objects(self, objs, using=DEFAULT_DB_ALIAS):
        # Fix delete screen. Workaround for https://github.com/chrisglass/django_polymorphic/issues/34
        return super(ContentItemRelation, self).bulk_related_objects(objs).non_polymorphic()

Again guys, please provide a usable example project. My situation can be completely different from yours, so I won't be able to track all edge-cases without an example project.

vdboor avatar Sep 19 '13 16:09 vdboor

I ran into the problem today. What is the status?

habfast avatar Dec 12 '14 12:12 habfast

I am also running into this problem, is the GenericRelation still a valid solution? The documentation of Django says this object is deprecated since 1.7 and will be completely removed in 1.9.

svengt avatar Feb 17 '15 09:02 svengt

The _base_manager = Manager() fix from @stephrdev worked for us too. Seems something in the PolymorphicModel's manager isn't quite right?

mlncn avatar Mar 20 '15 01:03 mlncn

mincn, can you elaborate more about that solution? I'm having the same problem with version 0.6.1. I have a polymorphic class called Action, which has a foreign key to Campaign. I can't delete campaigns from the admin view because this integrity error raises. I have to delete the child classes first.

so far I fixed the issue with a pre_delete signal in the Campaign class. I iterate over the relation and delete objects one at a time. I guess it's awful for performance, but at least it works.

egamonal avatar Mar 31 '15 11:03 egamonal

We have encountered this issue and chased it down to a bug in Django core's handling of reverse M2M relationships that target proxy models. In brief, the issue isn't specific to polymorphic but affects Django as well. It's probably just much more likely to occur if you use polymorphic.

We have followed up in the relevant Django Track ticket: https://code.djangoproject.com/ticket/23076

Also created a new, more specific Django ticket: https://code.djangoproject.com/ticket/25520

jmurty avatar Oct 06 '15 01:10 jmurty

Thanks a lot - that really helps!

vdboor avatar Oct 07 '15 14:10 vdboor

Because others continue to struggle with this I have created a gist of the (truly awful and hacky) work-around we are using for this issue, which monkey-patches Django's deletion collector to include proxy models: https://gist.github.com/jmurty/2034c24b6f91a3eaf51a

I had intended to package this up more nicely in an installable project and include some related unit tests we have, but as always it's been hard to find the time.

Please read the comments in that gist and use at your own risk. It works for us... for you, maybe not so much.

jmurty avatar Feb 03 '16 23:02 jmurty

I am also hitting this problem. The context I have seen it is when using the 'Delete selected' action in the admin. It only seems to happen if I try and bulk-delete objects of multiple polymorphic subclasses at once.

I.e. if I have classes ModelA(PolymorphicModel), ModelB(ModelA) and ModelC(ModelA), then I can successfully bulk delete if I select all ModelB or all ModelC objects, but if I select a combination of ModelB and ModelC objects, the delete fails with a FK constraint violation.

mkjpryor-stfc avatar Dec 09 '16 09:12 mkjpryor-stfc

@mkjpryor-stfc We're seeing this same issue with the exact use-case that you described in your example. We're currently figuring out a workaround.

chance-focalcast avatar Jan 24 '17 18:01 chance-focalcast

To follow up on the monkey patch work-around I supplied earlier, the patch is now available in a more official location as part of our open-sourced ICEkit project. This approach has been working for us in production systems for months now, so while it is ugly it does work for us and it might help you too.

There are monkey-patches for Django versions 1.8 and 1.7 starting in the code here: https://github.com/ic-labs/django-icekit/blob/0.16/icekit/publishing/monkey_patches.py#L76

The patch functions must be called to apply them, here's how we do it in an AppConfig.ready method: https://github.com/ic-labs/django-icekit/blob/0.16/icekit/publishing/apps.py#L56

The patch should not be necessary in the latest versions of Django, so upgrading is probably the best option if you can, but in the meantime try the monkey-patches above.

jmurty avatar Jan 24 '17 22:01 jmurty

I'm still seeing this with Django 1.11. What is the recommended workaround at this time?

I have re-opened the Django bug, because the previous "fix" didn't fix it (though may have fixed some other problems.) https://code.djangoproject.com/ticket/23076

jstray avatar Aug 25 '17 23:08 jstray

Found an excellent workaround here https://github.com/django-polymorphic/django-polymorphic/issues/229#issuecomment-246613138

Seems to be a duplicate bug.

jstray avatar Aug 28 '17 00:08 jstray

Thanks for the pointer to that workaround @jstray and for following up on the Django issue. It's unfortunate the underlying problem isn't fixed in Django 1.11. I wasn't following along closely enough on the Django side to make sure it was really fixed.

jmurty avatar Aug 28 '17 03:08 jmurty

@jstray's workaround doesn't work for me with Django 1.11.

bogdanpetrea avatar Oct 18 '17 08:10 bogdanpetrea

This appears to also be broken in Django 2.0; I have been unable to find a workaround.

nscaife avatar Feb 13 '18 17:02 nscaife

Greetings from November 2019. This is still broken.

iMerica avatar Nov 06 '19 04:11 iMerica

Update:

This fails:

Basemodel.objects.all().delete()

but this succeeds:

for i in Basemodel.objects.all(): i.delete()

Obviously it's not ideal, but it's a workaround until a fix is submitted.

iMerica avatar Nov 06 '19 05:11 iMerica

I think at this point you should search for alternative solutions. Introducing django-polymorphic as a dependency to your project is almost guaranteed to cause pain.

This (and other breaking bugs) should be stated upfront in the readme file. I think most people come here and are given the impression that everything is working fine and smooth.

I'm not saying this is a bad project, but as long as it's not part of core Django it's very likely to encounter such breaking bugs that will not be fixed anytime soon.

bogdanpetrea avatar Nov 06 '19 10:11 bogdanpetrea

Base.objects.all().non_polymorphic().delete() also works well.

TomD3Wiz avatar Nov 27 '19 06:11 TomD3Wiz

I have to agree with @bogdanpetrea, given this bug hasn't yet been addressed in Django core, and is likely to manifest when using django-polymorphic (I lost a few hours on a project today encountering and resolving this very issue), could it be noted somewhere in the docs?

sambonner avatar Jan 24 '20 00:01 sambonner

i had the same problem deleting the parent model i was using the generic Delete view then i manually deleted the items connected to the parent then delete the parent item in view

Usamaraoo avatar Sep 02 '20 11:09 Usamaraoo

t's very likely to encounter such breaking bugs that will not be fixed anytime soon.

Well its 2021. How late can they get man

ZeroCoolHacker avatar Apr 21 '21 04:04 ZeroCoolHacker