test_urls.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. from django.conf import settings
  2. from django.core.checks.messages import Error, Warning
  3. from django.core.checks.urls import (
  4. E006,
  5. check_url_config,
  6. check_url_namespaces_unique,
  7. check_url_settings,
  8. get_warning_for_invalid_pattern,
  9. )
  10. from django.test import SimpleTestCase
  11. from django.test.utils import override_settings
  12. class CheckUrlConfigTests(SimpleTestCase):
  13. @override_settings(ROOT_URLCONF="check_framework.urls.no_warnings")
  14. def test_no_warnings(self):
  15. result = check_url_config(None)
  16. self.assertEqual(result, [])
  17. @override_settings(ROOT_URLCONF="check_framework.urls.no_warnings_i18n")
  18. def test_no_warnings_i18n(self):
  19. self.assertEqual(check_url_config(None), [])
  20. @override_settings(ROOT_URLCONF="check_framework.urls.warning_in_include")
  21. def test_check_resolver_recursive(self):
  22. # The resolver is checked recursively (examining URL patterns in include()).
  23. result = check_url_config(None)
  24. self.assertEqual(len(result), 1)
  25. warning = result[0]
  26. self.assertEqual(warning.id, "urls.W001")
  27. @override_settings(ROOT_URLCONF="check_framework.urls.include_with_dollar")
  28. def test_include_with_dollar(self):
  29. result = check_url_config(None)
  30. self.assertEqual(len(result), 1)
  31. warning = result[0]
  32. self.assertEqual(warning.id, "urls.W001")
  33. self.assertEqual(
  34. warning.msg,
  35. (
  36. "Your URL pattern '^include-with-dollar$' uses include with a "
  37. "route ending with a '$'. Remove the dollar from the route to "
  38. "avoid problems including URLs."
  39. ),
  40. )
  41. @override_settings(ROOT_URLCONF="check_framework.urls.contains_tuple")
  42. def test_contains_tuple_not_url_instance(self):
  43. result = check_url_config(None)
  44. warning = result[0]
  45. self.assertEqual(warning.id, "urls.E004")
  46. self.assertRegex(
  47. warning.msg,
  48. (
  49. r"^Your URL pattern \('\^tuple/\$', <function <lambda> at 0x(\w+)>\) "
  50. r"is invalid. Ensure that urlpatterns is a list of path\(\) and/or "
  51. r"re_path\(\) instances\.$"
  52. ),
  53. )
  54. @override_settings(ROOT_URLCONF="check_framework.urls.include_contains_tuple")
  55. def test_contains_included_tuple(self):
  56. result = check_url_config(None)
  57. warning = result[0]
  58. self.assertEqual(warning.id, "urls.E004")
  59. self.assertRegex(
  60. warning.msg,
  61. (
  62. r"^Your URL pattern \('\^tuple/\$', <function <lambda> at 0x(\w+)>\) "
  63. r"is invalid. Ensure that urlpatterns is a list of path\(\) and/or "
  64. r"re_path\(\) instances\.$"
  65. ),
  66. )
  67. @override_settings(ROOT_URLCONF="check_framework.urls.beginning_with_slash")
  68. def test_beginning_with_slash(self):
  69. msg = (
  70. "Your URL pattern '%s' has a route beginning with a '/'. Remove "
  71. "this slash as it is unnecessary. If this pattern is targeted in "
  72. "an include(), ensure the include() pattern has a trailing '/'."
  73. )
  74. warning1, warning2 = check_url_config(None)
  75. self.assertEqual(warning1.id, "urls.W002")
  76. self.assertEqual(warning1.msg, msg % "/path-starting-with-slash/")
  77. self.assertEqual(warning2.id, "urls.W002")
  78. self.assertEqual(warning2.msg, msg % "/url-starting-with-slash/$")
  79. @override_settings(
  80. ROOT_URLCONF="check_framework.urls.beginning_with_slash",
  81. APPEND_SLASH=False,
  82. )
  83. def test_beginning_with_slash_append_slash(self):
  84. # It can be useful to start a URL pattern with a slash when
  85. # APPEND_SLASH=False (#27238).
  86. result = check_url_config(None)
  87. self.assertEqual(result, [])
  88. @override_settings(ROOT_URLCONF="check_framework.urls.name_with_colon")
  89. def test_name_with_colon(self):
  90. result = check_url_config(None)
  91. self.assertEqual(len(result), 1)
  92. warning = result[0]
  93. self.assertEqual(warning.id, "urls.W003")
  94. expected_msg = (
  95. "Your URL pattern '^$' [name='name_with:colon'] has a name including a ':'."
  96. )
  97. self.assertIn(expected_msg, warning.msg)
  98. @override_settings(ROOT_URLCONF=None)
  99. def test_no_root_urlconf_in_settings(self):
  100. delattr(settings, "ROOT_URLCONF")
  101. result = check_url_config(None)
  102. self.assertEqual(result, [])
  103. def test_get_warning_for_invalid_pattern_string(self):
  104. warning = get_warning_for_invalid_pattern("")[0]
  105. self.assertEqual(
  106. warning.hint,
  107. "Try removing the string ''. The list of urlpatterns should "
  108. "not have a prefix string as the first element.",
  109. )
  110. def test_get_warning_for_invalid_pattern_tuple(self):
  111. warning = get_warning_for_invalid_pattern((r"^$", lambda x: x))[0]
  112. self.assertEqual(warning.hint, "Try using path() instead of a tuple.")
  113. def test_get_warning_for_invalid_pattern_other(self):
  114. warning = get_warning_for_invalid_pattern(object())[0]
  115. self.assertIsNone(warning.hint)
  116. @override_settings(ROOT_URLCONF="check_framework.urls.non_unique_namespaces")
  117. def test_check_non_unique_namespaces(self):
  118. result = check_url_namespaces_unique(None)
  119. self.assertEqual(len(result), 2)
  120. non_unique_namespaces = ["app-ns1", "app-1"]
  121. warning_messages = [
  122. "URL namespace '{}' isn't unique. You may not be able to reverse "
  123. "all URLs in this namespace".format(namespace)
  124. for namespace in non_unique_namespaces
  125. ]
  126. for warning in result:
  127. self.assertIsInstance(warning, Warning)
  128. self.assertEqual("urls.W005", warning.id)
  129. self.assertIn(warning.msg, warning_messages)
  130. @override_settings(ROOT_URLCONF="check_framework.urls.unique_namespaces")
  131. def test_check_unique_namespaces(self):
  132. result = check_url_namespaces_unique(None)
  133. self.assertEqual(result, [])
  134. @override_settings(ROOT_URLCONF="check_framework.urls.cbv_as_view")
  135. def test_check_view_not_class(self):
  136. self.assertEqual(
  137. check_url_config(None),
  138. [
  139. Error(
  140. "Your URL pattern 'missing_as_view' has an invalid view, pass "
  141. "EmptyCBV.as_view() instead of EmptyCBV.",
  142. id="urls.E009",
  143. ),
  144. ],
  145. )
  146. @override_settings(
  147. ROOT_URLCONF="check_framework.urls.path_compatibility.matched_angle_brackets"
  148. )
  149. def test_no_warnings_matched_angle_brackets(self):
  150. self.assertEqual(check_url_config(None), [])
  151. @override_settings(
  152. ROOT_URLCONF="check_framework.urls.path_compatibility.unmatched_angle_brackets"
  153. )
  154. def test_warning_unmatched_angle_brackets(self):
  155. self.assertEqual(
  156. check_url_config(None),
  157. [
  158. Warning(
  159. "Your URL pattern 'beginning-with/<angle_bracket' has an unmatched "
  160. "'<' bracket.",
  161. id="urls.W010",
  162. ),
  163. Warning(
  164. "Your URL pattern 'ending-with/angle_bracket>' has an unmatched "
  165. "'>' bracket.",
  166. id="urls.W010",
  167. ),
  168. Warning(
  169. "Your URL pattern 'closed_angle>/x/<opened_angle' has an unmatched "
  170. "'>' bracket.",
  171. id="urls.W010",
  172. ),
  173. Warning(
  174. "Your URL pattern 'closed_angle>/x/<opened_angle' has an unmatched "
  175. "'<' bracket.",
  176. id="urls.W010",
  177. ),
  178. Warning(
  179. "Your URL pattern '<mixed>angle_bracket>' has an unmatched '>' "
  180. "bracket.",
  181. id="urls.W010",
  182. ),
  183. ],
  184. )
  185. class UpdatedToPathTests(SimpleTestCase):
  186. @override_settings(
  187. ROOT_URLCONF="check_framework.urls.path_compatibility.contains_re_named_group"
  188. )
  189. def test_contains_re_named_group(self):
  190. result = check_url_config(None)
  191. self.assertEqual(len(result), 1)
  192. warning = result[0]
  193. self.assertEqual(warning.id, "2_0.W001")
  194. expected_msg = "Your URL pattern '(?P<named_group>\\d+)' has a route"
  195. self.assertIn(expected_msg, warning.msg)
  196. @override_settings(
  197. ROOT_URLCONF="check_framework.urls.path_compatibility.beginning_with_caret"
  198. )
  199. def test_beginning_with_caret(self):
  200. result = check_url_config(None)
  201. self.assertEqual(len(result), 1)
  202. warning = result[0]
  203. self.assertEqual(warning.id, "2_0.W001")
  204. expected_msg = "Your URL pattern '^beginning-with-caret' has a route"
  205. self.assertIn(expected_msg, warning.msg)
  206. @override_settings(
  207. ROOT_URLCONF="check_framework.urls.path_compatibility.ending_with_dollar"
  208. )
  209. def test_ending_with_dollar(self):
  210. result = check_url_config(None)
  211. self.assertEqual(len(result), 1)
  212. warning = result[0]
  213. self.assertEqual(warning.id, "2_0.W001")
  214. expected_msg = "Your URL pattern 'ending-with-dollar$' has a route"
  215. self.assertIn(expected_msg, warning.msg)
  216. class CheckCustomErrorHandlersTests(SimpleTestCase):
  217. @override_settings(
  218. ROOT_URLCONF="check_framework.urls.bad_function_based_error_handlers",
  219. )
  220. def test_bad_function_based_handlers(self):
  221. result = check_url_config(None)
  222. self.assertEqual(len(result), 4)
  223. for code, num_params, error in zip([400, 403, 404, 500], [2, 2, 2, 1], result):
  224. with self.subTest("handler{}".format(code)):
  225. self.assertEqual(
  226. error,
  227. Error(
  228. "The custom handler{} view 'check_framework.urls."
  229. "bad_function_based_error_handlers.bad_handler' "
  230. "does not take the correct number of arguments "
  231. "(request{}).".format(
  232. code, ", exception" if num_params == 2 else ""
  233. ),
  234. id="urls.E007",
  235. ),
  236. )
  237. @override_settings(
  238. ROOT_URLCONF="check_framework.urls.bad_class_based_error_handlers",
  239. )
  240. def test_bad_class_based_handlers(self):
  241. result = check_url_config(None)
  242. self.assertEqual(len(result), 4)
  243. for code, num_params, error in zip([400, 403, 404, 500], [2, 2, 2, 1], result):
  244. with self.subTest("handler%s" % code):
  245. self.assertEqual(
  246. error,
  247. Error(
  248. "The custom handler%s view 'check_framework.urls."
  249. "bad_class_based_error_handlers.HandlerView.as_view."
  250. "<locals>.view' does not take the correct number of "
  251. "arguments (request%s)."
  252. % (
  253. code,
  254. ", exception" if num_params == 2 else "",
  255. ),
  256. id="urls.E007",
  257. ),
  258. )
  259. @override_settings(
  260. ROOT_URLCONF="check_framework.urls.bad_error_handlers_invalid_path"
  261. )
  262. def test_bad_handlers_invalid_path(self):
  263. result = check_url_config(None)
  264. paths = [
  265. "django.views.bad_handler",
  266. "django.invalid_module.bad_handler",
  267. "invalid_module.bad_handler",
  268. "django",
  269. ]
  270. hints = [
  271. "Could not import '{}'. View does not exist in module django.views.",
  272. "Could not import '{}'. Parent module django.invalid_module does not "
  273. "exist.",
  274. "No module named 'invalid_module'",
  275. "Could not import '{}'. The path must be fully qualified.",
  276. ]
  277. for code, path, hint, error in zip([400, 403, 404, 500], paths, hints, result):
  278. with self.subTest("handler{}".format(code)):
  279. self.assertEqual(
  280. error,
  281. Error(
  282. "The custom handler{} view '{}' could not be imported.".format(
  283. code, path
  284. ),
  285. hint=hint.format(path),
  286. id="urls.E008",
  287. ),
  288. )
  289. @override_settings(
  290. ROOT_URLCONF="check_framework.urls.good_function_based_error_handlers",
  291. )
  292. def test_good_function_based_handlers(self):
  293. result = check_url_config(None)
  294. self.assertEqual(result, [])
  295. @override_settings(
  296. ROOT_URLCONF="check_framework.urls.good_class_based_error_handlers",
  297. )
  298. def test_good_class_based_handlers(self):
  299. result = check_url_config(None)
  300. self.assertEqual(result, [])
  301. class CheckURLSettingsTests(SimpleTestCase):
  302. @override_settings(STATIC_URL="a/", MEDIA_URL="b/")
  303. def test_slash_no_errors(self):
  304. self.assertEqual(check_url_settings(None), [])
  305. @override_settings(STATIC_URL="", MEDIA_URL="")
  306. def test_empty_string_no_errors(self):
  307. self.assertEqual(check_url_settings(None), [])
  308. @override_settings(STATIC_URL="noslash")
  309. def test_static_url_no_slash(self):
  310. self.assertEqual(check_url_settings(None), [E006("STATIC_URL")])
  311. @override_settings(STATIC_URL="slashes//")
  312. def test_static_url_double_slash_allowed(self):
  313. # The check allows for a double slash, presuming the user knows what
  314. # they are doing.
  315. self.assertEqual(check_url_settings(None), [])
  316. @override_settings(MEDIA_URL="noslash")
  317. def test_media_url_no_slash(self):
  318. self.assertEqual(check_url_settings(None), [E006("MEDIA_URL")])