我创建了'profile'模型(与用户模型的关系为1对1),如Extending the existing user model所述 . 配置文件模型与另一个模型具有可选的多对一关系:
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
,则不会创建配置文件模型 . 所以我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
就可以正常工作,但如果我这样做,我会得到一个 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
相关问题:
- Creating a extended user profile(类似的症状,但原因结果却不同)
2 回答
可以通过在指向
User
模型的OneToOneField
上设置primary_key=True
来避免此问题,因为您已经弄明白了 .这种方法起作用的原因似乎相当简单 .
当您尝试创建模型实例并在保存之前手动设置
pk
时,Django将尝试使用pk
在数据库中查找记录并更新它而不是盲目地尝试创建新记录 . 如果不存在,则按预期创建新记录 .当您将
OneToOneField
设置为主键并且Django Admin将该字段设置为相关的User
模型的ID时,这意味着pk
已经设置,Django将首先尝试查找现有记录 .这是将
OneToOneField
设置为主键时发生的情况:Django Admin创建新的
User
实例,没有id
.Django Admin保存
User
实例 .因为未设置
pk
(在这种情况下为id
),Django会尝试创建新记录 .新记录的
id
由数据库自动设置 .post_save
hook为该User
实例创建一个新的Profile
实例 .Django Admin创建新的
Profile
实例,其user
设置为用户的id
.Django Admin保存
Profile
实例 .因为已经设置了
pk
(在这种情况下为user
),Django会尝试使用pk
获取现有记录 .Django查找现有记录并对其进行更新 .
如果您没有't set the primary key explicitly, Django instead adds a field that uses the database'功能:数据库将
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抛出异常 . 你今天不会去太空 .
似乎在将配置文件模型连接到
User
模型的OneToOneField
上设置primary_key=True
修复了此问题 . 但是,我不认为我理解其中的所有含义及其有用的原因 .我会把这个留在这里作为暗示,但如果这是最好的解决方案,有人可以提出一个写得很好的解释,我会投票/接受,并可能删除我的 .