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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 00:59:52