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