Search code examples
djangodjango-modelsdjango-1.9

Django cascade delete doesn't remove pk of referenced model instance


In Django 1.9 I have related objects defined in models.py as follows:

from django.db import models

class Course(models.Model):
    title = models.CharField(max_length=10)

class Note(models.Model):
    course = models.ForeignKey(Course)

When I delete a Course, I expect all related Note s to delete via the default cascade behaviour. What I get is the following behaviour:

>>> from test1.models import Course, Note

#First, create a Course and associated Note
>>> c1 = Course()
>>> c1.save()
>>> n1 = Note(course=c1)
>>> n1.save()
>>> c1.pk
4
>>> n1.pk
4

#Next, delete the Course, and see if Note deletes too
>>> c1.delete()
(2, {'test1.Course': 1, 'test1.Note': 1})
>>> c1.pk
>>> n1.pk
1    #pk remains
>>> Note.objects.get(pk=4)
Traceback (most recent call last):
...    test1.models.DoesNotExist: Note matching query does not exist.

#Finally, try deleting the Note manually
>>> n1.delete()
(0, {'test1.Note': 0})
>>> n1.pk
>>> #finally the pk is gone!

It seems that the database is correctly updated but only the Course object is updated locally, while the Note object is not (i.e. keeps its pk).

Why is this, and how can I get Note to also delete pk so that it's in sync with the db?

Extra info: My reason for needing this behaviour is that I have used statements like if note.pk: elsewhere to check if a given Note is already saved in the database. Unfortunately, this style of cascade delete renders these statements useless because pk's exist even when db entries have been deleted.


Solution

  • This is expected behaviour. Once Django has fetched something from the database (n1 in this case) it isn't going to know about changes to it until you fetch it again (either with Note.objects.get(pk=4) or n1.refresh_from_db().

    When you call c1.delete(), Django deletes that row from the database and clears the pk on the object. It also deletes all related objects in the database that are set to cascade. However it has no way of knowing what other already-instantiated objects are referencing that object - how could it?

    Here is a simpler example of the same behaviour, without foreign keys:

    # Fetch a Course
    >>> c1 = Course.objects.get(pk=1)
    # Fetch the same course again, but save it to a different variable
    >>> c2 = Course.objects.get(pk=1)
    

    Note that you now have two instances of the same row in the database. Now:

    # Delete c1
    >>> c1.delete()
    
    # c2 Hasn't changed... how could it? That would be magical.
    >>> c2.pk
    1
    
    # but if you refresh c2 from the database... it isn't there
    >>> c2.refresh_from_db()
    DoesNotExist: Course matching query does not exist.