Search code examples
python-3.xdjangodjango-viewsdjango-3.0

Django PasswordResetConfirmView password reset failed not working


When using Django3.0 internal PasswordResetConfirmView PasswordResetForm reset_password.html and tampering with the token generated it raises a form "is_bound" error. It doesn't redirect to some failure url.

Internal Server Error: /account/password/reset/MQ/akbdyj-e5f18868fas35e748160dd6ef006803a5/
Traceback (most recent call last):
  File "C:\Users\JoyStick\.virtualenvs\django-template-2StZ7f6a\lib\site-packages\django\core\handlers\exception.py", line 47, in inner
    response = get_response(request)
  File "C:\Users\JoyStick\.virtualenvs\django-template-2StZ7f6a\lib\site-packages\django\core\handlers\base.py", line 204, in _get_response
    response = response.render()
  File "C:\Users\JoyStick\.virtualenvs\django-template-2StZ7f6a\lib\site-packages\django\template\response.py", line 108, in render
    self.content = self.rendered_content
  File "C:\Users\JoyStick\.virtualenvs\django-template-2StZ7f6a\lib\site-packages\django\template\response.py", line 86, in rendered_content
    return template.render(context, self._request)
  File "C:\Users\JoyStick\.virtualenvs\django-template-2StZ7f6a\lib\site-packages\django\template\backends\django.py", line 61, in render
    return self.template.render(context)
  File "C:\Users\JoyStick\.virtualenvs\django-template-2StZ7f6a\lib\site-packages\django\template\base.py", line 170, in render
    return self._render(context)
  File "C:\Users\JoyStick\.virtualenvs\django-template-2StZ7f6a\lib\site-packages\django\template\base.py", line 162, in _render
    return self.nodelist.render(context)
  File "C:\Users\JoyStick\.virtualenvs\django-template-2StZ7f6a\lib\site-packages\django\template\base.py", line 938, in render
    bit = node.render_annotated(context)
  File "C:\Users\JoyStick\.virtualenvs\django-template-2StZ7f6a\lib\site-packages\django\template\base.py", line 905, in render_annotated
    return self.render(context)
  File "C:\Users\JoyStick\.virtualenvs\django-template-2StZ7f6a\lib\site-packages\django\template\loader_tags.py", line 150, in render
    return compiled_parent._render(context)
  File "C:\Users\JoyStick\.virtualenvs\django-template-2StZ7f6a\lib\site-packages\django\template\base.py", line 162, in _render
    return self.nodelist.render(context)
  File "C:\Users\JoyStick\.virtualenvs\django-template-2StZ7f6a\lib\site-packages\django\template\base.py", line 938, in render
    bit = node.render_annotated(context)
  File "C:\Users\JoyStick\.virtualenvs\django-template-2StZ7f6a\lib\site-packages\django\template\base.py", line 905, in render_annotated
    return self.render(context)
  File "C:\Users\JoyStick\.virtualenvs\django-template-2StZ7f6a\lib\site-packages\django\template\loader_tags.py", line 62, in render
    result = block.nodelist.render(context)
  File "C:\Users\JoyStick\.virtualenvs\django-template-2StZ7f6a\lib\site-packages\django\template\base.py", line 938, in render
    bit = node.render_annotated(context)
  File "C:\Users\JoyStick\.virtualenvs\django-template-2StZ7f6a\lib\site-packages\django\template\base.py", line 905, in render_annotated
    return self.render(context)
  File "C:\Users\JoyStick\.virtualenvs\django-template-2StZ7f6a\lib\site-packages\crispy_forms\templatetags\crispy_forms_tags.py", line 203, in render
    c = self.get_render(context).flatten()
  File "C:\Users\JoyStick\.virtualenvs\django-template-2StZ7f6a\lib\site-packages\crispy_forms\templatetags\crispy_forms_tags.py", line 112, in get_render
    node_context.update({"is_bound": actual_form.is_bound})
AttributeError: 'NoneType' object has no attribute 'is_bound'

I have tried passing the failure template name in "extra_context" but still, it doesn't work.

class UserAccountResetPasswordView(PasswordResetConfirmView):
    template_name = 'user_account/profile/reset_password.html'
    form_class = UserAccountPasswordResetForm
    success_url = reverse_lazy('user_account:login')
    extra_context = {
        'template_name': 'user_account/profile/reset_password_failed.html'
    }

Form

class UserAccountPasswordResetForm(SetPasswordForm):
    new_password1 = forms.CharField(
        label=_("New password"),
        strip=False,
        widget=forms.PasswordInput(
            attrs={
                'class': 'form-control',
                'id': 'toggle-password-type',
                'placeholder': 'New Password'
            }),
    )
    new_password2 = forms.CharField(
        label=_("Confirm new password"),
        strip=False,
        widget=forms.PasswordInput(
            attrs={
                'class': 'form-control',
                'placeholder': 'Confirm New Password'
            }),
    )

    class Meta:
        fields = ['new_password1', 'new_password2']

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.helper = FormHelper(self)
        self.helper.form_show_labels = False
        self.helper.form_tag = False
        self.helper.layout = Layout(
            Field(AppendedText('new_password1',
                  '<i onclick="passwordTypeToggle()" class="fa fa-eye"></i>')),
            'new_password2',
        )

    def clean_new_password1(self):
        new_password1 = self.cleaned_data.get("new_password1")
        if not re.search(
                "^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)[A-Za-z\d@!#$%&*()_+-= ]{8,}$",
                new_password1
        ):
            raise ValidationError(
                'Password should have 8 to 15 characters which contain only characters, numeric digits, underscore and first character must be a letter', code='signup'
            )
        return new_password1

reset_password.html

{% extends 'base.html' %}
{% load static %}
{% load crispy_forms_tags %}

{% block content %}
<div class="content" id="container-fluid">
    <div class="card shadow-sm rounded" style="max-width: 500px; margin: auto;">
        <div class="card-body">
            <h1>Reset Password</h1>
            <hr>
            <form method="post">
                {% crispy form %}
                <div class="row">
                    <div class="col-12 text-right">
                        <button type="submit" class="btn btn-primary">Confirm</button>
                    </div>
                </div>
            </form>
        </div>
    </div>
</div>
{% endblock content %}

Solution

  • Passing a template name in extra_context does nothing. If the token is not valid the PasswordResetConfirmView sets the form to None and passes it to the context. You try to render that form getting an error. Instead you should check the variable validlink passed in the context to indicate that the link is correct or not and render appropriately:

    {% extends 'base.html' %}
    {% load static %}
    {% load crispy_forms_tags %}
    
    {% block content %}
    {% if validlink %}
        <div class="content" id="container-fluid">
            <div class="card shadow-sm rounded" style="max-width: 500px; margin: auto;">
                <div class="card-body">
                    <h1>Reset Password</h1>
                    <hr>
                    <form method="post">
                        {% crispy form %}
                        <div class="row">
                            <div class="col-12 text-right">
                                <button type="submit" class="btn btn-primary">Confirm</button>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    {% else %}
        <!--Render here as per your preferences to indicate the link is invalid-->
        Looks like you clicked on an invalid password reset link. Please try again.
    {% endif %}
    {% endblock content %}