test_urls.py 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. from django.conf import settings
  2. from django.core.checks.messages import Error, Warning
  3. from django.core.checks.urls import (
  4. E006, check_url_config, check_url_namespaces_unique, check_url_settings,
  5. get_warning_for_invalid_pattern,
  6. )
  7. from django.test import SimpleTestCase
  8. from django.test.utils import override_settings
  9. class CheckUrlConfigTests(SimpleTestCase):
  10. @override_settings(ROOT_URLCONF='check_framework.urls.no_warnings')
  11. def test_no_warnings(self):
  12. result = check_url_config(None)
  13. self.assertEqual(result, [])
  14. @override_settings(ROOT_URLCONF='check_framework.urls.no_warnings_i18n')
  15. def test_no_warnings_i18n(self):
  16. self.assertEqual(check_url_config(None), [])
  17. @override_settings(ROOT_URLCONF='check_framework.urls.warning_in_include')
  18. def test_check_resolver_recursive(self):
  19. # The resolver is checked recursively (examining URL patterns in include()).
  20. result = check_url_config(None)
  21. self.assertEqual(len(result), 1)
  22. warning = result[0]
  23. self.assertEqual(warning.id, 'urls.W001')
  24. @override_settings(ROOT_URLCONF='check_framework.urls.include_with_dollar')
  25. def test_include_with_dollar(self):
  26. result = check_url_config(None)
  27. self.assertEqual(len(result), 1)
  28. warning = result[0]
  29. self.assertEqual(warning.id, 'urls.W001')
  30. self.assertEqual(warning.msg, (
  31. "Your URL pattern '^include-with-dollar$' uses include with a "
  32. "route ending with a '$'. Remove the dollar from the route to "
  33. "avoid problems including URLs."
  34. ))
  35. @override_settings(ROOT_URLCONF='check_framework.urls.contains_tuple')
  36. def test_contains_tuple_not_url_instance(self):
  37. result = check_url_config(None)
  38. warning = result[0]
  39. self.assertEqual(warning.id, 'urls.E004')
  40. self.assertRegex(warning.msg, (
  41. r"^Your URL pattern \('\^tuple/\$', <function <lambda> at 0x(\w+)>\) is "
  42. r"invalid. Ensure that urlpatterns is a list of path\(\) and/or re_path\(\) "
  43. r"instances\.$"
  44. ))
  45. @override_settings(ROOT_URLCONF='check_framework.urls.include_contains_tuple')
  46. def test_contains_included_tuple(self):
  47. result = check_url_config(None)
  48. warning = result[0]
  49. self.assertEqual(warning.id, 'urls.E004')
  50. self.assertRegex(warning.msg, (
  51. r"^Your URL pattern \('\^tuple/\$', <function <lambda> at 0x(\w+)>\) is "
  52. r"invalid. Ensure that urlpatterns is a list of path\(\) and/or re_path\(\) "
  53. r"instances\.$"
  54. ))
  55. @override_settings(ROOT_URLCONF='check_framework.urls.beginning_with_slash')
  56. def test_beginning_with_slash(self):
  57. msg = (
  58. "Your URL pattern '%s' has a route beginning with a '/'. Remove "
  59. "this slash as it is unnecessary. If this pattern is targeted in "
  60. "an include(), ensure the include() pattern has a trailing '/'."
  61. )
  62. warning1, warning2 = check_url_config(None)
  63. self.assertEqual(warning1.id, 'urls.W002')
  64. self.assertEqual(warning1.msg, msg % '/path-starting-with-slash/')
  65. self.assertEqual(warning2.id, 'urls.W002')
  66. self.assertEqual(warning2.msg, msg % '/url-starting-with-slash/$')
  67. @override_settings(
  68. ROOT_URLCONF='check_framework.urls.beginning_with_slash',
  69. APPEND_SLASH=False,
  70. )
  71. def test_beginning_with_slash_append_slash(self):
  72. # It can be useful to start a URL pattern with a slash when
  73. # APPEND_SLASH=False (#27238).
  74. result = check_url_config(None)
  75. self.assertEqual(result, [])
  76. @override_settings(ROOT_URLCONF='check_framework.urls.name_with_colon')
  77. def test_name_with_colon(self):
  78. result = check_url_config(None)
  79. self.assertEqual(len(result), 1)
  80. warning = result[0]
  81. self.assertEqual(warning.id, 'urls.W003')
  82. expected_msg = "Your URL pattern '^$' [name='name_with:colon'] has a name including a ':'."
  83. self.assertIn(expected_msg, warning.msg)
  84. @override_settings(ROOT_URLCONF=None)
  85. def test_no_root_urlconf_in_settings(self):
  86. delattr(settings, 'ROOT_URLCONF')
  87. result = check_url_config(None)
  88. self.assertEqual(result, [])
  89. def test_get_warning_for_invalid_pattern_string(self):
  90. warning = get_warning_for_invalid_pattern('')[0]
  91. self.assertEqual(
  92. warning.hint,
  93. "Try removing the string ''. The list of urlpatterns should "
  94. "not have a prefix string as the first element.",
  95. )
  96. def test_get_warning_for_invalid_pattern_tuple(self):
  97. warning = get_warning_for_invalid_pattern((r'^$', lambda x: x))[0]
  98. self.assertEqual(warning.hint, "Try using path() instead of a tuple.")
  99. def test_get_warning_for_invalid_pattern_other(self):
  100. warning = get_warning_for_invalid_pattern(object())[0]
  101. self.assertIsNone(warning.hint)
  102. @override_settings(ROOT_URLCONF='check_framework.urls.non_unique_namespaces')
  103. def test_check_non_unique_namespaces(self):
  104. result = check_url_namespaces_unique(None)
  105. self.assertEqual(len(result), 2)
  106. non_unique_namespaces = ['app-ns1', 'app-1']
  107. warning_messages = [
  108. "URL namespace '{}' isn't unique. You may not be able to reverse "
  109. "all URLs in this namespace".format(namespace)
  110. for namespace in non_unique_namespaces
  111. ]
  112. for warning in result:
  113. self.assertIsInstance(warning, Warning)
  114. self.assertEqual('urls.W005', warning.id)
  115. self.assertIn(warning.msg, warning_messages)
  116. @override_settings(ROOT_URLCONF='check_framework.urls.unique_namespaces')
  117. def test_check_unique_namespaces(self):
  118. result = check_url_namespaces_unique(None)
  119. self.assertEqual(result, [])
  120. class UpdatedToPathTests(SimpleTestCase):
  121. @override_settings(ROOT_URLCONF='check_framework.urls.path_compatibility.contains_re_named_group')
  122. def test_contains_re_named_group(self):
  123. result = check_url_config(None)
  124. self.assertEqual(len(result), 1)
  125. warning = result[0]
  126. self.assertEqual(warning.id, '2_0.W001')
  127. expected_msg = "Your URL pattern '(?P<named_group>\\d+)' has a route"
  128. self.assertIn(expected_msg, warning.msg)
  129. @override_settings(ROOT_URLCONF='check_framework.urls.path_compatibility.beginning_with_caret')
  130. def test_beginning_with_caret(self):
  131. result = check_url_config(None)
  132. self.assertEqual(len(result), 1)
  133. warning = result[0]
  134. self.assertEqual(warning.id, '2_0.W001')
  135. expected_msg = "Your URL pattern '^beginning-with-caret' has a route"
  136. self.assertIn(expected_msg, warning.msg)
  137. @override_settings(ROOT_URLCONF='check_framework.urls.path_compatibility.ending_with_dollar')
  138. def test_ending_with_dollar(self):
  139. result = check_url_config(None)
  140. self.assertEqual(len(result), 1)
  141. warning = result[0]
  142. self.assertEqual(warning.id, '2_0.W001')
  143. expected_msg = "Your URL pattern 'ending-with-dollar$' has a route"
  144. self.assertIn(expected_msg, warning.msg)
  145. class CheckCustomErrorHandlersTests(SimpleTestCase):
  146. @override_settings(ROOT_URLCONF='check_framework.urls.bad_error_handlers')
  147. def test_bad_handlers(self):
  148. result = check_url_config(None)
  149. self.assertEqual(len(result), 4)
  150. for code, num_params, error in zip([400, 403, 404, 500], [2, 2, 2, 1], result):
  151. with self.subTest('handler{}'.format(code)):
  152. self.assertEqual(error, Error(
  153. "The custom handler{} view "
  154. "'check_framework.urls.bad_error_handlers.bad_handler' "
  155. "does not take the correct number of arguments (request{})."
  156. .format(code, ', exception' if num_params == 2 else ''),
  157. id='urls.E007',
  158. ))
  159. @override_settings(ROOT_URLCONF='check_framework.urls.bad_error_handlers_invalid_path')
  160. def test_bad_handlers_invalid_path(self):
  161. result = check_url_config(None)
  162. paths = [
  163. 'django.views.bad_handler',
  164. 'django.invalid_module.bad_handler',
  165. 'invalid_module.bad_handler',
  166. 'django',
  167. ]
  168. hints = [
  169. "Could not import '{}'. View does not exist in module django.views.",
  170. "Could not import '{}'. Parent module django.invalid_module does not exist.",
  171. "No module named 'invalid_module'",
  172. "Could not import '{}'. The path must be fully qualified.",
  173. ]
  174. for code, path, hint, error in zip([400, 403, 404, 500], paths, hints, result):
  175. with self.subTest('handler{}'.format(code)):
  176. self.assertEqual(error, Error(
  177. "The custom handler{} view '{}' could not be imported.".format(code, path),
  178. hint=hint.format(path),
  179. id='urls.E008',
  180. ))
  181. @override_settings(ROOT_URLCONF='check_framework.urls.good_error_handlers')
  182. def test_good_handlers(self):
  183. result = check_url_config(None)
  184. self.assertEqual(result, [])
  185. class CheckURLSettingsTests(SimpleTestCase):
  186. @override_settings(STATIC_URL='a/', MEDIA_URL='b/')
  187. def test_slash_no_errors(self):
  188. self.assertEqual(check_url_settings(None), [])
  189. @override_settings(STATIC_URL='', MEDIA_URL='')
  190. def test_empty_string_no_errors(self):
  191. self.assertEqual(check_url_settings(None), [])
  192. @override_settings(STATIC_URL='noslash')
  193. def test_static_url_no_slash(self):
  194. self.assertEqual(check_url_settings(None), [E006('STATIC_URL')])
  195. @override_settings(STATIC_URL='slashes//')
  196. def test_static_url_double_slash_allowed(self):
  197. # The check allows for a double slash, presuming the user knows what
  198. # they are doing.
  199. self.assertEqual(check_url_settings(None), [])
  200. @override_settings(MEDIA_URL='noslash')
  201. def test_media_url_no_slash(self):
  202. self.assertEqual(check_url_settings(None), [E006('MEDIA_URL')])