Django使用signals保存Brand模型触发UNIQUE constraint failed报错求助
异常原因
- Django ModelForm的唯一性校验逻辑执行时机早于模型
save()方法、pre_save信号的执行时机,校验时使用的是表单提交的原始值,不会提前触发你在save()里定义的name转小写逻辑。 - 当你提交全小写的已存在品牌名时,表单校验阶段直接用输入的小写值查询数据库,匹配到已存在的记录,直接返回校验错误,不会进入后续的写入流程。
- 当你提交大写的已存在品牌名时,表单校验阶段用大写值查询数据库,和库中已存的小写值无法匹配,校验通过。之后执行
save()方法时name被转为小写,pre_save信号用转成小写的name生成slug,此时生成的slug和已有记录的slug重复,而此时已经过了表单校验阶段,所以直接触发数据库层面的UNIQUE约束报错。
解决方案
推荐优先调整name转小写的执行时机,把转小写逻辑提前到表单校验阶段,从根源避免校验不匹配的问题:
- 在BrandForm中新增
clean_name方法,统一处理输入的品牌名转小写,同时删除模型save()方法中的self.name = self.name.lower()逻辑,避免重复处理。
修改后的BrandForm代码如下:
class BrandForm(ModelForm): class Meta: model = Brand fields = ['name', 'log'] def clean_name(self): name = self.cleaned_data.get('name') # 在校验前就把品牌名转小写,后续唯一性校验直接使用转换后的值 return name.lower()
- 可选兜底方案:为避免后续slug生成重复的其他场景,可以优化pre_save信号的slug生成逻辑,增加重复校验,遇到重复时自动添加后缀:
@receiver(pre_save, sender=Brand) def create_brand_slug(sender, instance, *args, **kwargs): if not instance.slug: base_slug = slugify(instance.name) slug = base_slug counter = 1 # 检查slug是否已存在 while Brand.objects.filter(slug=slug).exclude(id=instance.id).exists(): slug = f"{base_slug}-{counter}" counter += 1 instance.slug = slug
调整后无论输入的品牌名是大写还是小写,表单校验阶段都会使用转换后的小写值做唯一性判断,匹配到已存在记录时直接返回正常的校验提示,不会触发数据库层面的slug约束报错。
内容的提问来源于stack exchange,提问作者Amjed saleel
相关产品推荐
相关产品推荐

