在Django中使用InlineAdmin和post_save信号创建配置文件模型

Hen*_*ger 11 python django django-signals django-models django-admin

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

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

正如那里记录的那样,我还创建了一个内联管理员:

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
Run Code Online (Sandbox Code Playgroud)

现在,如果account在创建用户时未在管理员中选择a ,则不会创建配置文件模型.所以我再次连接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()
Run Code Online (Sandbox Code Playgroud)

只要我没有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在创建用户期间在管理员中选择了一个,则之后account将在新profile模型上设置.如果没有,该领域是null.

环境:Django 1.5,Python 2.7

相关问题:

Ala*_*lum 23

可以通过设置来避免这个问题primary_key=TrueOneToOneField的指点User模式,因为你已经找到了自己.

这个工作的原因似乎相当简单.

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

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

这是OneToOneField设置为主键时发生的情况:

  1. Django Admin创建新User实例,没有id.
  2. Django Admin保存User实例.
    1. 因为pk(在这种情况下id)没有设置,Django尝试创建一个新记录.
    2. 新记录id由数据库自动设置.
    3. post_save钩创建一个新的Profile用于该实例User的实例.
  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抛出异常.你今天不会去太空.