Title: Unable to change password in Django with custom user model
Tags: python,django,authentication,django-authentication,django-users

Question: I have been trying to get the default Django authentication system to work with custom users. I am able to create the user types, login and logout. However, when it comes to changing the password, I get logged out and then the password is not changed. I also tried to see if by resetting the password through email anything would change, yet I still can't change the password. My app name is account.
This is my custom user model, I tried adding the set_password method to make sure it could change the password. I have only included the user manager for readability :
class UserManager(BaseUserManager):
    def _create_user(self, email, password=None, **extra_fields):
        if not email:
            raise ValueError("The given email must be set")
        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)
        user.set_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)
    USERNAME_FIELD = "email"
    REQUIRED_FIELDS = []
    objects = UserManager()

    def set_password(self, raw_password):
        self.password = make_password(raw_password)
        self._password = raw_password


class Student(CustomUser):
    student = StudentManager()

    class Meta:
        proxy = True


class Teacher(CustomUser):
    teacher = TeacherManager()

    class Meta:
        proxy = True

For the rest I use all the default Django authentication urls and views but i have tried modifying the password change view to make sure I don't log out, however the issue persists :
urlpatterns = [
    path('password_change/', CustomPasswordChangeView.as_view(), name='password_change')
    path('', include('django.contrib.auth.urls')),   
]

CustomPasswordChangeView :
class CustomPasswordChangeView(auth.views.PasswordChangeView):
    template_name = "registration/password_change_form.html"
    success_url = reverse_lazy("password_change_done")

    def form_valid(self, form):
        response = super().form_valid(form)
        update_session_auth_hash(self.request, form.user)
        return response

Even with the form_valid method changed, I am still logged out when the form is submitted. I enter the old email and password and I am redirected to the password_change_done view, and the password is not changed.
Finally, I have made sure to add the following to the settings :
AUTH_USER_MODEL = "account.CustomUser"

Answer: It seems that you have correctly defined a custom user model in Django and have implemented a custom password change view. However, there are a few modifications you can make to ensure the password change process works as expected.

Remove the set_password method in your CustomUser model:
The set_password method you implemented in your CustomUser model is unnecessary because Django's built-in AbstractBaseUser class already provides a set_password method. By overriding it in your custom user model, you may be introducing unintended behavior. Remove the set_password method from your CustomUser model.
Use Django's built-in PasswordChangeView directly:
Instead of creating a custom password change view, you can use Django's built-in PasswordChangeView directly in your URL configuration. Remove the CustomPasswordChangeView and update your urls.py file as follows:

 from django.contrib.auth import views as auth_views
urlpatterns = [ path('password_change/', auth_views.PasswordChangeView.as_view(template_name="registration/password_change_form.html", success_url=reverse_lazy("password_change_done")), name='password_change'), path('password_change_done/', auth_views.PasswordChangeDoneView.as_view(template_name="registration/password_change_done.html"), name='password_change_done'), # Other authentication URLs ]

Ensure the URL patterns are correct:
Make sure you have included the authentication URLs correctly in your project's urls.py file. Confirm that the urlpatterns list includes the path for the password_change_done view, as shown in the example above.
Check your registration/password_change_form.html template:
Ensure that the form in your password_change_form.html template correctly submits the old password, new password, and confirmation. Make sure the form fields are named appropriately (old_password, new_password1, new_password2) to match Django's default behavior.

After making these modifications, try the password change process again and see if the issue is resolved.