-
-
Notifications
You must be signed in to change notification settings - Fork 283
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Another cascading bug on CASCADE deletion #229
Comments
Have the problem too, but in Django 1.10 solution with class Animal(PolymorphicModel):
...
class Meta:
base_manager_name = 'base_objects' |
I've faced the same issue when trying to delete objects. Adding base manager solved it. What does it actually do, can somebody explain please? And why without base manager it throws an error? Thanks. |
... same issue here with Django 1.10. Adding the base_manager_name solved the problem, too. |
Thank you! @patient |
This is a horrifying bug -- and took up about two days of my team's effort to understand before we found this workaround. These seem to be the same bug: The latter has lots of intricate detail on what is going on. It's complicated. I'm not sure if the correct solution would be to change django or django-polymorphic. Also, it is likely to be difficult to write a failing test for this bug as sqlite does not enforce constraints, so it only fails on Postgres (and therefore only on a production server, not a development environment!) Nonetheless it would be really great to fix this. |
It's worth noting that tests are now running against postgres. |
When I updated from 2.0 to 2.0.1 the workaround with |
I'm on django 1.11 and django-polymorphic 2.0.2. This is the workaround I'm currently using:
|
I'm getting the same issue as well (Django 1.11, django-polymorphic 2.0.2) and used the fix proposed in #229 (comment). I don't know if this has any other implication so it would be nice to have a proper fix for that. |
I worked around by setting my own
|
Thanks @mumumumu |
The workaround of @mumumumu #229 (comment) worked for me, however now django admin generally uses the Edit: on second note, even on the frontend now all classes fail to display the child classes properly. |
I'm actually using the solution provided by #229 (comment) now. I'm on Django 2.0.6 and django-polymorphic 2.0.2.
|
Any update on this one? I'm facing the same issue in Django 2.0.6 and django-polymorphic 2.0.3. The workarounds provided above are not working in my case. |
I'm having this issue on the latest version of both Django and polymorphic. I've used the code from #229 (comment) and it's working for now. |
I'm having this issue with Django 2.1.7 and polymorphic 2.0.3. Solution #229 (comment) works! |
Could someone explain how to implement #299? Do I place this on the base polymorphic model, or the inheriting one, or the one referencing the base and what is the 'to' required argument?
|
I'm not in the habit of testing the admin interface, so I don't know what things _were_ like. But after this change, it's impossible to delete a workflow from the admin interface. Probably related to jazzband/django-polymorphic#229 which was never fixed; our workaround for it stopped working (and I'm keen to delete that workaround anyway, because we veered way into jank-ville here).
Proposed workarounds did not work for me on
|
I worked around this by reverting the default This fixes the cascade delete issue because Django's cascade deletion uses the default manager and needs the non-polymorphic queryset. However, we need to add a custom This was tested with Django 2.0.7 and django-polymorphic 2.0.2. # models.py
class Animal(PolymorphicModel):
farm = models.ForeignKey('Farm', on_delete=models.CASCADE)
name = models.CharField(max_length=100)
objects = models.Manager()
polymorphic = PolymorphicManager()
# admin.py
class AnimalInline(GenericStackedPolymorphicInline):
...
def get_queryset(self, request):
qs = self.model.polymorphic.get_queryset()
ordering = self.get_ordering(request)
if ordering:
qs = qs.order_by(*ordering)
if not self.has_change_permission(request):
qs = qs.none()
return qs |
Want to add here that we're on Django 3.0.2, Django-polymorphic 2.1.2 and #229 (comment) this solution from mumumumu works for us. We have a structure like so:
so a foreign key exists on the parent, base model, and it points at |
the above solution worked for me with Django 3.1.1, django-polymorphic 3.0.0 |
Noting that a tweak to @mumumumu's workaround works for SET_NULL too (which was the problem in my case).
I'm using Django 3.1 and django-polymorphic 3.0.0. |
workaround from #229 (comment) Works on Django 3.2.7 and django-polymorphic 3.0.0 |
For anyone coming here from the web, here's a full example of how to implement #229 (comment) from polymorphic.managers import PolymorphicManager, PolymorphicQuerySet
from polymorphic.models import PolymorphicModel
class CascadeDeletePolymorphicQuerySet(PolymorphicQuerySet):
"""
Patch the QuerySet to call delete on the non_polymorphic QuerySet, avoiding models.deletion.Collector typing problem
Based on workarounds proposed in: https://github.com/django-polymorphic/django-polymorphic/issues/229
See also: https://github.com/django-polymorphic/django-polymorphic/issues/34,
https://github.com/django-polymorphic/django-polymorphic/issues/84
Related Django ticket: https://code.djangoproject.com/ticket/23076
"""
def delete(self):
if not self.polymorphic_disabled:
return self.non_polymorphic().delete()
else:
return super().delete()
class CascadeDeletePolymorphicManager(PolymorphicManager):
queryset_class = CascadeDeletePolymorphicQuerySet
class Parent(PolymorphicModel):
objects = CascadeDeletePolymorphicManager()
class ChildA(Parent):
pass
class ChildB(Parent):
pass Honestly this looks like adding the updated delete method here on PolymorphicQuerySet would fix things. I'll see if I can turn this into a PR. |
While testing the PR, I discovered this bug because I couldn't delete applications I've created. After doing some research, I figured out this is due to a known bug on django-polymorphic. More on this issue can be found in this issue: jazzband/django-polymorphic#229
* add missing migration * WIP: "Provided Assets" from PSF to sponsors creates the ability to configure "Provided Assets" associated with a sponsorship that will be fulfilled by the PSF. The initial "ProvidedTextAsset" is intended to be used for PyCon US 2022 voucher codes, which will be unique to each voucher "type" and sponsorship level. Additionally, we will likely include "ProvidedFileAsset" that will be used for all sponsorships with Expo Hall benefits to share with them a common "Exhibitor Packet" * upload files/shared provided file assets for configured benefits * Make sure delete operations work as expected for Polymorphic models While testing the PR, I discovered this bug because I couldn't delete applications I've created. After doing some research, I figured out this is due to a known bug on django-polymorphic. More on this issue can be found in this issue: jazzband/django-polymorphic#229 * Refactor how to reconfigure sponsor benefit to avoid delete operations * Make sure the assets are also being updated * Add docstring to make it more clear that the input assets are decoupled from their configuration * Fix bad context variable name * Fields that configure relationship between assets and configuration should be read only for editing Co-authored-by: Bernardo Fontes <[email protected]>
While testing the PR, I discovered this bug because I couldn't delete applications I've created. After doing some research, I figured out this is due to a known bug on django-polymorphic. More on this issue can be found in this issue: jazzband/django-polymorphic#229
While testing the PR, I discovered this bug because I couldn't delete applications I've created. After doing some research, I figured out this is due to a known bug on django-polymorphic. More on this issue can be found in this issue: jazzband/django-polymorphic#229
* Make sure delete operations work as expected for Polymorphic models While testing the PR, I discovered this bug because I couldn't delete applications I've created. After doing some research, I figured out this is due to a known bug on django-polymorphic. More on this issue can be found in this issue: jazzband/django-polymorphic#229 * Fix identation * Add new due date column to required assets * Base command to notify sponsorship applications which have expiring required assets * Create new notification about required assets close to due date * Dispatch notifications via management command * Management command should look for future expiration dates * Add test to ensure due date within the email context * Disable asset input if past due date * Revert "Disable asset input if past due date" It's OK to allow them to submit after the deadline, as it is mostly for notifying them. If uploads after the deadline are acceptable, the sponsorship team will collect anything put into the boxes * cast "target_date" to a datetime.date so comparison works * update styling/wording on forms and emails * add max_length to RequiredTextAssets, render form to suit Co-authored-by: Ee Durbin <[email protected]>
I can confirm that this still works on polymorphic 3.1.0. I'd be happy to raise a PR for this, although it seems hacky. Any other suggestion on how to nicely integrate this into polymorphic? |
For some reason, this does not work for me. The delete method is never called when deleting all Farms. It would have seemed like a nice solution though. |
It seems, that this works as well:
|
setting update: the |
Not sure if this is the right place, but deletions seem to have multiple issue in django-polymorphic. The issues #34 and #229 appear to have similar root causes, but #34 leads here, so this is where ill post too. For my setup, i stumbled into either of them, depending on what/where i tried to delete my model instances. Most of the workarounds posted here did not help me, as some are outdated and others came with inconveniences, such as preventing polymorphic list entries in admin views. For some reason the NON_POLYMORPHIC_CASCADE did nothing for me, maybe it's for an issue i did not experience (yet). Might be useful to apply on top of the below, but my current models did not require it. To be clear, i did have problems with deletions in admin lists, as well as with simple actions such as qs.delete() - as long as the sets contained different child types. Looking for the errors always brought me to either #229 or #34, but im not sure if this actually has anything to do with CASCADES. Neither problem occurs when deleting a single instance. The issues appear to only occur when deleting a PolymorphicQuerySet with different child types included. At the core of this appears to sit django.db.models.deletion.Collector, which does not allow deletion for differing types and is used in QuerySet.delete() as well as separately in admin delete_selected, through get_deletion_objects. Except for the admin delete action, most deletion problems should thus be caused by/within QuerySet.delete() calls, for which no django-polymorphic specific implementation exists in the PolymorphicQuerySet. So i wrote my own. In theory these deletion problems could be avoided by deleting the objects sequentially, but that wouldnt be very efficient. Instead i attempted to write a version which splits the multi-typed PolymorphicQuerySet into multiple uniform-typed PolymorphicQuerySets (ie, each containing only instances of one polymorphic_ctype). Delete()ing those in an atomic block should both: simulate normal .delete() behavior and be decently efficient for larger deletions. class MyPolymorphicQuerySet(PolymorphicQuerySet):
def delete(self):
"""Delete the records in the current QuerySet."""
# Attempt to use Default
try:
with transaction.atomic():
deleted, _rows_count = super(MyPolymorphicQuerySet, self).delete()
return deleted, _rows_count
except (IntegrityError, ValueError):
pass
# Otherwise we have >= 2 types. Handle by splitting PolymorphicQuerySet per type
distinct_types = self._chain().values('polymorphic_ctype').distinct()
typed_qs_list: List(QuerySet) = []
for type_entry in distinct_types:
typed_qs = self._chain().filter(polymorphic_ctype=type_entry['polymorphic_ctype'])
typed_qs_list.append(typed_qs)
# Execute separate deletions per type to avoid above exceptions
deleted = 0
_rows_count = dict()
with transaction.atomic():
for typed_qs in typed_qs_list:
deletion_counter, rows = typed_qs.delete()
# Combine the returns of all deletion operations
deleted += deletion_counter
for key, value in rows.items():
if key in _rows_count:
_rows_count[key] += value
else:
_rows_count[key] = value
return deleted, _rows_count
class MyPolymorphicModel(PolymorphicModel):
# Adjust which QuerySet the default manager uses to include fix for QS Deletions
objects = PolymorphicManager.from_queryset(MyPolymorphicQuerySet)()
class Meta:
abstract = True With the above, simply use MyPolymorphicModel instead of the default PolymorphicModel for your definitions. Which potentially leaves us with admin-only deletion problems, which for some reason i only encountered for one of my models, not the others. This issue could be fixed by overwriting the base_manager with the non-polymorphic variant, as others have done. However, using the above, thats not possible anymore (i think). It's also not necessary, as we can simply overwrite the get_deletion_objects method to pass non_polymorphic() to super() instead. Which should have the same effect, but without the disadvantages of not being able to have polymorphic children in admin list views, among others. This fixed my remaining admin-related problems: class MyPolymorphicParentModelAdmin(MyModelAdmin, PolymorphicParentModelAdmin):
def get_deleted_objects(self, objs, request):
# Pass non_polymorphic() Queryset Contents to workaround 'Cannot Query X: must be Y instance' error
return super(MyPolymorphicParentModelAdmin, self).get_deleted_objects(objs.non_polymorphic(), request) |
# What this PR does Sometimes plugin sync fails with the following exception: ``` Cannot delete or update a parent row: a foreign key constraint fails (`schedules_oncallschedule`, CONSTRAINT `alerts_oncallschedul_team_id_4e633f4b_fk_user_mana` FOREIGN KEY (`team_id`) REFERENCES `user_management_team` (`id`))' ``` How to reproduce: 1. Create a new Grafana team 2. Create two schedules with different types (e.g. ICal and Web) and assign both schedules to the new team 3. Delete the team in Grafana 4. Trigger plugin sync, the sync will fail with the exception above This happens because the `OnCallSchedule` Django model is a polymorphic model and there's a [known bug](jazzband/django-polymorphic#229) in `django-polymorphic` with deleting related objects when using `SET_NULL` and `CASCADE`. This PR adds non-polymorphic versions of `SET_NULL` and `CASCADE` to use in schedule FKs as per this [comment](jazzband/django-polymorphic#229 (comment)). This also applies to two other schedule FKs: `organization` and `user_group`, which are not working properly as well. ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated - [x] Documentation added (or `pr:no public docs` PR label added if not required) - [x] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not required)
The root of the issue is that Django doesn't expect models to be automatically polymorphic during deletion and this messes up the order of the deletions leading to constraint errors. I've implemented a workaround on my project by first including the library source code to my repo so I could change it then I added a static global_polymorphic_disabled = 0 to the PolymorphicQuerySet class and in PolymorphicModelIterable.__iter__ I altered the if to take into account global_polymorphic_disabled:
Then I changed my base model classes to disable auto polymorphism for all deletions. I had to set global_polymorphic_disabled even for non polymorphic models to avoid the foreign key error.
|
While the accepted workaround works when foreign keys are defined on the parent PolymorphicModel, it will not mark foreign keys defined on the child classes. def POLYMORPHIC_CASCADE(collector, field, sub_objs, using):
CASCADE(collector, field, sub_objs.non_polymorphic(), using)
for sub_class in {type(sub_obj) for sub_obj in sub_objs}:
sub_class_objs = [sub_obj for sub_obj in sub_objs if isinstance(sub_obj, sub_class)]
CASCADE(collector, field, sub_class_objs, using) This will mark for deletion the foreign keys of the parent class in the first line, and then will use the default polymorphic behaviour to also go through child class foreign keys EDIT: I was using the bare-bones Collector class to find relations, using the NestedObjects collector from django.contrib.admin.utils actually works fine without this |
The solution in comment#229 worked with |
Hi,
I have an issue in django-polymorphic when deleting models from django-admin.
Models are defined as below
Now I am creating a
Farm
object with both aDog
and aCat
(problem doesn't appear if all polymorphic related objects are of the same sub-class.When trying to delete the
Farm
object in admin, I get the below hierarchyAnd when confirming the deletion, I get the error below
I tracked the problem down to be a problem in the deletion
Collector
class where related object toFarm
(i.e.Animal
) are of the sub-classes eitherDog
orCat
. So in the case above, we try to delete theCat
object from theDog
table.I solved my problem by using the
_base_manager
property of theModel
class . And referring to a non polymorphic object manager like belowNow when trying to delete the
Farm
object in admin I get the correct hierarchyI am using the below
My thought would be to add
_base_manager = models.Manager()
in the django-polymorphicPolymorphicModel
. What do you guy think about this ?Thanks,
Theo
The text was updated successfully, but these errors were encountered: