Existing username field preventing users from updating details

Hi, I have created a Manage Account page where users can go to update details on their account, specifically, name, email, phone number, marketing consent and password. I have included the fields of username and date of birth on the page and disabled them as I want to show the user that these cannot be altered.

When clicking Save Changes on the page, it returns errors ‘User with this username already exists’, and ‘This field is required’ for both the password fields’. I believed I had mitigated this with the code I had written but apparently not and I’m confused as to how to resolve this now. I have posted all the relevant code below:

views.py:

@login_required
def manage_account(request):
    if request.method == 'POST':
        form = ManageAccountForm(request.POST, instance=request.user)
        if form.is_valid():
            user = form.save(commit=False)
            user.username = request.user.username
            user.dob = request.user.dob
            user.save()
            messages.success(request, 'Changes made successfully')
            return redirect('manage_account')
    else:
        form = ManageAccountForm(instance=request.user)
    return render(request, 'manage_account.html', {'form': form})

forms.py:

class CustomRegisterUserForm(UserCreationForm):
    name = forms.CharField(
        max_length=255, 
        required=True, 
        widget=forms.TextInput(attrs={'class': 'form-control'})
    )
    username = forms.CharField(
        max_length=150,
        required=True,
        widget=forms.TextInput(attrs={'class': 'form-control'}),
        help_text=mark_safe('Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.')
    )
    dob = forms.DateField(
        required=True, 
        widget=forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
        label='Date of Birth'
    )
    email = forms.EmailField(
        required=True, 
        widget=forms.EmailInput(attrs={'class': 'form-control'})
    )
    phone_number = forms.CharField(
        max_length=15, 
        required=False, 
        widget=forms.TextInput(attrs={'class': 'form-control'})
    )
    marketing_consent = forms.BooleanField(
        required=False, 
        widget=forms.CheckboxInput(attrs={'class': 'form-check-input'}),
        label='Marketing Consent'
    )
    password1 = forms.CharField(
        max_length=100,
        required=True,
        label='Password',
        widget=forms.PasswordInput(attrs={'class': 'form-control'}),
    )
    password2 = forms.CharField(
        max_length=100,
        required=True,
        label='Password Confirmation',
        widget=forms.PasswordInput(attrs={'class': 'form-control'})
    )

    class Meta:
        model = CustomUser
        fields = (
            'name', 
            'username', 
            'dob', 
            'email', 
            'phone_number', 
            'password1', 
            'password2', 
            'marketing_consent'
        )
        labels = {
            'dob': 'Date of Birth',
        }

class ManageAccountForm(CustomRegisterUserForm):
    class Meta(CustomRegisterUserForm.Meta):
        fields = [
            'name',
            'username',
            'dob',
            'email',
            'phone_number',
            'marketing_consent',
            'password1',
            'password2'
        ]
    
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # Disabling/greying out username and DOB fields
        self.fields['username'].disabled = True
        self.fields['dob'].disabled = True
        self.fields['username'].widget.attrs.update({
            'readonly': True, 
            'title': 'This field cannot be changed.'
        })
        self.fields['dob'].widget.attrs.update({
            'readonly': True, 
            'title': 'This field cannot be changed.'
        })
        # Making password fields optional
        self.fields['password1'].required = False
        self.fields['password2'].required = False
        self.fields['password1'].widget.attrs.update({'placeholder': 'Leave blank to keep current password.'})
        self.fields['password2'].widget.attrs.update({'placeholder': 'Leave blank to keep current password.'})

    def clean(self):
        cleaned_data = super().clean()
        password1 = cleaned_data.get('password1')   
        password2 = cleaned_data.get('password2')

        # If passwords are provided, validate them
        if password1 or password2:
            if password1 != password2:
                self.add_error('password2', 'Passwords must match.')
        return cleaned_data
    
    def save(self, commit=True):
        instance = super().save(commit=False)
        # Preserve username
        instance.username = self.instance.username
        # Hash the new password if provided
        password1 = self.cleaned_data.get('password1')
        if password1:
            instance.set_password(password1)
        if commit: 
            instance.save()
        return instance

HTML:

{% block content %}

<div class="account__container">
    <div class="account__content">
        {% if messages %}
            <ul>
                {% for message in messages %}
                    <li class="messages">{{ message }}</li>
                {% endfor %}
            </ul>
        {% endif %}
        <h2>Manage Your Account</h2>
        <p>Update your personal details and preferences here.</p>
        <form method='POST'>
            {% csrf_token %}
            {{ form.as_p }}
            <button type='submit'>Save Changes</button>
        </form>
    </div>
</div>

{% endblock %}

If anymore information is needed I’m more than happy to provide it - thanks for any help.

I suggest you simplify the entire process by modeling more closely what Django does internally - especially if you’re going to inherit from the Django-provided forms.

Django does not try to use the same form for creating a new user (UserCreationForm) as it does for modifying an existing user (UserChangeForm).

Now, to specifically address the error you are getting, look at the clean_username function provided by UserCreationForm, and think about how that’s going to interact with what you’re trying to do here.

So, my recommendations would be to either:

  • Create your own forms from scratch, implementing whatever functionality is desired.
    or
  • Follow Django’s usage patterns for these forms more precisely to how the Django admin uses them - working with the inherited functionality provided in the base classes instead of trying to code around them.

Thanks - I created a separate form that differed from UserCreationForm, worked a charm. Wasn’t aware I wasn’t supposed to use the same forms. Appreciate your help!