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')
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.