Repository: jazzband/django-debug-toolbar

Issue:
Using Django's cached template loader causes an exception in the templates panel
Given:

```
TEMPLATE_LOADERS = (
    ('django.template.loaders.cached.Loader', (
        'django.template.loaders.filesystem.Loader',
        'django.template.loaders.app_directories.Loader',
    )),
)
```
- Open a page which shows djdt
- Open the templates panel (obviously, this panel needs to be enabled)
- Try and view one of the templates used.
- You should receive a 500 ISE.

```
NotImplementedError at /__debug__/template_source/
subclasses of BaseLoader must provide a load_template_source() method
Django Version: 1.7.1
Python Version: 2.7.6
Traceback:
File "/SITE_PACKAGES/django/core/handlers/base.py" in get_response
  111.                     response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/SITE_PACKAGES/debug_toolbar/panels/templates/views.py" in template_source
  27.             source, display_name = loader.load_template_source(template_name)
File "/SITE_PACKAGES/django/template/loader.py" in load_template_source
  65.         raise NotImplementedError('subclasses of BaseLoader must provide a load_template_source() method')
```

Which makes some amount of sense, as the caching Loader doesn't implement load_template_source (arguably upstream could make a change to provide one, though, possibly by just re-calling `find_template`)

There may very well be nothing that can done in djdt, but I couldn't see any documentation caveats that explain as much (having searched RTD and google), and it was a bit confusing, given that the template panel appeared to work fine -- showed the correct templates used, etc.



Discussion:
Thanks for the bug report. The toolbar replicates part of Django's logic for getting the source of a template, but it didn't handle the slight difference of the cached template loader.


Resolving patch:
diff --git a/debug_toolbar/panels/templates/views.py b/debug_toolbar/panels/templates/views.py
--- a/debug_toolbar/panels/templates/views.py
+++ b/debug_toolbar/panels/templates/views.py
@@ -21,7 +21,13 @@ def template_source(request):
     for loader_name in settings.TEMPLATE_LOADERS:
         loader = find_template_loader(loader_name)
         if loader is not None:
-            loaders.append(loader)
+            # When the loader has loaders associated with it,
+            # append those loaders to the list. This occurs with
+            # django.template.loaders.cached.Loader
+            if hasattr(loader, 'loaders'):
+                loaders += loader.loaders
+            else:
+                loaders.append(loader)
     for loader in loaders:
         try:
             source, display_name = loader.load_template_source(template_name)


Test patch:
diff --git a/tests/test_integration.py b/tests/test_integration.py
--- a/tests/test_integration.py
+++ b/tests/test_integration.py
@@ -159,3 +159,28 @@ def test_expired_store(self):
         error = WebDriverWait(self.selenium, timeout=10).until(
             lambda selenium: version_panel.find_element_by_tag_name('p'))
         self.assertIn("Data for this panel isn't available anymore.", error.text)
+
+    @override_settings(TEMPLATE_LOADERS=[(
+        'django.template.loaders.cached.Loader', (
+            'django.template.loaders.filesystem.Loader',
+            'django.template.loaders.app_directories.Loader',
+        ),
+    )])
+    def test_django_cached_template_loader(self):
+        self.selenium.get(self.live_server_url + '/regular/basic/')
+        version_panel = self.selenium.find_element_by_id('TemplatesPanel')
+
+        # Click to show the versions panel
+        self.selenium.find_element_by_class_name('TemplatesPanel').click()
+
+        # Version panel loads
+        trigger = WebDriverWait(self.selenium, timeout=10).until(
+            lambda selenium: version_panel.find_element_by_css_selector(
+                '.remoteCall'))
+        trigger.click()
+
+        # Verify the code is displayed
+        WebDriverWait(self.selenium, timeout=10).until(
+            lambda selenium: self.selenium.find_element_by_css_selector(
+                '#djDebugWindow code'))
+