0

After successful password reset (first step) I have message in my email with next redirect link to next step:

127.0.0.1:8000/en/account/password-reset-confirm/NA-4ei-53e725f1ba4cbfc54fb8/


Unfortunately I always have this Message in password_reset_confirm.html template:

The password reset link was invalid, possibly because it has already been used. Please request a new password reset.


Question: Whats wrong I did?!


urls.py

    url(r'^password-reset/$',
        ResetPasswordRequestView.as_view(),
        name="reset_password"),
    url(r'^password-reset-confirm/(?P<uidb64>[0-9A-Za-z]+)-(?P<token>.+)/$',
        PasswordResetConfirmView.as_view(),
        name='reset_password_confirm'),

views.py

class ResetPasswordRequestView(FormView):
    template_name = "registration/password_reset.html"
    success_url = '/account/password-reset'
    form_class = PasswordResetRequestForm

    @staticmethod
    def validate_email_address(email):
        try:
            validate_email(email)
            return True
        except ValidationError:
            return False

    def post(self, request, *args, **kwargs):
        form = self.form_class(request.POST)
        try:
            if form.is_valid():
                data = form.cleaned_data["email_or_username"]
            if self.validate_email_address(data) is True:
                associated_users = User.objects.filter(Q(email=data) | Q(username=data))
                if associated_users.exists():
                    for user in associated_users:
                        c = {
                            'email': user.email,
                            'domain': request.META['HTTP_HOST'],
                            'site_name': _('Requirements Management System'),
                            'uid': urlsafe_base64_encode(force_bytes(user.pk)),
                            'user': user,
                            'token': default_token_generator.make_token(user),
                            'protocol': 'http',
                        }
                        subject_template_name = 'registration/password_reset_subject.txt'
                        email_template_name = 'registration/password_reset_email.html'
                        subject = loader.render_to_string(subject_template_name, c)
                        subject = ''.join(subject.splitlines())
                        email = loader.render_to_string(email_template_name, c)
                        send_mail(subject, email, DEFAULT_FROM_EMAIL, [user.email], fail_silently=False)
                    result = self.form_valid(form)
                    messages.success(request, 'An email has been sent to ' +
                                     data + ". Please check its inbox to continue reseting password.")
                    return result
                result = self.form_invalid(form)
                messages.error(request, _('No user is associated with this email address'))
                return result
            else:
                associated_users = User.objects.filter(username=data)
                if associated_users.exists():
                    for user in associated_users:
                        c = {
                            'email': user.email,
                            'domain': '127.0.0.1:8000',
                            'site_name': _('Requirements Management System'),
                            'uid': urlsafe_base64_encode(force_bytes(user.pk)),
                            'user': user,
                            'token': default_token_generator.make_token(user),
                            'protocol': 'http',
                        }
                        subject_template_name = 'registration/password_reset_subject.txt'
                        email_template_name = 'registration/password_reset_email.html'
                        subject = loader.render_to_string(subject_template_name, c)
                        # Email subject *must not* contain newlines
                        subject = ''.join(subject.splitlines())
                        email = loader.render_to_string(email_template_name, c)
                        send_mail(subject, email, DEFAULT_FROM_EMAIL, [user.email], fail_silently=False)
                    result = self.form_valid(form)
                    messages.success(request, 'Email has been sent to ' +
                                     data + "'s email address. Please check its inbox to continue reseting password.")
                    return result
                result = self.form_invalid(form)
                messages.error(request, _('This username does not exist in the system.'))
                return result
        except Exception as e:
            print(e)
        return self.form_invalid(form)


class PasswordResetConfirmView(FormView):
    template_name = "registration/password_reset_confirm.html"
    success_url = '/account/password-reset-confirm/'
    form_class = SetPasswordForm

    def post(self, request, uidb64=None, token=None, *arg, **kwargs):
        UserModel = get_user_model()
        form = self.form_class(request.POST)
        assert uidb64 is not None and token is not None
        try:
            uid = urlsafe_base64_decode(uidb64)
            user = UserModel._default_manager.get(pk=uid)
        except (TypeError, ValueError, OverflowError, UserModel.DoesNotExist):
            user = None

        if user is not None and default_token_generator.check_token(user, token):
            if form.is_valid():
                new_password = form.cleaned_data['new_password2']
                user.set_password(new_password)
                user.save()
                messages.success(request, 'Password has been reset.')
                return self.form_valid(form)
            else:
                messages.error(request, 'Password reset has not been unsuccessful.')
                return self.form_invalid(form)
        else:
            messages.error(request, 'The reset password link is no longer valid.')
            return self.form_invalid(form)

forms.py

class PasswordResetRequestForm(forms.Form):
    email_or_username = forms.CharField(required=True,
                                        widget=TextInput(attrs={'placeholder': _('Email address or Username')}))


class SetPasswordForm(forms.Form):
    error_messages = {
        'password_mismatch': _("The two password fields didn't match."),
    }
    new_password1 = forms.CharField(
        widget=forms.PasswordInput(attrs={'placeholder': _('Enter your new password')}))
    new_password2 = forms.CharField(
        widget=forms.PasswordInput(attrs={'placeholder': _('Enter your new password one more time')}))

    def clean_new_password2(self):
        first_password = self.cleaned_data.get('new_password1')
        second_password = self.cleaned_data.get('new_password2')
        if first_password and second_password:
            if first_password != second_password:
                raise forms.ValidationError(self.error_messages['password_mismatch'], code='password_mismatch')
        return second_password

password_reset.html

            <form action="." class="form" method="post">
                    {% csrf_token %}
                    {{ form.email_or_username }}
                    <button type="submit" class="button">Send e-mail</button>
            </form>
Nurzhan Nogerbek
  • 4,806
  • 16
  • 87
  • 193
  • can you show us your ``PasswordResetRequestForm`` and ``registration/password_reset.html``? – an0o0nym Aug 16 '16 at 00:50
  • There are nothing unusual inside these files I think but anyway I updated my post, check it please. Do you have any ideas?! – Nurzhan Nogerbek Aug 16 '16 at 05:32
  • It seems like there is some problem with your url formatting. The one you are generating is not matching the pattern. Did you try changing from ``'^password-reset-confirm/(?P[0-9A-Za-z]+)/(?P.+)/$'`` to ``'^password-reset-confirm/(?P[0-9A-Za-z]+)-(?P.+)/$'``? – an0o0nym Aug 16 '16 at 09:53
  • I tried both of them yesterday and result was as I described before in my post but now when I try it gives me error in log as `Reverse for 'reset_password_confirm' with arguments '()' and keyword arguments '{'uidb64': b'NA', 'token': '4ei-53e725f1ba4cbfc54fb8'}' not found. 1 pattern(s) tried: ['en/account/password-reset-confirm/(?P[0-9\u200c\u200bA-Za-z]+)-(?P.\u200c\u200b+)/$']` I am really comfused. Pls help! – Nurzhan Nogerbek Aug 16 '16 at 10:06
  • I also tried this one `'^password-reset-confirm/(?P[-\w]+)-(?P[-\w]+)/$'` but result was the same. What can you advice to me @an0o0nym ? – Nurzhan Nogerbek Aug 16 '16 at 16:59
  • can you reconstruct your files so that you get the ``Reverse for 'reset_password_conf‌​irm' with arguments '()' and keyword arguments...`` error message again, and tell me which file is causing that error? eventually post that file's content – an0o0nym Aug 16 '16 at 22:32

1 Answers1

1

I was facing the same problem then I removed the custom ResetPasswordRequestView view and used the default one given by django framework. All you need to do is create a template of named registration/password_reset.html and write url like below:

from django.contrib.auth import view as auth_view 
url(r'^password-reset/$', auth_view.ResetPasswordRequestView.as_view(),{'template_name':'registration/password_reset.html'} name="reset_password")

You can do the same for other urls also.You can find the code of ResetPasswordRequestView that we are calling. here

bSr
  • 1,410
  • 3
  • 16
  • 30