继承抽象类FK引发Django CircularDependencyError及created_by/updated_by设置问题
解决循环依赖并正确设置created_by/updated_by
首先得先搞定那个循环依赖的问题,不然迁移都过不了,更别说设置字段了。你的问题根源在于User和Account互相持有外键:Account继承UserStamp后有指向User的created_by/updated_by,而User又有指向Account的外键,这就形成了死循环。加上User还继承了UserStamp,等于User自己的字段也指向自己,进一步加剧了依赖混乱。
第一步:打破循环依赖
最合理的调整是重构模型关系,让Account依附于User,而不是反过来。因为用户模型是认证系统的核心,应该让Account作为User的附属信息存在:
修改模型定义
- User模型:去掉对UserStamp的继承(避免自身字段循环),如果需要记录创建/更新者,手动添加字段并使用字符串引用自身:
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin from django.db import models from django.conf import settings class User(AbstractBaseUser, PermissionsMixin): # 保留你的其他字段(比如username, email等) # 移除原来的account外键,改为通过Account反向关联 created_by = models.ForeignKey('self', blank=True, null=True, related_name='created_users', on_delete=models.CASCADE) updated_by = models.ForeignKey('self', blank=True, null=True, related_name='updated_users', on_delete=models.CASCADE) # 你的USERNAME_FIELD、REQUIRED_FIELDS等配置...
- Account模型:继续继承UserStamp,添加OneToOneField关联User(这样可以通过
user.account反向获取Account):
from django.db import models from django.conf import settings from .models import UserStamp class Account(UserStamp): user = models.OneToOneField(settings.AUTH_USER_MODEL, related_name='account', on_delete=models.CASCADE) # 你的其他Account字段...
这样调整后,User不再依赖Account,Account依赖User,迁移时会先创建User表,再创建Account表,循环依赖就解决了。
第二步:自动设置created_by和updated_by
解决了迁移问题后,接下来要实现自动给继承UserStamp的模型(比如Account)设置创建/更新者,同时处理User模型自身的字段。
方法1:用中间件+信号(推荐,解耦性强)
1. 写一个中间件存储当前登录用户
因为信号无法直接获取请求上下文里的用户,我们用线程本地存储来保存当前用户:
# 在你的核心app(比如core)里创建middleware.py import threading _thread_locals = threading.local() def get_current_user(): return getattr(_thread_locals, 'user', None) class CurrentUserMiddleware: def __init__(self, get_response): self.get_response = get_response def __call__(self, request): _thread_locals.user = request.user response = self.get_response(request) # 清理线程存储,避免内存泄漏 del _thread_locals.user return response
然后在settings.py的MIDDLEWARE列表里添加这个中间件,注意要放在AuthenticationMiddleware之后:
MIDDLEWARE = [ # ...其他中间件 'django.contrib.auth.middleware.AuthenticationMiddleware', 'core.middleware.CurrentUserMiddleware', # 你的中间件路径 # ... ]
2. 写信号处理函数自动设置字段
# 在core里创建signals.py from django.db.models.signals import pre_save from django.dispatch import receiver from .middleware import get_current_user from .models import UserStamp from django.contrib.auth import get_user_model User = get_user_model() @receiver(pre_save) def auto_set_stamps(sender, instance, **kwargs): # 处理继承UserStamp的模型(比如Account) if issubclass(sender, UserStamp): user = get_current_user() if user and user.is_authenticated: if not instance.pk: # 新建对象时设置created_by instance.created_by = user instance.updated_by = user # 每次保存都更新updated_by # 单独处理User模型的created_by/updated_by if sender == User: user = get_current_user() if user and user.is_authenticated: if not instance.pk: instance.created_by = user instance.updated_by = user
3. 注册信号
在core的apps.py里添加信号注册:
from django.apps import AppConfig class CoreConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'core' def ready(self): import core.signals # 导入信号模块
方法2:重写UserStamp的save方法(更直接)
如果不想用信号,可以在UserStamp抽象类里重写save方法,让子类自动继承这个逻辑:
class UserStamp(models.Model): created_by = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, related_name='%(app_label)s_%(class)s_created_by', on_delete=models.CASCADE) updated_by = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, related_name='%(app_label)s_%(class)s_updated_by', on_delete=models.CASCADE) class Meta: abstract = True def save(self, *args, **kwargs): from core.middleware import get_current_user user = get_current_user() if user and user.is_authenticated: if not self.pk: self.created_by = user self.updated_by = user super().save(*args, **kwargs)
然后User模型的save方法也可以单独重写,或者在创建时手动设置。
注意事项
- 后台任务场景:如果用Celery等后台任务保存模型,因为没有请求上下文,
get_current_user()会返回None,这时候需要手动设置created_by和updated_by,比如传入执行任务的用户。 - 超级用户创建:用
createsuperuser命令创建用户时,没有当前用户,所以created_by会是null,你可以修改命令或者手动更新超级用户的created_by字段。
内容的提问来源于stack exchange,提问作者user3541631
相关产品推荐
相关产品推荐

