diff --git a/debug_toolbar/apps.py b/debug_toolbar/apps.py index 09c489f90..a49dc6b72 100644 --- a/debug_toolbar/apps.py +++ b/debug_toolbar/apps.py @@ -1,16 +1,66 @@ from __future__ import absolute_import, unicode_literals from django.apps import AppConfig +from django.conf import settings +from django.core.checks import Error, register +from django.middleware.gzip import GZipMiddleware +from django.utils.module_loading import import_string from django.utils.translation import ugettext_lazy as _ -from debug_toolbar import settings as dt_settings +from debug_toolbar.middleware import DebugToolbarMiddleware class DebugToolbarConfig(AppConfig): name = 'debug_toolbar' verbose_name = _("Debug Toolbar") - def ready(self): - if dt_settings.get_patch_settings(): - dt_settings.patch_all() - dt_settings.check_middleware() + +@register +def check_middleware(app_configs, **kwargs): + errors = [] + gzip_index = None + debug_toolbar_index = None + + setting = getattr(settings, 'MIDDLEWARE', None) + setting_name = 'MIDDLEWARE' + if setting is None: + setting = settings.MIDDLEWARE_CLASSES + setting_name = 'MIDDLEWARE_CLASSES' + + # Determine the indexes which gzip and/or the toolbar are installed at + for i, middleware in enumerate(setting): + if is_middleware_class(GZipMiddleware, middleware): + gzip_index = i + elif is_middleware_class(DebugToolbarMiddleware, middleware): + debug_toolbar_index = i + + if debug_toolbar_index is None: + # If the toolbar does not appear, report an error. + errors.append( + Error( + "debug_toolbar.middleware.DebugToolbarMiddleware is missing " + "from %s." % setting_name, + hint="Add debug_toolbar.middleware.DebugToolbarMiddleware to " + "%s." % setting_name, + ) + ) + elif gzip_index is not None and debug_toolbar_index < gzip_index: + # If the toolbar appears before the gzip index, report an error. + errors.append( + Error( + "debug_toolbar.middleware.DebugToolbarMiddleware occurs before " + "django.middleware.gzip.GZipMiddleware in %s." % setting_name, + hint="Move debug_toolbar.middleware.DebugToolbarMiddleware to " + "after django.middleware.gzip.GZipMiddleware in %s." % setting_name, + ) + ) + + return errors + + +def is_middleware_class(middleware_class, middleware_path): + try: + middleware_cls = import_string(middleware_path) + except ImportError: + return + return issubclass(middleware_cls, middleware_class) diff --git a/debug_toolbar/settings.py b/debug_toolbar/settings.py index fdf0bbbcd..43ede9138 100644 --- a/debug_toolbar/settings.py +++ b/debug_toolbar/settings.py @@ -1,12 +1,10 @@ from __future__ import absolute_import, unicode_literals import warnings -from importlib import import_module from django.conf import settings from django.utils import six from django.utils.lru_cache import lru_cache -from django.utils.module_loading import import_string # Always import this module as follows: # from debug_toolbar import settings [as dt_settings] @@ -157,87 +155,3 @@ def get_panels(): "setting." % (old_panel, new_panel), DeprecationWarning) PANELS[index] = new_panel return PANELS - - -@lru_cache() -def get_patch_settings(): - return getattr(settings, 'DEBUG_TOOLBAR_PATCH_SETTINGS', settings.DEBUG) - - -# The following functions can monkey-patch settings automatically. Several -# imports are placed inside functions to make it safe to import this module. - - -def check_middleware(): - from django.middleware.gzip import GZipMiddleware - from debug_toolbar.middleware import DebugToolbarMiddleware - gzip_index = None - debug_toolbar_index = None - - # Determine the indexes which gzip and/or the toolbar are installed at - for i, middleware in enumerate(settings.MIDDLEWARE_CLASSES): - if is_middleware_class(GZipMiddleware, middleware): - gzip_index = i - elif is_middleware_class(DebugToolbarMiddleware, middleware): - debug_toolbar_index = i - # If the toolbar appears before the gzip index, raise a warning - if gzip_index is not None and debug_toolbar_index < gzip_index: - warnings.warn( - "Please use an explicit setup with the " - "debug_toolbar.middleware.DebugToolbarMiddleware " - "after django.middleware.gzip.GZipMiddlware " - "in MIDDLEWARE_CLASSES.", Warning) - - -def is_middleware_class(middleware_class, middleware_path): - try: - middleware_cls = import_string(middleware_path) - except ImportError: - return - return issubclass(middleware_cls, middleware_class) - - -def is_toolbar_middleware_installed(): - from debug_toolbar.middleware import DebugToolbarMiddleware - return any(is_middleware_class(DebugToolbarMiddleware, middleware) - for middleware in settings.MIDDLEWARE_CLASSES) - - -def prepend_to_setting(setting_name, value): - """Insert value at the beginning of a list or tuple setting.""" - values = getattr(settings, setting_name) - # Make a list [value] or tuple (value,) - value = type(values)((value,)) - setattr(settings, setting_name, value + values) - - -def patch_internal_ips(): - if not settings.INTERNAL_IPS: - prepend_to_setting('INTERNAL_IPS', '127.0.0.1') - prepend_to_setting('INTERNAL_IPS', '::1') - - -def patch_middleware_classes(): - if not is_toolbar_middleware_installed(): - prepend_to_setting('MIDDLEWARE_CLASSES', - 'debug_toolbar.middleware.DebugToolbarMiddleware') - - -def patch_root_urlconf(): - from django.conf.urls import include, url - from django.core.urlresolvers import clear_url_caches, reverse, NoReverseMatch - import debug_toolbar - try: - reverse('djdt:render_panel') - except NoReverseMatch: - urlconf_module = import_module(settings.ROOT_URLCONF) - urlconf_module.urlpatterns = [ - url(r'^__debug__/', include(debug_toolbar.urls)), - ] + urlconf_module.urlpatterns - clear_url_caches() - - -def patch_all(): - patch_internal_ips() - patch_middleware_classes() - patch_root_urlconf() diff --git a/docs/changes.rst b/docs/changes.rst index e9587598d..ffcb94716 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -4,6 +4,14 @@ Change log 1.6 (upcoming) -------------- +Removed features +~~~~~~~~~~~~~~~~ + +* Support for automatic setup has been removed as it was frequently + problematic. Installation now requires explicit setup. The + ``DEBUG_TOOLBAR_PATCH_SETTINGS`` setting has also been removed as it is now + unused. See the :doc:`installation documentation ` for details. + Bugfixes ~~~~~~~~ diff --git a/docs/configuration.rst b/docs/configuration.rst index 7461891dd..d39aafbf8 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -12,13 +12,6 @@ settings module to customize its behavior. it'll prevent you from taking advantage of better defaults that may be introduced in future releases. -DEBUG_TOOLBAR_PATCH_SETTINGS ----------------------------- - -This setting defines whether the toolbar will attempt to automatically adjust -your project's settings, as described in the :doc:`installation instructions -`. By default it has the same value as your ``DEBUG`` setting. - DEBUG_TOOLBAR_PANELS -------------------- diff --git a/docs/installation.rst b/docs/installation.rst index f3e37ddac..5619397a8 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -37,48 +37,8 @@ Make sure that ``'django.contrib.staticfiles'`` is `set up properly If you're upgrading from a previous version, you should review the :doc:`change log ` and look for specific upgrade instructions. -Automatic setup ---------------- - -If you just add the Debug Toolbar to the ``INSTALLED_APPS`` setting as shown -above, when the ``DEBUG`` setting is ``True``, the Debug Toolbar will attempt -to patch your settings to configure itself automatically. - -.. warning:: - - The automatic setup is known to interfere with the start-up sequence of - some projects and to prevent them from loading or functioning properly. - - **The explicit setup described below is recommended for all but the most - trivial projects. The automatic setup is kept for backwards-compatibility.** - -.. note:: - - The automatic setup imports your project's URLconf in order to add the - Debug Toolbar's URLs. This is likely to trigger circular imports, for - instance when the URLconf imports views that import models, a pattern - found in almost every Django project. - - If the development server crashes with a long stack trace after hitting an - :exc:`ImportError`, an :exc:`~django.apps.exceptions.AppRegistryNotReady` - or an :exc:`~django.core.exceptions.ImproperlyConfigured` exception, use - the explicit setup described below. - - When the automatic setup is used, the Debug Toolbar is not compatible with - :class:`~django.middleware.gzip.GZipMiddleware`. Please disable that - middleware during development or use the explicit setup to allow the - toolbar to function properly. - -Explicit setup --------------- - -This is the recommended way to configure the Debug Toolbar. First, disable the -automatic setup by adding this line in your settings module:: - - DEBUG_TOOLBAR_PATCH_SETTINGS = False - URLconf -~~~~~~~ +------- Add the Debug Toolbar's URLs to your project's URLconf as follows:: @@ -95,41 +55,38 @@ This example uses the ``__debug__`` prefix, but you can use any prefix that doesn't clash with your application's URLs. Note the lack of quotes around ``debug_toolbar.urls``. -.. note:: - - The automatic setup appends the Debug Toolbar URLs to the root URLconf. - Middleware -~~~~~~~~~~ +---------- The Debug Toolbar is mostly implemented in a middleware. Enable it in your settings module as follows:: - MIDDLEWARE_CLASSES = [ + MIDDLEWARE = [ # ... 'debug_toolbar.middleware.DebugToolbarMiddleware', # ... ] -The order of ``MIDDLEWARE_CLASSES`` is important. You should include the Debug -Toolbar middleware as early as possible in the list. However, it must come -after any other middleware that encodes the response's content, such as -:class:`~django.middleware.gzip.GZipMiddleware`. +Old-style middleware:: + + MIDDLEWARE_CLASSES = [ + # ... + 'debug_toolbar.middleware.DebugToolbarMiddleware', + # ... + ] -.. note:: +.. warning:: - The automatic setup inserts the Debug Toolbar middleware at the beginning - of ``MIDDLEWARE_CLASSES``, unless it's already included. + The order of ``MIDDLEWARE`` and ``MIDDLEWARE_CLASSES`` is important. You + should include the Debug Toolbar middleware as early as possible in the + list. However, it must come after any other middleware that encodes the + response's content, such as + :class:`~django.middleware.gzip.GZipMiddleware`. Internal IPs -~~~~~~~~~~~~ +------------ The Debug Toolbar is shown only if your IP is listed in the ``INTERNAL_IPS`` setting. (You can change this logic with the ``SHOW_TOOLBAR_CALLBACK`` option.) For local development, you should add ``'127.0.0.1'`` to ``INTERNAL_IPS``. - -.. note:: - - The automatic setup sets ``INTERNAL_IPS`` to ``'127.0.0.1'`` and - ``'::1'``, unless it's already set to a non-empty value. diff --git a/example/settings.py b/example/settings.py index 7169ec430..0d394d09a 100644 --- a/example/settings.py +++ b/example/settings.py @@ -11,10 +11,11 @@ DEBUG = True +INTERNAL_IPS = ['127.0.0.1', '::1'] # Application definition -INSTALLED_APPS = ( +INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', @@ -22,15 +23,18 @@ 'django.contrib.messages', 'django.contrib.staticfiles', 'debug_toolbar', -) +] -MIDDLEWARE_CLASSES = ( - 'django.middleware.common.CommonMiddleware', +MIDDLEWARE = [ + 'debug_toolbar.middleware.DebugToolbarMiddleware', + 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', -) + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] ROOT_URLCONF = 'example.urls' @@ -43,6 +47,12 @@ 'DIRS': [os.path.join(BASE_DIR, 'example', 'templates')], 'OPTIONS': { 'debug': True, + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], }, }, ] diff --git a/example/urls.py b/example/urls.py index a057ee7ef..a5fa388c1 100644 --- a/example/urls.py +++ b/example/urls.py @@ -1,3 +1,4 @@ +from django.conf import settings from django.conf.urls import include, url from django.contrib import admin from django.views.generic import TemplateView @@ -9,3 +10,9 @@ url(r'^prototype/$', TemplateView.as_view(template_name='prototype/index.html')), url(r'^admin/', include(admin.site.urls)), ] + +if settings.DEBUG: + import debug_toolbar + urlpatterns += [ + url(r'^__debug__/', include(debug_toolbar.urls)), + ] diff --git a/tests/settings.py b/tests/settings.py index 834707b5d..77272c106 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -31,8 +31,8 @@ MIDDLEWARE_CLASSES = [ 'debug_toolbar.middleware.DebugToolbarMiddleware', + 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', @@ -46,7 +46,14 @@ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'APP_DIRS': True, - 'OPTIONS': {}, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, }, ] diff --git a/tests/test_integration.py b/tests/test_integration.py index d2d9f955b..6e400ae26 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -7,6 +7,7 @@ from xml.etree import ElementTree as ET from django.contrib.staticfiles.testing import StaticLiveServerTestCase +from django.core.checks import Error, run_checks from django.test import RequestFactory, TestCase from django.test.utils import override_settings @@ -190,3 +191,55 @@ def test_django_cached_template_loader(self): WebDriverWait(self.selenium, timeout=10).until( lambda selenium: self.selenium.find_element_by_css_selector( '#djDebugWindow code')) + + +@override_settings(DEBUG=True) +class DebugToolbarSystemChecksTestCase(BaseTestCase): + @override_settings( + MIDDLEWARE=None, + MIDDLEWARE_CLASSES=[ + 'django.middleware.gzip.GZipMiddleware', + 'debug_toolbar.middleware.DebugToolbarMiddleware', + ] + ) + def test_check_good_configuration(self): + messages = run_checks() + self.assertEqual(messages, []) + + @override_settings(MIDDLEWARE=None, MIDDLEWARE_CLASSES=[]) + def test_check_missing_middleware_error(self): + messages = run_checks() + self.assertEqual( + messages, + [ + Error( + "debug_toolbar.middleware.DebugToolbarMiddleware is " + "missing from MIDDLEWARE_CLASSES.", + hint="Add debug_toolbar.middleware.DebugToolbarMiddleware " + "to MIDDLEWARE_CLASSES.", + ), + ] + ) + + @override_settings( + MIDDLEWARE=None, + MIDDLEWARE_CLASSES=[ + 'debug_toolbar.middleware.DebugToolbarMiddleware', + 'django.middleware.gzip.GZipMiddleware', + ] + ) + def test_check_gzip_middleware_error(self): + messages = run_checks() + self.assertEqual( + messages, + [ + Error( + "debug_toolbar.middleware.DebugToolbarMiddleware occurs " + "before django.middleware.gzip.GZipMiddleware in " + "MIDDLEWARE_CLASSES.", + hint="Move debug_toolbar.middleware.DebugToolbarMiddleware " + "to after django.middleware.gzip.GZipMiddleware in " + "MIDDLEWARE_CLASSES.", + ), + ] + )