实现AbstractUser后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")
问题修复原因分析
修复了save方法的逻辑漏洞:
最初的User类save方法只在用户新创建(无pk)时调用父类保存,修改用户时(已有pk)直接跳过保存逻辑,导致修改的字段无法写入数据库,Admin页面无响应。修改后的UserProfile移除了这个错误逻辑,确保新增和修改操作都会执行保存。移除了冲突的代理模型注册:
最初同时注册User、Student、Teacher到Admin,后两者是User的代理模型,会导致Admin处理用户数据时出现逻辑冲突。修改后只注册主用户模型UserProfile,避免了代理模型带来的操作混乱。正确适配了自定义UserAdmin:
最初直接注册User模型到Admin,没有适配自定义字段(role、school_id)的表单处理逻辑。修改后继承AuthUserAdmin并自定义fieldsets和add_fieldsets,确保Admin能正确渲染、处理自定义字段的修改请求,表单数据能正确传递到保存流程。消除了代理模型的错误引用:
最初Student、Teacher类的save方法中错误使用User.role.STUDENT(应为User.Role.STUDENT,Role是类而非实例属性),虽未在创建用户时暴露,但会触发潜在错误。修改后移除这些代理模型,避免了此类问题。
内容的提问来源于stack exchange,提问作者Jordan Meidinger

