Django Admin自定义表单引发Password字段显示异常求助
问题描述
默认Django Admin站点中,Password字段会显示加密状态并提供修改链接。由于站点采用RTL布局,需将部分字段设置为LTR,因此编写了自定义表单。但使用该表单后,Password字段显示样式异常,且未修改任何与Password字段相关的代码,其他配置与默认Admin完全一致,请问为何会出现此变化?
自定义表单代码
from django.contrib import admin from django.contrib.auth.admin import UserAdmin from django.utils.translation import gettext_lazy as _ from django import forms from .models import CustomUser # Register your models here. class CustomUserForm(forms.ModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields['email'].widget.attrs.update({'dir': 'ltr'}) self.fields['national_id'].widget.attrs.update({'dir': 'ltr'}) self.fields['mobile_number'].widget.attrs.update({'dir': 'ltr'}) @admin.register(CustomUser) class CustomUserAdmin(UserAdmin): form = CustomUserForm fieldsets = ( (None, {'fields': ('email', 'password')}), ( _('Personal info'), { 'fields': ( 'first_name', 'last_name', ) }, ), ( _('Profile info'), { 'fields': ( 'national_id', 'mobile_number', 'home_address', 'work_address', ) }, ), ( _('Permissions'), { 'fields': ( 'is_active', 'is_staff', 'is_superuser', 'is_verified', 'groups', 'user_permissions', ), }, ), (_('Important dates'), {'fields': ('last_login', 'date_joined')}), ) add_fieldsets = ( ( None, { 'classes': ('wide',), 'fields': ('email', 'password1', 'password2'), }, ), ) list_display = ('email', 'first_name', 'last_name', 'is_staff') list_filter = ('is_staff', 'is_superuser', 'is_active', 'groups') search_fields = ('email', 'first_name', 'last_name', 'national_id', 'mobile_number') ordering = ('email',) filter_horizontal = ( 'groups', 'user_permissions', )
自定义用户模型代码
from django.db import models from django.contrib.auth.models import BaseUserManager, AbstractUser from django.contrib.auth.hashers import make_password from django.utils.translation import gettext_lazy as _ # Create your models here. class CustomUserManager(BaseUserManager): use_in_migrations = True def _create_user(self, email, password, **extra_fields): """ Create and save a user with the given email and password """ if not email: raise ValueError('The given email must be set') email = self.normalize_email(email) user = self.model(email=email, **extra_fields) user.password = make_password(password) user.save(using=self._db) return user def create_user(self, email, password=None, **extra_fields): extra_fields.setdefault('is_staff', False) extra_fields.setdefault('is_superuser', False) return self._create_user(email, password, **extra_fields) def create_superuser(self, email, password=None, **extra_fields): extra_fields.setdefault('is_staff', True) extra_fields.setdefault('is_superuser', True) if extra_fields.get('is_staff') is not True: raise ValueError('Superuser must have is_staff=True.') if extra_fields.get('is_superuser') is not True: raise ValueError('Superuser must have is_superuser=True.') return self._create_user(email, password, **extra_fields) class CustomUser(AbstractUser): username = None email = models.EmailField(_('email address'), unique=True) is_verified = models.BooleanField( _('verified status'), blank=True, default=False, help_text=_('Designates whether this user has verified their email address.'), ) national_id = models.CharField( _('National ID'), max_length=10, unique=True, blank=True, null=True, help_text=_('10-digit National ID number'), ) mobile_number = models.CharField( 'Mobile Number', max_length=13, unique=True, blank=True, null=True, help_text='13-digit mobile number including country code (+98)', ) home_address = models.TextField( 'home address', blank=True, ) work_address = models.TextField( 'work address', blank=True, ) objects = CustomUserManager() USERNAME_FIELD = 'email' REQUIRED_FIELDS = [] class Meta: verbose_name = _('user') verbose_name_plural = _('users') permissions = (('can_verify_user', _('Can verify user')),)
原因及解决方案
原因
Django默认的UserAdmin会为password字段使用特殊的AdminPasswordWidget小部件,用来渲染加密状态显示和修改链接。但当你为CustomUserAdmin指定自定义的ModelForm后,UserAdmin中针对password字段的特殊小部件配置会被覆盖——自定义表单默认会根据模型字段类型生成普通小部件,而非原来的特殊小部件,因此导致显示异常。
解决方案
在自定义表单中显式指定password字段使用AdminPasswordWidget即可恢复默认显示效果,修改后的CustomUserForm代码如下:
from django.contrib import admin from django.contrib.auth.admin import UserAdmin, AdminPasswordWidget # 导入AdminPasswordWidget from django.utils.translation import gettext_lazy as _ from django import forms from .models import CustomUser class CustomUserForm(forms.ModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields['email'].widget.attrs.update({'dir': 'ltr'}) self.fields['national_id'].widget.attrs.update({'dir': 'ltr'}) self.fields['mobile_number'].widget.attrs.update({'dir': 'ltr'}) # 为password字段设置特殊小部件 self.fields['password'].widget = AdminPasswordWidget() # 后续代码不变
或者,也可以在CustomUserAdmin中通过formfield_overrides统一配置:
@admin.register(CustomUser) class CustomUserAdmin(UserAdmin): form = CustomUserForm # 添加以下配置 formfield_overrides = { models.CharField: {'widget': AdminPasswordWidget}, } # 其他原有配置不变
内容的提问来源于stack exchange,提问作者Omid Shojaee
相关产品推荐
相关产品推荐

