ofnowhere
ofnowhere

Reputation: 1135

Combining normal Django Views with Django Rest Framework View

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?

Upvotes: 1

Views: 2987

Answers (1)

Daniel Hepper
Daniel Hepper

Reputation: 29967

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.

Upvotes: 1

Related Questions