You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

urls.py 3.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. from collections import Counter
  2. from django.conf import settings
  3. from . import Error, Tags, Warning, register
  4. @register(Tags.urls)
  5. def check_url_config(app_configs, **kwargs):
  6. if getattr(settings, 'ROOT_URLCONF', None):
  7. from django.urls import get_resolver
  8. resolver = get_resolver()
  9. return check_resolver(resolver)
  10. return []
  11. def check_resolver(resolver):
  12. """
  13. Recursively check the resolver.
  14. """
  15. check_method = getattr(resolver, 'check', None)
  16. if check_method is not None:
  17. return check_method()
  18. elif not hasattr(resolver, 'resolve'):
  19. return get_warning_for_invalid_pattern(resolver)
  20. else:
  21. return []
  22. @register(Tags.urls)
  23. def check_url_namespaces_unique(app_configs, **kwargs):
  24. """
  25. Warn if URL namespaces used in applications aren't unique.
  26. """
  27. if not getattr(settings, 'ROOT_URLCONF', None):
  28. return []
  29. from django.urls import get_resolver
  30. resolver = get_resolver()
  31. all_namespaces = _load_all_namespaces(resolver)
  32. counter = Counter(all_namespaces)
  33. non_unique_namespaces = [n for n, count in counter.items() if count > 1]
  34. errors = []
  35. for namespace in non_unique_namespaces:
  36. errors.append(Warning(
  37. "URL namespace '{}' isn't unique. You may not be able to reverse "
  38. "all URLs in this namespace".format(namespace),
  39. id="urls.W005",
  40. ))
  41. return errors
  42. def _load_all_namespaces(resolver, parents=()):
  43. """
  44. Recursively load all namespaces from URL patterns.
  45. """
  46. url_patterns = getattr(resolver, 'url_patterns', [])
  47. namespaces = [
  48. ':'.join(parents + (url.namespace,)) for url in url_patterns
  49. if getattr(url, 'namespace', None) is not None
  50. ]
  51. for pattern in url_patterns:
  52. namespace = getattr(pattern, 'namespace', None)
  53. current = parents
  54. if namespace is not None:
  55. current += (namespace,)
  56. namespaces.extend(_load_all_namespaces(pattern, current))
  57. return namespaces
  58. def get_warning_for_invalid_pattern(pattern):
  59. """
  60. Return a list containing a warning that the pattern is invalid.
  61. describe_pattern() cannot be used here, because we cannot rely on the
  62. urlpattern having regex or name attributes.
  63. """
  64. if isinstance(pattern, str):
  65. hint = (
  66. "Try removing the string '{}'. The list of urlpatterns should not "
  67. "have a prefix string as the first element.".format(pattern)
  68. )
  69. elif isinstance(pattern, tuple):
  70. hint = "Try using path() instead of a tuple."
  71. else:
  72. hint = None
  73. return [Error(
  74. "Your URL pattern {!r} is invalid. Ensure that urlpatterns is a list "
  75. "of path() and/or re_path() instances.".format(pattern),
  76. hint=hint,
  77. id="urls.E004",
  78. )]
  79. @register(Tags.urls)
  80. def check_url_settings(app_configs, **kwargs):
  81. errors = []
  82. for name in ('STATIC_URL', 'MEDIA_URL'):
  83. value = getattr(settings, name)
  84. if value and not value.endswith('/'):
  85. errors.append(E006(name))
  86. return errors
  87. def E006(name):
  88. return Error(
  89. 'The {} setting must end with a slash.'.format(name),
  90. id='urls.E006',
  91. )