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复现同类错误的根本原因。
修复方案
- 在自定义的
UserAdmin类中显式指定内联配置为空,覆盖默认的自动探测逻辑:
@register(User) class UserAdmin(BaseUserAdmin): inlines = [] # 新增该行即可阻断错误的自动内联加载 # 其余原有配置保持不变
- 对其他出现同类报错的模型,在对应ModelAdmin中同样显式指定
inlines配置,不要依赖Django的自动内联探测,避免多对多中间表被误识别。 - 如果确实需要在User编辑页展示关联的组织分组数据,不要直接将
OrganizationGroups作为内联使用(它没有到User的外键,不符合内联模型要求),可以自定义展示字段或者通过Group做二次关联查询实现,不要走默认的inlineformset生成逻辑。
内容的提问来源于stack exchange,提问作者Santiago Muñoz
相关产品推荐
相关产品推荐

