【问题标题】:custom password validation in django 1.7django 1.7 中的自定义密码验证
【发布时间】:2026-02-01 01:30:01
【问题描述】:

我正在尝试向创建用户和更改密码管理表单添加自定义密码验证。我在 django 文档中没有看到有关如何执行此操作的任何内容。我在 SO:Enforcing password strength requirements with django.contrib.auth.views.password_change 上找到了这篇文章,它提供了 2 个解决方案。我都试过了,但都不适合我。

这是我现在在我的应用 admin.py 中的内容:

def validate_password_strength(value):
    """Validates that a password is as least 10 characters long and has at least
    2 digits and 1 Upper case letter.
    """
    min_length = 10

    if len(value) < min_length:
        raise ValidationError(_('Password must be at least {0} characters '
                                'long.').format(min_length))

    # check for 2 digits
    if sum(c.isdigit() for c in value) < 2:
        raise ValidationError(_('Password must container at least 2 digits.'))

    # check for uppercase letter
    if not any(c.isupper() for c in value):
        raise ValidationError(_('Password must container at least 1 uppercase letter.'))

class MySetPasswordForm(SetPasswordForm):
    def __init__(self, *args, **kwargs):
        super(MySetPasswordForm, self).__init__(*args, **kwargs)
        self.fields['password1'].validators.append(validate_password_strength)

但我不知道如何使用 MySetPasswordForm。

我尝试了几种不同的方法。首先我是这样做的:

class UserAdmin(UserAdmin):
    form = MySetPasswordForm

# Re-register UserAdmin
admin.site.unregister(User)
admin.site.register(User, UserAdmin)

我得到了这个错误:

<class 'elex_apis.energy.webservice.admin.UserAdmin'>: (admin.E016) The value of 'form' must inherit from 'BaseModelForm'.

然后我将 MySetPasswordForm 更改为从 BaseModelForm 继承,然后出现此错误:

__init__() missing 1 required positional argument: 'user'

然后我添加了用户参数,所以现在 MySetPasswordForm 看起来像这样:

class MySetPasswordForm(BaseModelForm):
    def __init__(self, user, *args, **kwargs):
        super(MySetPasswordForm, self).__init__(user, *args, **kwargs)
        self.fields['password1'].validators.append(validate_password_strength)

但我仍然遇到与以前相同的错误。

我不敢相信添加密码验证器会这么难。这一定是一个非常普遍的需求,所以很明显我一定错过了一些简单的东西。任何人都可以在这里提供一些帮助。

【问题讨论】:

    标签: django django-admin django-authentication


    【解决方案1】:

    最简单的方法是继承原来的UserAdmin,然后覆盖change_password_form

    例子:

    from django.contrib.auth import models as auth_models
    from django.contrib.auth import admin as auth_admin
    from django.contrib.auth import forms as auth_forms
    from django.core.exceptions import ValidationError
    
    
    def validate_password_strength(value):
        """Validates that a password is as least 10 characters long and has at least
        2 digits and 1 Upper case letter.
        """
        min_length = 10
    
        if len(value) < min_length:
            raise ValidationError(_('Password must be at least {0} characters '
                                    'long.').format(min_length))
    
        # check for 2 digits
        if sum(c.isdigit() for c in value) < 2:
            raise ValidationError(_('Password must container at least 2 digits.'))
    
        # check for uppercase letter
        if not any(c.isupper() for c in value):
            raise ValidationError(_('Password must container at least 1 uppercase letter.'))
    
        return value
    
    
    class AdminPasswordChangeForm(auth_forms.AdminPasswordChangeForm):
        def clean_password1(self):
            return validate_password_strength(self.cleaned_data['password1'])
    
    
    class UserCreationForm(auth_forms.UserCreationForm):
        def clean_password1(self):
            return validate_password_strength(self.cleaned_data['password1'])
    
    
    class UserAdmin(auth_admin.UserAdmin):
        change_password_form = AdminPasswordChangeForm
        add_form = UserCreationForm
    
    
    # Re-register UserAdmin
    admin.site.unregister(auth_models.User)
    admin.site.register(auth_models.User, UserAdmin)
    

    【讨论】:

    • 这确实验证了密码,但是当输入有效密码并且管理员返回上一页后,它显示“未设置密码”。但是,当我查看数据库时,auth_user 中有一个密码。
    • @LarryMartell:哎呀...忘记添加return value,尝试使用新版本:)
    • 完美!非常感谢!!
    【解决方案2】:

    另一种方法是创建一个 validators.py 文件,并在其中创建基于对象的自己的类,如果输入的密码失败,则引发 ValidationError。更多信息here.

    import re
    
    from django.core.exceptions import ValidationError
    from django.utils.translation import ugettext as _
    
    class NumberValidator(object):
        def validate(self, password, user=None):
            if not re.findall('\d', password):
                raise ValidationError(
                   _("The password must contain at least 1 digit, 0-9."),
                   code='password_no_number',
                )
    
        def get_help_text(self):
            return _(
               "Your password must contain at least 1 digit, 0-9."
            )
    
    class UppercaseValidator(object):
        def validate(self, password, user=None):
            if not re.findall('[A-Z]', password):
                raise ValidationError(
                    _("The password must contain at least 1 uppercase letter, A-Z."),
                    code='password_no_upper',
                )
    
        def get_help_text(self):
            return _(
                "Your password must contain at least 1 uppercase letter, A-Z."
            )
    

    使用验证器的正确虚线路径更新 AUTH_PASSWORD_VALIDATORS 设置:

    AUTH_PASSWORD_VALIDATORS = [
        {'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
            'OPTIONS': {
                'min_length': 12, }
         },
        {'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', },
        {'NAME': 'my.project.validators.NumberValidator',
            'OPTIONS': {
                'min_digits': 3, }},
        {'NAME': 'my.project.validators.UppercaseValidator', },        
    ]
    

    【讨论】: