I have a Django 1.3 app for which I am using South 0.7.3 for DB migrations. I have a problem where an on_delete=models.SET_NULL
rule doesn't seem to be firing when the parent entity is deleted, thus giving me a constraint violation from the underlying DB (which is Postgres 8.4).
The relevant parts of the entity defns are:
class AccessPeriod:
....
class Payment:
period = models.ForeignKey(
AccessPeriod, related_name = "payments", db_index = True,
null = True, on_delete = models.SET_NULL )
After some digging, I discovered that South doesn't actually insert the ON DELETE
clause into the SQL it generates for migrations, so the DB is definitely not going to do the nullifying on the broken relationships itself:
http://south.aeracode.org/ticket/763
Then I read the Django docs for on_delete rules, which state (my emphasis):
When an object referenced by a ForeignKey is deleted, Django by default emulates the behavior of the SQL constraint ON DELETE CASCADE and also deletes the object containing the ForeignKey. This behavior can be overridden by specifying the on_delete argument.
The "emulates" part suggested to me that Django tries to implement the on_delete behaviour itself and does not rely on the underlying DB to automatically execute this as part of the transaction, thus making the South bug irrelevant.
I had a poke through db/models/deletion.py
in the Django source and based on the implementation of SET()
/ SET_NULL()
and collect()
it certainly seems like Django is supposed to do this itself, however, if I try to delete an AccessPeriod from the Django admin, I get constraint violations on the Payments table for the ID it still references which is now deleted, i.e. it doesn't seem like Django is calling SET_NULL()
on the Payment.period
relationship as part of the call to accessPeriod.delete()
.
Am I doing something wrong here, or misunderstanding what Django should be doing? Just trying to avoid manually hacking the DB to insert the ON DELETE rule myself, which feels extremely brittle and horrible.
In my specific case, I traced this bug down to declarations of ModelAdmin inline in my models.py code causing my model classes to be instantiated incorrectly, with broken on_delete
behaviour the most visible side-effect of this. From my commit message:
Fixed issue with cascading deletes not being done properly in Django DB.
Turns out it's really important to not declare your ModelAdmins at the global scope in models.py, otherwise all the relationships between the different models get calculated before all the models are loaded and lots of them get left out. Really isn't that emphatic about this in the Django documentation.
So in my original (broken) code, I would have the ModelAdmin for each Model declared immediately after it, a la:
class AccessPeriod( models.Model ):
....
class AccessPeriodAdmin( models.ModelAdmin ):
....
# This causes the metaclass setup for AccessPeriod to happen right now,
# and since related models for AccessPeriod are not all declared yet,
# relationship handling behaviour becomes broken for AccessPeriod
admin.site.register( AccessPeriod, AccessPeriodAdmin )
class Payment( models.Model ):
....
class PaymentAdmin( models.ModelAdmin ):
....
# Same effect on the Payment model here
admin.site.register( Payment, PaymentAdmin )
The solution was to move all the ModelAdmin declarations out of myapp/models.py
and into myapp/admin.py
so that all the metaclass setup for each class happens after all the model declarations have been handled, and then all the relationships started behaving properly again.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With