test_urlfield.py 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. import sys
  2. from types import ModuleType
  3. from django.conf import FORMS_URLFIELD_ASSUME_HTTPS_DEPRECATED_MSG, Settings, settings
  4. from django.core.exceptions import ValidationError
  5. from django.forms import URLField
  6. from django.test import SimpleTestCase, ignore_warnings
  7. from django.utils.deprecation import RemovedInDjango60Warning
  8. from . import FormFieldAssertionsMixin
  9. @ignore_warnings(category=RemovedInDjango60Warning)
  10. class URLFieldTest(FormFieldAssertionsMixin, SimpleTestCase):
  11. def test_urlfield_widget(self):
  12. f = URLField()
  13. self.assertWidgetRendersTo(f, '<input type="url" name="f" id="id_f" required>')
  14. def test_urlfield_widget_max_min_length(self):
  15. f = URLField(min_length=15, max_length=20)
  16. self.assertEqual("http://example.com", f.clean("http://example.com"))
  17. self.assertWidgetRendersTo(
  18. f,
  19. '<input id="id_f" type="url" name="f" maxlength="20" '
  20. 'minlength="15" required>',
  21. )
  22. msg = "'Ensure this value has at least 15 characters (it has 12).'"
  23. with self.assertRaisesMessage(ValidationError, msg):
  24. f.clean("http://f.com")
  25. msg = "'Ensure this value has at most 20 characters (it has 37).'"
  26. with self.assertRaisesMessage(ValidationError, msg):
  27. f.clean("http://abcdefghijklmnopqrstuvwxyz.com")
  28. def test_urlfield_clean(self):
  29. # RemovedInDjango60Warning: When the deprecation ends, remove the
  30. # assume_scheme argument.
  31. f = URLField(required=False, assume_scheme="https")
  32. tests = [
  33. ("http://localhost", "http://localhost"),
  34. ("http://example.com", "http://example.com"),
  35. ("http://example.com/test", "http://example.com/test"),
  36. ("http://example.com.", "http://example.com."),
  37. ("http://www.example.com", "http://www.example.com"),
  38. ("http://www.example.com:8000/test", "http://www.example.com:8000/test"),
  39. (
  40. "http://example.com?some_param=some_value",
  41. "http://example.com?some_param=some_value",
  42. ),
  43. ("valid-with-hyphens.com", "https://valid-with-hyphens.com"),
  44. ("subdomain.domain.com", "https://subdomain.domain.com"),
  45. ("http://200.8.9.10", "http://200.8.9.10"),
  46. ("http://200.8.9.10:8000/test", "http://200.8.9.10:8000/test"),
  47. ("http://valid-----hyphens.com", "http://valid-----hyphens.com"),
  48. (
  49. "http://some.idn.xyzäöüßabc.domain.com:123/blah",
  50. "http://some.idn.xyz\xe4\xf6\xfc\xdfabc.domain.com:123/blah",
  51. ),
  52. (
  53. "www.example.com/s/http://code.djangoproject.com/ticket/13804",
  54. "https://www.example.com/s/http://code.djangoproject.com/ticket/13804",
  55. ),
  56. # Normalization.
  57. ("http://example.com/ ", "http://example.com/"),
  58. # Valid IDN.
  59. ("http://עברית.idn.icann.org/", "http://עברית.idn.icann.org/"),
  60. ("http://sãopaulo.com/", "http://sãopaulo.com/"),
  61. ("http://sãopaulo.com.br/", "http://sãopaulo.com.br/"),
  62. ("http://пример.испытание/", "http://пример.испытание/"),
  63. ("http://مثال.إختبار/", "http://مثال.إختبار/"),
  64. ("http://例子.测试/", "http://例子.测试/"),
  65. ("http://例子.測試/", "http://例子.測試/"),
  66. (
  67. "http://उदाहरण.परीक्षा/",
  68. "http://उदाहरण.परीक्षा/",
  69. ),
  70. ("http://例え.テスト/", "http://例え.テスト/"),
  71. ("http://مثال.آزمایشی/", "http://مثال.آزمایشی/"),
  72. ("http://실례.테스트/", "http://실례.테스트/"),
  73. ("http://العربية.idn.icann.org/", "http://العربية.idn.icann.org/"),
  74. # IPv6.
  75. ("http://[12:34::3a53]/", "http://[12:34::3a53]/"),
  76. ("http://[a34:9238::]:8080/", "http://[a34:9238::]:8080/"),
  77. ]
  78. for url, expected in tests:
  79. with self.subTest(url=url):
  80. self.assertEqual(f.clean(url), expected)
  81. def test_urlfield_clean_invalid(self):
  82. f = URLField()
  83. tests = [
  84. "foo",
  85. "com.",
  86. ".",
  87. "http://",
  88. "http://example",
  89. "http://example.",
  90. "http://.com",
  91. "http://invalid-.com",
  92. "http://-invalid.com",
  93. "http://inv-.alid-.com",
  94. "http://inv-.-alid.com",
  95. "[a",
  96. "http://[a",
  97. # Non-string.
  98. 23,
  99. # Hangs "forever" before fixing a catastrophic backtracking,
  100. # see #11198.
  101. "http://%s" % ("X" * 60,),
  102. # A second example, to make sure the problem is really addressed,
  103. # even on domains that don't fail the domain label length check in
  104. # the regex.
  105. "http://%s" % ("X" * 200,),
  106. # urlsplit() raises ValueError.
  107. "////]@N.AN",
  108. # Empty hostname.
  109. "#@A.bO",
  110. ]
  111. msg = "'Enter a valid URL.'"
  112. for value in tests:
  113. with self.subTest(value=value):
  114. with self.assertRaisesMessage(ValidationError, msg):
  115. f.clean(value)
  116. def test_urlfield_clean_required(self):
  117. f = URLField()
  118. msg = "'This field is required.'"
  119. with self.assertRaisesMessage(ValidationError, msg):
  120. f.clean(None)
  121. with self.assertRaisesMessage(ValidationError, msg):
  122. f.clean("")
  123. def test_urlfield_clean_not_required(self):
  124. f = URLField(required=False)
  125. self.assertEqual(f.clean(None), "")
  126. self.assertEqual(f.clean(""), "")
  127. def test_urlfield_strip_on_none_value(self):
  128. f = URLField(required=False, empty_value=None)
  129. self.assertIsNone(f.clean(""))
  130. self.assertIsNone(f.clean(None))
  131. def test_urlfield_unable_to_set_strip_kwarg(self):
  132. msg = "got multiple values for keyword argument 'strip'"
  133. with self.assertRaisesMessage(TypeError, msg):
  134. URLField(strip=False)
  135. def test_urlfield_assume_scheme(self):
  136. f = URLField()
  137. # RemovedInDjango60Warning: When the deprecation ends, replace with:
  138. # "https://example.com"
  139. self.assertEqual(f.clean("example.com"), "http://example.com")
  140. f = URLField(assume_scheme="http")
  141. self.assertEqual(f.clean("example.com"), "http://example.com")
  142. f = URLField(assume_scheme="https")
  143. self.assertEqual(f.clean("example.com"), "https://example.com")
  144. class URLFieldAssumeSchemeDeprecationTest(FormFieldAssertionsMixin, SimpleTestCase):
  145. def test_urlfield_raises_warning(self):
  146. msg = (
  147. "The default scheme will be changed from 'http' to 'https' in Django 6.0. "
  148. "Pass the forms.URLField.assume_scheme argument to silence this warning, "
  149. "or set the FORMS_URLFIELD_ASSUME_HTTPS transitional setting to True to "
  150. "opt into using 'https' as the new default scheme."
  151. )
  152. with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx:
  153. f = URLField()
  154. self.assertEqual(f.clean("example.com"), "http://example.com")
  155. self.assertEqual(ctx.filename, __file__)
  156. @ignore_warnings(category=RemovedInDjango60Warning)
  157. def test_urlfield_forms_urlfield_assume_https(self):
  158. with self.settings(FORMS_URLFIELD_ASSUME_HTTPS=True):
  159. f = URLField()
  160. self.assertEqual(f.clean("example.com"), "https://example.com")
  161. f = URLField(assume_scheme="http")
  162. self.assertEqual(f.clean("example.com"), "http://example.com")
  163. def test_override_forms_urlfield_assume_https_setting_warning(self):
  164. msg = FORMS_URLFIELD_ASSUME_HTTPS_DEPRECATED_MSG
  165. with self.assertRaisesMessage(RemovedInDjango60Warning, msg):
  166. # Changing FORMS_URLFIELD_ASSUME_HTTPS via self.settings() raises a
  167. # deprecation warning.
  168. with self.settings(FORMS_URLFIELD_ASSUME_HTTPS=True):
  169. pass
  170. def test_settings_init_forms_urlfield_assume_https_warning(self):
  171. settings_module = ModuleType("fake_settings_module")
  172. settings_module.FORMS_URLFIELD_ASSUME_HTTPS = True
  173. sys.modules["fake_settings_module"] = settings_module
  174. msg = FORMS_URLFIELD_ASSUME_HTTPS_DEPRECATED_MSG
  175. try:
  176. with self.assertRaisesMessage(RemovedInDjango60Warning, msg):
  177. Settings("fake_settings_module")
  178. finally:
  179. del sys.modules["fake_settings_module"]
  180. def test_access_forms_urlfield_assume_https(self):
  181. # Warning is not raised on access.
  182. self.assertEqual(settings.FORMS_URLFIELD_ASSUME_HTTPS, False)