Commit fe3fc521 authored by Alasdair Nicol's avatar Alasdair Nicol Committed by Josh Smeaton
Browse files

Fixed #23813 -- Added checks for common URL pattern errors

Thanks jwa and lamby for the suggestions, and timgraham and jarshwah
for their reviews.
parent 2f53d342
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -15,6 +15,8 @@ import django.core.checks.security.base # NOQA isort:skip
import django.core.checks.security.csrf  # NOQA isort:skip
import django.core.checks.security.sessions  # NOQA isort:skip
import django.core.checks.templates  # NOQA isort:skip
import django.core.checks.urls  # NOQA isort:skip


__all__ = [
    'CheckMessage',
+1 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@ class Tags(object):
    security = 'security'
    signals = 'signals'
    templates = 'templates'
    urls = 'urls'


class CheckRegistry(object):
+87 −0
Original line number Diff line number Diff line
from __future__ import unicode_literals

from . import Tags, Warning, register


@register(Tags.urls)
def check_url_config(app_configs, **kwargs):
    from django.core.urlresolvers import get_resolver
    resolver = get_resolver()
    return check_resolver(resolver)


def check_resolver(resolver):
    """
    Recursively check the resolver
    """
    from django.core.urlresolvers import RegexURLPattern, RegexURLResolver
    warnings = []
    for pattern in resolver.url_patterns:
        if isinstance(pattern, RegexURLResolver):
            warnings.extend(check_include_trailing_dollar(pattern))
            # Check resolver recursively
            warnings.extend(check_resolver(pattern))
        elif isinstance(pattern, RegexURLPattern):
            warnings.extend(check_pattern_name(pattern))

        warnings.extend(check_pattern_startswith_slash(pattern))

    return warnings


def describe_pattern(pattern):
    """
    Formats the URL pattern for display in warning messages
    """
    description = "'{}'".format(pattern.regex.pattern)
    if getattr(pattern, 'name', False):
        description += " [name='{}']".format(pattern.name)
    return description


def check_include_trailing_dollar(pattern):
    """
    Checks that include is not used with a regex ending with a dollar.
    """
    regex_pattern = pattern.regex.pattern
    if regex_pattern.endswith('$') and not regex_pattern.endswith('\$'):
        warning = Warning(
            "Your URL pattern {} uses include with a regex ending with a '$'. "
            "Remove the dollar from the regex to avoid problems including "
            "URLs.".format(describe_pattern(pattern)),
            id="urls.W001",
        )
        return [warning]
    else:
        return []


def check_pattern_startswith_slash(pattern):
    """
    Checks that the pattern does not begin with a forward slash
    """
    regex_pattern = pattern.regex.pattern
    if regex_pattern.startswith('/') or regex_pattern.startswith('^/'):
        warning = Warning(
            "Your URL pattern {} has a regex beginning with a '/'. "
            "Remove this slash as it is unnecessary.".format(describe_pattern(pattern)),
            id="urls.W002",
        )
        return [warning]
    else:
        return []


def check_pattern_name(pattern):
    """
    Checks that the pattern name does not contain a colon
    """
    if pattern.name is not None and ":" in pattern.name:
        warning = Warning(
            "Your URL pattern {} has a name including a ':'. Remove the colon, to "
            "avoid ambiguous namespace references.".format(describe_pattern(pattern)),
            id="urls.W003",
        )
        return [warning]
    else:
        return []
+1 −1
Original line number Diff line number Diff line
@@ -146,7 +146,7 @@ def get_callable(lookup_view, can_fail=False):


@lru_cache.lru_cache(maxsize=None)
def get_resolver(urlconf):
def get_resolver(urlconf=None):
    if urlconf is None:
        from django.conf import settings
        urlconf = settings.ROOT_URLCONF
+16 −0
Original line number Diff line number Diff line
@@ -81,6 +81,7 @@ Django's system checks are organized using the following tags:
* ``security``: Checks security related configuration.
* ``templates``: Checks template related configuration.
* ``caches``: Checks cache related configuration.
* ``urls``: Checks URL configuration.

Some checks may be registered with multiple tags.

@@ -580,3 +581,18 @@ configured:

* **caches.E001**: You must define a ``'default'`` cache in your
  :setting:`CACHES` setting.

URLs
----

The following checks are performed on your URL configuration:

* **urls.W001**: Your URL pattern ``<pattern>`` uses
  :func:`~django.conf.urls.include` with a ``regex`` ending with a
  ``$``. Remove the dollar from the ``regex`` to avoid problems
  including URLs.
* **urls.W002**: Your URL pattern ``<pattern>`` has a ``regex``
  beginning with a ``/``. Remove this slash as it is unnecessary.
* **urls.W003**: Your URL pattern ``<pattern>`` has a ``name``
  including a ``:``. Remove the colon, to avoid ambiguous namespace
  references.
Loading