Django重复键错误,但键不存在

1 投票
1 回答
4134 浏览
提问于 2025-04-18 08:44

我正在测试一个小应用,扩展了用户模型以添加一些额外的信息。用户可以通过django-allauth(谷歌OAuth2)顺利注册,相关信息也会被添加到数据库中。

class LabUser(models.Model):
    user = models.OneToOneField(User)
    verified = models.BooleanField(default=False, blank=False)
    phone_number = models.CharField(max_length=30, verbose_name="Phone Number", null=True, blank=True)
    home_phone = models.CharField(max_length=30,verbose_name="Home Phone", null=True, blank=True)

    def __unicode__(self):
        return self.user.username

但是,当我尝试通过django-admin面板添加另一个用户时,出现了以下数据库错误:

IntegrityError: duplicate key value violates unique constraint "lab_manager_labuser_user_id_key"
DETAIL:  Key (user_id)=(24) already exists.

在检查了psql中的表后,我发现了以下情况:

l=# select id, username from auth_user;
 id |    username
----+----------------
 13 | xxxx
 23 | xxxx
 18 | xxxx
 12 | xxxx
 21 | xxxx
 14 | xxxx
 22 | xxxx
  1 | xxxx
(8 rows)

l=# select id, user_id from lab_manager_labuser;
 id | user_id
----+---------
  9 |      13
  1 |       1
 16 |      18
  8 |      12
 10 |      14
 21 |      21
 22 |      22
 23 |      23
(8 rows)

查看我的序列表,我发现这些值比各自模型表中的值要大:

l=# SELECT sequence_name, last_value FROM auth_user_id_seq;
  sequence_name   | last_value
------------------+------------
 auth_user_id_seq |         24
(1 row)

l=# SELECT sequence_name, last_value FROM lab_manager_labuser_id_seq;
       sequence_name        | last_value
----------------------------+------------
 lab_manager_labuser_id_seq |         25
(1 row)

我查阅了这个类似的问题,但找不到为什么在我这里会发生这种冲突的原因。两个序列似乎都比我表中的值要大。

也就是说,我尝试了以下操作,虽然它增加了我的键值,但仍然导致了所谓的完整性错误,而这些键在数据库中显然并不存在。

SELECT setval('lab_manager_labuser_id_seq', (SELECT MAX(user_id) from lab_manager_labuser)+1)")

任何帮助都将不胜感激。

更新:

我设置了一个接收器来创建labuser模型,具体如下,也许我这样做不正确,导致了数据库不匹配:

@receiver(post_save, sender=User)
def add_labuser(sender, created, instance, **kwargs):
    if created:
        LabUser.objects.create(user=instance)

我认为这可能是问题的一部分,因为我可以通过以下代码在shell中成功创建用户对象:

a = User()
##(add fields)##
a.save()
LabUser.objects.create(user=a)

出现重复键错误的原因是因为我通过admin面板添加用户时,使用了一个内联的方式:

class LabUserInline(admin.StackedInline):
    model = LabUser
    can_delete = False

显然,当用户通过Admin创建时,会自动通过内联创建一个LabUser记录,在调用保存之前。然后,当接收到post_save信号时,django尝试为同一个用户对象再创建一个LabUser记录,这时就发生了键冲突。

有没有人知道如何避免这种冗余呢?

1 个回答

1

我找到了问题所在。当用户通过注册表单注册时,django-allauth 默认并不会创建一个 LabUser 对象,但 django 管理后台却会,因为我在后台实现了以下的代码。

class LabUserInline(admin.StackedInline):
    model = LabUser
    can_delete = False
    verbose_name = 'Lab User'
    verbose_name_plural = 'Lab Users'

而且,它是在调用 user.save() 之前就创建了这个对象,所以当我的 post_save 信号被发送时,接收者已经被调用,这时 LabUser 对象已经存在了,导致了键冲突。

我通过去掉 post-save 信号,直接在 django-allauth 的注册表单的 save() 方法中创建这个对象来解决了这个问题。

    def save(self, user):
        user.first_name = self.cleaned_data['first_name']
        user.last_name = self.cleaned_data['last_name']
        user.save()
        labuser = LabUser.objects.create(user=user)
        ...populate fields...
        labuser.save()

撰写回答