OneToOne relation with the User model (django.contrib.auth) without cascading delete

Jordan Reiter picture Jordan Reiter · Aug 6, 2011 · Viewed 7.5k times · Source

I'm a bit confused about how the OneToOneField works when deletion comes into play. The only quasi-authoritative info I can find is from this thread on django-developers:

I don't know if you discovered this yet, but the delete is working in one direction, but not in the direction you're expecting it to. For instance, using the models you posted in another message:

class Place(models.Model): 
    name = models.CharField(max_length = 100)  
class Restaurant(models.Model): 
    place = models.OneToOneField(Place, primary_key=True)  

If you create a Place and a Restaurant that is linked to it, deleting the Restaurant will not delete the Place (this is the problem you're reporting here), but deleting the Place will delete the Restaurant.

I have the following model:

class Person(models.Model):
    name = models.CharField(max_length=50)
    # ... etc ...
    user = models.OneToOneField(User, related_name="person", null=True, blank=True)

It's set up this way so I can easily access person from a User instance using user.person.

However, when I try to delete a User record in admin, naturally it's cascading backwards to my Person model, just as the thread discussed, showing something along the lines of:

Are you sure you want to delete the user "JordanReiter2"? All of the following related items will be deleted:

  • User: JordanReiter
    • Person: JordanReiter
      • Submission: Title1
      • Submission: Title2

Needless to say I do not want to delete the Person record or any of its descendants!

I guess I understand the logic: because there is a value in the OneToOne field in the Person record, deleting the User record would create a bad reference in the user_id column in the database.

Normally, the solution would be to switch where the OneToOne field is located. Of course, that's not realistically possible since the User object is pretty much set by django.contrib.auth.

Is there any way to prevent a deletion cascade while still having a straightforward way to access person from user? Is the only way to do it creating a User model that extends the django.contrib version?

Update

I changed the model names so hopefully now it's a little clearer. Basically, there a thousands of Person records. Not every person has a login, but if they do, they have one and only one login.

Answer

Jordan Reiter picture Jordan Reiter · Aug 6, 2011

Turns out that both ForeignKey and OneToOneField have an attribute on_delete that you can set to models.SET_NULL, like so:

class Person(models.Model):
    name = models.CharField(max_length=50)
    # ... etc ...
    user = models.OneToOneField(User, on_delete=models.SET_NULL, related_name="person", null=True, blank=True)

This results in the behavior I was wanting: the User model is deleted without touching the Person record. I overlooked it because it's not explicitly listed under OneToOneField, it simply says

Additionally, OneToOneField accepts all of the extra arguments accepted by ForeignKey...

Easy to miss.