12

我创建了一个“配置文件”模型(与用户模型一对一的关系),如扩展现有用户模型中所述。配置文件模型与另一个模型具有可选的多对一关系:

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在创建用户时没有在管理员中选择一个,则不会创建配置文件模型。因此,我再次按照文档连接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,但如果我这样做,我会得到一个IntegrityError异常,告诉我duplicate key value violates unique constraint "app_profile_user_id_key" DETAIL: Key (user_id)=(15) already exists.

显然,内联管理员试图自己创建profile实例,但我的post_save信号处理程序当时已经创建了它。

如何解决此问题,同时满足以下所有要求?

  1. 无论新用户是如何创建的,之后总会有一个profile模型链接到它。
  2. 如果用户account在创建用户时在 admin 中选择了一个,这account将在profile之后在新模型上设置。如果不是,则该字段为null.

环境:Django 1.5,Python 2.7

相关问题:

4

2 回答 2

24

正如您自己发现的那样,可以通过设置指向模型primary_key=True来避免该问题OneToOneFieldUser

这样做的原因似乎很简单。

当您尝试创建模型实例并pk在保存之前手动设置它时,Django 将尝试在数据库中找到一条记录pk并更新它,而不是盲目地尝试创建新记录。如果不存在,它将按预期创建新记录。

当您将 设置OneToOneField为主键并且 Django 管理员将该字段设置为相关User模型的 ID 时,这意味着pk已经设置,Django 将首先尝试查找现有记录。

OneToOneField这是将集合作为主键发生的情况:

  1. Django Admin 创建新User实例,没有id.
  2. Django Admin 保存User实例。
    1. 因为pk(在这种情况下id)没有设置,Django 尝试创建一个新记录。
    2. 新记录id由数据库自动设置。
    3. 该钩子为该实例post_save创建一个新实例。ProfileUser
  3. Django Admin 创建新Profile实例,并将其user设置为用户的id.
  4. Django Admin 保存Profile实例。
    1. 因为pk(在这种情况下user)已经设置,Django 尝试使用该来获取现有记录pk
    2. Django 找到现有记录并更新它。

如果您没有显式设置主键,Django 会添加一个使用数据库auto_increment功能的字段:数据库将 设置pk为下一个不存在的最大值。这意味着该字段实际上将留空,除非您手动设置它,因此 Django 将始终尝试插入新记录,从而导致与OneToOneField.

这就是导致原始问题的原因:

  1. Django Admin 创建新User实例,没有id.
  2. Django Admin 保存User实例,post_save钩子像以前一样创建一个新Profile实例。
  3. Django Admin 创建新Profile实例,没有id(自动添加的pk字段)。
  4. Django Admin 保存Profile实例。
    1. 因为pk(在这种情况下id)没有设置,Django 尝试创建一个新记录。
    2. 数据库报告违反了表在user字段上的唯一性约束。
    3. Django 抛出异常。你今天不会去太空。
于 2013-08-16T03:05:11.310 回答
2

似乎设置primary_key=TrueOneToOneField配置文件模型连接到User模型可以解决此问题。但是,我认为我并不理解这一切的含义以及它为什么会有所帮助。

我将把它留在这里作为提示,但如果这是最好的解决方案并且有人可以提出一个写得很好的解释,我会赞成/接受并可能删除我的。

于 2013-01-15T19:23:55.063 回答