Django 5.x结合DRF、Djoser实现注册接口时出现500错误:Save with update_fields did not affect any rows
我帮你梳理下这个问题的来龙去脉,看起来是自定义User模型的主键生成逻辑和Djoser的注册流程在Django 5.x下产生了冲突。
从错误栈能定位到,问题出在Djoser序列化器调用user.save(update_fields=["is_active"])的环节——Django找不到要更新的数据库行。结合你的代码分析,核心原因是你用了PostgreSQL的RandomUUID函数作为主键默认值,这是数据库端生成的UUID,而非Python层面生成的:
# 你的models.py中的主键定义 user_id = models.UUIDField(primary_key=True, default=RandomUUID, editable=False)
当你的manager调用user.save()创建用户时,数据库会自动生成UUID并写入,但Django的User对象实例不会自动同步这个数据库生成的主键值——此时内存中的user对象user_id还是None。之后Djoser修改is_active字段并调用save(update_fields=["is_active"])时,Django会尝试更新user_id IS NULL的行,自然找不到匹配记录,就抛出了这个错误。
给你两个可行的解决方案,选其一即可:
方案1:改用Python层面生成UUID(推荐)
把主键默认值换成Python标准库的uuid.uuid4(),这样在创建User实例时就会生成UUID,无需依赖数据库生成,内存对象和数据库记录的主键会保持一致:
修改users/models.py的主键字段:
import uuid # 记得顶部导入uuid模块 from django.db import models from django.utils import timezone from django.contrib.auth.models import ( AbstractBaseUser, PermissionsMixin ) from .managers import UserAccountManager from django.utils.translation import gettext_lazy as _ class UserAccount(AbstractBaseUser, PermissionsMixin): # 替换原来的RandomUUID为uuid.uuid4 user_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) # 其他字段保持不变...
之后重新执行数据库迁移(python manage.py makemigrations + python manage.py migrate),再测试注册接口应该就能正常工作了。
方案2:在manager中主动刷新对象数据
如果你坚持使用PostgreSQL的RandomUUID,那就在manager的创建方法中,保存用户后主动刷新对象,让内存中的实例同步数据库生成的主键:
修改users/managers.py的create_user和create_superuser方法:
from django.contrib.auth.models import BaseUserManager from django.utils.translation import gettext_lazy as _ class UserAccountManager(BaseUserManager): def create_user(self, email, password=None, **kwargs): if not email: raise ValueError(_('Users must have an email address')) email = self.normalize_email(email) email = email.lower() user = self.model( email=email, **kwargs ) user.set_password(password) user.save() # 新增这一行,从数据库刷新对象数据,获取生成的user_id user.refresh_from_db() return user def create_superuser(self, email, password=None, **kwargs): user = self.create_user( email, password=password, **kwargs ) user.is_staff = True user.is_superuser = True user.save() # 同样新增刷新逻辑 user.refresh_from_db() return user
这样Djoser拿到的User对象就有了正确的user_id,后续更新is_active时就能找到对应的数据库行。
额外提一句,Django 5.x对数据库操作的细节做了更严格的校验,这也是为什么教程用4.x没问题但5.x出问题的原因——4.x对这种主键未同步的情况容错性更高。
备注:内容来源于stack exchange,提问作者user3125823

