test_forms.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  1. from __future__ import unicode_literals
  2. import re
  3. from django import forms
  4. from django.contrib.auth.forms import (
  5. AuthenticationForm, PasswordChangeForm, PasswordResetForm,
  6. ReadOnlyPasswordHashField, ReadOnlyPasswordHashWidget, SetPasswordForm,
  7. UserChangeForm, UserCreationForm,
  8. )
  9. from django.contrib.auth.models import User
  10. from django.core import mail
  11. from django.core.mail import EmailMultiAlternatives
  12. from django.forms.fields import CharField, Field
  13. from django.test import TestCase, override_settings
  14. from django.utils import translation
  15. from django.utils.encoding import force_text
  16. from django.utils.text import capfirst
  17. from django.utils.translation import ugettext as _
  18. from .settings import AUTH_TEMPLATES
  19. @override_settings(USE_TZ=False, PASSWORD_HASHERS=['django.contrib.auth.hashers.SHA1PasswordHasher'])
  20. class UserCreationFormTest(TestCase):
  21. fixtures = ['authtestdata.json']
  22. def test_user_already_exists(self):
  23. data = {
  24. 'username': 'testclient',
  25. 'password1': 'test123',
  26. 'password2': 'test123',
  27. }
  28. form = UserCreationForm(data)
  29. self.assertFalse(form.is_valid())
  30. self.assertEqual(form["username"].errors,
  31. [force_text(User._meta.get_field('username').error_messages['unique'])])
  32. def test_invalid_data(self):
  33. data = {
  34. 'username': 'jsmith!',
  35. 'password1': 'test123',
  36. 'password2': 'test123',
  37. }
  38. form = UserCreationForm(data)
  39. self.assertFalse(form.is_valid())
  40. validator = next(v for v in User._meta.get_field('username').validators if v.code == 'invalid')
  41. self.assertEqual(form["username"].errors, [force_text(validator.message)])
  42. def test_password_verification(self):
  43. # The verification password is incorrect.
  44. data = {
  45. 'username': 'jsmith',
  46. 'password1': 'test123',
  47. 'password2': 'test',
  48. }
  49. form = UserCreationForm(data)
  50. self.assertFalse(form.is_valid())
  51. self.assertEqual(form["password2"].errors,
  52. [force_text(form.error_messages['password_mismatch'])])
  53. def test_both_passwords(self):
  54. # One (or both) passwords weren't given
  55. data = {'username': 'jsmith'}
  56. form = UserCreationForm(data)
  57. required_error = [force_text(Field.default_error_messages['required'])]
  58. self.assertFalse(form.is_valid())
  59. self.assertEqual(form['password1'].errors, required_error)
  60. self.assertEqual(form['password2'].errors, required_error)
  61. data['password2'] = 'test123'
  62. form = UserCreationForm(data)
  63. self.assertFalse(form.is_valid())
  64. self.assertEqual(form['password1'].errors, required_error)
  65. self.assertEqual(form['password2'].errors, [])
  66. def test_success(self):
  67. # The success case.
  68. data = {
  69. 'username': 'jsmith@example.com',
  70. 'password1': 'test123',
  71. 'password2': 'test123',
  72. }
  73. form = UserCreationForm(data)
  74. self.assertTrue(form.is_valid())
  75. u = form.save()
  76. self.assertEqual(repr(u), '<User: jsmith@example.com>')
  77. @override_settings(USE_TZ=False, PASSWORD_HASHERS=['django.contrib.auth.hashers.SHA1PasswordHasher'])
  78. class AuthenticationFormTest(TestCase):
  79. fixtures = ['authtestdata.json']
  80. def test_invalid_username(self):
  81. # The user submits an invalid username.
  82. data = {
  83. 'username': 'jsmith_does_not_exist',
  84. 'password': 'test123',
  85. }
  86. form = AuthenticationForm(None, data)
  87. self.assertFalse(form.is_valid())
  88. self.assertEqual(form.non_field_errors(),
  89. [force_text(form.error_messages['invalid_login'] % {
  90. 'username': User._meta.get_field('username').verbose_name
  91. })])
  92. def test_inactive_user(self):
  93. # The user is inactive.
  94. data = {
  95. 'username': 'inactive',
  96. 'password': 'password',
  97. }
  98. form = AuthenticationForm(None, data)
  99. self.assertFalse(form.is_valid())
  100. self.assertEqual(form.non_field_errors(),
  101. [force_text(form.error_messages['inactive'])])
  102. def test_inactive_user_i18n(self):
  103. with self.settings(USE_I18N=True), translation.override('pt-br', deactivate=True):
  104. # The user is inactive.
  105. data = {
  106. 'username': 'inactive',
  107. 'password': 'password',
  108. }
  109. form = AuthenticationForm(None, data)
  110. self.assertFalse(form.is_valid())
  111. self.assertEqual(form.non_field_errors(),
  112. [force_text(form.error_messages['inactive'])])
  113. def test_custom_login_allowed_policy(self):
  114. # The user is inactive, but our custom form policy allows them to log in.
  115. data = {
  116. 'username': 'inactive',
  117. 'password': 'password',
  118. }
  119. class AuthenticationFormWithInactiveUsersOkay(AuthenticationForm):
  120. def confirm_login_allowed(self, user):
  121. pass
  122. form = AuthenticationFormWithInactiveUsersOkay(None, data)
  123. self.assertTrue(form.is_valid())
  124. # If we want to disallow some logins according to custom logic,
  125. # we should raise a django.forms.ValidationError in the form.
  126. class PickyAuthenticationForm(AuthenticationForm):
  127. def confirm_login_allowed(self, user):
  128. if user.username == "inactive":
  129. raise forms.ValidationError("This user is disallowed.")
  130. raise forms.ValidationError("Sorry, nobody's allowed in.")
  131. form = PickyAuthenticationForm(None, data)
  132. self.assertFalse(form.is_valid())
  133. self.assertEqual(form.non_field_errors(), ['This user is disallowed.'])
  134. data = {
  135. 'username': 'testclient',
  136. 'password': 'password',
  137. }
  138. form = PickyAuthenticationForm(None, data)
  139. self.assertFalse(form.is_valid())
  140. self.assertEqual(form.non_field_errors(), ["Sorry, nobody's allowed in."])
  141. def test_success(self):
  142. # The success case
  143. data = {
  144. 'username': 'testclient',
  145. 'password': 'password',
  146. }
  147. form = AuthenticationForm(None, data)
  148. self.assertTrue(form.is_valid())
  149. self.assertEqual(form.non_field_errors(), [])
  150. def test_username_field_label(self):
  151. class CustomAuthenticationForm(AuthenticationForm):
  152. username = CharField(label="Name", max_length=75)
  153. form = CustomAuthenticationForm()
  154. self.assertEqual(form['username'].label, "Name")
  155. def test_username_field_label_not_set(self):
  156. class CustomAuthenticationForm(AuthenticationForm):
  157. username = CharField()
  158. form = CustomAuthenticationForm()
  159. username_field = User._meta.get_field(User.USERNAME_FIELD)
  160. self.assertEqual(form.fields['username'].label, capfirst(username_field.verbose_name))
  161. def test_username_field_label_empty_string(self):
  162. class CustomAuthenticationForm(AuthenticationForm):
  163. username = CharField(label='')
  164. form = CustomAuthenticationForm()
  165. self.assertEqual(form.fields['username'].label, "")
  166. @override_settings(USE_TZ=False, PASSWORD_HASHERS=['django.contrib.auth.hashers.SHA1PasswordHasher'])
  167. class SetPasswordFormTest(TestCase):
  168. fixtures = ['authtestdata.json']
  169. def test_password_verification(self):
  170. # The two new passwords do not match.
  171. user = User.objects.get(username='testclient')
  172. data = {
  173. 'new_password1': 'abc123',
  174. 'new_password2': 'abc',
  175. }
  176. form = SetPasswordForm(user, data)
  177. self.assertFalse(form.is_valid())
  178. self.assertEqual(form["new_password2"].errors,
  179. [force_text(form.error_messages['password_mismatch'])])
  180. def test_success(self):
  181. user = User.objects.get(username='testclient')
  182. data = {
  183. 'new_password1': 'abc123',
  184. 'new_password2': 'abc123',
  185. }
  186. form = SetPasswordForm(user, data)
  187. self.assertTrue(form.is_valid())
  188. @override_settings(USE_TZ=False, PASSWORD_HASHERS=['django.contrib.auth.hashers.SHA1PasswordHasher'])
  189. class PasswordChangeFormTest(TestCase):
  190. fixtures = ['authtestdata.json']
  191. def test_incorrect_password(self):
  192. user = User.objects.get(username='testclient')
  193. data = {
  194. 'old_password': 'test',
  195. 'new_password1': 'abc123',
  196. 'new_password2': 'abc123',
  197. }
  198. form = PasswordChangeForm(user, data)
  199. self.assertFalse(form.is_valid())
  200. self.assertEqual(form["old_password"].errors,
  201. [force_text(form.error_messages['password_incorrect'])])
  202. def test_password_verification(self):
  203. # The two new passwords do not match.
  204. user = User.objects.get(username='testclient')
  205. data = {
  206. 'old_password': 'password',
  207. 'new_password1': 'abc123',
  208. 'new_password2': 'abc',
  209. }
  210. form = PasswordChangeForm(user, data)
  211. self.assertFalse(form.is_valid())
  212. self.assertEqual(form["new_password2"].errors,
  213. [force_text(form.error_messages['password_mismatch'])])
  214. def test_success(self):
  215. # The success case.
  216. user = User.objects.get(username='testclient')
  217. data = {
  218. 'old_password': 'password',
  219. 'new_password1': 'abc123',
  220. 'new_password2': 'abc123',
  221. }
  222. form = PasswordChangeForm(user, data)
  223. self.assertTrue(form.is_valid())
  224. def test_field_order(self):
  225. # Regression test - check the order of fields:
  226. user = User.objects.get(username='testclient')
  227. self.assertEqual(list(PasswordChangeForm(user, {}).fields),
  228. ['old_password', 'new_password1', 'new_password2'])
  229. @override_settings(USE_TZ=False, PASSWORD_HASHERS=['django.contrib.auth.hashers.SHA1PasswordHasher'])
  230. class UserChangeFormTest(TestCase):
  231. fixtures = ['authtestdata.json']
  232. def test_username_validity(self):
  233. user = User.objects.get(username='testclient')
  234. data = {'username': 'not valid'}
  235. form = UserChangeForm(data, instance=user)
  236. self.assertFalse(form.is_valid())
  237. validator = next(v for v in User._meta.get_field('username').validators if v.code == 'invalid')
  238. self.assertEqual(form["username"].errors, [force_text(validator.message)])
  239. def test_bug_14242(self):
  240. # A regression test, introduce by adding an optimization for the
  241. # UserChangeForm.
  242. class MyUserForm(UserChangeForm):
  243. def __init__(self, *args, **kwargs):
  244. super(MyUserForm, self).__init__(*args, **kwargs)
  245. self.fields['groups'].help_text = 'These groups give users different permissions'
  246. class Meta(UserChangeForm.Meta):
  247. fields = ('groups',)
  248. # Just check we can create it
  249. MyUserForm({})
  250. def test_unsuable_password(self):
  251. user = User.objects.get(username='empty_password')
  252. user.set_unusable_password()
  253. user.save()
  254. form = UserChangeForm(instance=user)
  255. self.assertIn(_("No password set."), form.as_table())
  256. def test_bug_17944_empty_password(self):
  257. user = User.objects.get(username='empty_password')
  258. form = UserChangeForm(instance=user)
  259. self.assertIn(_("No password set."), form.as_table())
  260. def test_bug_17944_unmanageable_password(self):
  261. user = User.objects.get(username='unmanageable_password')
  262. form = UserChangeForm(instance=user)
  263. self.assertIn(_("Invalid password format or unknown hashing algorithm."),
  264. form.as_table())
  265. def test_bug_17944_unknown_password_algorithm(self):
  266. user = User.objects.get(username='unknown_password')
  267. form = UserChangeForm(instance=user)
  268. self.assertIn(_("Invalid password format or unknown hashing algorithm."),
  269. form.as_table())
  270. def test_bug_19133(self):
  271. "The change form does not return the password value"
  272. # Use the form to construct the POST data
  273. user = User.objects.get(username='testclient')
  274. form_for_data = UserChangeForm(instance=user)
  275. post_data = form_for_data.initial
  276. # The password field should be readonly, so anything
  277. # posted here should be ignored; the form will be
  278. # valid, and give back the 'initial' value for the
  279. # password field.
  280. post_data['password'] = 'new password'
  281. form = UserChangeForm(instance=user, data=post_data)
  282. self.assertTrue(form.is_valid())
  283. self.assertEqual(form.cleaned_data['password'], 'sha1$6efc0$f93efe9fd7542f25a7be94871ea45aa95de57161')
  284. def test_bug_19349_bound_password_field(self):
  285. user = User.objects.get(username='testclient')
  286. form = UserChangeForm(data={}, instance=user)
  287. # When rendering the bound password field,
  288. # ReadOnlyPasswordHashWidget needs the initial
  289. # value to render correctly
  290. self.assertEqual(form.initial['password'], form['password'].value())
  291. @override_settings(
  292. PASSWORD_HASHERS=['django.contrib.auth.hashers.SHA1PasswordHasher'],
  293. TEMPLATES=AUTH_TEMPLATES,
  294. USE_TZ=False,
  295. )
  296. class PasswordResetFormTest(TestCase):
  297. fixtures = ['authtestdata.json']
  298. def create_dummy_user(self):
  299. """
  300. Create a user and return a tuple (user_object, username, email).
  301. """
  302. username = 'jsmith'
  303. email = 'jsmith@example.com'
  304. user = User.objects.create_user(username, email, 'test123')
  305. return (user, username, email)
  306. def test_invalid_email(self):
  307. data = {'email': 'not valid'}
  308. form = PasswordResetForm(data)
  309. self.assertFalse(form.is_valid())
  310. self.assertEqual(form['email'].errors, [_('Enter a valid email address.')])
  311. def test_nonexistent_email(self):
  312. """
  313. Test nonexistent email address. This should not fail because it would
  314. expose information about registered users.
  315. """
  316. data = {'email': 'foo@bar.com'}
  317. form = PasswordResetForm(data)
  318. self.assertTrue(form.is_valid())
  319. self.assertEqual(len(mail.outbox), 0)
  320. def test_cleaned_data(self):
  321. (user, username, email) = self.create_dummy_user()
  322. data = {'email': email}
  323. form = PasswordResetForm(data)
  324. self.assertTrue(form.is_valid())
  325. form.save(domain_override='example.com')
  326. self.assertEqual(form.cleaned_data['email'], email)
  327. self.assertEqual(len(mail.outbox), 1)
  328. def test_custom_email_subject(self):
  329. data = {'email': 'testclient@example.com'}
  330. form = PasswordResetForm(data)
  331. self.assertTrue(form.is_valid())
  332. # Since we're not providing a request object, we must provide a
  333. # domain_override to prevent the save operation from failing in the
  334. # potential case where contrib.sites is not installed. Refs #16412.
  335. form.save(domain_override='example.com')
  336. self.assertEqual(len(mail.outbox), 1)
  337. self.assertEqual(mail.outbox[0].subject, 'Custom password reset on example.com')
  338. def test_custom_email_constructor(self):
  339. data = {'email': 'testclient@example.com'}
  340. class CustomEmailPasswordResetForm(PasswordResetForm):
  341. def send_mail(self, subject_template_name, email_template_name,
  342. context, from_email, to_email,
  343. html_email_template_name=None):
  344. EmailMultiAlternatives(
  345. "Forgot your password?",
  346. "Sorry to hear you forgot your password.",
  347. None, [to_email],
  348. ['site_monitor@example.com'],
  349. headers={'Reply-To': 'webmaster@example.com'},
  350. alternatives=[("Really sorry to hear you forgot your password.",
  351. "text/html")]).send()
  352. form = CustomEmailPasswordResetForm(data)
  353. self.assertTrue(form.is_valid())
  354. # Since we're not providing a request object, we must provide a
  355. # domain_override to prevent the save operation from failing in the
  356. # potential case where contrib.sites is not installed. Refs #16412.
  357. form.save(domain_override='example.com')
  358. self.assertEqual(len(mail.outbox), 1)
  359. self.assertEqual(mail.outbox[0].subject, 'Forgot your password?')
  360. self.assertEqual(mail.outbox[0].bcc, ['site_monitor@example.com'])
  361. self.assertEqual(mail.outbox[0].content_subtype, "plain")
  362. def test_preserve_username_case(self):
  363. """
  364. Preserve the case of the user name (before the @ in the email address)
  365. when creating a user (#5605).
  366. """
  367. user = User.objects.create_user('forms_test2', 'tesT@EXAMple.com', 'test')
  368. self.assertEqual(user.email, 'tesT@example.com')
  369. user = User.objects.create_user('forms_test3', 'tesT', 'test')
  370. self.assertEqual(user.email, 'tesT')
  371. def test_inactive_user(self):
  372. """
  373. Test that inactive user cannot receive password reset email.
  374. """
  375. (user, username, email) = self.create_dummy_user()
  376. user.is_active = False
  377. user.save()
  378. form = PasswordResetForm({'email': email})
  379. self.assertTrue(form.is_valid())
  380. form.save()
  381. self.assertEqual(len(mail.outbox), 0)
  382. def test_unusable_password(self):
  383. user = User.objects.create_user('testuser', 'test@example.com', 'test')
  384. data = {"email": "test@example.com"}
  385. form = PasswordResetForm(data)
  386. self.assertTrue(form.is_valid())
  387. user.set_unusable_password()
  388. user.save()
  389. form = PasswordResetForm(data)
  390. # The form itself is valid, but no email is sent
  391. self.assertTrue(form.is_valid())
  392. form.save()
  393. self.assertEqual(len(mail.outbox), 0)
  394. def test_save_plaintext_email(self):
  395. """
  396. Test the PasswordResetForm.save() method with no html_email_template_name
  397. parameter passed in.
  398. Test to ensure original behavior is unchanged after the parameter was added.
  399. """
  400. (user, username, email) = self.create_dummy_user()
  401. form = PasswordResetForm({"email": email})
  402. self.assertTrue(form.is_valid())
  403. form.save()
  404. self.assertEqual(len(mail.outbox), 1)
  405. message = mail.outbox[0].message()
  406. self.assertFalse(message.is_multipart())
  407. self.assertEqual(message.get_content_type(), 'text/plain')
  408. self.assertEqual(message.get('subject'), 'Custom password reset on example.com')
  409. self.assertEqual(len(mail.outbox[0].alternatives), 0)
  410. self.assertEqual(message.get_all('to'), [email])
  411. self.assertTrue(re.match(r'^http://example.com/reset/[\w+/-]', message.get_payload()))
  412. def test_save_html_email_template_name(self):
  413. """
  414. Test the PasswordResetFOrm.save() method with html_email_template_name
  415. parameter specified.
  416. Test to ensure that a multipart email is sent with both text/plain
  417. and text/html parts.
  418. """
  419. (user, username, email) = self.create_dummy_user()
  420. form = PasswordResetForm({"email": email})
  421. self.assertTrue(form.is_valid())
  422. form.save(html_email_template_name='registration/html_password_reset_email.html')
  423. self.assertEqual(len(mail.outbox), 1)
  424. self.assertEqual(len(mail.outbox[0].alternatives), 1)
  425. message = mail.outbox[0].message()
  426. self.assertEqual(message.get('subject'), 'Custom password reset on example.com')
  427. self.assertEqual(len(message.get_payload()), 2)
  428. self.assertTrue(message.is_multipart())
  429. self.assertEqual(message.get_payload(0).get_content_type(), 'text/plain')
  430. self.assertEqual(message.get_payload(1).get_content_type(), 'text/html')
  431. self.assertEqual(message.get_all('to'), [email])
  432. self.assertTrue(re.match(r'^http://example.com/reset/[\w/-]+', message.get_payload(0).get_payload()))
  433. self.assertTrue(
  434. re.match(r'^<html><a href="http://example.com/reset/[\w/-]+/">Link</a></html>$',
  435. message.get_payload(1).get_payload())
  436. )
  437. class ReadOnlyPasswordHashTest(TestCase):
  438. def test_bug_19349_render_with_none_value(self):
  439. # Rendering the widget with value set to None
  440. # mustn't raise an exception.
  441. widget = ReadOnlyPasswordHashWidget()
  442. html = widget.render(name='password', value=None, attrs={})
  443. self.assertIn(_("No password set."), html)
  444. def test_readonly_field_has_changed(self):
  445. field = ReadOnlyPasswordHashField()
  446. self.assertFalse(field.has_changed('aaa', 'bbb'))