Django: Resolving "Reverse for 'password_reset_complete' not found."

Viewed 194

I am attempting to do a password reset using Django using custom html templates. I am able to request an password reset by email (using the EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'), and I then get an "email" in the console, as I would like. The email does lead me to the "password_reset_confirm.html" page as I would like, and I can input the new passwords for the user associated with the user.

However I am getting the following error when I click the "Reset password!" button:

"Reverse for 'password_reset_complete' not found. 'password_reset_complete' is not a valid view function or pattern name."

Despite this error, the password does actually change, however I (obviously) do not want this error.

The structure of the project has a users "app" within the main structure, so it is:

project
main_app
users
----templates
--------registration
------------password_reset_form.html
------------password_reset_confirm.html
------------password_reset_done.html
------------password_reset_email.html
------------password_reset_complete.html

My code for urls.py in users is as follows:

from django.urls import path, include, reverse_lazy

from . import views
from django.contrib.auth import views as auth_views

app_name = 'users'
urlpatterns = [
    # Default authorisation urls.
    path('', include('django.contrib.auth.urls')),
    # Registration page.
    path('register/', views.register, name='register'),
    # Password reset urls.
    path(
        'password_reset',
        auth_views.PasswordResetView.as_view(
            template_name='registration/password_reset_form.html',
            email_template_name='registration/password_reset_email.html',
            success_url=reverse_lazy('users:password_reset_done')
        ),
        name='password_reset'
    ),
    path(
        'password_reset/done/',
        auth_views.PasswordResetDoneView.as_view(template_name='registration/password_reset_done.html'),
        name='password_reset_done'
    ),
    path(
        'reset/<uidb64>/<token>/',
        auth_views.PasswordResetConfirmView.as_view(
            template_name='registration/password_reset_confirm.html',
            success_url=reverse_lazy('users:password_reset_complete.html')
        ),
        name='password_reset_confirm'
    ),
    path(
        'reset/done/',
        auth_views.PasswordResetCompleteView.as_view(template_name='registration/password_reset_complete.html'),
        name='password_reset_complete'
    ),

]

I had a similar issue but with "password_reset_done", however I was able to resolve this thanks to looking through this site, by adding

success_url=reverse_lazy('users:password_reset_done')

to the path for the password reset, and that resolved that issue, however I then bumped into a subsequent issue as above, and I haven't found any solutions that have resolved it. Adding the following to the path for the password_reset_confirmation didn't help:

success_url=reverse_lazy('users:password_reset_complete.html')

I tried changing this to a login page, for example, which appeared to change nothing, and on investigation found that I could remove the following and the program would be unaltered:

    path(
        'reset/<uidb64>/<token>/',
        auth_views.PasswordResetConfirmView.as_view(
            template_name='registration/password_reset_confirm.html',
            success_url=reverse_lazy('users:password_reset_complete.html')
        ),
        name='password_reset_confirm'
    ),
    path(
        'reset/done/',
        auth_views.PasswordResetCompleteView.as_view(template_name='registration/password_reset_complete.html'),
        name='password_reset_complete'
    ),

Which is why the success_url in this case isn't changing the behaviour. I'm wondering if the password_reset_email.html is the culprit, in case this is resulting in "password_reset_confirm" being called, as opposed to "user:password_reset_confirm" (because the urlpattern is being ignored).

password_reset_email.html:

{% autoescape off %}
To initiate the password reset process for your account, click the link below:

{{ protocol }}://{{ domain }}{% url 'users:password_reset_confirm' uidb64=uid token=token %}
If clicking the link above doesn't, please copy and paste it into a new browser instead.

{% endautoescape %}

I have also included the password_reset_confirm.html code in case it helps:

{% extends "demeter_app/base.html" %}

{% block content %}
    <p>Please enter your new password:</p>
    <form method="post">
        {% csrf_token %}
        {{ form.as_p }}
        <input type="submit" value="Reset password!">
    </form>
{% endblock content %}

Does anyone have any ideas as to how I can solve this? I've tried looking on Stackoverflow however most queries relate to password_reset_done and not password_reset_complete.

More of the error is below:

Environment:


Request Method: POST
Request URL: http://localhost:8000/users/reset/MQ/set-password/

Django Version: 3.1.7
Python Version: 3.9.2
Installed Applications:
['demeter_app',
 'users',
 'import_export',
 'django.contrib.admin',
 'django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.staticfiles']
Installed Middleware:
['django.middleware.security.SecurityMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.common.CommonMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'django.middleware.clickjacking.XFrameOptionsMiddleware']



Traceback (most recent call last):
  File "C:\Users\marcj\Documents\demeter\demeter_env\lib\site-packages\django\core\handlers\exception.py", line 47, in inner
    response = get_response(request)
  File "C:\Users\marcj\Documents\demeter\demeter_env\lib\site-packages\django\core\handlers\base.py", line 181, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "C:\Users\marcj\Documents\demeter\demeter_env\lib\site-packages\django\views\generic\base.py", line 70, in view
    return self.dispatch(request, *args, **kwargs)
  File "C:\Users\marcj\Documents\demeter\demeter_env\lib\site-packages\django\utils\decorators.py", line 43, in _wrapper
    return bound_method(*args, **kwargs)
  File "C:\Users\marcj\Documents\demeter\demeter_env\lib\site-packages\django\views\decorators\debug.py", line 89, in sensitive_post_parameters_wrapper
    return view(request, *args, **kwargs)
  File "C:\Users\marcj\Documents\demeter\demeter_env\lib\site-packages\django\utils\decorators.py", line 43, in _wrapper
    return bound_method(*args, **kwargs)
  File "C:\Users\marcj\Documents\demeter\demeter_env\lib\site-packages\django\views\decorators\cache.py", line 44, in _wrapped_view_func
    response = view_func(request, *args, **kwargs)
  File "C:\Users\marcj\Documents\demeter\demeter_env\lib\site-packages\django\contrib\auth\views.py", line 272, in dispatch
    return super().dispatch(*args, **kwargs)
  File "C:\Users\marcj\Documents\demeter\demeter_env\lib\site-packages\django\views\generic\base.py", line 98, in dispatch
    return handler(request, *args, **kwargs)
  File "C:\Users\marcj\Documents\demeter\demeter_env\lib\site-packages\django\views\generic\edit.py", line 142, in post
    return self.form_valid(form)
  File "C:\Users\marcj\Documents\demeter\demeter_env\lib\site-packages\django\contrib\auth\views.py", line 305, in form_valid
    return super().form_valid(form)
  File "C:\Users\marcj\Documents\demeter\demeter_env\lib\site-packages\django\views\generic\edit.py", line 57, in form_valid
    return HttpResponseRedirect(self.get_success_url())
  File "C:\Users\marcj\Documents\demeter\demeter_env\lib\site-packages\django\views\generic\edit.py", line 51, in get_success_url
    if not self.success_url:
  File "C:\Users\marcj\Documents\demeter\demeter_env\lib\site-packages\django\utils\functional.py", line 135, in __wrapper__
    res = func(*self.__args, **self.__kw)
  File "C:\Users\marcj\Documents\demeter\demeter_env\lib\site-packages\django\urls\base.py", line 87, in reverse
    return iri_to_uri(resolver._reverse_with_prefix(view, prefix, *args, **kwargs))
  File "C:\Users\marcj\Documents\demeter\demeter_env\lib\site-packages\django\urls\resolvers.py", line 685, in _reverse_with_prefix
    raise NoReverseMatch(msg)

Exception Type: NoReverseMatch at /users/reset/MQ/set-password/
Exception Value: Reverse for 'password_reset_complete' not found. 'password_reset_complete' is not a valid view function or pattern name.

Please let me know if there is anything else I can provide to help.

1 Answers

It doesn't work for you, because Link order broken. First, the redesignated ones should go, and only then, the standard ones. For proper operation, it is enough to do this:

urlpatterns = [
    path('password_reset/',
        auth_views.PasswordResetView.as_view(
            success_url=reverse_lazy('users:password_reset_done')), 
        name='password_reset'),
    path('reset/<uidb64>/<token>/', 
        auth_views.PasswordResetConfirmView.as_view(
            success_url=reverse_lazy('users:password_reset_complete')), 
        name='password_reset_confirm'),
    path('', include('django.contrib.auth.urls')),
]

This order is necessary because 'django.contrib.auth.urls' already contains 'reset/< uidb64>/< token>/'. As a result, your code is not running.

Related