Repository: jazzband/django-debug-toolbar

Issue:
Cache panel does not update for per-view level caching
I'm using the toolbar 1.3 and Django 1.8.2.  The toolbar displays fine and the SQL, Time, Headers, Request, Static Files, Templates panels all update but the Cache panel stays fixed at 0 calls even though it's activated.  I've tried it using the LocMemCache and Memcached with same result.  I'm using the explicit setup with DEBUG_TOOLBAR_PATCH_SETTINGS = False.

Here are my Middleware classes:

MIDDLEWARE_CLASSES = (
    'debug_toolbar.middleware.DebugToolbarMiddleware',
    'django.middleware.cache.UpdateCacheMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.auth.middleware.RemoteUserMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'django.middleware.cache.FetchFromCacheMiddleware',
)

In my urls I have:

if settings.DEBUG:
    import debug_toolbar
    urlpatterns += patterns('',
        url(r'^**debug**/', include(debug_toolbar.urls)),
    )

Is there anything else I'm missing to get the panel to update?



Discussion:
I also tried moving the DebugToolbarMiddleware position in the list but it didn't have any effect.

I noticed you didn't include the `INSTALLED_APPS` setting. Do you have `'debug_toolbar'` included in it?

Yes, I have it set in there.  The toolbar displays fine and the rest of the
panels work, so this problem seems to be Cache specific.  This is the rest
of my config:

DEBUG_TOOLBAR_PATCH_SETTINGS = False
DEBUG_TOOLBAR_CONFIG = {
    'SHOW_COLLAPSED': True,
}
INTERNAL_IPS = ('127.0.0.1', '::1', '10.0.0.49')

On Mon, Jun 1, 2015 at 2:34 PM, Tim Schilling notifications@github.com
wrote:

> I noticed you didn't include the INSTALLED_APPS setting. Do you have
> 'debug_toolbar' included in it?
> 
> —
> Reply to this email directly or view it on GitHub
> https://github.com/django-debug-toolbar/django-debug-toolbar/issues/718#issuecomment-107718904
> .

I also get the same result when I run my app in the Django test server or in Apache.

Where are your calls to the cache occurring? In the view or the middleware?

I'm actually not explicitly calling anything, but caching the whole site to test it out.  Does the panel only display low-level cache API calls?

Seems to be that way. I was able to reproduce this.


Resolving patch:
diff --git a/debug_toolbar/panels/cache.py b/debug_toolbar/panels/cache.py
--- a/debug_toolbar/panels/cache.py
+++ b/debug_toolbar/panels/cache.py
@@ -11,6 +11,7 @@
     get_cache as original_get_cache)
 from django.core.cache.backends.base import BaseCache
 from django.dispatch import Signal
+from django.middleware import cache as middleware_cache
 from django.utils.translation import ugettext_lazy as _, ungettext
 
 
@@ -124,17 +125,28 @@ def get_cache(*args, **kwargs):
     return CacheStatTracker(original_get_cache(*args, **kwargs))
 
 
-def get_cache_handler():
-    if CacheHandler is None:
-        return None
-
+if CacheHandler is not None:
     class CacheHandlerPatch(CacheHandler):
         def __getitem__(self, alias):
             actual_cache = super(CacheHandlerPatch, self).__getitem__(alias)
             return CacheStatTracker(actual_cache)
+
+
+def get_cache_handler():
+    if CacheHandler is None:
+        return None
     return CacheHandlerPatch()
 
 
+# Must monkey patch the middleware's cache module as well in order to
+# cover per-view level caching. This needs to be monkey patched outside
+# of the enable_instrumentation method since the django's
+# decorator_from_middleware_with_args will store the cache from core.caches
+# when it wraps the view.
+middleware_cache.get_cache = get_cache
+middleware_cache.caches = get_cache_handler()
+
+
 class CachePanel(Panel):
     """
     Panel that displays the cache statistics.
@@ -212,19 +224,24 @@ def title(self):
                          count) % dict(count=count)
 
     def enable_instrumentation(self):
-        # This isn't thread-safe because cache connections aren't thread-local
-        # in Django, unlike database connections.
         cache.get_cache = get_cache
         if CacheHandler is None:
             cache.cache = CacheStatTracker(original_cache)
         else:
-            cache.caches = get_cache_handler()
+            if isinstance(middleware_cache.caches, CacheHandlerPatch):
+                cache.caches = middleware_cache.caches
+            else:
+                cache.caches = get_cache_handler()
 
     def disable_instrumentation(self):
         if CacheHandler is None:
             cache.cache = original_cache
         else:
             cache.caches = original_caches
+            # While it can be restored to the original, any views that were
+            # wrapped with the cache_page decorator will continue to use a
+            # monkey patched cache.
+            middleware_cache.caches = original_caches
         cache.get_cache = original_get_cache
 
     def process_response(self, request, response):


Test patch:
diff --git a/tests/test_integration.py b/tests/test_integration.py
--- a/tests/test_integration.py
+++ b/tests/test_integration.py
@@ -86,6 +86,14 @@ def test_middleware_response_insertion(self):
         # check toolbar insertion before "</body>"
         self.assertContains(resp, '</div>\n</body>')
 
+    def test_cache_page(self):
+        self.client.get('/cached_view/')
+        self.assertEqual(
+            len(self.toolbar.get_panel_by_id('CachePanel').calls), 3)
+        self.client.get('/cached_view/')
+        self.assertEqual(
+            len(self.toolbar.get_panel_by_id('CachePanel').calls), 5)
+
 
 @override_settings(DEBUG=True)
 class DebugToolbarIntegrationTestCase(TestCase):
diff --git a/tests/urls.py b/tests/urls.py
--- a/tests/urls.py
+++ b/tests/urls.py
@@ -20,5 +20,6 @@
     url(r'^non_ascii_request/$', 'regular_view', {'title': NonAsciiRepr()}),
     url(r'^new_user/$', 'new_user'),
     url(r'^execute_sql/$', 'execute_sql'),
+    url(r'^cached_view/$', 'cached_view'),
     url(r'^__debug__/', include(debug_toolbar.urls)),
 )
diff --git a/tests/views.py b/tests/views.py
--- a/tests/views.py
+++ b/tests/views.py
@@ -5,6 +5,7 @@
 from django.contrib.auth.models import User
 from django.http import HttpResponse
 from django.shortcuts import render
+from django.views.decorators.cache import cache_page
 
 
 def execute_sql(request):
@@ -24,3 +25,8 @@ def new_user(request, username='joe'):
 def resolving_view(request, arg1, arg2):
     # see test_url_resolving in tests.py
     return HttpResponse()
+
+
+@cache_page(60)
+def cached_view(request):
+    return HttpResponse()