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

Django admin随机报OrganizationGroups无指向auth.User外键异常

Django admin 随机抛出无外键关联异常问题

问题现象

Django admin后台会随机抛出如下异常:organizations.OrganizationGroups has no ForeignKey to 'auth.User',同类异常也会在系统其他应用中出现,报错格式统一为organizations.OrganizationGroups has no ForeignKey to 'app.Model'。

运行环境

  • Django Version: 3.2.13
  • Python Version: 3.9.2

相关代码

OrganizationGroups 模型定义

class OrganizationGroups(models.Model):
    id = models.BigAutoField(primary_key=True)
    organization = models.ForeignKey(
        Organization, models.DO_NOTHING, null=True, blank=True
    )
    group = models.ForeignKey(Group, models.CASCADE, blank=True, null=True)

    class Meta:
        db_table = 'organization_groups'
        unique_together = (('organization', 'group'),)

    def __str__(self) -> str:
        return self.organization.name

自定义UserAdmin配置

from django.contrib.admin import ModelAdmin, register
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import User
from django.http import HttpRequest

from apps.organizations.models import OrganizationGroups
from apps.users.forms import UserForm
from apps.users.models import *

@register(User)
class UserAdmin(BaseUserAdmin):
    # 前端页面配置
    actions = None
    fieldsets = (('Users', {'fields': ('username',)}),)
    form = UserForm
    list_display = ('username', 'is_active')
    list_filter = ('is_active',)
    list_per_page = 10
    ordering = ('username',)
    readonly_fields = ('username',)

    # 重写方法
    def get_fieldsets(self, request: HttpRequest, obj=None):
        if not obj:
            return self.add_fieldsets

        if request.user.is_superuser:
            perm_fields = (
                'is_active',
                'is_superuser',
                'groups',
                'user_permissions'
            )
        else:
            perm_fields = (
                'is_active',
                'groups',
                'user_permissions'
            )

        return [
            (None, {'fields': ('username',)}),
            (('Permisos'), {'fields': perm_fields}),
            (('Fechas importantes'), {'fields': ('last_login', 'date_joined')})
        ]

    def get_form(self, request, obj=None, **kwargs):
        form = super(UserAdmin, self).get_form(request, obj, **kwargs)
        form.user = request.user
        if not request.user.is_superuser:
            if 'groups' in form.base_fields:
                groups = form.base_fields['groups']
                try:
                    user = LegacyUser.objects.get(
                        user_name=request.user.username
                    )
                    og_not_in_users_org = OrganizationGroups.objects.exclude(
                        organization=user.organization
                    )
                    exclude_group_id_list = []
                    for organization_group in og_not_in_users_org:
                        exclude_group_id_list.append(
                            organization_group.group_id
                        )
                    groups.queryset = groups.queryset.exclude(
                        id__in=exclude_group_id_list
                    )
                except LegacyUser.DoesNotExist:
                    pass
            if 'user_permissions' in form.base_fields:
                permissions = form.base_fields['user_permissions']
                permissions.queryset = permissions.queryset.exclude(
                    content_type__app_label__in=[
                        'admin', 'admin_interface', 'auth', 'contenttypes',
                        'organizations', 'sessions'
                    ]
                )
        return form

    def get_queryset(self, request: HttpRequest):
        qs = super().get_queryset(request)
        if request.user.is_superuser:
            return qs
        try:
            user = LegacyUser.objects.get(user_name=request.user.username)
            legacy_user_organization = LegacyUser.objects.filter(
                organization=user.organization
            )
            user_name_list = []
            for user in legacy_user_organization:
                user_name_list.append(user.user_name)
            return qs.filter(username__in=user_name_list)
        except LegacyUser.DoesNotExist:
            # 默认判定为超级用户
            return qs

    def has_add_permission(self, request: HttpRequest):
        return False

    def has_delete_permission(self, request: HttpRequest, obj=None) -> bool:
        return False

UserForm表单定义

from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
from django.forms.models import ModelForm

class UserForm(ModelForm):
    def __init__(self, *args, **kwargs):
        super(UserForm, self).__init__(*args, **kwargs)
        try:
            user: User = User.objects.get(id=self.instance.pk)
            if user == self.user:
                if self.user.is_superuser:
                    self.fields['is_superuser'].disabled = True
                self.fields['is_active'].disabled = True
                self.fields['groups'].disabled = True
                self.fields['user_permissions'].disabled = True
                self.fields['last_login'].disabled = True
                self.fields['date_joined'].disabled = True
        except User.DoesNotExist:
            pass

    def clean(self):
        for string, field in self.fields.items():
            if field.disabled is True:
                raise ValidationError(
                    'No puede realizar modificaciones a su propio usuario.',
                    'own_user_is_not_editable'
                )
        return super().clean()

完整报错栈

Traceback (most recent call last):
  File "/usr/local/lib/python3.9/dist-packages/django/core/handlers/exception.py", line 47, in inner
    response = get_response(request)
  File "/usr/local/lib/python3.9/dist-packages/django/core/handlers/base.py", line 181, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/usr/local/lib/python3.9/dist-packages/django/contrib/admin/options.py", line 616, in wrapper
    return self.admin_site.admin_view(view)(*args, **kwargs)
  File "/usr/local/lib/python3.9/dist-packages/django/utils/decorators.py", line 130, in _wrapped_view
    response = view_func(request, *args, **kwargs)
  File "/usr/local/lib/python3.9/dist-packages/django/views/decorators/cache.py", line 44, in _wrapped_view_func
    response = view_func(request, *args, **kwargs)
  File "/usr/local/lib/python3.9/dist-packages/django/contrib/admin/sites.py", line 232, in inner
    return view(request, *args, **kwargs)
  File "/usr/local/lib/python3.9/dist-packages/django/contrib/admin/options.py", line 1660, in change_view
    return self.changeform_view(request, object_id, form_url, extra_context)
  File "/usr/local/lib/python3.9/dist-packages/django/utils/decorators.py", line 43, in _wrapper
    return bound_method(*args, **kwargs)
  File "/usr/local/lib/python3.9/dist-packages/django/utils/decorators.py", line 130, in _wrapped_view
    response = view_func(request, *args, **kwargs)
  File "/usr/local/lib/python3.9/dist-packages/django/contrib/admin/options.py", line 1540, in changeform_view
    return self._changeform_view(request, object_id, form_url, extra_context)
  File "/usr/local/lib/python3.9/dist-packages/django/contrib/admin/options.py", line 1604, in _changeform_view
    formsets, inline_instances = self._create_formsets(request, obj, change=True)
  File "/usr/local/lib/python3.9/dist-packages/django/contrib/admin/options.py", line 1961, in _create_formsets
    for FormSet, inline in self.get_formsets_with_inlines(*get_formsets_args):
  File "/usr/local/lib/python3.9/dist-packages/django/contrib/admin/options.py", line 799, in get_formsets_with_inlines
    yield inline.get_formset(request, obj), inline
  File "/usr/local/lib/python3.9/dist-packages/django/contrib/admin/options.py", line 2059, in get_formset
    fields = flatten_fieldsets(self.get_fieldsets(request, obj))
  File "/usr/local/lib/python3.9/dist-packages/django/contrib/admin/options.py", line 337, in get_fieldsets
    return [(None, {'fields': self.get_fields(request, obj)})]
  File "/usr/local/lib/python3.9/dist-packages/django/contrib/admin/options.py", line 328, in get_fields
    form = self._get_form_for_get_fields(request, obj)
  File "/usr/local/lib/python3.9/dist-packages/django/contrib/admin/options.py", line 2144, in _get_form_for_get_fields
    return self.get_formset(request, obj, fields=None).form
  File "/usr/local/lib/python3.9/dist-packages/django/contrib/admin/options.py", line 2141, in get_formset
    return inlineformset_factory(self.parent_model, self.model, **defaults)
  File "/usr/local/lib/python3.9/dist-packages/django/forms/models.py", line 1069, in inlineformset_factory
    fk = _get_foreign_key(parent_model, model, fk_name=fk_name)
  File "/usr/local/lib/python3.9/dist-packages/django/forms/models.py", line 1038, in _get_foreign_key
    raise ValueError(
Exception Type: ValueError at /auth/user/1/change/
Exception Value: 'organizations.OrganizationGroups' has no ForeignKey to 'auth.User'.

问题原因

报错核心触发逻辑:Django admin渲染User模型编辑页时,会自动探测所有已注册模型,尝试将存在外键指向当前模型的类作为内联(Inline)加载。OrganizationGroups是Organization和Group的多对多中间表,而Group和User本身存在多对多关联,Django的自动探测逻辑受模型加载顺序影响,会随机误将OrganizationGroups识别为User的关联内联模型,生成内联表单集时发现OrganizationGroups没有指向auth.User的外键,就会抛出异常。这也是报错随机出现、会在其他app复现同类错误的根本原因。

修复方案

  1. 在自定义的UserAdmin类中显式指定内联配置为空,覆盖默认的自动探测逻辑:
@register(User)
class UserAdmin(BaseUserAdmin):
    inlines = []  # 新增该行即可阻断错误的自动内联加载
    # 其余原有配置保持不变
  1. 对其他出现同类报错的模型,在对应ModelAdmin中同样显式指定inlines配置,不要依赖Django的自动内联探测,避免多对多中间表被误识别。
  2. 如果确实需要在User编辑页展示关联的组织分组数据,不要直接将OrganizationGroups作为内联使用(它没有到User的外键,不符合内联模型要求),可以自定义展示字段或者通过Group做二次关联查询实现,不要走默认的inlineformset生成逻辑。

内容的提问来源于stack exchange,提问作者Santiago Muñoz

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.02 06:12:59