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

如何在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")

问题原因

  1. 冗余的null=True设置:业务上该字段需要自动生成值、不允许为空,但代码里设置了null=True,可能导致数据库迁移时字段约束与预期不符。
  2. 无效的异常捕获:pre_save里的try-except无法捕获重复键的IntegrityError——该异常仅在数据库保存阶段抛出,设置模型属性时不会触发,因此无法处理UUID重复的情况。
  3. 模型与数据库迁移不匹配:如果之前该字段是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")

必要的迁移操作

  1. 清除之前可能存在的无效迁移文件(若为迁移导致的约束问题),重新生成迁移:
python manage.py makemigrations
  1. 将迁移应用到数据库,同步字段约束:
python manage.py migrate

这样修改后,字段会被设置为NOT NULL约束,在pre_save阶段提前校验UUID的唯一性,避免保存时抛出异常,同时确保新增实例自动生成符合要求的短UUID。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 15:10:30