简体   繁体   English

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

[英]Creating a profile model with both an InlineAdmin and a post_save signal in Django

I created a 'profile' model (with a 1-to-1 relationship to the User model) as described on Extending the existing user model . 我创建了一个“配置文件”模型(与用户模型具有一对一的关系),如扩展现有用户模型所述 The profile model has an optional many-to-one relationship to another 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)

As documented there, I also created an inline admin: 正如那里记录的那样,我还创建了一个内联管理员:

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

Now if I don't select an account in the admin when creating the user, the profile model won't be created. 现在,如果在创建用户时未在管理员中选择account ,则不会创建配置文件模型。 So I connect to the post_save signal, again just following the documentation: 所以我再次连接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()

This works fine as long as I do not select an account in the admin, but if I do, I'll get an IntegrityError exception, telling me that duplicate key value violates unique constraint "app_profile_user_id_key" DETAIL: Key (user_id)=(15) already exists. 只要我没有在管理员中选择一个account ,这样就行得很好 ,但如果我这样做,我将收到一个IntegrityError异常,告诉我duplicate key value violates unique constraint "app_profile_user_id_key" DETAIL: Key (user_id)=(15) already exists.

Apparently, the inline admin tries to creates the profile instance itself, but my post_save signal handler has already created it at that time. 显然,内联管理员尝试自己创建profile实例,但我的post_save信号处理程序已经在那时创建了它。

How do I fix this problem, while keeping all of the following requirements? 如何解决此问题,同时保持以下所有要求?

  1. No matter how the new user is created, there will always be a profile model linking to it as well afterwards. 无论新用户如何创建,之后都会有一个链接到它的profile模型。
  2. If the user selects an account in the admin during user creation, this account will be set on the new profile model afterwards. 如果用户在创建用户期间在管理员中选择了一个account ,则此account将在之后的新profile模型中设置。 If not, the field is null. 如果不是,则该字段为null.

Environment: Django 1.5, Python 2.7 环境:Django 1.5,Python 2.7

Related questions: 相关问题:

The problem can be avoided by setting primary_key=True on the OneToOneField pointing at the User model, as you have figured out yourself. 通过在指向User模型的OneToOneField上设置primary_key=True可以避免此问题,因为您已经弄明白了。

The reason that this works seems to be rather simple. 这个工作的原因似乎相当简单。

When you try to create a model instance and set the pk manually before saving it, Django will try to find a record in the database with that pk and update it rather than blindly attempting to create a new one. 当您尝试创建模型实例并在保存之前手动设置pk时,Django将尝试使用该pk在数据库中查找记录并更新它而不是盲目地尝试创建新记录。 If none exists, it creates the new record as expected. 如果不存在,则按预期创建新记录。

When you set the OneToOneField as the primary key and Django Admin sets that field to the related User model's ID, that means the pk is already set and Django will attempt to find an existing record first. 当您将OneToOneField设置为主键并且Django Admin将该字段设置为相关User模型的ID时,这意味着已经设置了pk ,Django将首先尝试查找现有记录。

This is what happens with the OneToOneField set as primary key: 这是将OneToOneField设置为主键时发生的情况:

  1. Django Admin creates the new User instance, with no id . Django Admin创建了新的User实例,没有id
  2. Django Admin saves the User instance. Django Admin保存User实例。
    1. Because the pk (in this case id ) is not set, Django attempts to create a new record. 由于未设置pk (在本例中为id ),Django会尝试创建新记录。
    2. The new record's id is set automatically by the database. 新记录的id由数据库自动设置。
    3. The post_save hook creates a new Profile instance for that User instance. post_save挂钩为该User实例创建一个新的Profile实例。
  3. Django Admin creates the new Profile instance, with its user set to the user's id . Django Admin创建新的Profile实例,其user设置为用户的id
  4. Django Admin saves the Profile instance. Django Admin保存Profile实例。
    1. Because the pk (in this case user ) is already set, Django attempts to fetch an existing record with that pk . 因为已经设置了pk (在这种情况下是user ),Django尝试用该pk获取现有记录。
    2. Django finds the existing record and updates it. Django找到现有记录并更新它。

If you don't set the primary key explicitly, Django instead adds a field that uses the database's auto_increment functionality: the database sets the pk to the next largest value that doesn't exist. 如果没有显式设置主键,Django会添加一个使用数据库auto_increment功能的auto_increment :数据库将pk设置为不存在的下一个最大值。 This means the field will actually be left blank unless you set it manually and Django will therefore always attempt to insert a new record, resulting in a conflict with the uniqueness-constraint on the OneToOneField . 这意味着该字段实际上将保留为空,除非您手动设置它,因此Django将始终尝试插入新记录,从而导致与OneToOneField上的唯一性约束冲突。

This is what causes the original problem: 这是导致原始问题的原因:

  1. Django Admin creates the new User instance, with no id . Django Admin创建了新的User实例,没有id
  2. Django Admin saves the User instance, the post_save hook creating a new Profile instance as before. Django Admin保存User实例, post_save挂钩像以前一样创建一个新的Profile实例。
  3. Django Admin creates the new Profile instance, with no id (the automatically added pk field). Django Admin创建了新的Profile实例,没有id (自动添加的pk字段)。
  4. Django Admin saves the Profile instance. Django Admin保存Profile实例。
    1. Because the pk (in this case id ) is not set, Django attempts to create a new record. 由于未设置pk (在本例中为id ),Django会尝试创建新记录。
    2. The database reports a violation of the table's uniqueness-constraint on the user field. 数据库报告违反了表的user字段的唯一性约束。
    3. Django throws an Exception. Django抛出异常。 You will not go to space today. 你今天不会去太空。

It seems like setting primary_key=True on the OneToOneField connecting the profile model to the User model fixes this issue. 似乎在将配置文件模型连接到User模型的OneToOneField上设置primary_key=True可以解决此问题。 However, I don't think I understand all the implications of that and why it helps. 但是,我认为我不了解其中的所有含义及其有用的原因。

I'll leave this here as a hint, but if that's the best solution and someone could come up with a well-written explanation, I'd upvote/accept that and possibly delete mine. 我会把这个留在这里作为暗示,但如果这是最好的解决方案,有人可以提出一个写得很好的解释,我会投票/接受,并可能删除我的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM