Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is Django supposed to implement DB on_delete rules itself?

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.

like image 548
glenc Avatar asked Jul 11 '11 13:07

glenc


1 Answers

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.

like image 137
glenc Avatar answered Oct 15 '22 15:10

glenc