【问题标题】:Creating a profile model with both an InlineAdmin and a post_save signal in Django在 Django 中使用 InlineAdmin 和 post_save 信号创建配置文件模型
【发布时间】:2012-12-29 23:57:33
【问题描述】:

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,则不会创建配置文件模型。所以我connectpost_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,则此account 将在之后设置在新的profile 模型上。如果不是,则字段为null.

环境:Django 1.5、Python 2.7

相关问题:

【问题讨论】:

  • 我也想知道使用Profile.objects.get_or_create(user=instance)创建profile模型会不会更好?
  • 我遇到了同样的问题。我们正在使用 get_or_create,这似乎没有任何区别。

标签: python django django-models django-admin django-signals


【解决方案1】:

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

这行得通的原因似乎很简单。

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

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

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

  1. Django 管理员创建新的User 实例,没有id
  2. Django 管理员保存User 实例。
    1. 由于未设置pk(在本例中为id),Django 尝试创建新记录。
    2. 新记录的id由数据库自动设置。
    3. post_save 挂钩为该 User 实例创建一个新的 Profile 实例。
  3. Django Admin 创建新的Profile 实例,并将其user 设置为用户的id
  4. Django 管理员保存Profile 实例。
    1. 因为pk(在本例中为user)已设置,Django 尝试使用pk 获取现有记录。
    2. Django 找到现有记录并更新它。

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

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

  1. Django 管理员创建新的User 实例,没有id
  2. Django Admin 保存 User 实例,post_save 挂钩像以前一样创建一个新的 Profile 实例。
  3. Django 管理员创建新的Profile 实例,没有id(自动添加的pk 字段)。
  4. Django 管理员保存Profile 实例。
    1. 由于未设置pk(在本例中为id),Django 尝试创建新记录。
    2. 数据库报告在user 字段上违反了表的唯一性约束。
    3. Django 抛出异常。你今天不会去太空。

【讨论】:

  • 嗯,现在如何从我没有 primary_key=True 的模型迁移到现在我有的模型?
【解决方案2】:

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

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

【讨论】:

    猜你喜欢
    • 2017-05-12
    • 1970-01-01
    • 2023-03-23
    • 1970-01-01
    • 1970-01-01
    • 2020-08-18
    • 2021-05-10
    • 2021-11-05
    • 2016-05-04
    相关资源
    最近更新 更多