Отношение OneToOne с моделью пользователя (django.contrib.auth) без каскадного удаления

Я немного смущен тем, как работает OneToOneField, когда в игру вступает удаление. Единственная квазиавторитетная информация, которую я могу найти, находится в этой ветке на django-developers. :

Я не знаю, обнаружили ли вы это еще, но удаление работает в одном направлении, но не в том направлении, в котором вы ожидаете. Например, используя модели, которые вы разместили в другом сообщении:

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

Если вы создаете место и связанный с ним ресторан, удаление ресторана не приведет к удалению места (это проблема, о которой вы сообщаете здесь), но удаление места приведет к удалению ресторана.

У меня есть следующая модель:

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

Он настроен таким образом, что я могу легко получить доступ к person из экземпляра User, используя user.person.

Однако, когда я пытаюсь удалить запись User в admin, естественно, она каскадируется обратно к моей модели Person, как обсуждалось в ветке, показывая что-то вроде:

Вы уверены, что хотите удалить пользователя "JordanReiter2"? Все следующие связанные элементы будут удалены:

  • User: JordanReiter
    • Person: JordanReiter
      • Submission: Title1
      • Представление: Title2

Излишне говорить, что я не хочу удалять запись Person или любые ее потомки!

Думаю, я понимаю логику: поскольку в поле OneToOne в записи Person есть значение, удаление записи User создаст неверную ссылку в столбце user_id в базе данных.

Обычно решением было бы переключить, где находится поле OneToOne. Конечно, на самом деле это невозможно, так как объект User в значительной степени задается django.contrib.auth.

Есть ли способ предотвратить каскадное удаление, сохраняя при этом прямой доступ к человеку из user? Является ли единственным способом создать модель User, расширяющую версию django.contrib?

Обновлять

Я изменил названия моделей, так что, надеюсь, теперь это немного понятнее. По сути, там тысячи записей о людях. Не у каждого человека есть логин, но если он есть, то у него один и только один логин.


person Jordan Reiter    schedule 05.08.2011    source источник


Ответы (2)


Оказывается, оба ForeignKey и OneToOneField имеют атрибут on_delete, для которого вы можете установить значение models.SET_NULL, например:

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)

Это приводит к тому поведению, которого я хотел: модель User удаляется, не касаясь записи Person. Я пропустил его, потому что он явно не указан в разделе OneToOneField, он просто говорит

Кроме того, OneToOneField принимает все дополнительные аргументы, принимаемые ForeignKey...

Легко пропустить.

person Jordan Reiter    schedule 05.08.2011
comment
Привет, Джордан, я некоторое время искал решение простого вопроса: применяется ли каскадное поведение при удалении только к полям ManyToOne или также к полям OneToOne. В документах Django это упоминается только для поля ManyToOne. По вашему вопросу я понял, что отношение OneToOne ведет себя точно так же, как ManyToOne, когда дело доходит до удаления объектов... можете ли вы это подтвердить? - person diegopau; 30.04.2015
comment
Поле ManyToOne отсутствует; только поле ForeignKey. И, как я заметил, OneToOneField принимает все те же аргументы, что и ForeignKey. Я думаю, что OneToOneField в основном идентичен полю ForeignKey в реализации, с добавлением привязки только к одной связанной записи, а не ко многим. На самом деле в реляционных базах данных один-к-одному не существует (хотя у вас могут быть ограничения, навязывающие его); это просто реализовано программно в Django. - person Jordan Reiter; 01.05.2015
comment
Большое спасибо за информацию Джордан. Извините за ошибку, я хотел сказать отношения многие-к-одному. - person diegopau; 01.05.2015
comment
Они обрабатываются полем ForeignKey. - person Jordan Reiter; 01.05.2015

Для этого варианта использования вы должны использовать простой ForeignKey. OneToOne означает, что есть один, и может быть только один, и он может быть связан только с этим одним конкретным другим объектом. Удаление происходит потому, что не имеет смысла иметь поле onetoone null, оно НЕ МОЖЕТ быть связано ни с чем другим.

person Rob Osborne    schedule 05.08.2011
comment
За исключением того, что это предполагает, что более одного Foo может быть привязано к одному пользователю, что совсем не так. Рассмотрим случай, когда у вас есть база данных лиц в школе. У кого-то есть логины, у кого-то нет. Но не бывает случаев, когда один логин связан с несколькими людьми. На самом деле, знаете что, позвольте мне изменить мой пример, чтобы он был понятнее. - person Jordan Reiter; 06.08.2011
comment
Вы можете использовать ForeignKey с уникальным = True. Это по-прежнему означает, что вам нужно получить доступ через User.person_set.get(), но предотвратит дублирование. - person Matthew Schinckel; 06.08.2011
comment
Что сказал Мэтью :-) Может быть, это только я, но я думаю об OneToOne, поскольку он есть и ВСЕГДА один; двунаправленный. Если это необязательно, но уникально, я использую ForeignKey с уникальным значением True. - person Rob Osborne; 06.08.2011
comment
Наверное, я всегда думал об отношениях один-к-одному примерно так: если объект А имеет взаимно-однозначный указатель на объект Б, это означает, что объект А может иметь объект Б (если разрешено взаимно-однозначное быть NULL). Я предполагаю, что другая интерпретация заключается в том, что если объект A имеет взаимно-однозначное соответствие с B, то действительно B может иметь или не иметь объект A, присоединенный к ему. Думаю, я понимаю, что, поскольку A.ForeignKey -> B означает, что B может иметь один или несколько прикрепленных к нему As. Имеет смысл, но для простоты я придерживаюсь OneToOne - person Jordan Reiter; 23.08.2011