1

I have a django application which is using Django Rest Framework.

I am trying to create user password reset flow using Django Class Based Form View. For that I have defined URL's like:

url(r'^reset/confirm/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/$',
        views.PasswordResetConfirmView.as_view(), name='reset_confirm'),
    url(r'^reset/$', views.ResetPasswordRequestView.as_view(), name='reset')

But the problem is that when I try visit the reset url, it shows the following error.

Expected a `Response`, `HttpResponse` or `HttpStreamingResponse` to be returned from the view, but received a `<type 'NoneType'>`

I am unsure of whether mixing DRF views and Django Views is allowed.

What am I doing wrong? What should be the best approach to create a password reset flow using only DRF.

Edit , code of ResetPasswordRequestView

class ResetPasswordRequestView(FormView):

    template_name = "users/password_reset_template.html"
    form_class = PasswordResetRequestForm
    success_url = "#"

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

            # default method to handle post requests

    def post(self, request, *args, **kwargs):
        form = self.form_class(request.POST)
        if form.is_valid():
            email = form.cleaned_data['email']

        if self.validate_email_address(email) is True:
            try:
                user_profile = Profile.objects.get(email=email)
            except Profile.DoesNotExist:
                user_profile = None
            if user_profile:
                # send mail here

                subject_template_name = 'registration/password_reset_subject.txt'
                email_template_name = 'registration/password_reset_email.html'

                subject = loader.render_to_string(subject_template_name, message)

                # subject should not contain newlines
                subject = ''.join(subject.splitlines())
                email = loader.render_to_string(email_template_name, message)
                send_mail(subject, email, settings.DEFAULT_FROM_EMAIL, [user_profile.email], fail_silently=False)
                result = self.form_valid(form)
                messages.success(request, 'Email has been sent to ' + email +
                                 "'s email address. Please check its inbox to "
                                 "continue resetting password.")
                return result
            result = self.form_invalid(form)
            messages.error(request, 'This username does not exist in the system.')
            return result
        messages.error(request, 'Invalid Input')
        return self.form_invalid(form)

Code for PasswordResetConfirmView

class PasswordResetConfirmView(FormView):
    template_name = 'users/change_password.html'
    success_url = "/"
    form_class = ChangePasswordForm

    def post(self, request, uidb64=None, token=None, *args, **kwargs):
        form = self.form_class(request.POST)
        assert uidb64 is not None and token is not None
        try:
            uid = urlsafe_base64_decode(uidb64)
            user_profile = Profile.objects.get(pk=uid)
        except (TypeError, ValueError, OverflowError, Profile.DoesNotExist):
            user_profile = None

        if user_profile is not None and default_token_generator.check_token(user_profile, token):
            if form.is_valid():
                new_password = form.cleaned_data['password']
                user_profile.set_password(new_password)
                user_profile.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)

The stacktrace shows the following, have changed the python path here(it is correct on my pc)

Traceback:
File "/[base path here]/local/lib/python2.7/site-packages/django/core/handlers/base.py" in get_response
  132.                     response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/[base path here]/local/lib/python2.7/site-packages/django/views/decorators/csrf.py" in wrapped_view
  58.         return view_func(*args, **kwargs)
File "/[base path here]/local/lib/python2.7/site-packages/django/views/generic/base.py" in view
  71.             return self.dispatch(request, *args, **kwargs)
File "/[base path here]/local/lib/python2.7/site-packages/rest_framework/views.py" in dispatch
  468.         self.response = self.finalize_response(request, response, *args, **kwargs)
File "/[base path here]/local/lib/python2.7/site-packages/rest_framework/views.py" in finalize_response
  396.             % type(response)

Exception Type: AssertionError at /user/reset/
Exception Value: Expected a `Response`, `HttpResponse` or `HttpStreamingResponse` to be returned from the view, but received a `<type 'NoneType'>`

urls.py

from django.conf.urls import url
from users import views

urlpatterns = [
    url(r'^signup/$', views.register),
    url(r'^check-username/$', views.check_username),
    url(r'^change-password/$', views.change_password),
    url(r'^interests/$', views.get_all_interests),
    url(r'^signin/$', 'rest_framework_jwt.views.obtain_jwt_token'),
    url(r'^verify-token/$', 'rest_framework_jwt.views.verify_jwt_token'),
    url(r'^(?P<user_id>[0-9]{1,6})/$', views.generate_feed),
    url(r'^community/(?P<community_id>[0-9]{1,6})/'
        r'(?P<user_id>[0-9]{1,6})/$', views.community_feed),
    url(r'^(?P<username>[A-Za-z_\.]{1,20})/$', views.user_profile),
    # url's for reset password
    url(r'^reset/confirm/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/$',
        views.PasswordResetConfirmView.as_view(), name='reset_confirm'),
    url(r'^reset/$', views.ResetPasswordRequestView.as_view(), name='reset'),
]

Edit: It works fine when I put the reset urls before other urls, but still don't know how and why?

10
  • The problem lies in the code for views.ResetPasswordRequestView. Can you post that class? Commented Nov 18, 2015 at 11:21
  • @joerick added code of the view Commented Nov 18, 2015 at 11:30
  • @ofnowhereland Mixin the two "paradigms" is totally allowed and valid! Commented Nov 18, 2015 at 11:30
  • Hmm, don't see anything wrong there. Perhaps in views.PasswordResetConfirmView? Commented Nov 18, 2015 at 11:56
  • @joerick Added that as well Commented Nov 18, 2015 at 12:14

1 Answer 1

1

If you look at the stack trace, you will see that the error is raised in the Django Rest Framework code. That should not happen, because you a django.views.generic.FormView has nothing to do with DRF, so rest_framework.views.dispatch should never be called.

All the code you posted looks fine, the problem must be somewhere else. My guess is that something is wrong with urls.py, but this is really just a guess. As I first step, I would make sure that the right URL pattern matches.

Update:

The regular expression r'^(?P<username>[A-Za-z_\.]{1,20})/$' in the URL pattern for views.user_profile matches 'reset/'. The URL resolver uses the first URL pattern that matches, therefor you have to put the URL pattern for reset further up in the list.

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks, the problem was with urls.py .If the place the reset urls in the beginning it is working fine, but I still don't know why.
@ofnowhereland if my answer solved your problem, it would be great if you could accept it. Thanks!
I am still searching for the reason.
@ofnowhereland I didn't see that you posted the complete urls.py, please see my update

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.