首页 文章

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

提问于
浏览
11

我创建了'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 ,则不会创建配置文件模型 . 所以我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 信号处理程序已经在那时创建了它 .

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

  • 无论新用户如何创建,之后都会有一个 profile 模型链接到它 .

  • 如果用户在创建用户期间在管理员中选择 account ,则此 account 将在之后的新 profile 模型上设置 . 如果没有,该字段是 null.

环境:Django 1.5,Python 2.7

相关问题:

2 回答

  • 1

    可以通过在指向 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抛出异常 . 你今天不会去太空 .

  • 21

    似乎在将配置文件模型连接到 User 模型的 OneToOneField 上设置 primary_key=True 修复了此问题 . 但是,我不认为我理解其中的所有含义及其有用的原因 .

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

相关问题