Django自定义用户模型创建Driver时唯一键约束冲突求助
问题分析与解决方案
核心错误原因
- Driver表空邮箱冲突:post_save信号创建Driver时硬编码了
email = "",而Driver的email字段设为unique=True,第一次创建后空邮箱已存在,后续创建直接触发唯一约束错误。 - CustomUser邮箱无唯一校验:默认AbstractUser的email不是唯一字段,导致可以创建重复邮箱的CustomUser,但Driver的email要求唯一,两者产生冲突。
- 冗余字段增加风险:Driver中重复存储email字段,既没必要(可通过关联的CustomUser获取),又增加了数据不一致和冲突的概率。
分步解决步骤
1. 修复post_save信号的硬编码空邮箱
修改Models.py中的信号接收器,改用CustomUser的email填充Driver的email字段,避免空值重复:
@receiver(post_save, sender=CustomUser) def create_user_profile(sender, instance, created, **kwargs): if created and instance.is_driver: Driver.objects.create( driver_user=instance, full_name="123", phone_number="123", email=instance.email, # 关联User的邮箱,避免空值 address="123", country="123", state="123", city="123", zipp="213", birth_date='2022-01-01', license_no=1234, # 注意IntegerField不能传字符串 license_exp_date='2022-01-01', last_medical='2022-01-01', next_medical='2022-01-01', last_drug_test='2022-01-01', next_drug_test='2022-01-01', )
2. 给CustomUser添加邮箱唯一约束
修改CustomUser模型,确保email字段唯一,从根源避免重复用户:
class CustomUser(AbstractUser): is_driver = models.BooleanField(default=False) is_accountant = models.BooleanField(default=False) is_dispatcher = models.BooleanField(default=False) email = models.EmailField(unique=True, max_length=255) # 添加唯一约束
执行数据库迁移:
python manage.py makemigrations python manage.py migrate
3. 移除Driver中冗余的email字段(推荐)
由于Driver和CustomUser是一对一关联,完全可以通过driver.driver_user.email获取邮箱,删除重复字段减少维护成本:
class Driver(models.Model): driver_user = models.OneToOneField(CustomUser, on_delete=models.CASCADE, primary_key=True) full_name = models.CharField(max_length=50) phone_number = models.CharField(max_length=50) # 移除email字段 address = models.CharField(max_length=70) country = models.CharField(max_length=50) state = models.CharField(choices=US_STATES,max_length=50) city = models.CharField(max_length=50) zipp = models.CharField(max_length=50) birth_date = models.DateField() license_no = models.IntegerField() license_exp_date = models.DateField() last_medical = models.DateField(blank=True, null=True) next_medical = models.DateField(blank=True, null=True) last_drug_test = models.DateField(blank=True, null=True) next_drug_test = models.DateField(blank=True, null=True) date_created = models.DateTimeField(auto_now_add=True) date_edited = models.DateTimeField(auto_now=True) status = models.IntegerField(choices=STATUS, default=1)
执行迁移更新数据库。
4. 优化视图逻辑,提前校验+正确保存
在创建用户前先检查邮箱是否已存在,更新Driver时直接保存Driver实例:
def create_driver_view(request): if request.method == "POST": add_driver = DriverForm(request.POST) add_driver_file = request.FILES.getlist("file") if add_driver.is_valid(): password = 'Ga20224$5%' cleaned_data = add_driver.cleaned_data email = cleaned_data['email'] # 提前检查邮箱是否已注册 if CustomUser.objects.filter(email=email).exists(): add_driver.add_error('email', '该邮箱已被注册') return render(request, "drivers/add.html", { "add_driver": add_driver, "add_driver_files": DriverFormUpload() }) username = email.split('@')[0] + uuid.uuid4().hex[:2] user = CustomUser.objects.create_user( username=username, password=password, is_driver=True, email=email ) # 更新Driver并保存 driver = user.driver driver.full_name = cleaned_data['full_name'] driver.phone_number = cleaned_data['phone_number'] driver.address = cleaned_data['address'] driver.country = cleaned_data['country'] driver.state = cleaned_data['state'] driver.city = cleaned_data['city'] driver.zipp = cleaned_data['zipp'] driver.birth_date = cleaned_data['birth_date'] driver.license_no = cleaned_data['license_no'] driver.license_exp_date = cleaned_data['license_exp_date'] driver.last_medical = cleaned_data['last_medical'] driver.next_medical = cleaned_data['next_medical'] driver.last_drug_test = cleaned_data['last_drug_test'] driver.next_drug_test = cleaned_data['next_drug_test'] driver.save() # 处理文件上传 for file in add_driver_file: DriversFiles.objects.create(driver_files=driver, file=file) return redirect('drivers:list_driver') else: print(add_driver.errors) else: add_driver = DriverForm() add_driver_files = DriverFormUpload() return render(request, "drivers/add.html", {"add_driver": add_driver, "add_driver_files": add_driver_files})
自定义用户模型最佳实践
- 必须给
email字段添加unique=True,这是用户标识和登录的常用字段 - 扩展模型(如Driver)不要重复存储User中已有的字段,通过关联关系获取即可
- 使用post_save信号自动创建扩展模型时,避免硬编码不合理的默认值(如空字符串)
- 创建用户前主动校验唯一字段,提前返回错误提示,避免触发数据库约束错误
内容的提问来源于stack exchange,提问作者Giorgi Kurdadze
相关产品推荐
相关产品推荐

