Подтвердить что ты не робот

Создание модели профиля с использованием как InlineAdmin, так и post_save сигнала в Django

Я создал модель профиля (с отношением 1 к 1 с моделью пользователя), как описано в Расширение существующей модели пользователя. Модель профиля имеет необязательное отношение "много-к-одному" к другой модели:

class Profile(models.Model):
    user = models.OneToOneField(User, primary_key=True)
    account = models.ForeignKey(Account, blank=True, null=True, on_delete=models.SET_NULL)

Как описано там, я также создал встроенный администратор:

class ProfileInline(admin.StackedInline):
    model = Profile
    can_delete = False
    verbose_name_plural = 'profiles'
# UserAdmin and unregister()/register() calls omitted, they are straight copies from the Django docs

Теперь, если я не выбираю account в admin при создании пользователя, модель профиля не будет создана. Поэтому я connect в сигнал post_save, снова просто следуя документации:

@receiver(post_save, sender=User)
def create_profile_for_new_user(sender, created, instance, **kwargs):
    if created:
        profile = Profile(user=instance)
        profile.save()

Это работает отлично, пока я не выбираю account в admin, но если да, я получу исключение IntegrityError, сообщая мне, что duplicate key value violates unique constraint "app_profile_user_id_key" DETAIL: Key (user_id)=(15) already exists.

По-видимому, встроенный администратор пытается создать сам экземпляр profile, но мой обработчик сигнала post_save уже создал его в это время.

Как исправить эту проблему, соблюдая все следующие требования?

  • Независимо от того, как создается новый пользователь, с ним всегда будет ссылка profile.
  • Если пользователь выбирает account в админе во время создания пользователя, этот account будет установлен в новой модели profile. Если нет, поле null.

Среда: Django 1.5, Python 2.7

Похожие вопросы:

4b9b3361

Ответ 1

Проблему можно избежать, установив primary_key=True на OneToOneField, указывая на модель User, как вы сами поняли.

Причина, по которой это работает, кажется довольно простой.

При попытке создать экземпляр модели и установить pk вручную перед ее сохранением, Django попытается найти запись в базе данных с этим pk и обновить ее, а не слепо пытаться создать новую. Если он не существует, он создает новую запись, как ожидалось.

Когда вы устанавливаете OneToOneField в качестве первичного ключа, а Django Admin устанавливает это поле в соответствующий идентификатор модели User, это означает, что pk уже установлен, и Django попытается сначала найти существующую запись.

Это то, что происходит с OneToOneField в качестве первичного ключа:

  • Django Admin создает новый экземпляр User без id.
  • Django Admin сохраняет экземпляр User.
    • Так как pk (в данном случае id) не установлен, Django пытается создать новую запись.
    • Новая запись id автоматически устанавливается базой данных.
    • Крючок post_save создает новый экземпляр Profile для этого экземпляра User.
  • Django Admin создает новый экземпляр Profile с его User, установленным пользователем id.
  • Django Admin сохраняет экземпляр Profile.
    • Так как pk (в данном случае User) уже установлен, Django пытается извлечь существующую запись с этим pk.
    • Django находит существующую запись и обновляет ее.

Если ядро ​​не задает первичный ключ, Django вместо этого добавляет поле, использующее функциональность базы данных auto_increment: база данных устанавливает pk в следующее наибольшее значение, которое не существует. Это означает, что поле фактически останется пустым, если вы не установите его вручную, и поэтому Django всегда будет пытаться вставить новую запись, что приведет к конфликту с ограничением уникальности на OneToOneField.

Вот что вызывает исходную проблему:

  • Django Admin создает новый экземпляр User без id.
  • Django Admin сохраняет экземпляр User, крюк post_save, создавая новый экземпляр Profile, как и раньше.
  • Django Admin создает новый экземпляр Profile без id (автоматически добавленное поле pk).
  • Django Admin сохраняет экземпляр Profile.
    • Так как pk (в данном случае id) не установлен, Django пытается создать новую запись.
    • В базе данных сообщается о нарушении ограничений уникальности таблицы в поле User.
    • Django выбрасывает исключение. Сегодня вы не пойдете в космос.

Ответ 2

Кажется, что установка primary_key=True в OneToOneField, соединяющая модель профиля с моделью User, устраняет эту проблему. Тем не менее, я не думаю, что понимаю все последствия этого и почему это помогает.

Я оставлю это здесь как подсказку, но если это лучшее решение и кто-то может придумать хорошо написанное объяснение, я бы поднял/принял это и, возможно, удалил мой.