Django 5.1 UserCreationForm won't allow empty passwords

I am upgrading a Django 3.0 app to 5.1 and have been moving slowly through each minor release. So far so good.

However, once I upgraded from Django 5.0 to 5.1, I saw changed behavior on my "Create New User" page, which used to allow empty passwords, with a random password being generated if none is supplied.

Now, I can no longer submit an empty password. I get a "required field" error on both the password fields, even though the fields are both explicitly set as required=False.

I know there were some changes (5.1.0, 5.1.1) to the UserCreationForm class in Django 5.1. I tried using AdminUserCreationForm instead of UserCreationForm and setting the usable_password field to None, but it still won't allow empty passwords like before.

Any ideas?

Environment
Python 3.12.8
Django 5.1.5

Simplified Code

class SignupForm(AdminUserCreationForm): # previously using UserCreationForm

    usable_password = None # Newly added

    # Form fields
    sharedaccountflag = forms.ChoiceField(
                            label='Cuenta compartida', 
                            required=True, 
                            choices=BOOLEAN_CHOICES
                        )

    # Constructor
    def __init__(self, *args, **kwargs):
                
        # Call base class constructor (i.e. SignupForm)
        super(SignupForm, self).__init__(*args, **kwargs)

        # Set password fields as optional
        self.fields['password1'].required=False
        self.fields['password2'].required=False

        # Set form helper properties
        self.helper = FormHelper()
        setFormHelper(self.helper) # set form method (POST / GET) and styling
        self.helper.form_tag = False

        # Set form layout
        self.helper.layout = Layout(
            Fieldset(
                'password1',
                'password2',
                'sharedaccountflag'
            )
        )

    # Specify model and which fields to include in form
    class Meta:
        model = get_user_model()
        fields = ('password1', 'password2', 'sharedaccountflag')

Screenshot
enter image description here

Yes. In new versions of Django, the source code has changed and the behaviour of the BaseUserCreationForm class has changed accordingly. The password1 and password2 fields are now created using the static method SetPasswordMixin.create_password_fields(), and they default to required=False. This can be easily checked here. But even though the fields are optional, the validate_passwords method is always called, in the clean method and checks that the fields are not empty. For example, when you call something like this form.is_valid(), clean will be called.

If you need behaviour where empty passwords are allowed, given required=False you can define a custom validate_passwords method like the one in the code below, this will allow you to create users with empty passwords:

from django.contrib.auth import forms


class CustomUserCreationForm(forms.UserCreationForm):  
    def validate_passwords(  
            self,  
            password1_field_name: str = "password1",  
            password2_field_name: str = "password2"):  
  
        def is_password_field_required_and_not_valid(field_name: str) -> bool:  
            is_required = self.fields[field_name].required  
            cleaned_value = self.cleaned_data.get(field_name)  
            is_field_has_errors = field_name in self.errors  
            return (  
                is_required  
                and not cleaned_value  
                and not is_field_has_errors  
            )  
  
        if is_password_field_required_and_not_valid(password1_field_name):  
            error = ValidationError(  
                self.fields[password1_field_name].error_messages["required"],  
                code="required",  
            )  
            self.add_error(password1_field_name, error)  
  
        if is_password_field_required_and_not_valid(password2_field_name):  
            error = ValidationError(  
                self.fields[password2_field_name].error_messages["required"],  
                code="required",  
            )  
            self.add_error(password2_field_name, error)  
  
        password1 = self.cleaned_data.get(password1_field_name)  
        password2 = self.cleaned_data.get(password2_field_name)  
        if password1 != password2:  
            error = ValidationError(  
                self.error_messages["password_mismatch"],  
                code="password_mismatch",  
            )  
            self.add_error(password2_field_name, error)

p.s. I'm not sure if that's the way it was intended, maybe there's just a mistake made, in validate_passwords, where the required flag is simply not taken into account.

Вернуться на верх