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

继承抽象类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的附属信息存在:

修改模型定义

  1. 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等配置...
  1. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 07:43:38