Repository: jazzband/django-debug-toolbar

Issue:
Unable to disable TemplatesPanel and SQLPanel using DISABLE_PANELS
Actually, the title is slightly misleading, because I did manage to disable them, but the documentation was misleading and I had to debug the app in order to find what was failing.

The problem is that the documentation says that these panels names are `debug_toolbar.panels.templates.TemplatesPanel` and `debug_toolbar.panels.sql.SQLPanel`, and while this is correct for usage on the `DEBUG_TOOLBAR_PANELS` config, it is wrong for usage on the `DISABLE_PANELS` config.

In order to understand why I had to look at the code that checks whether panels are enabled or disabled, in particular these two functions:

<pre>
@property
def enabled(self):
    # Check to see if settings has a default value for it
    <b>if get_name_from_obj(self) in dt_settings.CONFIG['DISABLE_PANELS']:</b>
        default = 'off'
    else:
        default = 'on'
    # The user's cookies should override the default value
    return self.toolbar.request.COOKIES.get('djdt' + self.panel_id, default) == 'on'
</pre>


<pre>
def get_name_from_obj(obj):
    if hasattr(obj, '__name__'):
        name = obj.__name__
    elif hasattr(obj, '__class__') and hasattr(obj.__class__, '__name__'):
        <b>name = obj.__class__.__name__</b>
    else:
        name = '<unknown>'

    if hasattr(obj, '__module__'):
        module = obj.__module__
        name = '%s.%s' % (module, name)

    return name
</pre>


The first one does the checking by calling the second one. The problem is, when it gets the name with `name = obj.__class__.__name__`, it gets the fully qualified name of the object which in the case of the SQL and Templates panels, which are not just a single file named `sql.py` or `templates.py` but a folder with that name with a `panel.py` inside, the fully qualified name becomes <code>debug_toolbar.panels.templates<b>.panel</b>.TemplatesPanel</code> and <code>debug_toolbar.panels.sql<b>.panel</b>.SQLPanel</code>.

So, these two names are the ones that actually work on the `DISABLE_PANELS` config but they are not mentioned anywhere in the docs so of course most users won't be able to find them.

I can see three potential solutions for this:
1. Changing the docs, replacing all references of `debug_toolbar.panels.templates.TemplatesPanel` with `debug_toolbar.panels.templates.panel.TemplatesPanel` (and the same for sql). This is not a great solution since it's easy to have the same problem in the future if we turn another panel from a .py file to a folder.
2. Change the way we get the object name to actually get the name that we use in the documentation, but I have no idea how to  do this properly.
3. Replace the `__init__.py` files in these two folders with the `panel.py` files. As long as there is no reference to the actual full path (including the `.panel`), this seems like the easiest solution as it makes the TemplatesPanel and SQLPanel full paths the same as those in the documentation. I tried this for the TemplatesPanel and it seemed to work fine.

Comments?



Discussion:
I'm in favor of option 2.

Option 3 is interesting but experience shows that putting code in `__init__.py` always creates pain down the line.


Resolving patch:
diff --git a/debug_toolbar/panels/__init__.py b/debug_toolbar/panels/__init__.py
--- a/debug_toolbar/panels/__init__.py
+++ b/debug_toolbar/panels/__init__.py
@@ -24,7 +24,16 @@ def panel_id(self):
     @property
     def enabled(self):
         # Check to see if settings has a default value for it
-        if get_name_from_obj(self) in dt_settings.CONFIG['DISABLE_PANELS']:
+        disabled_panels = dt_settings.CONFIG['DISABLE_PANELS']
+        panel_path = get_name_from_obj(self)
+        # Some panels such as the SQLPanel and TemplatesPanel exist in a
+        # panel module, but can be disabled without panel in the path.
+        # For that reason, replace .panel. in the path and check for that
+        # value in the disabled panels as well.
+        disable_panel = (
+            panel_path in disabled_panels or
+            panel_path.replace('.panel.', '.') in disabled_panels)
+        if disable_panel:
             default = 'off'
         else:
             default = 'on'


Test patch:
diff --git a/tests/panels/test_sql.py b/tests/panels/test_sql.py
--- a/tests/panels/test_sql.py
+++ b/tests/panels/test_sql.py
@@ -24,6 +24,14 @@ def tearDown(self):
         self.panel.disable_instrumentation()
         super(SQLPanelTestCase, self).tearDown()
 
+    def test_disabled(self):
+        config = {
+            'DISABLE_PANELS': set(['debug_toolbar.panels.sql.SQLPanel'])
+        }
+        self.assertTrue(self.panel.enabled)
+        with self.settings(DEBUG_TOOLBAR_CONFIG=config):
+            self.assertFalse(self.panel.enabled)
+
     def test_recording(self):
         self.assertEqual(len(self.panel._queries), 0)
 
diff --git a/tests/panels/test_template.py b/tests/panels/test_template.py
--- a/tests/panels/test_template.py
+++ b/tests/panels/test_template.py
@@ -58,6 +58,15 @@ def test_custom_context_processor(self):
         self.panel.process_response(self.request, self.response)
         self.assertIn('tests.panels.test_template.context_processor', self.panel.content)
 
+    def test_disabled(self):
+        config = {
+            'DISABLE_PANELS': set([
+                'debug_toolbar.panels.templates.TemplatesPanel'])
+        }
+        self.assertTrue(self.panel.enabled)
+        with self.settings(DEBUG_TOOLBAR_CONFIG=config):
+            self.assertFalse(self.panel.enabled)
+
 
 def context_processor(request):
     return {'content': 'set by processor'}