Django v1.10
FormView代码:
class PasswordResetConfirmView(FormView):
template_name = "dashboard/account/reset_password_form.html"
success_url = '/dashboard/'
form_class = SetPasswordForm
def authenticate_password_token(self, request, uidb64=None, token=None, encodedtimestring=None):
try:
uid = force_text(urlsafe_base64_decode(uidb64))
user = User.objects.get(pk=uid)
timestring = force_text(urlsafe_base64_decode(encodedtimestring))
timestamp = timeparse(timestring)
timediff = timezone.now() - timestamp
except (TypeError, ValueError, OverflowError, User.DoesNotExist):
user = None
timediff = None
if timediff is None or timediff.days < 0 or timediff.days > PASSWORD_RESET_TIMEOUT_DAYS:
messages.error(request, _(
'The reset password link is no longer valid.'))
return None
if user is None or not default_token_generator.check_token(user, token):
messages.error(request, _('The reset password link is not valid.'))
return None
return user
def get(self, request, uidb64=None, token=None, encodedtimestring=None, *arg, **kwargs):
form = self.form_class()
assert uidb64 is not None and token is not None and encodedtimestring is not None
user = self.authenticate_password_token(
request, uidb64, token, encodedtimestring)
if user is None:
return redirect(reverse('dashboard-login'))
return self.render_to_response(self.get_context_data(form=form))
def post(self, request, uidb64=None, token=None, encodedtimestring=None, *arg, **kwargs):
form = self.form_class(request.POST)
assert uidb64 is not None and token is not None and encodedtimestring is not None
user = self.authenticate_password_token(
request, uidb64, token, encodedtimestring)
if user is None:
return redirect(reverse('dashboard-login'))
if not form.is_valid():
return self.form_invalid(form)
new_password = form.cleaned_data['new_password2']
try:
with transaction.atomic():
user.auth_token.delete()
Token.objects.create(user=user)
user.set_password(new_password)
user.save()
except:
messages.error(request, _('Password reset was unsuccessful.'))
return redirect(reverse('dashboard-login'))
messages.success(request, _('Password has been reset.'))
return redirect(reverse('dashboard-login'))
urls.py:
url(r'^(?i)recover/password/(?P<uidb64>[0-9A-Za-z]+)/(?P<token>.+)/(?P<encodedtimestring>.+)/$',
views.PasswordResetConfirmView.as_view(), name='reset-password-confirm'),
testclass parent:
class BaseApiTest(TestCase):
def setUp(self):
superuser = User.objects.create_superuser(
'test', 'test@api.com', 'testpassword')
self.factory = RequestFactory()
self.user = superuser
self.client.login(username=superuser.username, password='testpassword')
我尝试编写测试用例:
class ResetPasswordEmailTest(BaseApiTest):
def test_password_reset_form(self):
"""
Ensure that the authenticate token works
"""
self.client.logout()
token = default_token_generator.make_token(self.user)
uidb64 = force_bytes(self.user.id)
timenow = force_bytes(timezone.now())
response = self.client.get(
reverse('reset-password-confirm',
args=[urlsafe_base64_encode(uidb64), token,
urlsafe_base64_encode(timenow)]))
self.assertEqual(response.status_code, status.HTTP_200_OK)
我收到错误消息:
tests/password_tests.py", line 129, in test_password_reset_form
self.assertEqual(response.status_code, status.HTTP_200_OK)
AssertionError: 302 != 200
我不确定如何编写测试类来测试formview的所有3种方法。我的尝试只是测试get
方法
更新:
失败的真正原因与用户登录无关,但check_token
中固有的PasswordTokenGenerator
方法未能通过我的测试。
当我做更多的研究时,我认为将Django v1.10升级到v1.11会更好,我需要重写整个事情,最终可能会使这个问题无效。
答案 0 :(得分:3)
PasswordResetConfirmView
的 get 方法正在回复2个不同的回复。
<强>重定向强>
当用户为“无”时,它正在使用重定向URI
进行响应if user is None:
return redirect(reverse('dashboard-login'))
因此,在这种情况下,响应的状态代码将是
HTTP 302
您被重定向到其他URL。
呈现回复
当有用户时,随响应返回的信息是请求中使用的方法。
return self.render_to_response(self.get_context_data(form=form))
因此,在这种情况下,响应的状态代码将为
HTTP 200
请求已成功。
为了让您的测试用例通过,您可以使用assertRedirects
def test_password_reset_form(self):
...
# A URL that redirects can be followed to termination.
response = self.client.get(reverse('reset-password-confirm', args=[...]), follow=True)
self.assertRedirects(response, reverse('dashboard-login'), status_code=302, target_status_code=200)
self.assertEqual(len(response.redirect_chain), 2)
如果您的请求使用了关注参数,则 expected_url 和 target_status_code 将成为重定向最终点的网址和状态代码链
参考: https://github.com/django/django/blob/master/tests/test_client/tests.py
或者,您可以创建两个单独的测试用例(当用户登录时,当用户未登录时)并使用 assertEqual
# Test for logged in user.
def test_password_reset_form_for_logged_in_user(self):
...
# do stuff
self.assertEqual(response.status_code, status.HTTP_200_OK)
# Test if user not logged in.
def test_password_reset_form_if_user_not_logged_in(self):
...
# do stuff
self.assertEqual(response.status_code, status.HTTP_302_FOUND)
答案 1 :(得分:1)
问题( Post Mortem ):
让我们从定义问题开始:
状态302:
请求的资源暂时驻留在不同的URI下。 - source
因此,我们假设您正在重定向,因此测试失败。
发生这种情况:
在get()
方法中,我们找到了以下代码:
if user is None:
return redirect(reverse('dashboard-login'))
因此,如果用户为Anonymous
(未经身份验证),则视图会将其重定向到登录页面。
从上面可以看出,当您向PasswordResetConfirmView.get()
发出未经身份验证的请求时,就会出现问题。
<强>解决方案:强>
在提出请求之前,您需要对用户进行身份验证(登录)
您可以使用force_login()
方法执行此操作:
如果您的网站使用Django的身份验证系统,您可以使用
force_login()
方法来模拟用户登录网站的效果。当测试要求用户登录时,请使用此方法代替login()
,并且用户登录方式的详细信息并不重要。
def my_test_case():
... Do stuff ...
# Login
self.client.force_login(self.user)
# Make the request
response = self.client.get(...)