You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django自定义用户模型测试触发IntegrityError问题排查:重复键值违反唯一约束

Django自定义用户模型测试时出现唯一约束错误的原因与解决办法

你遇到的这个问题确实是因为重复的空username字段导致的,我来给你拆解清楚原因和可行的解决办法:

问题根源

你继承了AbstractUser这个基类,它自带一个username字段,并且默认是unique=True(唯一约束)。而你把USERNAME_FIELD改成了email,同时REQUIRED_FIELDS设为空数组——这意味着调用create_user时,如果不主动传入username参数,Django会自动把它设为空字符串。

在你的test_user_email_normalized测试用例里,循环创建用户时完全没给username赋值,所以每个测试用户的username都是空字符串。第一次创建没问题,但第二次创建时,数据库就会抛出duplicate key错误——因为空字符串已经存在于有唯一约束的username字段里了。

你添加user.delete()后测试通过,本质是每次创建完就删掉用户,数据库里不会留下重复的空username记录,下一次创建自然就不会冲突了。

解决办法(按推荐程度排序)

1. 彻底移除username字段(最推荐)

既然你已经用email作为用户的唯一标识,完全可以抛弃AbstractUser自带的username字段,改用AbstractBaseUser重构模型,从根源避免这类冲突:

from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager

class UserManager(BaseUserManager):
    """用户管理器"""
    def create_user(self, email, password=None, **extra_fields):
        user = self.model(email=self.normalize_email(email), **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        return user

class User(AbstractBaseUser, PermissionsMixin):
    """用户模型"""
    email = models.EmailField(max_length=255, unique=True)
    name = models.CharField(max_length=255)
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)
    # 如果需要AbstractUser自带的last_login等字段,可以手动添加
    # last_login = models.DateTimeField(null=True, blank=True)

    objects = UserManager()

    REQUIRED_FIELDS = []
    USERNAME_FIELD = 'email'

这种方式完全贴合你用email作为唯一标识的设计,后续不会再出现username相关的隐性问题。

2. 自动生成唯一的username值

如果不想替换AbstractUser,可以在UserManager里自动给username生成唯一值,避免空字符串重复:

import uuid
from django.contrib.auth.models import AbstractUser, PermissionsMixin, BaseUserManager

class UserManager(BaseUserManager):
    """用户管理器"""
    def create_user(self, email, password=None, **extra_fields):
        # 自动生成唯一的username(截断到符合CharField的默认长度)
        if not extra_fields.get('username'):
            extra_fields['username'] = str(uuid.uuid4())[:30]
        user = self.model(email=self.normalize_email(email), **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        return user

这样每次创建用户时,username都会是随机唯一的字符串,不会出现重复空值的问题。

3. 测试用例中显式传入唯一username

如果只想临时解决测试里的问题,可以在测试循环中给每个用户指定唯一的username:

class ModelTest(TestCase):
    def test_user_email_normalized(self):
        """测试邮箱规范化功能"""
        test_emails = [
            ['test1@EXAMPLE.Com', 'test1@example.com'],
            ['TesT2@exaMple.com', 'TesT2@example.com'],
        ]
        for idx, (email, expected) in enumerate(test_emails):
            user = get_user_model().objects.create_user(
                email, 'password123', username=f'test_user_{idx}'
            )
            self.assertEqual(user.email, expected)

不过这种方法只解决测试场景的问题,生产环境如果创建用户时没传username,还是会触发同样的错误。

内容的提问来源于stack exchange,提问作者Darek Napierski

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 19:42:53