
本文揭示 django 模型实例化或序列化卡住(无报错、无响应)的典型原因:`default=` 函数中误用 `objects.create()` 导致数据库死锁或无限循环,重点解析 `generate_unique_id()` 的危险实现及安全替代方案。
在 Django(尤其是配合 Django REST Framework)开发中,模型创建“静默卡住”——如 shell 中执行 AllowedUser(...) 后光标悬停、DRF 视图中 serializer.save() 之后无日志无响应、进程不崩溃也不继续——往往并非数据库连接超时或硬件问题,而是 default 字段函数内部触发了未完成的数据库操作,形成隐式递归或事务阻塞。
你提供的 generate_unique_id() 函数看似合理,但原始错误版本的关键缺陷在于:
# ❌ 危险写法(原文未贴出,但答案已指出):
def generate_unique_id():
while True:
new_id = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(8)).upper()
# 错误:使用 create() 而非 filter(),且未提供必需字段 → 触发完整模型验证与保存
obj = AllowedUser.objects.create(id=new_id) # ← 这里会调用 __init__ + save() → 再次触发 generate_unique_id()!
return new_id该逻辑造成无限递归调用链:
AllowedUser(...) → 初始化时调用 default=generate_unique_id → generate_unique_id 内部调用 objects.create() → create() 实例化新 AllowedUser → 再次触发 default=generate_unique_id → ……
SQLite3 在此场景下因缺乏并发锁提示,常表现为“冻结”(实际是深度递归或死锁等待),而非抛出 RecursionError 或 IntegrityError。
✅ 正确做法是:default 函数必须是纯查询、无副作用、不触发模型保存。你修正后的版本使用 filter().exists() 是安全的:
import random
import string
from django.db import models
def generate_unique_id():
max_attempts = 100
characters = string.ascii_letters + string.digits
for _ in range(max_attempts):
new_id = ''.join(random.choice(characters) for _ in range(8)).upper()
# ✅ 安全:仅查询,不创建、不保存、不触发信号或验证
if not AllowedUser.objects.filter(id=new_id).exists():
return new_id
raise RuntimeError("Failed to generate unique ID after {} attempts".format(max_attempts))? 关键原则:default(或 default_callable)函数中禁止调用 Model.objects.create()、save()、full_clean() 等任何可能引发模型生命周期钩子(如 pre_save、post_save)或再次触发 default 计算的操作。
此外,为提升健壮性,建议对 id 字段补充数据库层唯一性保障,并优化默认值逻辑:
class AllowedUser(models.Model):
PLACE_CHOICES = [
(1, 'Loc1'),
(2, 'Loc2'),
(3, 'Loc3'),
(4, 'Loc4'),
(5, 'Loc5'),
]
id = models.CharField(
primary_key=True,
max_length=8,
unique=True,
default=generate_unique_id,
help_text="Auto-generated 8-character alphanumeric ID"
)
name = models.CharField(max_length=60)
place = models.IntegerField(choices=PLACE_CHOICES)
current_version = models.CharField(max_length=8, default="0.0.1")
last_updated = models.DateTimeField(default=lambda: datetime(1970, 1, 1, 0, 0, 0))
def __str__(self):
return f"{self.id} - {self.name}"⚠️ 注意事项:
- datetime.datetime(1970,1,1,0,0,0) 应改为 datetime(1970, 1, 1, 0, 0, 0)(避免 datetime.datetime 重复命名);
- 生产环境建议改用 uuid.uuid4().hex[:8].upper() 替代随机生成,显著降低碰撞概率;
- 若需强一致性,可结合数据库 UNIQUE 约束 + try/except IntegrityError 重试机制,而非纯应用层检查。
总结:Django 模型“冻结”多源于 default 函数的隐式副作用。坚守「只读查询、无状态、有限重试」三原则,即可彻底规避此类静默故障。










