From 3bf2b74f59a2195854157e5b283ccb4fb5e73d11 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 18 Oct 2018 11:42:14 -0700 Subject: [PATCH 1/6] Fix unicode error when trying to escape binary data --- debug_toolbar/panels/sql/tracking.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/debug_toolbar/panels/sql/tracking.py b/debug_toolbar/panels/sql/tracking.py index 63dbc6906..204cfa13c 100644 --- a/debug_toolbar/panels/sql/tracking.py +++ b/debug_toolbar/panels/sql/tracking.py @@ -6,7 +6,7 @@ from time import time from django.utils import six -from django.utils.encoding import force_text +from django.utils.encoding import force_text, DjangoUnicodeDecodeError from debug_toolbar import settings as dt_settings from debug_toolbar.utils import get_stack, get_template_info, tidy_stacktrace @@ -84,7 +84,10 @@ def __init__(self, cursor, db, logger): def _quote_expr(self, element): if isinstance(element, six.string_types): - return "'%s'" % force_text(element).replace("'", "''") + try: + return "'%s'" % force_text(element).replace("'", "''") + except DjangoUnicodeDecodeError: + return repr(element) else: return repr(element) From a1129991ce54183ddb6fc4acd7804ec63a762fdf Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 25 Oct 2018 14:55:29 -0700 Subject: [PATCH 2/6] Add test case and six.binary_type --- debug_toolbar/panels/sql/tracking.py | 4 +++- tests/panels/test_sql.py | 12 ++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/debug_toolbar/panels/sql/tracking.py b/debug_toolbar/panels/sql/tracking.py index 204cfa13c..fe1de6af8 100644 --- a/debug_toolbar/panels/sql/tracking.py +++ b/debug_toolbar/panels/sql/tracking.py @@ -6,7 +6,7 @@ from time import time from django.utils import six -from django.utils.encoding import force_text, DjangoUnicodeDecodeError +from django.utils.encoding import DjangoUnicodeDecodeError, force_text from debug_toolbar import settings as dt_settings from debug_toolbar.utils import get_stack, get_template_info, tidy_stacktrace @@ -88,6 +88,8 @@ def _quote_expr(self, element): return "'%s'" % force_text(element).replace("'", "''") except DjangoUnicodeDecodeError: return repr(element) + elif isinstance(element, six.binary_type): + return '(binary data)' else: return repr(element) diff --git a/tests/panels/test_sql.py b/tests/panels/test_sql.py index b46ece32d..df91b59a2 100644 --- a/tests/panels/test_sql.py +++ b/tests/panels/test_sql.py @@ -122,6 +122,18 @@ def test_param_conversion(self): '["2017-12-22 16:07:01"]' )) + @unittest.skipUnless(connection.vendor not in ('sqlite', 'postgresql'), '') + def test_binary_param_force_text(self): + self.assertEqual(len(self.panel._queries), 0) + + with connection.cursor() as cursor: + cursor.execute("SELECT * FROM auth_user WHERE username = %s", [b'\xff']) + + self.assertEqual(len(self.panel._queries), 1) + + self.panel.process_response(self.request, self.response) + self.panel.generate_stats(self.request, self.response) + @unittest.skipUnless(connection.vendor != 'sqlite', 'Test invalid for SQLite') def test_raw_query_param_conversion(self): From 201a65ba21b4c1d8b6f31e39b681e22da6765625 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 7 Nov 2018 13:34:12 -0800 Subject: [PATCH 3/6] Update binary parameter test --- tests/panels/test_sql.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/panels/test_sql.py b/tests/panels/test_sql.py index df91b59a2..1bad52373 100644 --- a/tests/panels/test_sql.py +++ b/tests/panels/test_sql.py @@ -122,7 +122,8 @@ def test_param_conversion(self): '["2017-12-22 16:07:01"]' )) - @unittest.skipUnless(connection.vendor not in ('sqlite', 'postgresql'), '') + @unittest.skipIf(connection.vendor in ('sqlite', 'postgresql'), + 'Mixing bytestrings and text is not allowed on PostgreSQL and SQLite') def test_binary_param_force_text(self): self.assertEqual(len(self.panel._queries), 0) @@ -130,12 +131,12 @@ def test_binary_param_force_text(self): cursor.execute("SELECT * FROM auth_user WHERE username = %s", [b'\xff']) self.assertEqual(len(self.panel._queries), 1) + self.assertEqual(self.panel._queries[0][1]['sql'], "SELECT * FROM auth_user WHERE username = '\ufffd'") self.panel.process_response(self.request, self.response) self.panel.generate_stats(self.request, self.response) - @unittest.skipUnless(connection.vendor != 'sqlite', - 'Test invalid for SQLite') + @unittest.skipUnless(connection.vendor != 'sqlite', 'Test invalid for SQLite') def test_raw_query_param_conversion(self): self.assertEqual(len(self.panel._queries), 0) From 78da08ef3fdcf213617bf090fb1033fa2e43b991 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 8 Nov 2018 10:48:18 -0800 Subject: [PATCH 4/6] Merge branch 'master' into patch-1 --- .travis.yml | 3 +- Makefile | 13 +- debug_toolbar/__init__.py | 11 +- debug_toolbar/apps.py | 19 +- debug_toolbar/decorators.py | 1 + .../management/commands/debugsqlshell.py | 5 +- debug_toolbar/middleware.py | 50 +-- debug_toolbar/panels/__init__.py | 17 +- debug_toolbar/panels/cache.py | 147 +++++---- debug_toolbar/panels/headers.py | 66 ++-- debug_toolbar/panels/logging.py | 26 +- debug_toolbar/panels/profiling.py | 57 ++-- debug_toolbar/panels/redirects.py | 10 +- debug_toolbar/panels/request.py | 52 +-- debug_toolbar/panels/settings.py | 14 +- debug_toolbar/panels/signals.py | 77 +++-- debug_toolbar/panels/sql/forms.py | 27 +- debug_toolbar/panels/sql/panel.py | 149 +++++---- debug_toolbar/panels/sql/tracking.py | 69 ++-- debug_toolbar/panels/sql/utils.py | 33 +- debug_toolbar/panels/sql/views.py | 70 ++-- debug_toolbar/panels/staticfiles.py | 56 ++-- debug_toolbar/panels/templates/panel.py | 109 ++++--- debug_toolbar/panels/templates/views.py | 14 +- debug_toolbar/panels/timer.py | 60 ++-- debug_toolbar/panels/versions.py | 24 +- debug_toolbar/settings.py | 172 +++++----- .../static/debug_toolbar/js/redirect.js | 1 + .../templates/debug_toolbar/base.html | 2 +- .../debug_toolbar/panels/profiling.html | 2 +- .../templates/debug_toolbar/panels/sql.html | 2 +- .../debug_toolbar/panels/sql_explain.html | 2 +- .../debug_toolbar/panels/sql_profile.html | 2 +- .../debug_toolbar/panels/sql_select.html | 2 +- .../templates/debug_toolbar/panels/timer.html | 2 +- .../templates/debug_toolbar/redirect.html | 6 +- debug_toolbar/toolbar.py | 24 +- debug_toolbar/utils.py | 87 +++-- debug_toolbar/views.py | 10 +- docs/changes.rst | 3 + docs/contributing.rst | 11 +- docs/tips.rst | 4 + example/settings.py | 122 ++++--- example/urls.py | 15 +- setup.cfg | 9 +- setup.py | 71 ++-- tests/__init__.py | 4 +- tests/base.py | 17 +- tests/commands/test_debugsqlshell.py | 3 +- tests/models.py | 2 +- tests/panels/test_cache.py | 33 +- tests/panels/test_logging.py | 37 ++- tests/panels/test_profiling.py | 45 +-- tests/panels/test_redirects.py | 31 +- tests/panels/test_request.py | 23 +- tests/panels/test_sql.py | 202 ++++++------ tests/panels/test_staticfiles.py | 37 ++- tests/panels/test_template.py | 54 ++-- tests/panels/test_versions.py | 32 +- tests/settings.py | 113 +++---- tests/test_integration.py | 302 ++++++++++-------- tests/test_utils.py | 9 +- tests/urls.py | 24 +- tests/views.py | 14 +- tox.ini | 15 +- 65 files changed, 1460 insertions(+), 1265 deletions(-) create mode 100644 debug_toolbar/static/debug_toolbar/js/redirect.js diff --git a/.travis.yml b/.travis.yml index 018954d66..86cf88856 100644 --- a/.travis.yml +++ b/.travis.yml @@ -43,7 +43,8 @@ matrix: addons: postgresql: "9.5" - env: TOXENV=flake8 - - env: TOXENV=isort + - python: 3.6 + env: TOXENV=style - env: TOXENV=readme allow_failures: - env: TOXENV=py35-djmaster diff --git a/Makefile b/Makefile index 800c0a579..071272179 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,16 @@ .PHONY: flake8 example test coverage translatable_strings update_translations -flake8: - flake8 debug_toolbar example tests - -isort: +style: isort -rc debug_toolbar example tests + black debug_toolbar example tests setup.py + flake8 debug_toolbar example tests -isort_check_only: +style_check: isort -rc -c debug_toolbar example tests + black --check debug_toolbar example tests setup.py + +flake8: + flake8 debug_toolbar example tests example: python example/manage.py runserver diff --git a/debug_toolbar/__init__.py b/debug_toolbar/__init__.py index a360cd2f0..fbd6308f5 100644 --- a/debug_toolbar/__init__.py +++ b/debug_toolbar/__init__.py @@ -1,17 +1,18 @@ from __future__ import absolute_import, unicode_literals -__all__ = ['VERSION'] +__all__ = ["VERSION"] try: import pkg_resources - VERSION = pkg_resources.get_distribution('django-debug-toolbar').version + + VERSION = pkg_resources.get_distribution("django-debug-toolbar").version except Exception: - VERSION = 'unknown' + VERSION = "unknown" # Code that discovers files or modules in INSTALLED_APPS imports this module. -urls = 'debug_toolbar.toolbar', 'djdt' +urls = "debug_toolbar.toolbar", "djdt" -default_app_config = 'debug_toolbar.apps.DebugToolbarConfig' +default_app_config = "debug_toolbar.apps.DebugToolbarConfig" diff --git a/debug_toolbar/apps.py b/debug_toolbar/apps.py index b29b3aadd..24e41ddbf 100644 --- a/debug_toolbar/apps.py +++ b/debug_toolbar/apps.py @@ -11,7 +11,7 @@ class DebugToolbarConfig(AppConfig): - name = 'debug_toolbar' + name = "debug_toolbar" verbose_name = _("Debug Toolbar") @@ -23,11 +23,11 @@ def check_middleware(app_configs, **kwargs): gzip_index = None debug_toolbar_indexes = [] - setting = getattr(settings, 'MIDDLEWARE', None) - setting_name = 'MIDDLEWARE' + setting = getattr(settings, "MIDDLEWARE", None) + setting_name = "MIDDLEWARE" if setting is None: setting = settings.MIDDLEWARE_CLASSES - setting_name = 'MIDDLEWARE_CLASSES' + setting_name = "MIDDLEWARE_CLASSES" # Determine the indexes which gzip and/or the toolbar are installed at for i, middleware in enumerate(setting): @@ -44,7 +44,7 @@ def check_middleware(app_configs, **kwargs): "from %s." % setting_name, hint="Add debug_toolbar.middleware.DebugToolbarMiddleware to " "%s." % setting_name, - id='debug_toolbar.W001', + id="debug_toolbar.W001", ) ) elif len(debug_toolbar_indexes) != 1: @@ -55,7 +55,7 @@ def check_middleware(app_configs, **kwargs): "multiple times in %s." % setting_name, hint="Load debug_toolbar.middleware.DebugToolbarMiddleware only " "once in %s." % setting_name, - id='debug_toolbar.W002', + id="debug_toolbar.W002", ) ) elif gzip_index is not None and debug_toolbar_indexes[0] < gzip_index: @@ -66,7 +66,7 @@ def check_middleware(app_configs, **kwargs): "django.middleware.gzip.GZipMiddleware in %s." % setting_name, hint="Move debug_toolbar.middleware.DebugToolbarMiddleware to " "after django.middleware.gzip.GZipMiddleware in %s." % setting_name, - id='debug_toolbar.W003', + id="debug_toolbar.W003", ) ) @@ -78,7 +78,6 @@ def is_middleware_class(middleware_class, middleware_path): middleware_cls = import_string(middleware_path) except ImportError: return - return ( - inspect.isclass(middleware_cls) and - issubclass(middleware_cls, middleware_class) + return inspect.isclass(middleware_cls) and issubclass( + middleware_cls, middleware_class ) diff --git a/debug_toolbar/decorators.py b/debug_toolbar/decorators.py index a1f7f3af8..8114b05d7 100644 --- a/debug_toolbar/decorators.py +++ b/debug_toolbar/decorators.py @@ -13,4 +13,5 @@ def inner(request, *args, **kwargs): raise Http404 return view(request, *args, **kwargs) + return inner diff --git a/debug_toolbar/management/commands/debugsqlshell.py b/debug_toolbar/management/commands/debugsqlshell.py index 2bb2b8d60..59a178429 100644 --- a/debug_toolbar/management/commands/debugsqlshell.py +++ b/debug_toolbar/management/commands/debugsqlshell.py @@ -3,10 +3,11 @@ from time import time import sqlparse -# 'debugsqlshell' is the same as the 'shell'. from django.core.management.commands.shell import Command # noqa from django.db.backends import utils as db_backends_utils +# 'debugsqlshell' is the same as the 'shell'. + class PrintQueryWrapper(db_backends_utils.CursorDebugWrapper): def execute(self, sql, params=()): @@ -18,7 +19,7 @@ def execute(self, sql, params=()): end_time = time() duration = (end_time - start_time) * 1000 formatted_sql = sqlparse.format(raw_sql, reindent=True) - print('%s [%.2fms]' % (formatted_sql, duration)) + print("%s [%.2fms]" % (formatted_sql, duration)) db_backends_utils.CursorDebugWrapper = PrintQueryWrapper diff --git a/debug_toolbar/middleware.py b/debug_toolbar/middleware.py index 3dbcd14ac..0c2087112 100644 --- a/debug_toolbar/middleware.py +++ b/debug_toolbar/middleware.py @@ -17,14 +17,14 @@ from debug_toolbar import settings as dt_settings from debug_toolbar.toolbar import DebugToolbar -_HTML_TYPES = ('text/html', 'application/xhtml+xml') +_HTML_TYPES = ("text/html", "application/xhtml+xml") def show_toolbar(request): """ Default function to determine whether to show the toolbar on a given page. """ - if request.META.get('REMOTE_ADDR', None) not in settings.INTERNAL_IPS: + if request.META.get("REMOTE_ADDR", None) not in settings.INTERNAL_IPS: return False return bool(settings.DEBUG) @@ -34,7 +34,7 @@ def show_toolbar(request): def get_show_toolbar(): # If SHOW_TOOLBAR_CALLBACK is a string, which is the recommended # setup, resolve it to the corresponding callable. - func_or_path = dt_settings.get_config()['SHOW_TOOLBAR_CALLBACK'] + func_or_path = dt_settings.get_config()["SHOW_TOOLBAR_CALLBACK"] if isinstance(func_or_path, six.string_types): return import_string(func_or_path) else: @@ -46,6 +46,7 @@ class DebugToolbarMiddleware(MiddlewareMixin): Middleware to set up Debug Toolbar on incoming request and render toolbar on outgoing response. """ + debug_toolbars = {} def process_request(self, request): @@ -87,7 +88,9 @@ def process_view(self, request, view_func, view_args, view_kwargs): return response def process_response(self, request, response): - toolbar = self.__class__.debug_toolbars.pop(threading.current_thread().ident, None) + toolbar = self.__class__.debug_toolbars.pop( + threading.current_thread().ident, None + ) if not toolbar: return response @@ -104,20 +107,24 @@ def process_response(self, request, response): panel.disable_instrumentation() # Check for responses where the toolbar can't be inserted. - content_encoding = response.get('Content-Encoding', '') - content_type = response.get('Content-Type', '').split(';')[0] - if any((getattr(response, 'streaming', False), - 'gzip' in content_encoding, - content_type not in _HTML_TYPES)): + content_encoding = response.get("Content-Encoding", "") + content_type = response.get("Content-Type", "").split(";")[0] + if any( + ( + getattr(response, "streaming", False), + "gzip" in content_encoding, + content_type not in _HTML_TYPES, + ) + ): return response # Collapse the toolbar by default if SHOW_COLLAPSED is set. - if toolbar.config['SHOW_COLLAPSED'] and 'djdt' not in request.COOKIES: - response.set_cookie('djdt', 'hide', 864000) + if toolbar.config["SHOW_COLLAPSED"] and "djdt" not in request.COOKIES: + response.set_cookie("djdt", "hide", 864000) # Insert the toolbar in the response. content = force_text(response.content, encoding=response.charset) - insert_before = dt_settings.get_config()['INSERT_BEFORE'] + insert_before = dt_settings.get_config()["INSERT_BEFORE"] pattern = re.escape(insert_before) bits = re.split(pattern, content, flags=re.IGNORECASE) if len(bits) > 1: @@ -126,12 +133,14 @@ def process_response(self, request, response): panel.generate_stats(request, response) panel.generate_server_timing(request, response) - response = self.generate_server_timing_header(response, toolbar.enabled_panels) + response = self.generate_server_timing_header( + response, toolbar.enabled_panels + ) bits[-2] += toolbar.render_toolbar() response.content = insert_before.join(bits) - if response.get('Content-Length', None): - response['Content-Length'] = len(response.content) + if response.get("Content-Length", None): + response["Content-Length"] = len(response.content) return response @staticmethod @@ -145,11 +154,12 @@ def generate_server_timing_header(response, panels): for key, record in stats.items(): # example: `SQLPanel_sql_time=0; "SQL 0 queries"` - data.append('{}_{}={}; "{}"'.format(panel.panel_id, - key, - record.get('value'), - record.get('title'))) + data.append( + '{}_{}={}; "{}"'.format( + panel.panel_id, key, record.get("value"), record.get("title") + ) + ) if data: - response['Server-Timing'] = ', '.join(data) + response["Server-Timing"] = ", ".join(data) return response diff --git a/debug_toolbar/panels/__init__.py b/debug_toolbar/panels/__init__.py index 7a074b5fc..ecead3cb9 100644 --- a/debug_toolbar/panels/__init__.py +++ b/debug_toolbar/panels/__init__.py @@ -12,6 +12,7 @@ class Panel(object): """ Base class for panels. """ + def __init__(self, toolbar): self.toolbar = toolbar @@ -24,21 +25,22 @@ def panel_id(self): @property def enabled(self): # Check to see if settings has a default value for it - disabled_panels = dt_settings.get_config()['DISABLE_PANELS'] + disabled_panels = dt_settings.get_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) + panel_path in disabled_panels + or panel_path.replace(".panel.", ".") in disabled_panels + ) if disable_panel: - default = 'off' + default = "off" else: - default = 'on' + default = "on" # The user's cookies should override the default value - return self.toolbar.request.COOKIES.get('djdt' + self.panel_id, default) == 'on' + return self.toolbar.request.COOKIES.get("djdt" + self.panel_id, default) == "on" # Titles and content @@ -54,7 +56,7 @@ def nav_subtitle(self): """ Subtitle shown in the side bar. Defaults to the empty string. """ - return '' + return "" @property def has_content(self): @@ -220,7 +222,6 @@ def generate_server_timing(self, request, response): # Backward-compatibility for 1.0, remove in 2.0. class DebugPanel(Panel): - def __init__(self, *args, **kwargs): warnings.warn("DebugPanel was renamed to Panel.", DeprecationWarning) super(DebugPanel, self).__init__(*args, **kwargs) diff --git a/debug_toolbar/panels/cache.py b/debug_toolbar/panels/cache.py index 366af3fb6..c8030a585 100644 --- a/debug_toolbar/panels/cache.py +++ b/debug_toolbar/panels/cache.py @@ -16,11 +16,15 @@ from debug_toolbar import settings as dt_settings from debug_toolbar.panels import Panel from debug_toolbar.utils import ( - get_stack, get_template_info, render_stacktrace, tidy_stacktrace, + get_stack, + get_template_info, + render_stacktrace, + tidy_stacktrace, ) -cache_called = Signal(providing_args=[ - "time_taken", "name", "return_value", "args", "kwargs", "trace"]) +cache_called = Signal( + providing_args=["time_taken", "name", "return_value", "args", "kwargs", "trace"] +) def send_signal(method): @@ -29,22 +33,31 @@ def wrapped(self, *args, **kwargs): value = method(self, *args, **kwargs) t = time.time() - t - if dt_settings.get_config()['ENABLE_STACKTRACES']: + if dt_settings.get_config()["ENABLE_STACKTRACES"]: stacktrace = tidy_stacktrace(reversed(get_stack())) else: stacktrace = [] template_info = get_template_info() - cache_called.send(sender=self.__class__, time_taken=t, - name=method.__name__, return_value=value, - args=args, kwargs=kwargs, trace=stacktrace, - template_info=template_info, backend=self.cache) + cache_called.send( + sender=self.__class__, + time_taken=t, + name=method.__name__, + return_value=value, + args=args, + kwargs=kwargs, + trace=stacktrace, + template_info=template_info, + backend=self.cache, + ) return value + return wrapped class CacheStatTracker(BaseCache): """A small class used to track cache calls.""" + def __init__(self, cache): self.cache = cache @@ -130,7 +143,8 @@ class CachePanel(Panel): """ Panel that displays the cache statistics. """ - template = 'debug_toolbar/panels/cache.html' + + template = "debug_toolbar/panels/cache.html" def __init__(self, *args, **kwargs): super(CachePanel, self).__init__(*args, **kwargs) @@ -138,32 +152,44 @@ def __init__(self, *args, **kwargs): self.hits = 0 self.misses = 0 self.calls = [] - self.counts = OrderedDict(( - ('add', 0), - ('get', 0), - ('set', 0), - ('delete', 0), - ('clear', 0), - ('get_many', 0), - ('set_many', 0), - ('delete_many', 0), - ('has_key', 0), - ('incr', 0), - ('decr', 0), - ('incr_version', 0), - ('decr_version', 0), - )) + self.counts = OrderedDict( + ( + ("add", 0), + ("get", 0), + ("set", 0), + ("delete", 0), + ("clear", 0), + ("get_many", 0), + ("set_many", 0), + ("delete_many", 0), + ("has_key", 0), + ("incr", 0), + ("decr", 0), + ("incr_version", 0), + ("decr_version", 0), + ) + ) cache_called.connect(self._store_call_info) - def _store_call_info(self, sender, name=None, time_taken=0, - return_value=None, args=None, kwargs=None, - trace=None, template_info=None, backend=None, **kw): - if name == 'get': + def _store_call_info( + self, + sender, + name=None, + time_taken=0, + return_value=None, + args=None, + kwargs=None, + trace=None, + template_info=None, + backend=None, + **kw + ): + if name == "get": if return_value is None: self.misses += 1 else: self.hits += 1 - elif name == 'get_many': + elif name == "get_many": for key, value in return_value.items(): if value is None: self.misses += 1 @@ -173,15 +199,17 @@ def _store_call_info(self, sender, name=None, time_taken=0, self.total_time += time_taken self.counts[name] += 1 - self.calls.append({ - 'time': time_taken, - 'name': name, - 'args': args, - 'kwargs': kwargs, - 'trace': render_stacktrace(trace), - 'template_info': template_info, - 'backend': backend - }) + self.calls.append( + { + "time": time_taken, + "name": name, + "args": args, + "kwargs": kwargs, + "trace": render_stacktrace(trace), + "template_info": template_info, + "backend": backend, + } + ) # Implement the Panel API @@ -190,17 +218,20 @@ def _store_call_info(self, sender, name=None, time_taken=0, @property def nav_subtitle(self): cache_calls = len(self.calls) - return ungettext("%(cache_calls)d call in %(time).2fms", - "%(cache_calls)d calls in %(time).2fms", - cache_calls) % {'cache_calls': cache_calls, - 'time': self.total_time} + return ungettext( + "%(cache_calls)d call in %(time).2fms", + "%(cache_calls)d calls in %(time).2fms", + cache_calls, + ) % {"cache_calls": cache_calls, "time": self.total_time} @property def title(self): - count = len(getattr(settings, 'CACHES', ['default'])) - return ungettext("Cache calls from %(count)d backend", - "Cache calls from %(count)d backends", - count) % dict(count=count) + count = len(getattr(settings, "CACHES", ["default"])) + return ungettext( + "Cache calls from %(count)d backend", + "Cache calls from %(count)d backends", + count, + ) % dict(count=count) def enable_instrumentation(self): if isinstance(middleware_cache.caches, CacheHandlerPatch): @@ -216,17 +247,19 @@ def disable_instrumentation(self): middleware_cache.caches = original_caches def generate_stats(self, request, response): - self.record_stats({ - 'total_calls': len(self.calls), - 'calls': self.calls, - 'total_time': self.total_time, - 'hits': self.hits, - 'misses': self.misses, - 'counts': self.counts, - }) + self.record_stats( + { + "total_calls": len(self.calls), + "calls": self.calls, + "total_time": self.total_time, + "hits": self.hits, + "misses": self.misses, + "counts": self.counts, + } + ) def generate_server_timing(self, request, response): stats = self.get_stats() - value = stats.get('total_time', 0) - title = 'Cache {} Calls'.format(stats.get('total_calls', 0)) - self.record_server_timing('total_time', title, value) + value = stats.get("total_time", 0) + title = "Cache {} Calls".format(stats.get("total_calls", 0)) + self.record_server_timing("total_time", title, value) diff --git a/debug_toolbar/panels/headers.py b/debug_toolbar/panels/headers.py index cec5b09dc..b6a493f2e 100644 --- a/debug_toolbar/panels/headers.py +++ b/debug_toolbar/panels/headers.py @@ -11,55 +11,57 @@ class HeadersPanel(Panel): """ A panel to display HTTP headers. """ + # List of environment variables we want to display - ENVIRON_FILTER = set(( - 'CONTENT_LENGTH', - 'CONTENT_TYPE', - 'DJANGO_SETTINGS_MODULE', - 'GATEWAY_INTERFACE', - 'QUERY_STRING', - 'PATH_INFO', - 'PYTHONPATH', - 'REMOTE_ADDR', - 'REMOTE_HOST', - 'REQUEST_METHOD', - 'SCRIPT_NAME', - 'SERVER_NAME', - 'SERVER_PORT', - 'SERVER_PROTOCOL', - 'SERVER_SOFTWARE', - 'TZ', - )) + ENVIRON_FILTER = set( + ( + "CONTENT_LENGTH", + "CONTENT_TYPE", + "DJANGO_SETTINGS_MODULE", + "GATEWAY_INTERFACE", + "QUERY_STRING", + "PATH_INFO", + "PYTHONPATH", + "REMOTE_ADDR", + "REMOTE_HOST", + "REQUEST_METHOD", + "SCRIPT_NAME", + "SERVER_NAME", + "SERVER_PORT", + "SERVER_PROTOCOL", + "SERVER_SOFTWARE", + "TZ", + ) + ) title = _("Headers") - template = 'debug_toolbar/panels/headers.html' + template = "debug_toolbar/panels/headers.html" def process_request(self, request): wsgi_env = list(sorted(request.META.items())) self.request_headers = OrderedDict( - (unmangle(k), v) for (k, v) in wsgi_env if is_http_header(k)) - if 'Cookie' in self.request_headers: - self.request_headers['Cookie'] = '=> see Request panel' + (unmangle(k), v) for (k, v) in wsgi_env if is_http_header(k) + ) + if "Cookie" in self.request_headers: + self.request_headers["Cookie"] = "=> see Request panel" self.environ = OrderedDict( - (k, v) for (k, v) in wsgi_env if k in self.ENVIRON_FILTER) - self.record_stats({ - 'request_headers': self.request_headers, - 'environ': self.environ, - }) + (k, v) for (k, v) in wsgi_env if k in self.ENVIRON_FILTER + ) + self.record_stats( + {"request_headers": self.request_headers, "environ": self.environ} + ) def generate_stats(self, request, response): self.response_headers = OrderedDict(sorted(response.items())) - self.record_stats({ - 'response_headers': self.response_headers, - }) + self.record_stats({"response_headers": self.response_headers}) def is_http_header(wsgi_key): # The WSGI spec says that keys should be str objects in the environ dict, # but this isn't true in practice. See issues #449 and #482. - return isinstance(wsgi_key, str) and wsgi_key.startswith('HTTP_') + return isinstance(wsgi_key, str) and wsgi_key.startswith("HTTP_") def unmangle(wsgi_key): - return wsgi_key[5:].replace('_', '-').title() + return wsgi_key[5:].replace("_", "-").title() diff --git a/debug_toolbar/panels/logging.py b/debug_toolbar/panels/logging.py index db0117934..bb594a8a7 100644 --- a/debug_toolbar/panels/logging.py +++ b/debug_toolbar/panels/logging.py @@ -13,15 +13,14 @@ except ImportError: threading = None -MESSAGE_IF_STRING_REPRESENTATION_INVALID = '[Could not get log message]' +MESSAGE_IF_STRING_REPRESENTATION_INVALID = "[Could not get log message]" class LogCollector(ThreadCollector): - 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 item.get('channel', '') == 'django.db.backends': + if item.get("channel", "") == "django.db.backends": return super(LogCollector, self).collect(item, thread) @@ -38,12 +37,12 @@ def emit(self, record): message = MESSAGE_IF_STRING_REPRESENTATION_INVALID record = { - 'message': message, - 'time': datetime.datetime.fromtimestamp(record.created), - 'level': record.levelname, - 'file': record.pathname, - 'line': record.lineno, - 'channel': record.name, + "message": message, + "time": datetime.datetime.fromtimestamp(record.created), + "level": record.levelname, + "file": record.pathname, + "line": record.lineno, + "channel": record.name, } self.collector.collect(record) @@ -57,7 +56,7 @@ def emit(self, record): class LoggingPanel(Panel): - template = 'debug_toolbar/panels/logging.html' + template = "debug_toolbar/panels/logging.html" def __init__(self, *args, **kwargs): super(LoggingPanel, self).__init__(*args, **kwargs) @@ -69,8 +68,9 @@ def __init__(self, *args, **kwargs): def nav_subtitle(self): records = self._records[threading.currentThread()] record_count = len(records) - return ungettext("%(count)s message", "%(count)s messages", - record_count) % {'count': record_count} + return ungettext("%(count)s message", "%(count)s messages", record_count) % { + "count": record_count + } title = _("Log messages") @@ -81,4 +81,4 @@ def generate_stats(self, request, response): records = collector.get_collection() self._records[threading.currentThread()] = records collector.clear_collection() - self.record_stats({'records': records}) + self.record_stats({"records": records}) diff --git a/debug_toolbar/panels/profiling.py b/debug_toolbar/panels/profiling.py index 4d361990e..387518edd 100644 --- a/debug_toolbar/panels/profiling.py +++ b/debug_toolbar/panels/profiling.py @@ -15,7 +15,7 @@ # Occasionally the disable method on the profiler is listed before # the actual view functions. This function call should be ignored as # it leads to an error within the tests. -INVALID_PROFILER_FUNC = '_lsprof.Profiler' +INVALID_PROFILER_FUNC = "_lsprof.Profiler" def contains_profiler(func_tuple): @@ -41,8 +41,9 @@ def get_root_func(self): class FunctionCall(object): - def __init__(self, statobj, func, depth=0, stats=None, - id=0, parent_ids=[], hsv=(0, 0.5, 1)): + def __init__( + self, statobj, func, depth=0, stats=None, id=0, parent_ids=[], hsv=(0, 0.5, 1) + ): self.statobj = statobj self.func = func if stats: @@ -59,28 +60,28 @@ def parent_classes(self): def background(self): r, g, b = hsv_to_rgb(*self.hsv) - return 'rgb(%f%%,%f%%,%f%%)' % (r * 100, g * 100, b * 100) + return "rgb(%f%%,%f%%,%f%%)" % (r * 100, g * 100, b * 100) def func_std_string(self): # match what old profile produced func_name = self.func - if func_name[:2] == ('~', 0): + if func_name[:2] == ("~", 0): # special case for built-in functions name = func_name[2] - if name.startswith('<') and name.endswith('>'): - return '{%s}' % name[1:-1] + if name.startswith("<") and name.endswith(">"): + return "{%s}" % name[1:-1] else: return name else: file_name, line_num, method = self.func - idx = file_name.find('/site-packages/') + idx = file_name.find("/site-packages/") if idx > -1: - file_name = file_name[(idx + 14):] + file_name = file_name[(idx + 14) :] split_path = file_name.rsplit(os.sep, 1) if len(split_path) > 1: file_path, file_name = file_name.rsplit(os.sep, 1) else: - file_path = '' + file_path = "" return format_html( '{0}/' @@ -90,7 +91,8 @@ def func_std_string(self): # match what old profile produced file_path, file_name, line_num, - method) + method, + ) def subfuncs(self): i = 0 @@ -103,13 +105,15 @@ def subfuncs(self): s1 = 0 else: s1 = s * (stats[3] / self.stats[3]) - yield FunctionCall(self.statobj, - func, - self.depth + 1, - stats=stats, - id=str(self.id) + '_' + str(i), - parent_ids=self.parent_ids + [self.id], - hsv=(h1, s1, 1)) + yield FunctionCall( + self.statobj, + func, + self.depth + 1, + stats=stats, + id=str(self.id) + "_" + str(i), + parent_ids=self.parent_ids + [self.id], + hsv=(h1, s1, 1), + ) def count(self): return self.stats[1] @@ -145,9 +149,10 @@ class ProfilingPanel(Panel): """ Panel that displays profiling information. """ + title = _("Profiling") - template = 'debug_toolbar/panels/profiling.html' + template = "debug_toolbar/panels/profiling.html" def process_view(self, request, view_func, view_args, view_kwargs): self.profiler = cProfile.Profile() @@ -164,7 +169,7 @@ def add_node(self, func_list, func, max_depth, cum_time=0.1): self.add_node(func_list, subfunc, max_depth, cum_time=cum_time) def generate_stats(self, request, response): - if not hasattr(self, 'profiler'): + if not hasattr(self, "profiler"): return None # Could be delayed until the panel content is requested (perf. optim.) self.profiler.create_stats() @@ -176,8 +181,10 @@ def generate_stats(self, request, response): if root_func: root = FunctionCall(self.stats, root_func, depth=0) func_list = [] - self.add_node(func_list, - root, - dt_settings.get_config()['PROFILER_MAX_DEPTH'], - root.stats[3] / 8) - self.record_stats({'func_list': func_list}) + self.add_node( + func_list, + root, + dt_settings.get_config()["PROFILER_MAX_DEPTH"], + root.stats[3] / 8, + ) + self.record_stats({"func_list": func_list}) diff --git a/debug_toolbar/panels/redirects.py b/debug_toolbar/panels/redirects.py index 01a60167e..eafb6c95b 100644 --- a/debug_toolbar/panels/redirects.py +++ b/debug_toolbar/panels/redirects.py @@ -17,13 +17,15 @@ class RedirectsPanel(Panel): def process_response(self, request, response): if 300 <= int(response.status_code) < 400: - redirect_to = response.get('Location', None) + redirect_to = response.get("Location", None) if redirect_to: - status_line = '%s %s' % (response.status_code, response.reason_phrase) + status_line = "%s %s" % (response.status_code, response.reason_phrase) cookies = response.cookies - context = {'redirect_to': redirect_to, 'status_line': status_line} + context = {"redirect_to": redirect_to, "status_line": status_line} # Using SimpleTemplateResponse avoids running global context processors. - response = SimpleTemplateResponse('debug_toolbar/redirect.html', context) + response = SimpleTemplateResponse( + "debug_toolbar/redirect.html", context + ) response.cookies = cookies response.render() return response diff --git a/debug_toolbar/panels/request.py b/debug_toolbar/panels/request.py index 6420e3c29..b3908e5f8 100644 --- a/debug_toolbar/panels/request.py +++ b/debug_toolbar/panels/request.py @@ -13,7 +13,8 @@ class RequestPanel(Panel): """ A panel to display request variables (POST/GET, session, cookies). """ - template = 'debug_toolbar/panels/request.html' + + template = "debug_toolbar/panels/request.html" title = _("Request") @@ -22,35 +23,42 @@ def nav_subtitle(self): """ Show abbreviated name of view function as subtitle """ - view_func = self.get_stats().get('view_func', '') - return view_func.rsplit('.', 1)[-1] + view_func = self.get_stats().get("view_func", "") + return view_func.rsplit(".", 1)[-1] def generate_stats(self, request, response): - self.record_stats({ - 'get': [(k, request.GET.getlist(k)) for k in sorted(request.GET)], - 'post': [(k, request.POST.getlist(k)) for k in sorted(request.POST)], - 'cookies': [(k, request.COOKIES.get(k)) for k in sorted(request.COOKIES)], - }) + self.record_stats( + { + "get": [(k, request.GET.getlist(k)) for k in sorted(request.GET)], + "post": [(k, request.POST.getlist(k)) for k in sorted(request.POST)], + "cookies": [ + (k, request.COOKIES.get(k)) for k in sorted(request.COOKIES) + ], + } + ) view_info = { - 'view_func': _(""), - 'view_args': 'None', - 'view_kwargs': 'None', - 'view_urlname': 'None', + "view_func": _(""), + "view_args": "None", + "view_kwargs": "None", + "view_urlname": "None", } try: match = resolve(request.path) func, args, kwargs = match - view_info['view_func'] = get_name_from_obj(func) - view_info['view_args'] = args - view_info['view_kwargs'] = kwargs - view_info['view_urlname'] = getattr(match, 'url_name', - _("")) + view_info["view_func"] = get_name_from_obj(func) + view_info["view_args"] = args + view_info["view_kwargs"] = kwargs + view_info["view_urlname"] = getattr(match, "url_name", _("")) except Http404: pass self.record_stats(view_info) - if hasattr(request, 'session'): - self.record_stats({ - 'session': [(k, request.session.get(k)) - for k in sorted(request.session.keys(), key=force_text)] - }) + if hasattr(request, "session"): + self.record_stats( + { + "session": [ + (k, request.session.get(k)) + for k in sorted(request.session.keys(), key=force_text) + ] + } + ) diff --git a/debug_toolbar/panels/settings.py b/debug_toolbar/panels/settings.py index f93095156..6026c8ea8 100644 --- a/debug_toolbar/panels/settings.py +++ b/debug_toolbar/panels/settings.py @@ -13,7 +13,8 @@ class SettingsPanel(Panel): """ A panel to display all variables in django.conf.settings """ - template = 'debug_toolbar/panels/settings.html' + + template = "debug_toolbar/panels/settings.html" nav_title = _("Settings") @@ -21,7 +22,10 @@ def title(self): return _("Settings from %s") % settings.SETTINGS_MODULE def generate_stats(self, request, response): - self.record_stats({ - 'settings': OrderedDict(sorted(get_safe_settings().items(), - key=lambda s: s[0])), - }) + self.record_stats( + { + "settings": OrderedDict( + sorted(get_safe_settings().items(), key=lambda s: s[0]) + ) + } + ) diff --git a/debug_toolbar/panels/signals.py b/debug_toolbar/panels/signals.py index cd647dbad..80796a4f4 100644 --- a/debug_toolbar/panels/signals.py +++ b/debug_toolbar/panels/signals.py @@ -2,13 +2,17 @@ import weakref -from django.core.signals import ( - got_request_exception, request_finished, request_started, -) +from django.core.signals import got_request_exception, request_finished, request_started from django.db.backends.signals import connection_created from django.db.models.signals import ( - class_prepared, post_delete, post_init, post_migrate, post_save, - pre_delete, pre_init, pre_save, + class_prepared, + post_delete, + post_init, + post_migrate, + post_save, + pre_delete, + pre_init, + pre_save, ) from django.utils.module_loading import import_string from django.utils.translation import ugettext_lazy as _, ungettext @@ -17,45 +21,48 @@ class SignalsPanel(Panel): - template = 'debug_toolbar/panels/signals.html' + template = "debug_toolbar/panels/signals.html" SIGNALS = { - 'request_started': request_started, - 'request_finished': request_finished, - 'got_request_exception': got_request_exception, - 'connection_created': connection_created, - 'class_prepared': class_prepared, - 'pre_init': pre_init, - 'post_init': post_init, - 'pre_save': pre_save, - 'post_save': post_save, - 'pre_delete': pre_delete, - 'post_delete': post_delete, - 'post_migrate': post_migrate, + "request_started": request_started, + "request_finished": request_finished, + "got_request_exception": got_request_exception, + "connection_created": connection_created, + "class_prepared": class_prepared, + "pre_init": pre_init, + "post_init": post_init, + "pre_save": pre_save, + "post_save": post_save, + "pre_delete": pre_delete, + "post_delete": post_delete, + "post_migrate": post_migrate, } def nav_subtitle(self): - signals = self.get_stats()['signals'] + signals = self.get_stats()["signals"] num_receivers = sum(len(s[2]) for s in signals) num_signals = len(signals) # here we have to handle a double count translation, hence the # hard coding of one signal if num_signals == 1: - return ungettext("%(num_receivers)d receiver of 1 signal", - "%(num_receivers)d receivers of 1 signal", - num_receivers) % {'num_receivers': num_receivers} - return ungettext("%(num_receivers)d receiver of %(num_signals)d signals", - "%(num_receivers)d receivers of %(num_signals)d signals", - num_receivers) % {'num_receivers': num_receivers, - 'num_signals': num_signals} + return ungettext( + "%(num_receivers)d receiver of 1 signal", + "%(num_receivers)d receivers of 1 signal", + num_receivers, + ) % {"num_receivers": num_receivers} + return ungettext( + "%(num_receivers)d receiver of %(num_signals)d signals", + "%(num_receivers)d receivers of %(num_signals)d signals", + num_receivers, + ) % {"num_receivers": num_receivers, "num_signals": num_signals} title = _("Signals") @property def signals(self): signals = self.SIGNALS.copy() - for signal in self.toolbar.config['EXTRA_SIGNALS']: - signal_name = signal.rsplit('.', 1)[-1] + for signal in self.toolbar.config["EXTRA_SIGNALS"]: + signal_name = signal.rsplit(".", 1)[-1] signals[signal_name] = import_string(signal) return signals @@ -70,12 +77,14 @@ def generate_stats(self, request, response): if receiver is None: continue - receiver = getattr(receiver, '__wraps__', receiver) - receiver_name = getattr(receiver, '__name__', str(receiver)) - if getattr(receiver, '__self__', None) is not None: - receiver_class_name = getattr(receiver.__self__, '__class__', type).__name__ + receiver = getattr(receiver, "__wraps__", receiver) + receiver_name = getattr(receiver, "__name__", str(receiver)) + if getattr(receiver, "__self__", None) is not None: + receiver_class_name = getattr( + receiver.__self__, "__class__", type + ).__name__ text = "%s.%s" % (receiver_class_name, receiver_name) - elif getattr(receiver, 'im_class', None) is not None: # Python 2 only + elif getattr(receiver, "im_class", None) is not None: # Python 2 only receiver_class_name = receiver.im_class.__name__ text = "%s.%s" % (receiver_class_name, receiver_name) else: @@ -83,4 +92,4 @@ def generate_stats(self, request, response): receivers.append(text) signals.append((name, signal, receivers)) - self.record_stats({'signals': signals}) + self.record_stats({"signals": signals}) diff --git a/debug_toolbar/panels/sql/forms.py b/debug_toolbar/panels/sql/forms.py index a4e622f51..6cc1554a1 100644 --- a/debug_toolbar/panels/sql/forms.py +++ b/debug_toolbar/panels/sql/forms.py @@ -25,18 +25,19 @@ class SQLSelectForm(forms.Form): duration: time for SQL to execute passed in from toolbar just for redisplay hash: the hash of (secret + sql + params) for tamper checking """ + sql = forms.CharField() raw_sql = forms.CharField() params = forms.CharField() - alias = forms.CharField(required=False, initial='default') + alias = forms.CharField(required=False, initial="default") duration = forms.FloatField() hash = forms.CharField() def __init__(self, *args, **kwargs): - initial = kwargs.get('initial', None) + initial = kwargs.get("initial", None) if initial is not None: - initial['hash'] = self.make_hash(initial) + initial["hash"] = self.make_hash(initial) super(SQLSelectForm, self).__init__(*args, **kwargs) @@ -44,23 +45,23 @@ def __init__(self, *args, **kwargs): self.fields[name].widget = forms.HiddenInput() def clean_raw_sql(self): - value = self.cleaned_data['raw_sql'] + value = self.cleaned_data["raw_sql"] - if not value.lower().strip().startswith('select'): + if not value.lower().strip().startswith("select"): raise ValidationError("Only 'select' queries are allowed.") return value def clean_params(self): - value = self.cleaned_data['params'] + value = self.cleaned_data["params"] try: return json.loads(value) except ValueError: - raise ValidationError('Is not valid JSON') + raise ValidationError("Is not valid JSON") def clean_alias(self): - value = self.cleaned_data['alias'] + value = self.cleaned_data["alias"] if value not in connections: raise ValidationError("Database alias '%s' not found" % value) @@ -68,25 +69,25 @@ def clean_alias(self): return value def clean_hash(self): - hash = self.cleaned_data['hash'] + hash = self.cleaned_data["hash"] if not constant_time_compare(hash, self.make_hash(self.data)): - raise ValidationError('Tamper alert') + raise ValidationError("Tamper alert") return hash def reformat_sql(self): - return reformat_sql(self.cleaned_data['sql']) + return reformat_sql(self.cleaned_data["sql"]) def make_hash(self, data): m = hmac.new(key=force_bytes(settings.SECRET_KEY), digestmod=hashlib.sha1) - for item in [data['sql'], data['params']]: + for item in [data["sql"], data["params"]]: m.update(force_bytes(item)) return m.hexdigest() @property def connection(self): - return connections[self.cleaned_data['alias']] + return connections[self.cleaned_data["alias"]] @cached_property def cursor(self): diff --git a/debug_toolbar/panels/sql/panel.py b/debug_toolbar/panels/sql/panel.py index 89a93bb9d..fac9c473b 100644 --- a/debug_toolbar/panels/sql/panel.py +++ b/debug_toolbar/panels/sql/panel.py @@ -13,15 +13,14 @@ from debug_toolbar.panels.sql import views from debug_toolbar.panels.sql.forms import SQLSelectForm from debug_toolbar.panels.sql.tracking import unwrap_cursor, wrap_cursor -from debug_toolbar.panels.sql.utils import ( - contrasting_color_generator, reformat_sql, -) +from debug_toolbar.panels.sql.utils import contrasting_color_generator, reformat_sql from debug_toolbar.utils import render_stacktrace def get_isolation_level_display(vendor, level): - if vendor == 'postgresql': + if vendor == "postgresql": import psycopg2.extensions + choices = { psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT: _("Autocommit"), psycopg2.extensions.ISOLATION_LEVEL_READ_UNCOMMITTED: _("Read uncommitted"), @@ -35,8 +34,9 @@ def get_isolation_level_display(vendor, level): def get_transaction_status_display(vendor, level): - if vendor == 'postgresql': + if vendor == "postgresql": import psycopg2.extensions + choices = { psycopg2.extensions.TRANSACTION_STATUS_IDLE: _("Idle"), psycopg2.extensions.TRANSACTION_STATUS_ACTIVE: _("Active"), @@ -54,6 +54,7 @@ class SQLPanel(Panel): Panel that displays information about the SQL queries run while processing the request. """ + def __init__(self, *args, **kwargs): super(SQLPanel, self).__init__(*args, **kwargs) self._offset = {k: len(connections[k].queries) for k in connections} @@ -71,7 +72,7 @@ def get_transaction_id(self, alias): if not conn: return - if conn.vendor == 'postgresql': + if conn.vendor == "postgresql": cur_status = conn.get_transaction_status() else: raise ValueError(conn.vendor) @@ -95,13 +96,13 @@ def record(self, alias, **kwargs): self._queries.append((alias, kwargs)) if alias not in self._databases: self._databases[alias] = { - 'time_spent': kwargs['duration'], - 'num_queries': 1, + "time_spent": kwargs["duration"], + "num_queries": 1, } else: - self._databases[alias]['time_spent'] += kwargs['duration'] - self._databases[alias]['num_queries'] += 1 - self._sql_time += kwargs['duration'] + self._databases[alias]["time_spent"] += kwargs["duration"] + self._databases[alias]["num_queries"] += 1 + self._sql_time += kwargs["duration"] self._num_queries += 1 # Implement the Panel API @@ -110,24 +111,28 @@ def record(self, alias, **kwargs): @property def nav_subtitle(self): - return __("%d query in %.2fms", "%d queries in %.2fms", - self._num_queries) % (self._num_queries, self._sql_time) + return __("%d query in %.2fms", "%d queries in %.2fms", self._num_queries) % ( + self._num_queries, + self._sql_time, + ) @property def title(self): count = len(self._databases) - return __('SQL queries from %(count)d connection', - 'SQL queries from %(count)d connections', - count) % {'count': count} + return __( + "SQL queries from %(count)d connection", + "SQL queries from %(count)d connections", + count, + ) % {"count": count} - template = 'debug_toolbar/panels/sql.html' + template = "debug_toolbar/panels/sql.html" @classmethod def get_urls(cls): return [ - url(r'^sql_select/$', views.sql_select, name='sql_select'), - url(r'^sql_explain/$', views.sql_explain, name='sql_explain'), - url(r'^sql_profile/$', views.sql_profile, name='sql_profile'), + url(r"^sql_select/$", views.sql_select, name="sql_select"), + url(r"^sql_explain/$", views.sql_explain, name="sql_explain"), + url(r"^sql_profile/$", views.sql_profile, name="sql_profile"), ] def enable_instrumentation(self): @@ -147,14 +152,16 @@ def generate_stats(self, request, response): # The keys used to determine similar and duplicate queries. def similar_key(query): - return query['raw_sql'] + return query["raw_sql"] def duplicate_key(query): - raw_params = () if query['raw_params'] is None else tuple(query['raw_params']) + raw_params = ( + () if query["raw_params"] is None else tuple(query["raw_params"]) + ) # saferepr() avoids problems because of unhashable types # (e.g. lists) when used as dictionary keys. # https://github.com/jazzband/django-debug-toolbar/issues/1091 - return (query['raw_sql'], saferepr(raw_params)) + return (query["raw_sql"], saferepr(raw_params)) if self._queries: width_ratio_tally = 0 @@ -172,7 +179,7 @@ def duplicate_key(query): if nn > 2: nn = 0 rgb[nn] = nc - db['rgb_color'] = rgb + db["rgb_color"] = rgb trans_ids = {} trans_id = None @@ -181,48 +188,51 @@ def duplicate_key(query): query_similar[alias][similar_key(query)] += 1 query_duplicates[alias][duplicate_key(query)] += 1 - trans_id = query.get('trans_id') + trans_id = query.get("trans_id") last_trans_id = trans_ids.get(alias) if trans_id != last_trans_id: if last_trans_id: - self._queries[(i - 1)][1]['ends_trans'] = True + self._queries[(i - 1)][1]["ends_trans"] = True trans_ids[alias] = trans_id if trans_id: - query['starts_trans'] = True + query["starts_trans"] = True if trans_id: - query['in_trans'] = True - - query['alias'] = alias - if 'iso_level' in query: - query['iso_level'] = get_isolation_level_display(query['vendor'], - query['iso_level']) - if 'trans_status' in query: - query['trans_status'] = get_transaction_status_display(query['vendor'], - query['trans_status']) - - query['form'] = SQLSelectForm(auto_id=None, initial=copy(query)) - - if query['sql']: - query['sql'] = reformat_sql(query['sql']) - query['rgb_color'] = self._databases[alias]['rgb_color'] + query["in_trans"] = True + + query["alias"] = alias + if "iso_level" in query: + query["iso_level"] = get_isolation_level_display( + query["vendor"], query["iso_level"] + ) + if "trans_status" in query: + query["trans_status"] = get_transaction_status_display( + query["vendor"], query["trans_status"] + ) + + query["form"] = SQLSelectForm(auto_id=None, initial=copy(query)) + + if query["sql"]: + query["sql"] = reformat_sql(query["sql"]) + query["rgb_color"] = self._databases[alias]["rgb_color"] try: - query['width_ratio'] = (query['duration'] / self._sql_time) * 100 - query['width_ratio_relative'] = ( - 100.0 * query['width_ratio'] / (100.0 - width_ratio_tally)) + query["width_ratio"] = (query["duration"] / self._sql_time) * 100 + query["width_ratio_relative"] = ( + 100.0 * query["width_ratio"] / (100.0 - width_ratio_tally) + ) except ZeroDivisionError: - query['width_ratio'] = 0 - query['width_ratio_relative'] = 0 - query['start_offset'] = width_ratio_tally - query['end_offset'] = query['width_ratio'] + query['start_offset'] - width_ratio_tally += query['width_ratio'] - query['stacktrace'] = render_stacktrace(query['stacktrace']) + query["width_ratio"] = 0 + query["width_ratio_relative"] = 0 + query["start_offset"] = width_ratio_tally + query["end_offset"] = query["width_ratio"] + query["start_offset"] + width_ratio_tally += query["width_ratio"] + query["stacktrace"] = render_stacktrace(query["stacktrace"]) i += 1 - query['trace_color'] = trace_colors[query['stacktrace']] + query["trace_color"] = trace_colors[query["stacktrace"]] if trans_id: - self._queries[(i - 1)][1]['ends_trans'] = True + self._queries[(i - 1)][1]["ends_trans"] = True # Queries are similar / duplicates only if there's as least 2 of them. # Also, to hide queries, we need to give all the duplicate groups an id @@ -246,12 +256,13 @@ def duplicate_key(query): for alias, query in self._queries: try: - (query["similar_count"], query["similar_color"]) = ( - query_similar_colors[alias][similar_key(query)] - ) - (query["duplicate_count"], query["duplicate_color"]) = ( - query_duplicates_colors[alias][duplicate_key(query)] - ) + (query["similar_count"], query["similar_color"]) = query_similar_colors[ + alias + ][similar_key(query)] + ( + query["duplicate_count"], + query["duplicate_color"], + ) = query_duplicates_colors[alias][duplicate_key(query)] except KeyError: pass @@ -266,14 +277,18 @@ def duplicate_key(query): except KeyError: pass - self.record_stats({ - 'databases': sorted(self._databases.items(), key=lambda x: -x[1]['time_spent']), - 'queries': [q for a, q in self._queries], - 'sql_time': self._sql_time, - }) + self.record_stats( + { + "databases": sorted( + self._databases.items(), key=lambda x: -x[1]["time_spent"] + ), + "queries": [q for a, q in self._queries], + "sql_time": self._sql_time, + } + ) def generate_server_timing(self, request, response): stats = self.get_stats() - title = 'SQL {} queries'.format(len(stats.get('queries', []))) - value = stats.get('sql_time', 0) - self.record_server_timing('sql_time', title, value) + title = "SQL {} queries".format(len(stats.get("queries", []))) + value = stats.get("sql_time", 0) + self.record_server_timing("sql_time", title, value) diff --git a/debug_toolbar/panels/sql/tracking.py b/debug_toolbar/panels/sql/tracking.py index fe1de6af8..69e7ed8e0 100644 --- a/debug_toolbar/panels/sql/tracking.py +++ b/debug_toolbar/panels/sql/tracking.py @@ -14,6 +14,7 @@ class SQLQueryTriggered(Exception): """Thrown when template panel triggers a query""" + pass @@ -36,7 +37,7 @@ def recording(self, v): def wrap_cursor(connection, panel): - if not hasattr(connection, '_djdt_cursor'): + if not hasattr(connection, "_djdt_cursor"): connection._djdt_cursor = connection.cursor def cursor(*args, **kwargs): @@ -46,14 +47,16 @@ def cursor(*args, **kwargs): # See: # https://github.com/jazzband/django-debug-toolbar/pull/615 # https://github.com/jazzband/django-debug-toolbar/pull/896 - return state.Wrapper(connection._djdt_cursor(*args, **kwargs), connection, panel) + return state.Wrapper( + connection._djdt_cursor(*args, **kwargs), connection, panel + ) connection.cursor = cursor return cursor def unwrap_cursor(connection): - if hasattr(connection, '_djdt_cursor'): + if hasattr(connection, "_djdt_cursor"): del connection._djdt_cursor del connection.cursor @@ -63,6 +66,7 @@ class ExceptionCursorWrapper(object): Wraps a cursor and raises an exception on any operation. Used in Templates panel. """ + def __init__(self, cursor, db, logger): pass @@ -89,7 +93,7 @@ def _quote_expr(self, element): except DjangoUnicodeDecodeError: return repr(element) elif isinstance(element, six.binary_type): - return '(binary data)' + return "(binary data)" else: return repr(element) @@ -114,7 +118,7 @@ def _decode(self, param): try: return force_text(param, strings_only=not isinstance(param, CONVERT_TYPES)) except UnicodeDecodeError: - return '(encoded string)' + return "(encoded string)" def _record(self, method, sql, params): start_time = time() @@ -123,11 +127,11 @@ def _record(self, method, sql, params): finally: stop_time = time() duration = (stop_time - start_time) * 1000 - if dt_settings.get_config()['ENABLE_STACKTRACES']: + if dt_settings.get_config()["ENABLE_STACKTRACES"]: stacktrace = tidy_stacktrace(reversed(get_stack())) else: stacktrace = [] - _params = '' + _params = "" try: _params = json.dumps(self._decode(params)) except TypeError: @@ -135,41 +139,44 @@ def _record(self, method, sql, params): template_info = get_template_info() - alias = getattr(self.db, 'alias', 'default') + alias = getattr(self.db, "alias", "default") conn = self.db.connection - vendor = getattr(conn, 'vendor', 'unknown') + vendor = getattr(conn, "vendor", "unknown") params = { - 'vendor': vendor, - 'alias': alias, - 'sql': self.db.ops.last_executed_query( - self.cursor, sql, self._quote_params(params)), - 'duration': duration, - 'raw_sql': sql, - 'params': _params, - 'raw_params': params, - 'stacktrace': stacktrace, - 'start_time': start_time, - 'stop_time': stop_time, - 'is_slow': duration > dt_settings.get_config()['SQL_WARNING_THRESHOLD'], - 'is_select': sql.lower().strip().startswith('select'), - 'template_info': template_info, + "vendor": vendor, + "alias": alias, + "sql": self.db.ops.last_executed_query( + self.cursor, sql, self._quote_params(params) + ), + "duration": duration, + "raw_sql": sql, + "params": _params, + "raw_params": params, + "stacktrace": stacktrace, + "start_time": start_time, + "stop_time": stop_time, + "is_slow": duration > dt_settings.get_config()["SQL_WARNING_THRESHOLD"], + "is_select": sql.lower().strip().startswith("select"), + "template_info": template_info, } - if vendor == 'postgresql': + if vendor == "postgresql": # If an erroneous query was ran on the connection, it might # be in a state where checking isolation_level raises an # exception. try: iso_level = conn.isolation_level except conn.InternalError: - iso_level = 'unknown' - params.update({ - 'trans_id': self.logger.get_transaction_id(alias), - 'trans_status': conn.get_transaction_status(), - 'iso_level': iso_level, - 'encoding': conn.encoding, - }) + iso_level = "unknown" + params.update( + { + "trans_id": self.logger.get_transaction_id(alias), + "trans_status": conn.get_transaction_status(), + "iso_level": iso_level, + "encoding": conn.encoding, + } + ) # We keep `sql` to maintain backwards compatibility self.logger.record(**params) diff --git a/debug_toolbar/panels/sql/utils.py b/debug_toolbar/panels/sql/utils.py index 197e4849c..5babe15d2 100644 --- a/debug_toolbar/panels/sql/utils.py +++ b/debug_toolbar/panels/sql/utils.py @@ -9,30 +9,33 @@ class BoldKeywordFilter: """sqlparse filter to bold SQL keywords""" + def process(self, stream): """Process the token stream""" for token_type, value in stream: is_keyword = token_type in T.Keyword if is_keyword: - yield T.Text, '' + yield T.Text, "" yield token_type, escape(value) if is_keyword: - yield T.Text, '' + yield T.Text, "" def reformat_sql(sql): stack = sqlparse.engine.FilterStack() stack.preprocess.append(BoldKeywordFilter()) # add our custom filter stack.postprocess.append(sqlparse.filters.SerializerUnicode()) # tokens -> strings - return swap_fields(''.join(stack.run(sql))) + return swap_fields("".join(stack.run(sql))) def swap_fields(sql): - expr = r'SELECT (...........*?) FROM' - subs = (r'SELECT ' - r'••• ' - r'\1 ' - r'FROM') + expr = r"SELECT (...........*?) FROM" + subs = ( + r"SELECT " + r'••• ' + r'\1 ' + r"FROM" + ) return re.sub(expr, subs, sql) @@ -41,11 +44,19 @@ def contrasting_color_generator(): Generate constrasting colors by varying most significant bit of RGB first, and then vary subsequent bits systematically. """ + def rgb_to_hex(rgb): - return '#%02x%02x%02x' % tuple(rgb) + return "#%02x%02x%02x" % tuple(rgb) - triples = [(1, 0, 0), (0, 1, 0), (0, 0, 1), - (1, 1, 0), (0, 1, 1), (1, 0, 1), (1, 1, 1)] + triples = [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1), + (1, 1, 0), + (0, 1, 1), + (1, 0, 1), + (1, 1, 1), + ] n = 1 << 7 so_far = [[0, 0, 0]] while True: diff --git a/debug_toolbar/panels/sql/views.py b/debug_toolbar/panels/sql/views.py index 47fca4280..4f17421c0 100644 --- a/debug_toolbar/panels/sql/views.py +++ b/debug_toolbar/panels/sql/views.py @@ -15,23 +15,23 @@ def sql_select(request): form = SQLSelectForm(request.POST or None) if form.is_valid(): - sql = form.cleaned_data['raw_sql'] - params = form.cleaned_data['params'] + sql = form.cleaned_data["raw_sql"] + params = form.cleaned_data["params"] cursor = form.cursor cursor.execute(sql, params) headers = [d[0] for d in cursor.description] result = cursor.fetchall() cursor.close() context = { - 'result': result, - 'sql': form.reformat_sql(), - 'duration': form.cleaned_data['duration'], - 'headers': headers, - 'alias': form.cleaned_data['alias'], + "result": result, + "sql": form.reformat_sql(), + "duration": form.cleaned_data["duration"], + "headers": headers, + "alias": form.cleaned_data["alias"], } # Using SimpleTemplateResponse avoids running global context processors. - return SimpleTemplateResponse('debug_toolbar/panels/sql_select.html', context) - return HttpResponseBadRequest('Form errors') + return SimpleTemplateResponse("debug_toolbar/panels/sql_select.html", context) + return HttpResponseBadRequest("Form errors") @csrf_exempt @@ -41,17 +41,17 @@ def sql_explain(request): form = SQLSelectForm(request.POST or None) if form.is_valid(): - sql = form.cleaned_data['raw_sql'] - params = form.cleaned_data['params'] + sql = form.cleaned_data["raw_sql"] + params = form.cleaned_data["params"] vendor = form.connection.vendor cursor = form.cursor - if vendor == 'sqlite': + if vendor == "sqlite": # SQLite's EXPLAIN dumps the low-level opcodes generated for a query; # EXPLAIN QUERY PLAN dumps a more human-readable summary # See https://www.sqlite.org/lang_explain.html for details cursor.execute("EXPLAIN QUERY PLAN %s" % (sql,), params) - elif vendor == 'postgresql': + elif vendor == "postgresql": cursor.execute("EXPLAIN ANALYZE %s" % (sql,), params) else: cursor.execute("EXPLAIN %s" % (sql,), params) @@ -60,15 +60,15 @@ def sql_explain(request): result = cursor.fetchall() cursor.close() context = { - 'result': result, - 'sql': form.reformat_sql(), - 'duration': form.cleaned_data['duration'], - 'headers': headers, - 'alias': form.cleaned_data['alias'], + "result": result, + "sql": form.reformat_sql(), + "duration": form.cleaned_data["duration"], + "headers": headers, + "alias": form.cleaned_data["alias"], } # Using SimpleTemplateResponse avoids running global context processors. - return SimpleTemplateResponse('debug_toolbar/panels/sql_explain.html', context) - return HttpResponseBadRequest('Form errors') + return SimpleTemplateResponse("debug_toolbar/panels/sql_explain.html", context) + return HttpResponseBadRequest("Form errors") @csrf_exempt @@ -78,8 +78,8 @@ def sql_profile(request): form = SQLSelectForm(request.POST or None) if form.is_valid(): - sql = form.cleaned_data['raw_sql'] - params = form.cleaned_data['params'] + sql = form.cleaned_data["raw_sql"] + params = form.cleaned_data["params"] cursor = form.cursor result = None headers = None @@ -90,7 +90,8 @@ def sql_profile(request): cursor.execute("SET PROFILING=0") # Disable profiling # The Query ID should always be 1 here but I'll subselect to get # the last one just in case... - cursor.execute(""" + cursor.execute( + """ SELECT * FROM information_schema.profiling WHERE query_id = ( @@ -99,20 +100,23 @@ def sql_profile(request): ORDER BY query_id DESC LIMIT 1 ) -""") +""" + ) headers = [d[0] for d in cursor.description] result = cursor.fetchall() except Exception: - result_error = "Profiling is either not available or not supported by your database." + result_error = ( + "Profiling is either not available or not supported by your database." + ) cursor.close() context = { - 'result': result, - 'result_error': result_error, - 'sql': form.reformat_sql(), - 'duration': form.cleaned_data['duration'], - 'headers': headers, - 'alias': form.cleaned_data['alias'], + "result": result, + "result_error": result_error, + "sql": form.reformat_sql(), + "duration": form.cleaned_data["duration"], + "headers": headers, + "alias": form.cleaned_data["alias"], } # Using SimpleTemplateResponse avoids running global context processors. - return SimpleTemplateResponse('debug_toolbar/panels/sql_profile.html', context) - return HttpResponseBadRequest('Form errors') + return SimpleTemplateResponse("debug_toolbar/panels/sql_profile.html", context) + return HttpResponseBadRequest("Form errors") diff --git a/debug_toolbar/panels/staticfiles.py b/debug_toolbar/panels/staticfiles.py index cd400d7c9..10661908b 100644 --- a/debug_toolbar/panels/staticfiles.py +++ b/debug_toolbar/panels/staticfiles.py @@ -25,6 +25,7 @@ class StaticFile(object): """ Representing the different properties of a static file. """ + def __init__(self, path): self.path = path @@ -39,10 +40,9 @@ def url(self): class FileCollector(ThreadCollector): - def collect(self, path, thread=None): # handle the case of {% static "admin/" %} - if path.endswith('/'): + if path.endswith("/"): return super(FileCollector, self).collect(StaticFile(path), thread) @@ -56,12 +56,12 @@ class DebugConfiguredStorage(LazyObject): are resolved by using the {% static %} template tag (which uses the `url` method). """ + 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 @@ -80,13 +80,16 @@ class StaticFilesPanel(panels.Panel): """ A panel to display the found staticfiles. """ - name = 'Static files' - template = 'debug_toolbar/panels/staticfiles.html' + + name = "Static files" + template = "debug_toolbar/panels/staticfiles.html" @property def title(self): - return (_("Static files (%(num_found)s found, %(num_used)s used)") % - {'num_found': self.num_found, 'num_used': self.num_used}) + return _("Static files (%(num_found)s found, %(num_used)s used)") % { + "num_found": self.num_found, + "num_used": self.num_used, + } def __init__(self, *args, **kwargs): super(StaticFilesPanel, self).__init__(*args, **kwargs) @@ -94,23 +97,27 @@ def __init__(self, *args, **kwargs): self._paths = {} def enable_instrumentation(self): - storage.staticfiles_storage = staticfiles.staticfiles_storage = DebugConfiguredStorage() + storage.staticfiles_storage = ( + staticfiles.staticfiles_storage + ) = DebugConfiguredStorage() def disable_instrumentation(self): - storage.staticfiles_storage = staticfiles.staticfiles_storage = _original_storage + storage.staticfiles_storage = ( + staticfiles.staticfiles_storage + ) = _original_storage @property def num_used(self): return len(self._paths[threading.currentThread()]) - nav_title = _('Static files') + 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} + 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() @@ -119,14 +126,16 @@ def generate_stats(self, request, response): 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_staticfiles_apps(), - 'staticfiles_dirs': self.get_staticfiles_dirs(), - 'staticfiles_finders': self.get_staticfiles_finders(), - }) + self.record_stats( + { + "num_found": self.num_found, + "num_used": self.num_used, + "staticfiles": used_paths, + "staticfiles_apps": self.get_staticfiles_apps(), + "staticfiles_dirs": self.get_staticfiles_dirs(), + "staticfiles_finders": self.get_staticfiles_finders(), + } + ) def get_staticfiles_finders(self): """ @@ -137,13 +146,12 @@ def get_staticfiles_finders(self): finders_mapping = OrderedDict() for finder in finders.get_finders(): for path, finder_storage in finder.list([]): - if getattr(finder_storage, 'prefix', None): + if getattr(finder_storage, "prefix", None): prefixed_path = join(finder_storage.prefix, path) else: prefixed_path = path finder_cls = finder.__class__ - finder_path = '.'.join([finder_cls.__module__, - finder_cls.__name__]) + finder_path = ".".join([finder_cls.__module__, finder_cls.__name__]) real_path = finder_storage.path(path) payload = (prefixed_path, real_path) finders_mapping.setdefault(finder_path, []).append(payload) diff --git a/debug_toolbar/panels/templates/panel.py b/debug_toolbar/panels/templates/panel.py index 56bd04ad5..15397dfe5 100644 --- a/debug_toolbar/panels/templates/panel.py +++ b/debug_toolbar/panels/templates/panel.py @@ -34,6 +34,7 @@ # Monkey-patch to store items added by template context processors. The # overhead is sufficiently small to justify enabling it unconditionally. + @contextmanager def _request_context_bind_template(self, template): if self.template is not None: @@ -41,12 +42,11 @@ def _request_context_bind_template(self, template): self.template = template # Set context processors according to the template engine's settings. - processors = (template.engine.template_context_processors + - self._processors) + processors = template.engine.template_context_processors + self._processors self.context_processors = OrderedDict() updates = {} for processor in processors: - name = '%s.%s' % (processor.__module__, processor.__name__) + name = "%s.%s" % (processor.__module__, processor.__name__) context = processor(self.request) self.context_processors[name] = context updates.update(context) @@ -67,6 +67,7 @@ class TemplatesPanel(Panel): """ A panel that lists all templates used during processing of a response. """ + def __init__(self, *args, **kwargs): super(TemplatesPanel, self).__init__(*args, **kwargs) self.templates = [] @@ -82,18 +83,21 @@ def __init__(self, *args, **kwargs): self.pformat_layers = [] def _store_template_info(self, sender, **kwargs): - template, context = kwargs['template'], kwargs['context'] + template, context = kwargs["template"], kwargs["context"] # Skip templates that we are generating through the debug toolbar. - if (isinstance(template.name, six.string_types) and ( - template.name.startswith('debug_toolbar/') or - template.name.startswith( - tuple(self.toolbar.config['SKIP_TEMPLATE_PREFIXES'])))): + is_debug_toolbar_template = isinstance(template.name, six.string_types) and ( + template.name.startswith("debug_toolbar/") + or template.name.startswith( + tuple(self.toolbar.config["SKIP_TEMPLATE_PREFIXES"]) + ) + ) + if is_debug_toolbar_template: return context_list = [] for context_layer in context.dicts: - if hasattr(context_layer, 'items') and context_layer: + if hasattr(context_layer, "items") and context_layer: # Refs GitHub issue #910 # If we can find this layer in our pseudo-cache then find the # matching prettified version in the associated list. @@ -109,30 +113,36 @@ def _store_template_info(self, sender, **kwargs): # unicode representation and the request data is # already made available from the Request panel. if isinstance(value, http.HttpRequest): - temp_layer[key] = '<>' + temp_layer[key] = "<>" # Replace the debugging sql_queries element. The SQL # data is already made available from the SQL panel. - elif key == 'sql_queries' and isinstance(value, list): - temp_layer[key] = '<>' - # Replace LANGUAGES, which is available in i18n context processor - elif key == 'LANGUAGES' and isinstance(value, tuple): - temp_layer[key] = '<>' - # QuerySet would trigger the database: user can run the query from SQL Panel + elif key == "sql_queries" and isinstance(value, list): + temp_layer[key] = "<>" + # Replace LANGUAGES, which is available in i18n context + # processor + elif key == "LANGUAGES" and isinstance(value, tuple): + temp_layer[key] = "<>" + # QuerySet would trigger the database: user can run the + # query from SQL Panel elif isinstance(value, (QuerySet, RawQuerySet)): model_name = "%s.%s" % ( - value.model._meta.app_label, value.model.__name__) - temp_layer[key] = '<<%s of %s>>' % ( - value.__class__.__name__.lower(), model_name) + value.model._meta.app_label, + value.model.__name__, + ) + temp_layer[key] = "<<%s of %s>>" % ( + value.__class__.__name__.lower(), + model_name, + ) else: try: recording(False) saferepr(value) # this MAY trigger a db query except SQLQueryTriggered: - temp_layer[key] = '<>' + temp_layer[key] = "<>" except UnicodeEncodeError: - temp_layer[key] = '<>' + temp_layer[key] = "<>" except Exception: - temp_layer[key] = '<>' + temp_layer[key] = "<>" else: temp_layer[key] = value finally: @@ -152,8 +162,8 @@ def _store_template_info(self, sender, **kwargs): self.pformat_layers.insert(index, pformatted) context_list.append(pformatted) - kwargs['context'] = context_list - kwargs['context_processors'] = getattr(context, 'context_processors', None) + kwargs["context"] = context_list + kwargs["context_processors"] = getattr(context, "context_processors", None) self.templates.append(kwargs) # Implement the Panel API @@ -163,20 +173,22 @@ def _store_template_info(self, sender, **kwargs): @property def title(self): num_templates = len(self.templates) - return _("Templates (%(num_templates)s rendered)") % {'num_templates': num_templates} + return _("Templates (%(num_templates)s rendered)") % { + "num_templates": num_templates + } @property def nav_subtitle(self): if self.templates: - return self.templates[0]['template'].name - return '' + return self.templates[0]["template"].name + return "" - template = 'debug_toolbar/panels/templates.html' + template = "debug_toolbar/panels/templates.html" @classmethod def get_urls(cls): return [ - url(r'^template_source/$', views.template_source, name='template_source'), + url(r"^template_source/$", views.template_source, name="template_source") ] def enable_instrumentation(self): @@ -190,33 +202,38 @@ def generate_stats(self, request, response): for template_data in self.templates: info = {} # Clean up some info about templates - template = template_data.get('template', None) - if hasattr(template, 'origin') and template.origin and template.origin.name: + template = template_data.get("template", None) + if hasattr(template, "origin") and template.origin and template.origin.name: template.origin_name = template.origin.name template.origin_hash = signing.dumps(template.origin.name) else: - template.origin_name = _('No origin') - template.origin_hash = '' - info['template'] = template + template.origin_name = _("No origin") + template.origin_hash = "" + info["template"] = template # Clean up context for better readability - if self.toolbar.config['SHOW_TEMPLATE_CONTEXT']: - context_list = template_data.get('context', []) - info['context'] = '\n'.join(context_list) + if self.toolbar.config["SHOW_TEMPLATE_CONTEXT"]: + context_list = template_data.get("context", []) + info["context"] = "\n".join(context_list) template_context.append(info) # Fetch context_processors/template_dirs from any template if self.templates: - context_processors = self.templates[0]['context_processors'] - template = self.templates[0]['template'] - # django templates have the 'engine' attribute, while jinja templates use 'backend' - engine_backend = getattr(template, 'engine', None) or getattr(template, 'backend') + context_processors = self.templates[0]["context_processors"] + template = self.templates[0]["template"] + # django templates have the 'engine' attribute, while jinja + # templates use 'backend' + engine_backend = getattr(template, "engine", None) or getattr( + template, "backend" + ) template_dirs = engine_backend.dirs else: context_processors = None template_dirs = [] - self.record_stats({ - 'templates': template_context, - 'template_dirs': [normpath(x) for x in template_dirs], - 'context_processors': context_processors, - }) + self.record_stats( + { + "templates": template_context, + "template_dirs": [normpath(x) for x in template_dirs], + "context_processors": context_processors, + } + ) diff --git a/debug_toolbar/panels/templates/views.py b/debug_toolbar/panels/templates/views.py index e6d8ab013..338a7acf2 100644 --- a/debug_toolbar/panels/templates/views.py +++ b/debug_toolbar/panels/templates/views.py @@ -16,14 +16,14 @@ def template_source(request): Return the source of a template, syntax-highlighted by Pygments if it's available. """ - template_origin_name = request.GET.get('template_origin', None) + template_origin_name = request.GET.get("template_origin", None) if template_origin_name is None: return HttpResponseBadRequest('"template_origin" key is required') try: template_origin_name = signing.loads(template_origin_name) except Exception: return HttpResponseBadRequest('"template_origin" is invalid') - template_name = request.GET.get('template', template_origin_name) + template_name = request.GET.get("template", template_origin_name) final_loaders = [] loaders = Engine.get_default().template_loaders @@ -33,7 +33,7 @@ def template_source(request): # 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'): + if hasattr(loader, "loaders"): final_loaders += loader.loaders else: final_loaders.append(loader) @@ -60,7 +60,7 @@ def template_source(request): pass # Using SimpleTemplateResponse avoids running global context processors. - return SimpleTemplateResponse('debug_toolbar/panels/template_source.html', { - 'source': source, - 'template_name': template_name - }) + return SimpleTemplateResponse( + "debug_toolbar/panels/template_source.html", + {"source": source, "template_name": template_name}, + ) diff --git a/debug_toolbar/panels/timer.py b/debug_toolbar/panels/timer.py index 812ab8dd1..8b73cf308 100644 --- a/debug_toolbar/panels/timer.py +++ b/debug_toolbar/panels/timer.py @@ -8,7 +8,7 @@ from debug_toolbar.panels import Panel try: - import resource # Not available on Win32 systems + import resource # Not available on Win32 systems except ImportError: resource = None @@ -20,23 +20,23 @@ class TimerPanel(Panel): def nav_subtitle(self): stats = self.get_stats() - if hasattr(self, '_start_rusage'): + if hasattr(self, "_start_rusage"): utime = self._end_rusage.ru_utime - self._start_rusage.ru_utime stime = self._end_rusage.ru_stime - self._start_rusage.ru_stime return _("CPU: %(cum)0.2fms (%(total)0.2fms)") % { - 'cum': (utime + stime) * 1000.0, - 'total': stats['total_time'] + "cum": (utime + stime) * 1000.0, + "total": stats["total_time"], } - elif 'total_time' in stats: - return _("Total: %0.2fms") % stats['total_time'] + elif "total_time" in stats: + return _("Total: %0.2fms") % stats["total_time"] else: - return '' + return "" has_content = resource is not None title = _("Time") - template = 'debug_toolbar/panels/timer.html' + template = "debug_toolbar/panels/timer.html" @property def content(self): @@ -46,9 +46,12 @@ def content(self): (_("System CPU time"), _("%(stime)0.3f msec") % stats), (_("Total CPU time"), _("%(total)0.3f msec") % stats), (_("Elapsed time"), _("%(total_time)0.3f msec") % stats), - (_("Context switches"), _("%(vcsw)d voluntary, %(ivcsw)d involuntary") % stats), + ( + _("Context switches"), + _("%(vcsw)d voluntary, %(ivcsw)d involuntary") % stats, + ), ) - return render_to_string(self.template, {'rows': rows}) + return render_to_string(self.template, {"rows": rows}) def process_request(self, request): self._start_time = time.time() @@ -57,20 +60,21 @@ def process_request(self, request): def generate_stats(self, request, response): stats = {} - if hasattr(self, '_start_time'): - stats['total_time'] = (time.time() - self._start_time) * 1000 - if hasattr(self, '_start_rusage'): + if hasattr(self, "_start_time"): + stats["total_time"] = (time.time() - self._start_time) * 1000 + if hasattr(self, "_start_rusage"): self._end_rusage = resource.getrusage(resource.RUSAGE_SELF) - stats['utime'] = 1000 * self._elapsed_ru('ru_utime') - stats['stime'] = 1000 * self._elapsed_ru('ru_stime') - stats['total'] = stats['utime'] + stats['stime'] - stats['vcsw'] = self._elapsed_ru('ru_nvcsw') - stats['ivcsw'] = self._elapsed_ru('ru_nivcsw') - stats['minflt'] = self._elapsed_ru('ru_minflt') - stats['majflt'] = self._elapsed_ru('ru_majflt') - # these are documented as not meaningful under Linux. If you're running BSD - # feel free to enable them, and add any others that I hadn't gotten to before - # I noticed that I was getting nothing but zeroes and that the docs agreed. :-( + stats["utime"] = 1000 * self._elapsed_ru("ru_utime") + stats["stime"] = 1000 * self._elapsed_ru("ru_stime") + stats["total"] = stats["utime"] + stats["stime"] + stats["vcsw"] = self._elapsed_ru("ru_nvcsw") + stats["ivcsw"] = self._elapsed_ru("ru_nivcsw") + stats["minflt"] = self._elapsed_ru("ru_minflt") + stats["majflt"] = self._elapsed_ru("ru_majflt") + # these are documented as not meaningful under Linux. If you're + # running BSD feel free to enable them, and add any others that I + # hadn't gotten to before I noticed that I was getting nothing but + # zeroes and that the docs agreed. :-( # # stats['blkin'] = self._elapsed_ru('ru_inblock') # stats['blkout'] = self._elapsed_ru('ru_oublock') @@ -85,10 +89,12 @@ def generate_stats(self, request, response): def generate_server_timing(self, request, response): stats = self.get_stats() - self.record_server_timing('utime', 'User CPU time', stats.get('utime', 0)) - self.record_server_timing('stime', 'System CPU time', stats.get('stime', 0)) - self.record_server_timing('total', 'Total CPU time', stats.get('total', 0)) - self.record_server_timing('total_time', 'Elapsed time', stats.get('total_time', 0)) + self.record_server_timing("utime", "User CPU time", stats.get("utime", 0)) + self.record_server_timing("stime", "System CPU time", stats.get("stime", 0)) + self.record_server_timing("total", "Total CPU time", stats.get("total", 0)) + self.record_server_timing( + "total_time", "Elapsed time", stats.get("total_time", 0) + ) def _elapsed_ru(self, name): return getattr(self._end_rusage, name) - getattr(self._start_rusage, name) diff --git a/debug_toolbar/panels/versions.py b/debug_toolbar/panels/versions.py index 2e8d58808..d35603ccb 100644 --- a/debug_toolbar/panels/versions.py +++ b/debug_toolbar/panels/versions.py @@ -13,24 +13,24 @@ class VersionsPanel(Panel): """ Shows versions of Python, Django, and installed apps if possible. """ + @property def nav_subtitle(self): - return 'Django %s' % django.get_version() + return "Django %s" % django.get_version() title = _("Versions") - template = 'debug_toolbar/panels/versions.html' + template = "debug_toolbar/panels/versions.html" def generate_stats(self, request, response): versions = [ - ('Python', '', '%d.%d.%d' % sys.version_info[:3]), - ('Django', '', self.get_app_version(django)), + ("Python", "", "%d.%d.%d" % sys.version_info[:3]), + ("Django", "", self.get_app_version(django)), ] versions += list(self.gen_app_versions()) - self.record_stats({ - 'versions': sorted(versions, key=lambda v: v[0]), - 'paths': sys.path, - }) + self.record_stats( + {"versions": sorted(versions, key=lambda v: v[0]), "paths": sys.path} + ) def gen_app_versions(self): for app_config in apps.get_app_configs(): @@ -45,11 +45,11 @@ def get_app_version(self, app): if isinstance(version, (list, tuple)): # We strip dots from the right because we do not want to show # trailing dots if there are empty elements in the list/tuple - version = '.'.join(str(o) for o in version).rstrip('.') + version = ".".join(str(o) for o in version).rstrip(".") return version def get_version_from_app(self, app): - if hasattr(app, 'get_version'): + if hasattr(app, "get_version"): get_version = app.get_version if callable(get_version): try: @@ -58,8 +58,8 @@ def get_version_from_app(self, app): pass else: return get_version - if hasattr(app, 'VERSION'): + if hasattr(app, "VERSION"): return app.VERSION - if hasattr(app, '__version__'): + if hasattr(app, "__version__"): return app.__version__ return diff --git a/debug_toolbar/settings.py b/debug_toolbar/settings.py index 76b2c6bc4..46e42487e 100644 --- a/debug_toolbar/settings.py +++ b/debug_toolbar/settings.py @@ -15,109 +15,117 @@ CONFIG_DEFAULTS = { # Toolbar options - 'DISABLE_PANELS': {'debug_toolbar.panels.redirects.RedirectsPanel'}, - 'INSERT_BEFORE': '', - 'RENDER_PANELS': None, - 'RESULTS_CACHE_SIZE': 10, - 'ROOT_TAG_EXTRA_ATTRS': '', - 'SHOW_COLLAPSED': False, - 'SHOW_TOOLBAR_CALLBACK': 'debug_toolbar.middleware.show_toolbar', + "DISABLE_PANELS": {"debug_toolbar.panels.redirects.RedirectsPanel"}, + "INSERT_BEFORE": "", + "RENDER_PANELS": None, + "RESULTS_CACHE_SIZE": 10, + "ROOT_TAG_EXTRA_ATTRS": "", + "SHOW_COLLAPSED": False, + "SHOW_TOOLBAR_CALLBACK": "debug_toolbar.middleware.show_toolbar", # Panel options - 'EXTRA_SIGNALS': [], - 'ENABLE_STACKTRACES': True, - 'HIDE_IN_STACKTRACES': ( - 'socketserver' if six.PY3 else 'SocketServer', - 'threading', - 'wsgiref', - 'debug_toolbar', - 'django.db', - 'django.core.handlers', - 'django.core.servers', - 'django.utils.decorators', - 'django.utils.deprecation', - 'django.utils.functional', + "EXTRA_SIGNALS": [], + "ENABLE_STACKTRACES": True, + "HIDE_IN_STACKTRACES": ( + "socketserver" if six.PY3 else "SocketServer", + "threading", + "wsgiref", + "debug_toolbar", + "django.db", + "django.core.handlers", + "django.core.servers", + "django.utils.decorators", + "django.utils.deprecation", + "django.utils.functional", ), - 'PROFILER_MAX_DEPTH': 10, - 'SHOW_TEMPLATE_CONTEXT': True, - 'SKIP_TEMPLATE_PREFIXES': ( - 'django/forms/widgets/', - 'admin/widgets/', - ), - 'SQL_WARNING_THRESHOLD': 500, # milliseconds + "PROFILER_MAX_DEPTH": 10, + "SHOW_TEMPLATE_CONTEXT": True, + "SKIP_TEMPLATE_PREFIXES": ("django/forms/widgets/", "admin/widgets/"), + "SQL_WARNING_THRESHOLD": 500, # milliseconds } @lru_cache() def get_config(): - USER_CONFIG = getattr(settings, 'DEBUG_TOOLBAR_CONFIG', {}) + USER_CONFIG = getattr(settings, "DEBUG_TOOLBAR_CONFIG", {}) # Backward-compatibility for 1.0, remove in 2.0. _RENAMED_CONFIG = { - 'RESULTS_STORE_SIZE': 'RESULTS_CACHE_SIZE', - 'ROOT_TAG_ATTRS': 'ROOT_TAG_EXTRA_ATTRS', - 'HIDDEN_STACKTRACE_MODULES': 'HIDE_IN_STACKTRACES' + "RESULTS_STORE_SIZE": "RESULTS_CACHE_SIZE", + "ROOT_TAG_ATTRS": "ROOT_TAG_EXTRA_ATTRS", + "HIDDEN_STACKTRACE_MODULES": "HIDE_IN_STACKTRACES", } for old_name, new_name in _RENAMED_CONFIG.items(): if old_name in USER_CONFIG: warnings.warn( "%r was renamed to %r. Update your DEBUG_TOOLBAR_CONFIG " - "setting." % (old_name, new_name), DeprecationWarning) + "setting." % (old_name, new_name), + DeprecationWarning, + ) USER_CONFIG[new_name] = USER_CONFIG.pop(old_name) - if 'HIDE_DJANGO_SQL' in USER_CONFIG: + if "HIDE_DJANGO_SQL" in USER_CONFIG: warnings.warn( - "HIDE_DJANGO_SQL was removed. Update your " - "DEBUG_TOOLBAR_CONFIG setting.", DeprecationWarning) - USER_CONFIG.pop('HIDE_DJANGO_SQL') + "HIDE_DJANGO_SQL was removed. Update your " "DEBUG_TOOLBAR_CONFIG setting.", + DeprecationWarning, + ) + USER_CONFIG.pop("HIDE_DJANGO_SQL") - if 'TAG' in USER_CONFIG: + if "TAG" in USER_CONFIG: warnings.warn( "TAG was replaced by INSERT_BEFORE. Update your " - "DEBUG_TOOLBAR_CONFIG setting.", DeprecationWarning) - USER_CONFIG['INSERT_BEFORE'] = '' % USER_CONFIG.pop('TAG') + "DEBUG_TOOLBAR_CONFIG setting.", + DeprecationWarning, + ) + USER_CONFIG["INSERT_BEFORE"] = "" % USER_CONFIG.pop("TAG") CONFIG = CONFIG_DEFAULTS.copy() CONFIG.update(USER_CONFIG) - if 'INTERCEPT_REDIRECTS' in USER_CONFIG: + if "INTERCEPT_REDIRECTS" in USER_CONFIG: warnings.warn( "INTERCEPT_REDIRECTS is deprecated. Please use the " "DISABLE_PANELS config in the " - "DEBUG_TOOLBAR_CONFIG setting.", DeprecationWarning) - if USER_CONFIG['INTERCEPT_REDIRECTS']: - if 'debug_toolbar.panels.redirects.RedirectsPanel' \ - in CONFIG['DISABLE_PANELS']: + "DEBUG_TOOLBAR_CONFIG setting.", + DeprecationWarning, + ) + if USER_CONFIG["INTERCEPT_REDIRECTS"]: + if ( + "debug_toolbar.panels.redirects.RedirectsPanel" + in CONFIG["DISABLE_PANELS"] + ): # RedirectsPanel should be enabled try: - CONFIG['DISABLE_PANELS'].remove( - 'debug_toolbar.panels.redirects.RedirectsPanel' + CONFIG["DISABLE_PANELS"].remove( + "debug_toolbar.panels.redirects.RedirectsPanel" ) except KeyError: # We wanted to remove it, but it didn't exist. This is fine pass - elif 'debug_toolbar.panels.redirects.RedirectsPanel' \ - not in CONFIG['DISABLE_PANELS']: + elif ( + "debug_toolbar.panels.redirects.RedirectsPanel" + not in CONFIG["DISABLE_PANELS"] + ): # RedirectsPanel should be disabled - CONFIG['DISABLE_PANELS'].add( - 'debug_toolbar.panels.redirects.RedirectsPanel' + CONFIG["DISABLE_PANELS"].add( + "debug_toolbar.panels.redirects.RedirectsPanel" ) return CONFIG PANELS_DEFAULTS = [ - 'debug_toolbar.panels.versions.VersionsPanel', - 'debug_toolbar.panels.timer.TimerPanel', - 'debug_toolbar.panels.settings.SettingsPanel', - 'debug_toolbar.panels.headers.HeadersPanel', - 'debug_toolbar.panels.request.RequestPanel', - 'debug_toolbar.panels.sql.SQLPanel', - 'debug_toolbar.panels.staticfiles.StaticFilesPanel', - 'debug_toolbar.panels.templates.TemplatesPanel', - 'debug_toolbar.panels.cache.CachePanel', - 'debug_toolbar.panels.signals.SignalsPanel', - 'debug_toolbar.panels.logging.LoggingPanel', - 'debug_toolbar.panels.redirects.RedirectsPanel', + "debug_toolbar.panels.versions.VersionsPanel", + "debug_toolbar.panels.timer.TimerPanel", + "debug_toolbar.panels.settings.SettingsPanel", + "debug_toolbar.panels.headers.HeadersPanel", + "debug_toolbar.panels.request.RequestPanel", + "debug_toolbar.panels.sql.SQLPanel", + "debug_toolbar.panels.staticfiles.StaticFilesPanel", + "debug_toolbar.panels.templates.TemplatesPanel", + "debug_toolbar.panels.cache.CachePanel", + "debug_toolbar.panels.signals.SignalsPanel", + "debug_toolbar.panels.logging.LoggingPanel", + "debug_toolbar.panels.redirects.RedirectsPanel", ] @@ -130,36 +138,26 @@ def get_panels(): else: # Backward-compatibility for 1.0, remove in 2.0. _RENAMED_PANELS = { - 'debug_toolbar.panels.version.VersionDebugPanel': - 'debug_toolbar.panels.versions.VersionsPanel', - 'debug_toolbar.panels.timer.TimerDebugPanel': - 'debug_toolbar.panels.timer.TimerPanel', - 'debug_toolbar.panels.settings_vars.SettingsDebugPanel': - 'debug_toolbar.panels.settings.SettingsPanel', - 'debug_toolbar.panels.headers.HeaderDebugPanel': - 'debug_toolbar.panels.headers.HeadersPanel', - 'debug_toolbar.panels.request_vars.RequestVarsDebugPanel': - 'debug_toolbar.panels.request.RequestPanel', - 'debug_toolbar.panels.sql.SQLDebugPanel': - 'debug_toolbar.panels.sql.SQLPanel', - 'debug_toolbar.panels.template.TemplateDebugPanel': - 'debug_toolbar.panels.templates.TemplatesPanel', - 'debug_toolbar.panels.cache.CacheDebugPanel': - 'debug_toolbar.panels.cache.CachePanel', - 'debug_toolbar.panels.signals.SignalDebugPanel': - 'debug_toolbar.panels.signals.SignalsPanel', - 'debug_toolbar.panels.logger.LoggingDebugPanel': - 'debug_toolbar.panels.logging.LoggingPanel', - 'debug_toolbar.panels.redirects.InterceptRedirectsDebugPanel': - 'debug_toolbar.panels.redirects.RedirectsPanel', - 'debug_toolbar.panels.profiling.ProfilingDebugPanel': - 'debug_toolbar.panels.profiling.ProfilingPanel', + "debug_toolbar.panels.version.VersionDebugPanel": "debug_toolbar.panels.versions.VersionsPanel", # noqa + "debug_toolbar.panels.timer.TimerDebugPanel": "debug_toolbar.panels.timer.TimerPanel", # noqa + "debug_toolbar.panels.settings_vars.SettingsDebugPanel": "debug_toolbar.panels.settings.SettingsPanel", # noqa + "debug_toolbar.panels.headers.HeaderDebugPanel": "debug_toolbar.panels.headers.HeadersPanel", # noqa + "debug_toolbar.panels.request_vars.RequestVarsDebugPanel": "debug_toolbar.panels.request.RequestPanel", # noqa + "debug_toolbar.panels.sql.SQLDebugPanel": "debug_toolbar.panels.sql.SQLPanel", # noqa + "debug_toolbar.panels.template.TemplateDebugPanel": "debug_toolbar.panels.templates.TemplatesPanel", # noqa + "debug_toolbar.panels.cache.CacheDebugPanel": "debug_toolbar.panels.cache.CachePanel", # noqa + "debug_toolbar.panels.signals.SignalDebugPanel": "debug_toolbar.panels.signals.SignalsPanel", # noqa + "debug_toolbar.panels.logger.LoggingDebugPanel": "debug_toolbar.panels.logging.LoggingPanel", # noqa + "debug_toolbar.panels.redirects.InterceptRedirectsDebugPanel": "debug_toolbar.panels.redirects.RedirectsPanel", # noqa + "debug_toolbar.panels.profiling.ProfilingDebugPanel": "debug_toolbar.panels.profiling.ProfilingPanel", # noqa } for index, old_panel in enumerate(PANELS): new_panel = _RENAMED_PANELS.get(old_panel) if new_panel is not None: warnings.warn( "%r was renamed to %r. Update your DEBUG_TOOLBAR_PANELS " - "setting." % (old_panel, new_panel), DeprecationWarning) + "setting." % (old_panel, new_panel), + DeprecationWarning, + ) PANELS[index] = new_panel return PANELS diff --git a/debug_toolbar/static/debug_toolbar/js/redirect.js b/debug_toolbar/static/debug_toolbar/js/redirect.js new file mode 100644 index 000000000..f73d9e52b --- /dev/null +++ b/debug_toolbar/static/debug_toolbar/js/redirect.js @@ -0,0 +1 @@ +document.getElementById('redirect_to').focus(); diff --git a/debug_toolbar/templates/debug_toolbar/base.html b/debug_toolbar/templates/debug_toolbar/base.html index 3d4740c60..d10740b0f 100644 --- a/debug_toolbar/templates/debug_toolbar/base.html +++ b/debug_toolbar/templates/debug_toolbar/base.html @@ -1,7 +1,7 @@ {% load i18n %}{% load static %} - +
- + diff --git a/debug_toolbar/templates/debug_toolbar/panels/sql.html b/debug_toolbar/templates/debug_toolbar/panels/sql.html index 37074a4c6..da7e161a2 100644 --- a/debug_toolbar/templates/debug_toolbar/panels/sql.html +++ b/debug_toolbar/templates/debug_toolbar/panels/sql.html @@ -114,4 +114,4 @@

{% trans "No SQL queries were recorded during this request." %}

{% endif %} - + diff --git a/debug_toolbar/templates/debug_toolbar/panels/sql_explain.html b/debug_toolbar/templates/debug_toolbar/panels/sql_explain.html index c7651e08d..0c6124c41 100644 --- a/debug_toolbar/templates/debug_toolbar/panels/sql_explain.html +++ b/debug_toolbar/templates/debug_toolbar/panels/sql_explain.html @@ -34,4 +34,4 @@

{% trans "SQL explained" %}

- + diff --git a/debug_toolbar/templates/debug_toolbar/panels/sql_profile.html b/debug_toolbar/templates/debug_toolbar/panels/sql_profile.html index 5a07fee88..8b1f711cf 100644 --- a/debug_toolbar/templates/debug_toolbar/panels/sql_profile.html +++ b/debug_toolbar/templates/debug_toolbar/panels/sql_profile.html @@ -41,4 +41,4 @@

{% trans "SQL profiled" %}

- + diff --git a/debug_toolbar/templates/debug_toolbar/panels/sql_select.html b/debug_toolbar/templates/debug_toolbar/panels/sql_select.html index 75b1a8458..6c3765163 100644 --- a/debug_toolbar/templates/debug_toolbar/panels/sql_select.html +++ b/debug_toolbar/templates/debug_toolbar/panels/sql_select.html @@ -38,4 +38,4 @@

{% trans "SQL selected" %}

- + diff --git a/debug_toolbar/templates/debug_toolbar/panels/timer.html b/debug_toolbar/templates/debug_toolbar/panels/timer.html index 2aa039af4..36c99db82 100644 --- a/debug_toolbar/templates/debug_toolbar/panels/timer.html +++ b/debug_toolbar/templates/debug_toolbar/panels/timer.html @@ -41,4 +41,4 @@

{% trans "Browser timing" %}

- + diff --git a/debug_toolbar/templates/debug_toolbar/redirect.html b/debug_toolbar/templates/debug_toolbar/redirect.html index 365fb482a..761712287 100644 --- a/debug_toolbar/templates/debug_toolbar/redirect.html +++ b/debug_toolbar/templates/debug_toolbar/redirect.html @@ -1,4 +1,4 @@ -{% load i18n %} +{% load i18n static %} @@ -9,8 +9,6 @@

{% trans "Location:" %} {{ redi

{% trans "The Django Debug Toolbar has intercepted a redirect to the above URL for debug viewing purposes. You can click the above link to continue with the redirect as normal." %}

- + diff --git a/debug_toolbar/toolbar.py b/debug_toolbar/toolbar.py index 21415b550..e93d54127 100644 --- a/debug_toolbar/toolbar.py +++ b/debug_toolbar/toolbar.py @@ -18,7 +18,6 @@ class DebugToolbar(object): - def __init__(self, request): self.request = request self.config = dt_settings.get_config().copy() @@ -61,14 +60,15 @@ def render_toolbar(self): if not self.should_render_panels(): self.store() try: - context = {'toolbar': self} - return render_to_string('debug_toolbar/base.html', context) + context = {"toolbar": self} + return render_to_string("debug_toolbar/base.html", context) except TemplateSyntaxError: - if not apps.is_installed('django.contrib.staticfiles'): + if not apps.is_installed("django.contrib.staticfiles"): raise ImproperlyConfigured( "The debug toolbar requires the staticfiles contrib app. " "Add 'django.contrib.staticfiles' to INSTALLED_APPS and " - "define STATIC_URL in your settings.") + "define STATIC_URL in your settings." + ) else: raise @@ -77,16 +77,16 @@ def render_toolbar(self): _store = OrderedDict() def should_render_panels(self): - render_panels = self.config['RENDER_PANELS'] + render_panels = self.config["RENDER_PANELS"] if render_panels is None: - render_panels = self.request.META['wsgi.multiprocess'] + render_panels = self.request.META["wsgi.multiprocess"] return render_panels def store(self): self.store_id = uuid.uuid4().hex cls = type(self) cls._store[self.store_id] = self - for _ in range(len(cls._store) - self.config['RESULTS_CACHE_SIZE']): + for _ in range(len(cls._store) - self.config["RESULTS_CACHE_SIZE"]): try: # collections.OrderedDict cls._store.popitem(last=False) @@ -108,8 +108,7 @@ def get_panel_classes(cls): if cls._panel_classes is None: # Load panels in a temporary variable for thread safety. panel_classes = [ - import_string(panel_path) - for panel_path in dt_settings.get_panels() + import_string(panel_path) for panel_path in dt_settings.get_panels() ] cls._panel_classes = panel_classes return cls._panel_classes @@ -120,10 +119,11 @@ def get_panel_classes(cls): def get_urls(cls): if cls._urlpatterns is None: from . import views + # Load URLs in a temporary variable for thread safety. # Global URLs urlpatterns = [ - url(r'^render_panel/$', views.render_panel, name='render_panel'), + url(r"^render_panel/$", views.render_panel, name="render_panel") ] # Per-panel URLs for panel_class in cls.get_panel_classes(): @@ -132,5 +132,5 @@ def get_urls(cls): return cls._urlpatterns -app_name = 'djdt' +app_name = "djdt" urlpatterns = DebugToolbar.get_urls() diff --git a/debug_toolbar/utils.py b/debug_toolbar/utils.py index d311e9ad4..eb84784b2 100644 --- a/debug_toolbar/utils.py +++ b/debug_toolbar/utils.py @@ -31,18 +31,17 @@ def get_module_path(module_name): try: module = import_module(module_name) except ImportError as e: - raise ImproperlyConfigured( - 'Error importing HIDE_IN_STACKTRACES: %s' % (e,)) + raise ImproperlyConfigured("Error importing HIDE_IN_STACKTRACES: %s" % (e,)) else: source_path = inspect.getsourcefile(module) - if source_path.endswith('__init__.py'): + if source_path.endswith("__init__.py"): source_path = os.path.dirname(source_path) return os.path.realpath(source_path) hidden_paths = [ get_module_path(module_name) - for module_name in dt_settings.get_config()['HIDE_IN_STACKTRACES'] + for module_name in dt_settings.get_config()["HIDE_IN_STACKTRACES"] ] @@ -63,7 +62,7 @@ def tidy_stacktrace(stack): for frame, path, line_no, func_name, text in (f[:5] for f in stack): if omit_path(os.path.realpath(path)): continue - text = (''.join(force_text(t) for t in text)).strip() if text else '' + text = ("".join(force_text(t) for t in text)).strip() if text else "" trace.append((path, line_no, func_name, text)) return trace @@ -74,16 +73,18 @@ def render_stacktrace(trace): params = (escape(v) for v in chain(frame[0].rsplit(os.path.sep, 1), frame[1:])) params_dict = {six.text_type(idx): v for idx, v in enumerate(params)} try: - stacktrace.append('%(0)s/' - '%(1)s' - ' in %(3)s' - '(%(2)s)\n' - ' %(4)s' - % params_dict) + stacktrace.append( + '%(0)s/' + '%(1)s' + ' in %(3)s' + '(%(2)s)\n' + ' %(4)s' % params_dict + ) except KeyError: - # This frame doesn't have the expected format, so skip it and move on to the next one + # This frame doesn't have the expected format, so skip it and move + # on to the next one continue - return mark_safe('\n'.join(stacktrace)) + return mark_safe("\n".join(stacktrace)) def get_template_info(): @@ -101,9 +102,9 @@ def get_template_info(): # If the method in the stack trace is this one # then break from the loop as it's being check recursively. break - elif cur_frame.f_code.co_name == 'render': - node = cur_frame.f_locals['self'] - context = cur_frame.f_locals['context'] + elif cur_frame.f_code.co_name == "render": + node = cur_frame.f_locals["self"] + context = cur_frame.f_locals["context"] if isinstance(node, Node): template_info = get_template_context(node, context) break @@ -115,46 +116,39 @@ def get_template_info(): def get_template_context(node, context, context_lines=3): - line, source_lines, name = get_template_source_from_exception_info( - node, context) + line, source_lines, name = get_template_source_from_exception_info(node, context) debug_context = [] start = max(1, line - context_lines) end = line + 1 + context_lines for line_num, content in source_lines: if start <= line_num <= end: - debug_context.append({ - 'num': line_num, - 'content': content, - 'highlight': (line_num == line), - }) + debug_context.append( + {"num": line_num, "content": content, "highlight": (line_num == line)} + ) - return { - 'name': name, - 'context': debug_context, - } + return {"name": name, "context": debug_context} def get_template_source_from_exception_info(node, context): - exception_info = context.template.get_exception_info( - Exception('DDT'), node.token) - line = exception_info['line'] - source_lines = exception_info['source_lines'] - name = exception_info['name'] + exception_info = context.template.get_exception_info(Exception("DDT"), node.token) + line = exception_info["line"] + source_lines = exception_info["source_lines"] + name = exception_info["name"] return line, source_lines, name def get_name_from_obj(obj): - if hasattr(obj, '__name__'): + if hasattr(obj, "__name__"): name = obj.__name__ - elif hasattr(obj, '__class__') and hasattr(obj.__class__, '__name__'): + elif hasattr(obj, "__class__") and hasattr(obj.__class__, "__name__"): name = obj.__class__.__name__ else: - name = '' + name = "" - if hasattr(obj, '__module__'): + if hasattr(obj, "__module__"): module = obj.__module__ - name = '%s.%s' % (module, name) + name = "%s.%s" % (module, name) return name @@ -178,37 +172,37 @@ def getframeinfo(frame, context=1): else: lineno = frame.f_lineno if not inspect.isframe(frame): - raise TypeError('arg is not a frame or traceback object') + raise TypeError("arg is not a frame or traceback object") filename = inspect.getsourcefile(frame) or inspect.getfile(frame) if context > 0: start = lineno - 1 - context // 2 try: lines, lnum = inspect.findsource(frame) - except Exception: # findsource raises platform-dependant exceptions + except Exception: # findsource raises platform-dependant exceptions first_lines = lines = index = None else: start = max(start, 1) start = max(0, min(start, len(lines) - context)) first_lines = lines[:2] - lines = lines[start:(start + context)] + lines = lines[start : (start + context)] index = lineno - 1 - start else: first_lines = lines = index = None # Code taken from Django's ExceptionReporter._get_lines_from_file if first_lines and isinstance(first_lines[0], bytes): - encoding = 'ascii' + encoding = "ascii" for line in first_lines[:2]: # File coding may be specified. Match pattern from PEP-263 # (https://www.python.org/dev/peps/pep-0263/) - match = re.search(br'coding[:=]\s*([-\w.]+)', line) + match = re.search(br"coding[:=]\s*([-\w.]+)", line) if match: - encoding = match.group(1).decode('ascii') + encoding = match.group(1).decode("ascii") break - lines = [line.decode(encoding, 'replace') for line in lines] + lines = [line.decode(encoding, "replace") for line in lines] - if hasattr(inspect, 'Traceback'): + if hasattr(inspect, "Traceback"): return inspect.Traceback(filename, lineno, frame.f_code.co_name, lines, index) else: return (filename, lineno, frame.f_code.co_name, lines, index) @@ -236,7 +230,8 @@ def __init__(self): if threading is None: raise NotImplementedError( "threading module is not available, " - "this panel cannot be used without it") + "this panel cannot be used without it" + ) self.collections = {} # a dictionary that maps threads to collections def get_collection(self, thread=None): diff --git a/debug_toolbar/views.py b/debug_toolbar/views.py index 5ed99bae3..04cc74b07 100644 --- a/debug_toolbar/views.py +++ b/debug_toolbar/views.py @@ -11,12 +11,14 @@ @require_show_toolbar def render_panel(request): """Render the contents of a panel""" - toolbar = DebugToolbar.fetch(request.GET['store_id']) + toolbar = DebugToolbar.fetch(request.GET["store_id"]) if toolbar is None: - content = _("Data for this panel isn't available anymore. " - "Please reload the page and retry.") + content = _( + "Data for this panel isn't available anymore. " + "Please reload the page and retry." + ) content = "

%s

" % escape(content) else: - panel = toolbar.get_panel_by_id(request.GET['panel_id']) + panel = toolbar.get_panel_by_id(request.GET["panel_id"]) content = panel.content return HttpResponse(content) diff --git a/docs/changes.rst b/docs/changes.rst index b14335143..33fb00e92 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -4,6 +4,9 @@ Change log UNRELEASED ---------- +* Use ``defer`` on all ``