aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJannis Leidel2012-04-06 12:59:56 +0200
committerJannis Leidel2013-12-08 23:33:39 +0100
commit184992f2d5773016e2c8315c91acbf8f601775bf (patch)
tree17753ea901f2db95e93f5897606fe0cbbda5e860
parente334bf6e4b4975fd87957c473a62c0d889e38cb0 (diff)
downloaddjango-debug-toolbar-184992f2d5773016e2c8315c91acbf8f601775bf.tar.bz2
Added staticfiles panel class.
-rw-r--r--debug_toolbar/panels/logging.py41
-rw-r--r--debug_toolbar/panels/staticfiles.py142
-rw-r--r--debug_toolbar/templates/debug_toolbar/panels/cache.html6
-rw-r--r--debug_toolbar/templates/debug_toolbar/panels/staticfiles.html57
-rw-r--r--debug_toolbar/utils.py33
-rw-r--r--example/settings.py3
-rw-r--r--example/static/test.css3
7 files changed, 250 insertions, 35 deletions
diff --git a/debug_toolbar/panels/logging.py b/debug_toolbar/panels/logging.py
index 051d5a3..1ee19ce 100644
--- a/debug_toolbar/panels/logging.py
+++ b/debug_toolbar/panels/logging.py
@@ -8,42 +8,19 @@ except ImportError:
threading = None
from django.utils.translation import ungettext, ugettext_lazy as _
from debug_toolbar.panels import Panel
+from debug_toolbar.utils import ThreadCollector
MESSAGE_IF_STRING_REPRESENTATION_INVALID = '[Could not get log message]'
-class LogCollector(object):
- def __init__(self):
- if threading is None:
- raise NotImplementedError(
- "threading module is not available, "
- "the logging panel cannot be used without it")
- self.records = {} # a dictionary that maps threads to log records
+class LogCollector(ThreadCollector):
- def add_record(self, record, thread=None):
+ def collect(self, item, thread=None):
# Avoid logging SQL queries since they are already in the SQL panel
# TODO: Make this check whether SQL panel is enabled
- if record.get('channel', '') == 'django.db.backends':
+ if item.get('channel', '') == 'django.db.backends':
return
-
- self.get_records(thread).append(record)
-
- def get_records(self, thread=None):
- """
- Returns a list of records for the provided thread, of if none is provided,
- returns a list for the current thread.
- """
- if thread is None:
- thread = threading.currentThread()
- if thread not in self.records:
- self.records[thread] = []
- return self.records[thread]
-
- def clear_records(self, thread=None):
- if thread is None:
- thread = threading.currentThread()
- if thread in self.records:
- del self.records[thread]
+ super(LogCollector, self).collect(item, thread)
class ThreadTrackingHandler(logging.Handler):
@@ -65,7 +42,7 @@ class ThreadTrackingHandler(logging.Handler):
'line': record.lineno,
'channel': record.name,
}
- self.collector.add_record(record)
+ self.collector.collect(record)
# We don't use enable/disable_instrumentation because logging is global.
@@ -96,10 +73,10 @@ class LoggingPanel(Panel):
title = _("Log messages")
def process_request(self, request):
- collector.clear_records()
+ collector.clear_collection()
def process_response(self, request, response):
- records = collector.get_records()
+ records = collector.get_collection()
self._records[threading.currentThread()] = records
- collector.clear_records()
+ collector.clear_collection()
self.record_stats({'records': records})
diff --git a/debug_toolbar/panels/staticfiles.py b/debug_toolbar/panels/staticfiles.py
new file mode 100644
index 0000000..9b3829d
--- /dev/null
+++ b/debug_toolbar/panels/staticfiles.py
@@ -0,0 +1,142 @@
+from __future__ import absolute_import
+from os.path import normpath, join
+try:
+ import threading
+except ImportError:
+ threading = None
+
+from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
+from django.core.files.storage import get_storage_class
+from django.contrib.staticfiles import finders, storage
+from django.contrib.staticfiles.templatetags import staticfiles
+
+from django.utils.translation import ungettext, ugettext_lazy as _
+from django.utils.datastructures import SortedDict
+from django.utils.functional import LazyObject
+
+from debug_toolbar import panels
+from debug_toolbar.utils import ThreadCollector
+
+
+class StaticFile(object):
+
+ def __init__(self, path):
+ self.path = path
+
+ def __unicode__(self):
+ return self.path
+
+ def real_path(self):
+ return finders.find(self.path)
+
+ def url(self):
+ return storage.staticfiles_storage.url(self.path)
+
+
+class FileCollector(ThreadCollector):
+
+ def collect(self, path, thread=None):
+ # handle the case of {% static "admin/" %}
+ if path.endswith('/'):
+ return
+ super(FileCollector, self).collect(StaticFile(path), thread)
+
+
+collector = FileCollector()
+
+
+class DebugConfiguredStorage(LazyObject):
+ def _setup(self):
+
+ configured_storage_cls = get_storage_class(settings.STATICFILES_STORAGE)
+
+ class DebugStaticFilesStorage(configured_storage_cls):
+
+ def __init__(self, collector, *args, **kwargs):
+ super(DebugStaticFilesStorage, self).__init__(*args, **kwargs)
+ self.collector = collector
+
+ def url(self, path):
+ self.collector.collect(path)
+ return super(DebugStaticFilesStorage, self).url(path)
+
+ self._wrapped = DebugStaticFilesStorage(collector)
+
+storage.staticfiles_storage = staticfiles.staticfiles_storage = DebugConfiguredStorage()
+
+
+class StaticFilesPanel(panels.Panel):
+ """
+ A panel to display the found staticfiles.
+ """
+ name = 'Static files'
+ template = 'debug_toolbar/panels/staticfiles.html'
+
+ @property
+ def title(self):
+ return (_("Static files (%(num_found)s found)") %
+ {'num_found': self.num_found, 'num_used': self.num_used})
+
+ def __init__(self, *args, **kwargs):
+ super(StaticFilesPanel, self).__init__(*args, **kwargs)
+ self.num_found = 0
+ self.ignore_patterns = []
+ self._paths = {}
+
+ @property
+ def has_content(self):
+ if "django.contrib.staticfiles" not in settings.INSTALLED_APPS:
+ raise ImproperlyConfigured("Could not find staticfiles in "
+ "INSTALLED_APPS setting.")
+ return True
+
+ @property
+ def num_used(self):
+ return len(self._paths[threading.currentThread()])
+
+ nav_title = _('Static files')
+
+ @property
+ def nav_subtitle(self):
+ num_used = self.num_used
+ return ungettext("%(num_used)s file used", "%(num_used)s files used",
+ num_used) % {'num_used': num_used}
+
+ def process_request(self, request):
+ collector.clear_collection()
+
+ def process_response(self, request, response):
+ staticfiles_finders = SortedDict()
+ for finder in finders.get_finders():
+ for path, finder_storage in finder.list(self.ignore_patterns):
+ if getattr(finder_storage, 'prefix', None):
+ prefixed_path = join(finder_storage.prefix, path)
+ else:
+ prefixed_path = path
+ finder_path = '.'.join([finder.__class__.__module__,
+ finder.__class__.__name__])
+ real_path = finder_storage.path(path)
+ payload = (prefixed_path, real_path)
+ staticfiles_finders.setdefault(finder_path, []).append(payload)
+ self.num_found += 1
+
+ dirs = getattr(settings, 'STATICFILES_DIRS', ())
+
+ used_paths = collector.get_collection()
+ self._paths[threading.currentThread()] = used_paths
+
+ self.record_stats({
+ 'num_found': self.num_found,
+ 'num_used': self.num_used,
+ 'staticfiles': used_paths,
+ 'staticfiles_apps': self.get_static_apps(),
+ 'staticfiles_dirs': [normpath(d) for d in dirs],
+ 'staticfiles_finders': staticfiles_finders,
+ })
+
+ def get_static_apps(self):
+ for finder in finders.get_finders():
+ if isinstance(finder, finders.AppDirectoriesFinder):
+ return finder.apps
+ return []
diff --git a/debug_toolbar/templates/debug_toolbar/panels/cache.html b/debug_toolbar/templates/debug_toolbar/panels/cache.html
index 595afd6..82ccc49 100644
--- a/debug_toolbar/templates/debug_toolbar/panels/cache.html
+++ b/debug_toolbar/templates/debug_toolbar/panels/cache.html
@@ -1,5 +1,5 @@
{% load i18n %}
-<h3>{% trans "Summary" %}</h3>
+<h4>{% trans "Summary" %}</h4>
<table>
<thead>
<tr>
@@ -18,7 +18,7 @@
</tr>
</tbody>
</table>
-<h3>{% trans "Commands" %}</h3>
+<h4>{% trans "Commands" %}</h4>
<table>
<thead>
<tr>
@@ -36,7 +36,7 @@
</tbody>
</table>
{% if calls %}
-<h3>{% trans "Calls" %}</h3>
+<h4>{% trans "Calls" %}</h4>
<table>
<thead>
<tr>
diff --git a/debug_toolbar/templates/debug_toolbar/panels/staticfiles.html b/debug_toolbar/templates/debug_toolbar/panels/staticfiles.html
new file mode 100644
index 0000000..e405a25
--- /dev/null
+++ b/debug_toolbar/templates/debug_toolbar/panels/staticfiles.html
@@ -0,0 +1,57 @@
+{% load i18n %}
+{% load static from staticfiles%}
+
+<h4>{% blocktrans count staticfiles_dirs|length as dirs_count %}Static file path{% plural %}Static file paths{% endblocktrans %}</h4>
+{% if staticfiles_dirs %}
+ <ol>
+ {% for staticfiles_dir in staticfiles_dirs %}
+ <li>{{ staticfiles_dir }}</li>
+ {% endfor %}
+ </ol>
+{% else %}
+ <p>{% trans "None" %}</p>
+{% endif %}
+
+<h4>{% blocktrans count staticfiles_apps|length as apps_count %}Static file app{% plural %}Static file apps{% endblocktrans %}</h4>
+{% if staticfiles_apps %}
+ <ol>
+ {% for static_app in staticfiles_apps %}
+ <li>{{ static_app }}</li>
+ {% endfor %}
+ </ol>
+{% else %}
+ <p>{% trans "None" %}</p>
+{% endif %}
+
+<h4>{% blocktrans count staticfiles|length as staticfiles_count %}Static file{% plural %}Static files{% endblocktrans %}</h4>
+{% if staticfiles %}
+<dl>
+{% for staticfile in staticfiles %}
+ <dt><strong><a class="toggleTemplate" href="{{ staticfile.url }}">{{ staticfile }}</a></strong></dt>
+ <dd><samp>{{ staticfile.real_path }}</samp></dd>
+{% endfor %}
+</dl>
+{% else %}
+ <p>{% trans "None" %}</p>
+{% endif %}
+
+
+{% for finder, payload in staticfiles_finders.items %}
+<h4>{{ finder }} ({{ payload|length }} files)</h4>
+<table>
+ <thead>
+ <tr>
+ <th>{% trans 'Path' %}</th>
+ <th>{% trans 'Location' %}</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for path, real_path in payload %}
+ <tr class="{% cycle 'djDebugOdd' 'djDebugEven' %}">
+ <td>{{ path }}</td>
+ <td>{{ real_path }}</td>
+ </tr>
+ {% endfor %}
+ </tbody>
+</table>
+{% endfor %}
diff --git a/debug_toolbar/utils.py b/debug_toolbar/utils.py
index d84c79c..0aedb54 100644
--- a/debug_toolbar/utils.py
+++ b/debug_toolbar/utils.py
@@ -4,6 +4,10 @@ import inspect
import os.path
import re
import sys
+try:
+ import threading
+except ImportError:
+ threading = None
import django
from django.core.exceptions import ImproperlyConfigured
@@ -199,3 +203,32 @@ def get_stack(context=1):
framelist.append((frame,) + getframeinfo(frame, context))
frame = frame.f_back
return framelist
+
+
+class ThreadCollector(object):
+ def __init__(self):
+ if threading is None:
+ raise NotImplementedError(
+ "threading module is not available, "
+ "this panel cannot be used without it")
+ self.collections = {} # a dictionary that maps threads to collections
+
+ def get_collection(self, thread=None):
+ """
+ Returns a list of collected items for the provided thread, of if none
+ is provided, returns a list for the current thread.
+ """
+ if thread is None:
+ thread = threading.currentThread()
+ if thread not in self.collections:
+ self.collections[thread] = []
+ return self.collections[thread]
+
+ def clear_collection(self, thread=None):
+ if thread is None:
+ thread = threading.currentThread()
+ if thread in self.collections:
+ del self.collections[thread]
+
+ def collect(self, item, thread=None):
+ self.get_collection(thread).append(item)
diff --git a/example/settings.py b/example/settings.py
index 4c29ba9..ca78f70 100644
--- a/example/settings.py
+++ b/example/settings.py
@@ -89,4 +89,7 @@ DEBUG_TOOLBAR_PANELS = [
'debug_toolbar.panels.logging.LoggingPanel',
'debug_toolbar.panels.redirects.RedirectsPanel',
'debug_toolbar.panels.profiling.ProfilingPanel',
+ 'debug_toolbar.panels.staticfiles.StaticFilesPanel',
]
+
+STATICFILES_DIRS = [os.path.join(BASE_DIR, 'example', 'static')]
diff --git a/example/static/test.css b/example/static/test.css
new file mode 100644
index 0000000..8d7d127
--- /dev/null
+++ b/example/static/test.css
@@ -0,0 +1,3 @@
+body {
+ color: green;
+} \ No newline at end of file