如何在Django中创建自动生成值的ShortUUIDField及报错排查
Django自定义ShortUUIDField自动生成值时的非空约束错误解决
问题场景
自定义了ShortUUIDField期望创建UserProfile实例时自动生成12位短UUID,但创建条目时抛出错误:
django.db.utils.IntegrityError: null value in column "uuid" of relation "profiles_userprofile" violates not-null constraint
自定义字段及模型代码如下:
原自定义ShortUUIDField代码
def generate_short_uuid(): string_uuid = str(uuid.uuid4()) return string_uuid.replace('-', '')[:12] class ShortUUIDField(models.CharField): def __init__(self, *args, **kwargs): kwargs['default'] = generate_short_uuid kwargs['editable'] = False kwargs['unique'] = True kwargs['blank'] = True kwargs['null'] = True kwargs['max_length'] = 12 super().__init__(*args, **kwargs) def pre_save(self, model_instance, add): if add: while True: try: value = generate_short_uuid() setattr(model_instance, self.attname, value) return value except IntegrityError as e: if 'duplicate key' in str(e): continue raise
原模型代码
class UserProfile(models.Model): uuid = ShortUUIDField() user = models.OneToOneField("users.User", on_delete=models.CASCADE, related_name="profile")
问题原因
- 冗余的
null=True设置:业务上该字段需要自动生成值、不允许为空,但代码里设置了null=True,可能导致数据库迁移时字段约束与预期不符。 - 无效的异常捕获:
pre_save里的try-except无法捕获重复键的IntegrityError——该异常仅在数据库保存阶段抛出,设置模型属性时不会触发,因此无法处理UUID重复的情况。 - 模型与数据库迁移不匹配:如果之前该字段是NOT NULL约束,后来修改为
null=True但未重新生成并应用迁移,会导致数据库约束和模型配置不一致。
修复方案
修改后的ShortUUIDField代码
import uuid from django.db import models def generate_short_uuid(): # 直接用uuid的hex属性获取无连字符的字符串,再截取前12位,更简洁高效 return uuid.uuid4().hex[:12] class ShortUUIDField(models.CharField): def __init__(self, *args, **kwargs): # 移除null=True,明确字段不允许为空 kwargs.update({ 'default': generate_short_uuid, 'editable': False, 'unique': True, 'blank': False, 'max_length': 12 }) super().__init__(*args, **kwargs) def pre_save(self, model_instance, add): if add: # 生成UUID前先检查数据库是否已存在,提前确保唯一性 while True: value = generate_short_uuid() # 通过当前模型的objects查询该值是否存在 if not model_instance.__class__.objects.filter(**{self.attname: value}).exists(): setattr(model_instance, self.attname, value) return value # 存在则继续生成新的UUID continue # 非新增场景,调用父类方法返回当前值 return super().pre_save(model_instance, add)
模型代码保持不变
class UserProfile(models.Model): uuid = ShortUUIDField() user = models.OneToOneField("users.User", on_delete=models.CASCADE, related_name="profile")
必要的迁移操作
- 清除之前可能存在的无效迁移文件(若为迁移导致的约束问题),重新生成迁移:
python manage.py makemigrations
- 将迁移应用到数据库,同步字段约束:
python manage.py migrate
这样修改后,字段会被设置为NOT NULL约束,在pre_save阶段提前校验UUID的唯一性,避免保存时抛出异常,同时确保新增实例自动生成符合要求的短UUID。
内容的提问来源于stack exchange,提问作者PietroPutelli
相关产品推荐
相关产品推荐

