Skip to content

Remove automatic setup feature #851

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 23, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 55 additions & 5 deletions debug_toolbar/apps.py
Original file line number Diff line number Diff line change
@@ -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)
86 changes: 0 additions & 86 deletions debug_toolbar/settings.py
Original file line number Diff line number Diff line change
@@ -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]
Expand Down Expand Up @@ -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()
8 changes: 8 additions & 0 deletions docs/changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <installation>` for details.

Bugfixes
~~~~~~~~

Expand Down
7 changes: 0 additions & 7 deletions docs/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
<installation>`. By default it has the same value as your ``DEBUG`` setting.

DEBUG_TOOLBAR_PANELS
--------------------

Expand Down
77 changes: 17 additions & 60 deletions docs/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <changes>` 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::

Expand All @@ -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.
22 changes: 16 additions & 6 deletions example/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,30 @@

DEBUG = True

INTERNAL_IPS = ['127.0.0.1', '::1']

# Application definition

INSTALLED_APPS = (
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'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'

Expand All @@ -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',
],
},
},
]
Expand Down
7 changes: 7 additions & 0 deletions example/urls.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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)),
]
Loading