Reputation: 1135
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
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