6

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.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Jordan Reiter
  • 20,467
  • 11
  • 95
  • 161

2 Answers2

23

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.

Jordan Reiter
  • 20,467
  • 11
  • 95
  • 161
  • Hi Jordan, I've been looking for a while for a solution for a simple question: Does the on delete cascade behaviour applies only to ManyToOne fields or also to OneToOne fields. In the Django docs they only mention it for the ManyToOne field. For your question I understood that a OneToOne relationship behaves exactly the same as a ManyToOne when it comes to deleting objects.. can you confirm it? – diegopau Apr 30 '15 at 10:44
  • 1
    There isn't a ManyToOne field; just the ForeignKey field. And as I noted, OneToOneField accepts all the same arguments as ForeignKey does. I think a OneToOneField is basically identical to the ForeignKey field in implementation, with the addition of sticking to just one related record rather than many. One-to-one does not actually exist within relational databases (although you can have constraints that enforce it); it's just implemented programmatically within Django. – Jordan Reiter May 01 '15 at 03:47
  • Thanks a lot for the info Jordan. Sorry for the mistake, I wanted to say many-to-one relationships. – diegopau May 01 '15 at 10:44
  • 1
    Those are handled by the ForeignKey field. – Jordan Reiter May 01 '15 at 16:57
0

For this use case you should use a simple ForeignKey. OneToOne means there is one, and can only be one and it can only be associated with this one specific other object. The deletion occurs because it doesn’t make sense to have a null onetoone field, it CAN'T be associated with anything else.

Rob Osborne
  • 4,897
  • 4
  • 32
  • 43
  • 1
    Except that this would suggest that more than one `Foo` could be tied to a single user, which is not the case at all. Consider a case where you have a database of individuals at a school. Some of them have logins, some of them don't. But there are no cases where a single login is associated with multiple People. Actually, you know what, let me change my example so it's clearer. – Jordan Reiter Aug 05 '11 at 22:38
  • 1
    You can use ForeignKey with unique=True. It still means you need to access via User.person_set.get(), but will prevent duplicates. – Matthew Schinckel Aug 06 '11 at 05:43
  • What Matthew said :-) Maybe it's just me but I think of OneToOne as there is one and there is ALWAYS one; bidirectional. If it's optional but unique I use ForeignKey with unique-True. – Rob Osborne Aug 06 '11 at 13:07
  • I guess I always thought about a one-to-one like this: if Object A has a one-to-one pointing to Object B, that means that Object A might have an Object B (if the one-to-one is allowed to be NULL). I guess the other interpretation is that if Object A has a one-to-one to B, then really B may or may not have an object A attached to **it**. I guess I understand that, since A.ForeignKey -> B means B may have one or more As attached to it. Makes sense, but for simplicity I'm sticking with the OneToOne – Jordan Reiter Aug 23 '11 at 18:49