diff options
| -rw-r--r-- | debug_toolbar/panels/logging.py | 41 | ||||
| -rw-r--r-- | debug_toolbar/panels/staticfiles.py | 142 | ||||
| -rw-r--r-- | debug_toolbar/templates/debug_toolbar/panels/cache.html | 6 | ||||
| -rw-r--r-- | debug_toolbar/templates/debug_toolbar/panels/staticfiles.html | 57 | ||||
| -rw-r--r-- | debug_toolbar/utils.py | 33 | ||||
| -rw-r--r-- | example/settings.py | 3 | ||||
| -rw-r--r-- | example/static/test.css | 3 |
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 |
