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

实现AbstractUser后Django Admin无法修改用户设置的问题

问题:Django Admin修改自定义用户无响应,创建功能正常

我参考视频开发一个区分学生、教师角色的网站,需要给用户关联School ID。实现AbstractUser后,Django Admin里修改用户设置点击保存无反应,但创建用户功能正常。试过自定义创建和修改表单没用,怀疑models.py存在错误或配置遗漏。之前做过完整自定义用户模型,这次只是简单修改本来不该出现这类问题。

已配置:

AUTH_USER_MODEL = "account_app.User"

最初代码

models.py

from django.db import models
from django.contrib.auth.models import AbstractUser, BaseUserManager
from django.db.models.signals import post_save
from django.dispatch import receiver


class School(models.Model):
    name = models.CharField('School Name', max_length=240)
    address1 = models.CharField('Address 1', max_length=240)
    address2 = models.CharField('Address 2', max_length=240)
    city = models.CharField('City', max_length=240)
    district = models.CharField('District', max_length=240)
    state = models.CharField('State', max_length=2)
    zip_code = models.IntegerField('Zip Code')
    country =models.CharField('Country', max_length=240)
    phone = models.IntegerField('School Phone Number')
    time_zone = models.CharField(max_length=50)

    def __str__(self):
        return f'{self.name}'

class User(AbstractUser):
    class Role(models.TextChoices):
        ADMIN = "ADMIN", "Admin"
        STUDENT = "STUDENT", "Student"
        TEACHER = "TEACHER", "Teacher"

    base_role = Role.ADMIN

    role = models.CharField(max_length=50, choices=Role.choices)
    school_id = models.ForeignKey(School,blank=True, null=True, on_delete=models.DO_NOTHING)

    def save(self, *args, **kwargs):
        if not self.pk:
            self.role = self.base_role
            return super().save(*args, **kwargs)


class StudentManager(BaseUserManager):
    def get_queryset(self, *args, **kwargs):
        results = super().get_queryset(*args, **kwargs)
        return results.filter(role=User.Role.STUDENT)


class Student(User):

    base_role = User.Role.STUDENT

    student = StudentManager()

    class Meta:
        proxy = True

    def save(self, *args, **kwargs):
        if not self.pk:
            self.role = User.role.STUDENT
            return super().save(*args, **kwargs)

    def welcome(self):
        return "Only for students"


class TeacherManager(BaseUserManager):
    def get_queryset(self, *args, **kwargs):
        results = super().get_queryset(*args, **kwargs)
        return results.filter(role=User.Role.TEACHER)


class Teacher(User):

    base_role = User.Role.TEACHER

    teacher = TeacherManager()

    class Meta:
        proxy = True

    def save(self, *args, **kwargs):
        if not self.pk:
            self.role = User.role.TEACHER
            return super().save(*args, **kwargs)

    def welcome(self):
        return "Only for teachers"

admin.py

from django.contrib import admin
from .models import User
from .models import Student
from .models import Teacher


admin.site.register(User)
admin.site.register(Teacher)
admin.site.register(Student)

修改后的代码

admin.py

from django.contrib import admin
from django.contrib.auth.models import User
from django.contrib.auth.admin import UserAdmin as AuthUserAdmin

from .forms import UserCreationForm
from .models import School
from .models import UserProfile

"""
*** Used to create users in the Admin
    if you didn't do this it would use the default account in django.
"""
class UserCreateForm(UserCreationForm):
    class Meta:
        model = UserProfile
        fields = ('email','first_name')

class AccountsUserAdmin(AuthUserAdmin):
    # Displays accounts better on the view
    list_display = ('id','username','email', 'first_name', 'last_name', 'role','is_staff')

    # Used to search people at top of view
    search_fields = ('username','last_name','email')

    filter_horizontal = ()
    list_filter = ()
    
    add_form: UserCreateForm
    ## Viewing an and changing existing user
    fieldsets = (
        (None, {'fields': ('username','password')}),
        (('Personal info'), {'fields': ('first_name', 'last_name', 'email')}),
        (('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser',
                                       'groups', 'user_permissions')}),
        (('Important dates'), {'fields': ('last_login', 'date_joined')}),
        (('Additional info'), {'fields': ('role', 'school_id')}),
    )
    ## Adding a new User
    add_fieldsets = (
        (None, {
            'classes': ('wide',),
            'fields': ('username','first_name','last_name','email','role','school_id','password1','password2'),}),)



admin.site.register(UserProfile, AccountsUserAdmin)
admin.site.register(School)

forms.py

from django import forms
from django.contrib.auth.forms import UserCreationForm

from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from django.contrib.auth.models import User
from .models import UserProfile, School

class RegisterUserForm(UserCreationForm):
    ROLE_CHOICES =(
    ("ADMIN", "Admin"),
    ("STUDENT", "Student"),
    ("TEACHER", "Teacher"),)
    role = forms.ChoiceField(choices=ROLE_CHOICES, required=True)
    school_id = forms.ModelChoiceField(queryset=School.objects.all(), required=False)

    class Meta:
        model = User
        fields = ('username', 'first_name', 'last_name','email', 'role' , 'school_id' , 'password1', 'password2')

class UserChangeForm(UserChangeForm):
    class Meta(UserChangeForm.Meta):
        model = User
        fields = '__all__'

models.py

from django.db import models
from django.contrib.auth.models import AbstractUser
from django.dispatch import receiver
from django.db.models.signals import post_save

class School(models.Model):
    name = models.CharField('School Name', max_length=240)
    address1 = models.CharField('Address 1', max_length=240)
    address2 = models.CharField('Address 2', max_length=240)
    city = models.CharField('City', max_length=240)
    district = models.CharField('District', max_length=240)
    state = models.CharField('State', max_length=2)
    zip_code = models.IntegerField('Zip Code')
    country =models.CharField('Country', max_length=240)
    phone = models.IntegerField('School Phone Number')
    time_zone = models.CharField(max_length=50)

    def __str__(self):
        return f'{self.name}'

class UserProfile(AbstractUser):
    class Role(models.TextChoices):
        ADMIN = "ADMIN", "Admin"
        STUDENT = "STUDENT", "Student"
        TEACHER = "TEACHER", "Teacher"
    role = models.CharField(max_length=50, choices=Role.choices, blank=True, null=True)
    school_id = models.ForeignKey(School,blank=True, null=True, on_delete=models.DO_NOTHING)

class UserProfileManagerTeachers(models.Manager):
    def get_queryset(self):
        return super(UserProfile, self).get_queryset().filter(role="TEACHER")

class UserProfileManagerStudents(models.Manager):
    def get_queryset(self):
        return super(UserProfile, self).get_queryset().filter(role="STUDENT")

问题修复原因分析

  1. 修复了save方法的逻辑漏洞:
    最初的User类save方法只在用户新创建(无pk)时调用父类保存,修改用户时(已有pk)直接跳过保存逻辑,导致修改的字段无法写入数据库,Admin页面无响应。修改后的UserProfile移除了这个错误逻辑,确保新增和修改操作都会执行保存。

  2. 移除了冲突的代理模型注册:
    最初同时注册User、Student、Teacher到Admin,后两者是User的代理模型,会导致Admin处理用户数据时出现逻辑冲突。修改后只注册主用户模型UserProfile,避免了代理模型带来的操作混乱。

  3. 正确适配了自定义UserAdmin:
    最初直接注册User模型到Admin,没有适配自定义字段(role、school_id)的表单处理逻辑。修改后继承AuthUserAdmin并自定义fieldsets和add_fieldsets,确保Admin能正确渲染、处理自定义字段的修改请求,表单数据能正确传递到保存流程。

  4. 消除了代理模型的错误引用:
    最初Student、Teacher类的save方法中错误使用User.role.STUDENT(应为User.Role.STUDENT,Role是类而非实例属性),虽未在创建用户时暴露,但会触发潜在错误。修改后移除这些代理模型,避免了此类问题。


内容的提问来源于stack exchange,提问作者Jordan Meidinger

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 11:35:36