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 ```` tags to avoid blocking HTML parsing. +* Reformatted the code using `black `__. + 1.10.1 (2018-09-11) ------------------- diff --git a/docs/contributing.rst b/docs/contributing.rst index ac8927da4..0f77beb15 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -85,14 +85,11 @@ SQLite. Style ----- -Python code for the Django Debug Toolbar follows PEP8. Line length is limited -to 100 characters. You can check for style violations with:: +The Django Debug Toolbar uses `black `__ +to format code and additionally uses flake8 and isort. You can reformat +the code using:: - $ make flake8 - -Import style is enforced by isort. You can sort import automatically with:: - - $ make isort + $ make style Patches ------- diff --git a/docs/tips.rst b/docs/tips.rst index b639cfa5f..85c29b2fd 100644 --- a/docs/tips.rst +++ b/docs/tips.rst @@ -14,6 +14,10 @@ requests and return responses. Putting the debug toolbar middleware *after* the Flatpage middleware, for example, means the toolbar will not show up on flatpages. +Browsers have become more aggressive with caching static assets, such as +JavaScript and CSS files. Check your browser's development console, and if +you see errors, try a hard browser refresh or clearing your cache. + Middleware isn't working correctly ---------------------------------- diff --git a/example/settings.py b/example/settings.py index 0d394d09a..517e13722 100644 --- a/example/settings.py +++ b/example/settings.py @@ -7,95 +7,91 @@ # Quick-start development settings - unsuitable for production -SECRET_KEY = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890' +SECRET_KEY = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890" DEBUG = True -INTERNAL_IPS = ['127.0.0.1', '::1'] +INTERNAL_IPS = ["127.0.0.1", "::1"] # Application definition INSTALLED_APPS = [ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'debug_toolbar', + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "debug_toolbar", ] MIDDLEWARE = [ - 'debug_toolbar.middleware.DebugToolbarMiddleware', - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', + "debug_toolbar.middleware.DebugToolbarMiddleware", + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", ] -ROOT_URLCONF = 'example.urls' +ROOT_URLCONF = "example.urls" -STATIC_URL = '/static/' +STATIC_URL = "/static/" TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'APP_DIRS': True, - 'DIRS': [os.path.join(BASE_DIR, 'example', 'templates')], - 'OPTIONS': { - 'debug': True, - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', + "BACKEND": "django.template.backends.django.DjangoTemplates", + "APP_DIRS": True, + "DIRS": [os.path.join(BASE_DIR, "example", "templates")], + "OPTIONS": { + "debug": True, + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", ], }, - }, + } ] -WSGI_APPLICATION = 'example.wsgi.application' +WSGI_APPLICATION = "example.wsgi.application" # Cache and database -CACHES = { - 'default': { - 'BACKEND': 'django.core.cache.backends.dummy.DummyCache', - } -} +CACHES = {"default": {"BACKEND": "django.core.cache.backends.dummy.DummyCache"}} DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'example', 'db.sqlite3'), + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": os.path.join(BASE_DIR, "example", "db.sqlite3"), } } # To use another database, set the DJANGO_DATABASE_ENGINE environment variable. -if os.environ.get('DJANGO_DATABASE_ENGINE', '').lower() == 'postgresql': +if os.environ.get("DJANGO_DATABASE_ENGINE", "").lower() == "postgresql": # % su postgres # % createuser debug_toolbar # % createdb debug_toolbar -O debug_toolbar DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.postgresql_psycopg2', - 'NAME': 'debug_toolbar', - 'USER': 'debug_toolbar', + "default": { + "ENGINE": "django.db.backends.postgresql_psycopg2", + "NAME": "debug_toolbar", + "USER": "debug_toolbar", } } -if os.environ.get('DJANGO_DATABASE_ENGINE', '').lower() == 'mysql': +if os.environ.get("DJANGO_DATABASE_ENGINE", "").lower() == "mysql": # % mysql # mysql> CREATE DATABASE debug_toolbar; # mysql> GRANT ALL PRIVILEGES ON debug_toolbar.* TO 'debug_toolbar'@'localhost'; DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.mysql', - 'NAME': 'debug_toolbar', - 'USER': 'debug_toolbar', + "default": { + "ENGINE": "django.db.backends.mysql", + "NAME": "debug_toolbar", + "USER": "debug_toolbar", } } @@ -103,19 +99,19 @@ # django-debug-toolbar DEBUG_TOOLBAR_PANELS = [ - '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.templates.TemplatesPanel', - 'debug_toolbar.panels.staticfiles.StaticFilesPanel', - 'debug_toolbar.panels.cache.CachePanel', - 'debug_toolbar.panels.signals.SignalsPanel', - 'debug_toolbar.panels.logging.LoggingPanel', - 'debug_toolbar.panels.redirects.RedirectsPanel', - 'debug_toolbar.panels.profiling.ProfilingPanel', + "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.templates.TemplatesPanel", + "debug_toolbar.panels.staticfiles.StaticFilesPanel", + "debug_toolbar.panels.cache.CachePanel", + "debug_toolbar.panels.signals.SignalsPanel", + "debug_toolbar.panels.logging.LoggingPanel", + "debug_toolbar.panels.redirects.RedirectsPanel", + "debug_toolbar.panels.profiling.ProfilingPanel", ] -STATICFILES_DIRS = [os.path.join(BASE_DIR, 'example', 'static')] +STATICFILES_DIRS = [os.path.join(BASE_DIR, "example", "static")] diff --git a/example/urls.py b/example/urls.py index c238e08a1..32c91c619 100644 --- a/example/urls.py +++ b/example/urls.py @@ -4,15 +4,14 @@ from django.views.generic import TemplateView urlpatterns = [ - url(r'^$', TemplateView.as_view(template_name='index.html')), - url(r'^jquery/$', TemplateView.as_view(template_name='jquery/index.html')), - url(r'^mootools/$', TemplateView.as_view(template_name='mootools/index.html')), - url(r'^prototype/$', TemplateView.as_view(template_name='prototype/index.html')), - url(r'^admin/', admin.site.urls), + url(r"^$", TemplateView.as_view(template_name="index.html")), + url(r"^jquery/$", TemplateView.as_view(template_name="jquery/index.html")), + url(r"^mootools/$", TemplateView.as_view(template_name="mootools/index.html")), + url(r"^prototype/$", TemplateView.as_view(template_name="prototype/index.html")), + url(r"^admin/", admin.site.urls), ] if settings.DEBUG: import debug_toolbar - urlpatterns += [ - url(r'^__debug__/', include(debug_toolbar.urls)), - ] + + urlpatterns += [url(r"^__debug__/", include(debug_toolbar.urls))] diff --git a/setup.cfg b/setup.cfg index aeef37479..2c6e391ea 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,16 +2,19 @@ tag_svn_revision = false [flake8] -ignore = W601 ; # noqa doesn't silence this one -max-line-length = 100 +exclude = .tox,venv,conf.py +ignore = E203,W503,W601 +max-line-length = 88 [isort] combine_as_imports = true default_section = THIRDPARTY include_trailing_comma = true known_first_party = debug_toolbar -multi_line_output = 5 +multi_line_output = 3 not_skip = __init__.py +force_grid_wrap = 0 +line_length = 88 [bdist_wheel] universal = 1 diff --git a/setup.py b/setup.py index 444470013..9fbb8b61b 100755 --- a/setup.py +++ b/setup.py @@ -4,43 +4,46 @@ from setuptools import find_packages, setup + +def readall(path): + with open(path, encoding="utf-8") as fp: + return fp.read() + + setup( - name='django-debug-toolbar', - version='1.10.1', - description='A configurable set of panels that display various debug ' - 'information about the current request/response.', - long_description=open('README.rst', encoding='utf-8').read(), - author='Rob Hudson', - author_email='rob@cogit8.org', - url='https://github.com/jazzband/django-debug-toolbar', - download_url='https://pypi.org/project/django-debug-toolbar/', - license='BSD', - packages=find_packages(exclude=('tests.*', 'tests', 'example')), + name="django-debug-toolbar", + version="1.10.1", + description="A configurable set of panels that display various debug " + "information about the current request/response.", + long_description=readall("README.rst"), + author="Rob Hudson", + author_email="rob@cogit8.org", + url="https://github.com/jazzband/django-debug-toolbar", + download_url="https://pypi.org/project/django-debug-toolbar/", + license="BSD", + packages=find_packages(exclude=("tests.*", "tests", "example")), python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*", - install_requires=[ - 'Django>=1.11', - 'sqlparse>=0.2.0', - ], + install_requires=["Django>=1.11", "sqlparse>=0.2.0"], include_package_data=True, - zip_safe=False, # because we're including static files + zip_safe=False, # because we're including static files classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Environment :: Web Environment', - 'Framework :: Django', - 'Framework :: Django :: 1.11', - 'Framework :: Django :: 2.0', - 'Framework :: Django :: 2.1', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: BSD License', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Topic :: Software Development :: Libraries :: Python Modules', + "Development Status :: 5 - Production/Stable", + "Environment :: Web Environment", + "Framework :: Django", + "Framework :: Django :: 1.11", + "Framework :: Django :: 2.0", + "Framework :: Django :: 2.1", + "Intended Audience :: Developers", + "License :: OSI Approved :: BSD License", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Topic :: Software Development :: Libraries :: Python Modules", ], ) diff --git a/tests/__init__.py b/tests/__init__.py index 446a6711d..f8fd2bf6c 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -9,14 +9,14 @@ @receiver(setting_changed) def update_toolbar_config(**kwargs): - if kwargs['setting'] == 'DEBUG_TOOLBAR_CONFIG': + if kwargs["setting"] == "DEBUG_TOOLBAR_CONFIG": dt_settings.get_config.cache_clear() # This doesn't account for deprecated configuration options. @receiver(setting_changed) def update_toolbar_panels(**kwargs): - if kwargs['setting'] == 'DEBUG_TOOLBAR_PANELS': + if kwargs["setting"] == "DEBUG_TOOLBAR_PANELS": dt_settings.get_panels.cache_clear() DebugToolbar._panel_classes = None # Not implemented: invalidate debug_toolbar.urls. diff --git a/tests/base.py b/tests/base.py index a7b57e551..682397d16 100644 --- a/tests/base.py +++ b/tests/base.py @@ -13,13 +13,14 @@ class BaseTestCase(TestCase): - def setUp(self): - request = rf.get('/') + request = rf.get("/") response = HttpResponse() toolbar = DebugToolbar(request) - DebugToolbarMiddleware.debug_toolbars[threading.current_thread().ident] = toolbar + DebugToolbarMiddleware.debug_toolbars[ + threading.current_thread().ident + ] = toolbar self.request = request self.response = response @@ -30,11 +31,11 @@ def assertValidHTML(self, content, msg=None): parser = html5lib.HTMLParser() parser.parseFragment(self.panel.content) if parser.errors: - default_msg = ['Content is invalid HTML:'] - lines = content.split('\n') + default_msg = ["Content is invalid HTML:"] + lines = content.split("\n") for position, errorcode, datavars in parser.errors: - default_msg.append(' %s' % html5lib.constants.E[errorcode] % datavars) - default_msg.append(' %s' % lines[position[0] - 1]) + default_msg.append(" %s" % html5lib.constants.E[errorcode] % datavars) + default_msg.append(" %s" % lines[position[0] - 1]) - msg = self._formatMessage(msg, '\n'.join(default_msg)) + msg = self._formatMessage(msg, "\n".join(default_msg)) raise self.failureException(msg) diff --git a/tests/commands/test_debugsqlshell.py b/tests/commands/test_debugsqlshell.py index a436570d8..bb9ed50cb 100644 --- a/tests/commands/test_debugsqlshell.py +++ b/tests/commands/test_debugsqlshell.py @@ -12,13 +12,12 @@ @override_settings(DEBUG=True) class DebugSQLShellTestCase(TestCase): - def setUp(self): self.original_cursor_wrapper = db_backends_utils.CursorDebugWrapper # Since debugsqlshell monkey-patches django.db.backends.utils, we can # test it simply by loading it, without executing it. But we have to # undo the monkey-patch on exit. - command_name = 'debugsqlshell' + command_name = "debugsqlshell" app_name = management.get_commands()[command_name] management.load_command_class(app_name, command_name) diff --git a/tests/models.py b/tests/models.py index 78c1eb8ed..93f860bb1 100644 --- a/tests/models.py +++ b/tests/models.py @@ -7,4 +7,4 @@ class NonAsciiRepr(object): def __repr__(self): - return 'nôt åscíì' if six.PY3 else 'nôt åscíì'.encode('utf-8') + return "nôt åscíì" if six.PY3 else "nôt åscíì".encode("utf-8") diff --git a/tests/panels/test_cache.py b/tests/panels/test_cache.py index 41bdbd79c..e07aa3106 100644 --- a/tests/panels/test_cache.py +++ b/tests/panels/test_cache.py @@ -8,10 +8,9 @@ class CachePanelTestCase(BaseTestCase): - def setUp(self): super(CachePanelTestCase, self).setUp() - self.panel = self.toolbar.get_panel_by_id('CachePanel') + self.panel = self.toolbar.get_panel_by_id("CachePanel") self.panel.enable_instrumentation() def tearDown(self): @@ -20,9 +19,9 @@ def tearDown(self): def test_recording(self): self.assertEqual(len(self.panel.calls), 0) - cache.cache.set('foo', 'bar') - cache.cache.get('foo') - cache.cache.delete('foo') + cache.cache.set("foo", "bar") + cache.cache.get("foo") + cache.cache.delete("foo") # Verify that the cache has a valid clear method. cache.cache.clear() self.assertEqual(len(self.panel.calls), 4) @@ -30,9 +29,9 @@ def test_recording(self): def test_recording_caches(self): self.assertEqual(len(self.panel.calls), 0) default_cache = cache.caches[cache.DEFAULT_CACHE_ALIAS] - second_cache = cache.caches['second'] - default_cache.set('foo', 'bar') - second_cache.get('foo') + second_cache = cache.caches["second"] + default_cache.set("foo", "bar") + second_cache.get("foo") self.assertEqual(len(self.panel.calls), 2) def test_insert_content(self): @@ -40,20 +39,20 @@ def test_insert_content(self): Test that the panel only inserts content after generate_stats and not the process_response. """ - cache.cache.get('café') + cache.cache.get("café") self.panel.process_response(self.request, self.response) # ensure the panel does not have content yet. - self.assertNotIn('café', self.panel.content) + self.assertNotIn("café", self.panel.content) self.panel.generate_stats(self.request, self.response) # ensure the panel renders correctly. - self.assertIn('café', self.panel.content) + self.assertIn("café", self.panel.content) self.assertValidHTML(self.panel.content) def test_generate_server_timin(self): self.assertEqual(len(self.panel.calls), 0) - cache.cache.set('foo', 'bar') - cache.cache.get('foo') - cache.cache.delete('foo') + cache.cache.set("foo", "bar") + cache.cache.get("foo") + cache.cache.delete("foo") self.assertEqual(len(self.panel.calls), 3) @@ -63,9 +62,9 @@ def test_generate_server_timin(self): stats = self.panel.get_stats() expected_data = { - 'total_time': { - 'title': 'Cache {} Calls'.format(stats['total_calls']), - 'value': stats['total_time'] + "total_time": { + "title": "Cache {} Calls".format(stats["total_calls"]), + "value": stats["total_time"], } } diff --git a/tests/panels/test_logging.py b/tests/panels/test_logging.py index cb5a63133..669bda0a9 100644 --- a/tests/panels/test_logging.py +++ b/tests/panels/test_logging.py @@ -5,17 +5,17 @@ import logging from debug_toolbar.panels.logging import ( - MESSAGE_IF_STRING_REPRESENTATION_INVALID, collector, + MESSAGE_IF_STRING_REPRESENTATION_INVALID, + collector, ) from ..base import BaseTestCase class LoggingPanelTestCase(BaseTestCase): - def setUp(self): super(LoggingPanelTestCase, self).setUp() - self.panel = self.toolbar.get_panel_by_id('LoggingPanel') + self.panel = self.toolbar.get_panel_by_id("LoggingPanel") self.logger = logging.getLogger(__name__) collector.clear_collection() @@ -24,53 +24,52 @@ def setUp(self): logging.root.setLevel(logging.DEBUG) def test_happy_case(self): - self.logger.info('Nothing to see here, move along!') + self.logger.info("Nothing to see here, move along!") self.panel.process_response(self.request, self.response) self.panel.generate_stats(self.request, self.response) - records = self.panel.get_stats()['records'] + records = self.panel.get_stats()["records"] self.assertEqual(1, len(records)) - self.assertEqual('Nothing to see here, move along!', - records[0]['message']) + self.assertEqual("Nothing to see here, move along!", records[0]["message"]) def test_formatting(self): - self.logger.info('There are %d %s', 5, 'apples') + self.logger.info("There are %d %s", 5, "apples") self.panel.process_response(self.request, self.response) self.panel.generate_stats(self.request, self.response) - records = self.panel.get_stats()['records'] + records = self.panel.get_stats()["records"] self.assertEqual(1, len(records)) - self.assertEqual('There are 5 apples', - records[0]['message']) + self.assertEqual("There are 5 apples", records[0]["message"]) def test_insert_content(self): """ Test that the panel only inserts content after generate_stats and not the process_response. """ - self.logger.info('café') + self.logger.info("café") self.panel.process_response(self.request, self.response) # ensure the panel does not have content yet. - self.assertNotIn('café', self.panel.content) + self.assertNotIn("café", self.panel.content) self.panel.generate_stats(self.request, self.response) # ensure the panel renders correctly. - self.assertIn('café', self.panel.content) + self.assertIn("café", self.panel.content) self.assertValidHTML(self.panel.content) def test_failing_formatting(self): class BadClass(object): def __str__(self): - raise Exception('Please not stringify me!') + raise Exception("Please not stringify me!") # should not raise exception, but fail silently - self.logger.debug('This class is misbehaving: %s', BadClass()) + self.logger.debug("This class is misbehaving: %s", BadClass()) self.panel.process_response(self.request, self.response) self.panel.generate_stats(self.request, self.response) - records = self.panel.get_stats()['records'] + records = self.panel.get_stats()["records"] self.assertEqual(1, len(records)) - self.assertEqual(MESSAGE_IF_STRING_REPRESENTATION_INVALID, - records[0]['message']) + self.assertEqual( + MESSAGE_IF_STRING_REPRESENTATION_INVALID, records[0]["message"] + ) diff --git a/tests/panels/test_profiling.py b/tests/panels/test_profiling.py index 7e0ef25af..e35e6e293 100644 --- a/tests/panels/test_profiling.py +++ b/tests/panels/test_profiling.py @@ -12,40 +12,45 @@ from ..views import listcomp_view, regular_view -@override_settings(DEBUG_TOOLBAR_PANELS=['debug_toolbar.panels.profiling.ProfilingPanel']) +@override_settings( + DEBUG_TOOLBAR_PANELS=["debug_toolbar.panels.profiling.ProfilingPanel"] +) class ProfilingPanelTestCase(BaseTestCase): - def setUp(self): super(ProfilingPanelTestCase, self).setUp() - self.panel = self.toolbar.get_panel_by_id('ProfilingPanel') + self.panel = self.toolbar.get_panel_by_id("ProfilingPanel") def test_regular_view(self): - self.panel.process_view(self.request, regular_view, ('profiling',), {}) + self.panel.process_view(self.request, regular_view, ("profiling",), {}) self.panel.process_response(self.request, self.response) self.panel.generate_stats(self.request, self.response) - self.assertIn('func_list', self.panel.get_stats()) - self.assertIn('regular_view', self.panel.content) + self.assertIn("func_list", self.panel.get_stats()) + self.assertIn("regular_view", self.panel.content) def test_insert_content(self): """ Test that the panel only inserts content after generate_stats and not the process_response. """ - self.panel.process_view(self.request, regular_view, ('profiling',), {}) + self.panel.process_view(self.request, regular_view, ("profiling",), {}) self.panel.process_response(self.request, self.response) # ensure the panel does not have content yet. - self.assertNotIn('regular_view', self.panel.content) + self.assertNotIn("regular_view", self.panel.content) self.panel.generate_stats(self.request, self.response) # ensure the panel renders correctly. - self.assertIn('regular_view', self.panel.content) + self.assertIn("regular_view", self.panel.content) self.assertValidHTML(self.panel.content) - @unittest.skipIf(six.PY2, 'list comprehension not listed on Python 2') + @unittest.skipIf(six.PY2, "list comprehension not listed on Python 2") def test_listcomp_escaped(self): self.panel.process_view(self.request, listcomp_view, (), {}) self.panel.generate_stats(self.request, self.response) - self.assertNotIn('', self.panel.content) - self.assertIn('<listcomp>', self.panel.content) + self.assertNotIn( + '', self.panel.content + ) + self.assertIn( + '<listcomp>', self.panel.content + ) def test_generate_stats_no_profiler(self): """ @@ -57,27 +62,27 @@ def test_generate_stats_no_root_func(self): """ Test generating stats using profiler without root function. """ - self.panel.process_view(self.request, regular_view, ('profiling',), {}) + self.panel.process_view(self.request, regular_view, ("profiling",), {}) self.panel.process_response(self.request, self.response) self.panel.profiler.clear() self.panel.profiler.enable() self.panel.profiler.disable() self.panel.generate_stats(self.request, self.response) - self.assertNotIn('func_list', self.panel.get_stats()) + self.assertNotIn("func_list", self.panel.get_stats()) -@override_settings(DEBUG=True, - DEBUG_TOOLBAR_PANELS=['debug_toolbar.panels.profiling.ProfilingPanel']) +@override_settings( + DEBUG=True, DEBUG_TOOLBAR_PANELS=["debug_toolbar.panels.profiling.ProfilingPanel"] +) class ProfilingPanelIntegrationTestCase(TestCase): - def test_view_executed_once(self): self.assertEqual(User.objects.count(), 0) - response = self.client.get('/new_user/') - self.assertContains(response, 'Profiling') + response = self.client.get("/new_user/") + self.assertContains(response, "Profiling") self.assertEqual(User.objects.count(), 1) with self.assertRaises(IntegrityError): with transaction.atomic(): - response = self.client.get('/new_user/') + response = self.client.get("/new_user/") self.assertEqual(User.objects.count(), 1) diff --git a/tests/panels/test_redirects.py b/tests/panels/test_redirects.py index 902b7a489..89adbeb9e 100644 --- a/tests/panels/test_redirects.py +++ b/tests/panels/test_redirects.py @@ -9,51 +9,52 @@ class RedirectsPanelTestCase(BaseTestCase): - def setUp(self): super(RedirectsPanelTestCase, self).setUp() - self.panel = self.toolbar.get_panel_by_id('RedirectsPanel') + self.panel = self.toolbar.get_panel_by_id("RedirectsPanel") def test_regular_response(self): response = self.panel.process_response(self.request, self.response) self.assertTrue(response is self.response) def test_not_a_redirect(self): - redirect = HttpResponse(status=304) # not modified + redirect = HttpResponse(status=304) # not modified response = self.panel.process_response(self.request, redirect) self.assertTrue(response is redirect) def test_redirect(self): redirect = HttpResponse(status=302) - redirect['Location'] = 'http://somewhere/else/' + redirect["Location"] = "http://somewhere/else/" response = self.panel.process_response(self.request, redirect) self.assertFalse(response is redirect) - self.assertContains(response, '302 Found') - self.assertContains(response, 'http://somewhere/else/') + self.assertContains(response, "302 Found") + self.assertContains(response, "http://somewhere/else/") def test_redirect_with_broken_context_processor(self): TEMPLATES = copy.deepcopy(settings.TEMPLATES) - TEMPLATES[1]['OPTIONS']['context_processors'] = ['tests.context_processors.broken'] + TEMPLATES[1]["OPTIONS"]["context_processors"] = [ + "tests.context_processors.broken" + ] with self.settings(TEMPLATES=TEMPLATES): redirect = HttpResponse(status=302) - redirect['Location'] = 'http://somewhere/else/' + redirect["Location"] = "http://somewhere/else/" response = self.panel.process_response(self.request, redirect) self.assertFalse(response is redirect) - self.assertContains(response, '302 Found') - self.assertContains(response, 'http://somewhere/else/') + self.assertContains(response, "302 Found") + self.assertContains(response, "http://somewhere/else/") def test_unknown_status_code(self): redirect = HttpResponse(status=369) - redirect['Location'] = 'http://somewhere/else/' + redirect["Location"] = "http://somewhere/else/" response = self.panel.process_response(self.request, redirect) - self.assertContains(response, '369 Unknown Status Code') + self.assertContains(response, "369 Unknown Status Code") def test_unknown_status_code_with_reason(self): - redirect = HttpResponse(status=369, reason='Look Ma!') - redirect['Location'] = 'http://somewhere/else/' + redirect = HttpResponse(status=369, reason="Look Ma!") + redirect["Location"] = "http://somewhere/else/" response = self.panel.process_response(self.request, redirect) - self.assertContains(response, '369 Look Ma!') + self.assertContains(response, "369 Look Ma!") def test_insert_content(self): """ diff --git a/tests/panels/test_request.py b/tests/panels/test_request.py index 17119b6ca..c14cddf61 100644 --- a/tests/panels/test_request.py +++ b/tests/panels/test_request.py @@ -8,42 +8,41 @@ class RequestPanelTestCase(BaseTestCase): - def setUp(self): super(RequestPanelTestCase, self).setUp() - self.panel = self.toolbar.get_panel_by_id('RequestPanel') + self.panel = self.toolbar.get_panel_by_id("RequestPanel") def test_non_ascii_session(self): - self.request.session = {'où': 'où'} + self.request.session = {"où": "où"} if not six.PY3: - self.request.session['là'.encode('utf-8')] = 'là'.encode('utf-8') + self.request.session["là".encode("utf-8")] = "là".encode("utf-8") self.panel.process_request(self.request) self.panel.process_response(self.request, self.response) self.panel.generate_stats(self.request, self.response) content = self.panel.content if six.PY3: - self.assertIn('où', content) + self.assertIn("où", content) else: - self.assertIn('o\\xf9', content) - self.assertIn('l\\xc3\\xa0', content) + self.assertIn("o\\xf9", content) + self.assertIn("l\\xc3\\xa0", content) def test_object_with_non_ascii_repr_in_request_params(self): - self.request.path = '/non_ascii_request/' + self.request.path = "/non_ascii_request/" self.panel.process_request(self.request) self.panel.process_response(self.request, self.response) self.panel.generate_stats(self.request, self.response) - self.assertIn('nôt åscíì', self.panel.content) + self.assertIn("nôt åscíì", self.panel.content) def test_insert_content(self): """ Test that the panel only inserts content after generate_stats and not the process_response. """ - self.request.path = '/non_ascii_request/' + self.request.path = "/non_ascii_request/" self.panel.process_response(self.request, self.response) # ensure the panel does not have content yet. - self.assertNotIn('nôt åscíì', self.panel.content) + self.assertNotIn("nôt åscíì", self.panel.content) self.panel.generate_stats(self.request, self.response) # ensure the panel renders correctly. - self.assertIn('nôt åscíì', self.panel.content) + self.assertIn("nôt åscíì", self.panel.content) self.assertValidHTML(self.panel.content) diff --git a/tests/panels/test_sql.py b/tests/panels/test_sql.py index 1bad52373..e17be15bd 100644 --- a/tests/panels/test_sql.py +++ b/tests/panels/test_sql.py @@ -16,10 +16,9 @@ class SQLPanelTestCase(BaseTestCase): - def setUp(self): super(SQLPanelTestCase, self).setUp() - self.panel = self.toolbar.get_panel_by_id('SQLPanel') + self.panel = self.toolbar.get_panel_by_id("SQLPanel") self.panel.enable_instrumentation() def tearDown(self): @@ -27,9 +26,7 @@ def tearDown(self): super(SQLPanelTestCase, self).tearDown() def test_disabled(self): - config = { - 'DISABLE_PANELS': {'debug_toolbar.panels.sql.SQLPanel'} - } + config = {"DISABLE_PANELS": {"debug_toolbar.panels.sql.SQLPanel"}} self.assertTrue(self.panel.enabled) with self.settings(DEBUG_TOOLBAR_CONFIG=config): self.assertFalse(self.panel.enabled) @@ -42,13 +39,13 @@ def test_recording(self): # ensure query was logged self.assertEqual(len(self.panel._queries), 1) query = self.panel._queries[0] - self.assertEqual(query[0], 'default') - self.assertTrue('sql' in query[1]) - self.assertTrue('duration' in query[1]) - self.assertTrue('stacktrace' in query[1]) + self.assertEqual(query[0], "default") + self.assertTrue("sql" in query[1]) + self.assertTrue("duration" in query[1]) + self.assertTrue("stacktrace" in query[1]) # ensure the stacktrace is populated - self.assertTrue(len(query[1]['stacktrace']) > 0) + self.assertTrue(len(query[1]["stacktrace"]) > 0) def test_generate_server_timing(self): self.assertEqual(len(self.panel._queries), 0) @@ -64,10 +61,7 @@ def test_generate_server_timing(self): query = self.panel._queries[0] expected_data = { - 'sql_time': { - 'title': 'SQL 1 queries', - 'value': query[1]['duration'] - } + "sql_time": {"title": "SQL 1 queries", "value": query[1]["duration"]} } self.assertEqual(self.panel.get_server_timing_stats(), expected_data) @@ -80,33 +74,31 @@ def test_non_ascii_query(self): self.assertEqual(len(self.panel._queries), 1) # non-ASCII text parameters - list(User.objects.filter(username='thé')) + list(User.objects.filter(username="thé")) self.assertEqual(len(self.panel._queries), 2) # non-ASCII bytes parameters - list(User.objects.filter(username='café'.encode('utf-8'))) + list(User.objects.filter(username="café".encode("utf-8"))) self.assertEqual(len(self.panel._queries), 3) self.panel.process_response(self.request, self.response) self.panel.generate_stats(self.request, self.response) # ensure the panel renders correctly - self.assertIn('café', self.panel.content) + self.assertIn("café", self.panel.content) def test_param_conversion(self): self.assertEqual(len(self.panel._queries), 0) list( - User.objects - .filter(first_name='Foo') - .filter(is_staff=True) - .filter(is_superuser=False) + User.objects.filter(first_name="Foo") + .filter(is_staff=True) + .filter(is_superuser=False) ) list( - User.objects - .annotate(group_count=Count('groups__id')) - .filter(group_count__lt=10) - .filter(group_count__gt=1) + User.objects.annotate(group_count=Count("groups__id")) + .filter(group_count__lt=10) + .filter(group_count__gt=1) ) list(User.objects.filter(date_joined=datetime.datetime(2017, 12, 22, 16, 7, 1))) @@ -116,57 +108,70 @@ def test_param_conversion(self): # ensure query was logged self.assertEqual(len(self.panel._queries), 3) - self.assertEqual(tuple([q[1]['params'] for q in self.panel._queries]), ( - '["Foo", true, false]', - '[10, 1]', - '["2017-12-22 16:07:01"]' - )) + self.assertEqual( + tuple([q[1]["params"] for q in self.panel._queries]), + ('["Foo", true, false]', "[10, 1]", '["2017-12-22 16:07:01"]'), + ) - @unittest.skipIf(connection.vendor in ('sqlite', 'postgresql'), - 'Mixing bytestrings and text is not allowed on PostgreSQL and SQLite') + @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) with connection.cursor() as cursor: - cursor.execute("SELECT * FROM auth_user WHERE username = %s", [b'\xff']) + 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.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) - list(User.objects.raw( - " ".join([ - "SELECT *", - "FROM auth_user", - "WHERE first_name = %s", - "AND is_staff = %s", - "AND is_superuser = %s", - "AND date_joined = %s", - ]), - params=['Foo', True, False, datetime.datetime(2017, 12, 22, 16, 7, 1)], - )) - - list(User.objects.raw( - " ".join([ - "SELECT *", - "FROM auth_user", - "WHERE first_name = %(first_name)s", - "AND is_staff = %(is_staff)s", - "AND is_superuser = %(is_superuser)s", - "AND date_joined = %(date_joined)s" - ]), - params={ - 'first_name': 'Foo', - 'is_staff': True, - 'is_superuser': False, - 'date_joined': datetime.datetime(2017, 12, 22, 16, 7, 1)}, - )) + list( + User.objects.raw( + " ".join( + [ + "SELECT *", + "FROM auth_user", + "WHERE first_name = %s", + "AND is_staff = %s", + "AND is_superuser = %s", + "AND date_joined = %s", + ] + ), + params=["Foo", True, False, datetime.datetime(2017, 12, 22, 16, 7, 1)], + ) + ) + + list( + User.objects.raw( + " ".join( + [ + "SELECT *", + "FROM auth_user", + "WHERE first_name = %(first_name)s", + "AND is_staff = %(is_staff)s", + "AND is_superuser = %(is_superuser)s", + "AND date_joined = %(date_joined)s", + ] + ), + params={ + "first_name": "Foo", + "is_staff": True, + "is_superuser": False, + "date_joined": datetime.datetime(2017, 12, 22, 16, 7, 1), + }, + ) + ) self.panel.process_response(self.request, self.response) self.panel.generate_stats(self.request, self.response) @@ -174,32 +179,38 @@ def test_raw_query_param_conversion(self): # ensure query was logged self.assertEqual(len(self.panel._queries), 2) - self.assertEqual(tuple([q[1]['params'] for q in self.panel._queries]), ( - '["Foo", true, false, "2017-12-22 16:07:01"]', - " ".join([ - '{"first_name": "Foo",', - '"is_staff": true,', - '"is_superuser": false,', - '"date_joined": "2017-12-22 16:07:01"}' - ]) - )) + self.assertEqual( + tuple([q[1]["params"] for q in self.panel._queries]), + ( + '["Foo", true, false, "2017-12-22 16:07:01"]', + " ".join( + [ + '{"first_name": "Foo",', + '"is_staff": true,', + '"is_superuser": false,', + '"date_joined": "2017-12-22 16:07:01"}', + ] + ), + ), + ) def test_insert_content(self): """ Test that the panel only inserts content after generate_stats and not the process_response. """ - list(User.objects.filter(username='café'.encode('utf-8'))) + list(User.objects.filter(username="café".encode("utf-8"))) self.panel.process_response(self.request, self.response) # ensure the panel does not have content yet. - self.assertNotIn('café', self.panel.content) + self.assertNotIn("café", self.panel.content) self.panel.generate_stats(self.request, self.response) # ensure the panel renders correctly. - self.assertIn('café', self.panel.content) + self.assertIn("café", self.panel.content) self.assertValidHTML(self.panel.content) - @unittest.skipUnless(connection.vendor == 'postgresql', - 'Test valid only on PostgreSQL') + @unittest.skipUnless( + connection.vendor == "postgresql", "Test valid only on PostgreSQL" + ) def test_erroneous_query(self): """ Test that an error in the query isn't swallowed by the middleware. @@ -207,29 +218,34 @@ def test_erroneous_query(self): try: connection.cursor().execute("erroneous query") except DatabaseError as e: - self.assertTrue('erroneous query' in str(e)) + self.assertTrue("erroneous query" in str(e)) def test_disable_stacktraces(self): self.assertEqual(len(self.panel._queries), 0) - with self.settings(DEBUG_TOOLBAR_CONFIG={'ENABLE_STACKTRACES': False}): + with self.settings(DEBUG_TOOLBAR_CONFIG={"ENABLE_STACKTRACES": False}): list(User.objects.all()) # ensure query was logged self.assertEqual(len(self.panel._queries), 1) query = self.panel._queries[0] - self.assertEqual(query[0], 'default') - self.assertTrue('sql' in query[1]) - self.assertTrue('duration' in query[1]) - self.assertTrue('stacktrace' in query[1]) + self.assertEqual(query[0], "default") + self.assertTrue("sql" in query[1]) + self.assertTrue("duration" in query[1]) + self.assertTrue("stacktrace" in query[1]) # ensure the stacktrace is empty - self.assertEqual([], query[1]['stacktrace']) - - @override_settings(DEBUG=True, TEMPLATES=[{ - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'OPTIONS': {'debug': True, 'loaders': ['tests.loaders.LoaderWithSQL']}, - }]) + self.assertEqual([], query[1]["stacktrace"]) + + @override_settings( + DEBUG=True, + TEMPLATES=[ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "OPTIONS": {"debug": True, "loaders": ["tests.loaders.LoaderWithSQL"]}, + } + ], + ) def test_regression_infinite_recursion(self): """ Test case for when the template loader runs a SQL query that causes @@ -243,10 +259,10 @@ def test_regression_infinite_recursion(self): # template is loaded and basic.html extends base.html. self.assertEqual(len(self.panel._queries), 2) query = self.panel._queries[0] - self.assertEqual(query[0], 'default') - self.assertTrue('sql' in query[1]) - self.assertTrue('duration' in query[1]) - self.assertTrue('stacktrace' in query[1]) + self.assertEqual(query[0], "default") + self.assertTrue("sql" in query[1]) + self.assertTrue("duration" in query[1]) + self.assertTrue("stacktrace" in query[1]) # ensure the stacktrace is populated - self.assertTrue(len(query[1]['stacktrace']) > 0) + self.assertTrue(len(query[1]["stacktrace"]) > 0) diff --git a/tests/panels/test_staticfiles.py b/tests/panels/test_staticfiles.py index 3773af65b..d1dcc3e16 100644 --- a/tests/panels/test_staticfiles.py +++ b/tests/panels/test_staticfiles.py @@ -8,25 +8,30 @@ class StaticFilesPanelTestCase(BaseTestCase): - def setUp(self): super(StaticFilesPanelTestCase, self).setUp() - self.panel = self.toolbar.get_panel_by_id('StaticFilesPanel') + self.panel = self.toolbar.get_panel_by_id("StaticFilesPanel") def test_default_case(self): self.panel.process_request(self.request) self.panel.process_response(self.request, self.response) self.panel.generate_stats(self.request, self.response) - self.assertIn('django.contrib.staticfiles.finders.' - 'AppDirectoriesFinder', self.panel.content) - self.assertIn('django.contrib.staticfiles.finders.' - 'FileSystemFinder (2 files)', self.panel.content) + self.assertIn( + "django.contrib.staticfiles.finders." "AppDirectoriesFinder", + self.panel.content, + ) + self.assertIn( + "django.contrib.staticfiles.finders." "FileSystemFinder (2 files)", + self.panel.content, + ) self.assertEqual(self.panel.num_used, 0) self.assertNotEqual(self.panel.num_found, 0) - self.assertEqual(self.panel.get_staticfiles_apps(), - ['django.contrib.admin', 'debug_toolbar']) - self.assertEqual(self.panel.get_staticfiles_dirs(), - finders.FileSystemFinder().locations) + self.assertEqual( + self.panel.get_staticfiles_apps(), ["django.contrib.admin", "debug_toolbar"] + ) + self.assertEqual( + self.panel.get_staticfiles_dirs(), finders.FileSystemFinder().locations + ) def test_insert_content(self): """ @@ -36,10 +41,14 @@ def test_insert_content(self): self.panel.process_request(self.request) self.panel.process_response(self.request, self.response) # ensure the panel does not have content yet. - self.assertNotIn('django.contrib.staticfiles.finders.' - 'AppDirectoriesFinder', self.panel.content) + self.assertNotIn( + "django.contrib.staticfiles.finders." "AppDirectoriesFinder", + self.panel.content, + ) self.panel.generate_stats(self.request, self.response) # ensure the panel renders correctly. - self.assertIn('django.contrib.staticfiles.finders.' - 'AppDirectoriesFinder', self.panel.content) + self.assertIn( + "django.contrib.staticfiles.finders." "AppDirectoriesFinder", + self.panel.content, + ) self.assertValidHTML(self.panel.content) diff --git a/tests/panels/test_template.py b/tests/panels/test_template.py index ea80763dd..17fd7cdb7 100644 --- a/tests/panels/test_template.py +++ b/tests/panels/test_template.py @@ -11,12 +11,11 @@ class TemplatesPanelTestCase(BaseTestCase): - def setUp(self): super(TemplatesPanelTestCase, self).setUp() - self.panel = self.toolbar.get_panel_by_id('TemplatesPanel') + self.panel = self.toolbar.get_panel_by_id("TemplatesPanel") self.panel.enable_instrumentation() - self.sql_panel = self.toolbar.get_panel_by_id('SQLPanel') + self.sql_panel = self.toolbar.get_panel_by_id("SQLPanel") self.sql_panel.enable_instrumentation() def tearDown(self): @@ -26,29 +25,29 @@ def tearDown(self): def test_queryset_hook(self): t = Template("No context variables here!") - c = Context({ - 'queryset': User.objects.all(), - 'deep_queryset': { - 'queryset': User.objects.all(), + c = Context( + { + "queryset": User.objects.all(), + "deep_queryset": {"queryset": User.objects.all()}, } - }) + ) t.render(c) # ensure the query was NOT logged self.assertEqual(len(self.sql_panel._queries), 0) - ctx = self.panel.templates[0]['context'][1] - self.assertIn('<>', ctx) - self.assertIn('<>', ctx) + ctx = self.panel.templates[0]["context"][1] + self.assertIn("<>", ctx) + self.assertIn("<>", ctx) def test_object_with_non_ascii_repr_in_context(self): self.panel.process_request(self.request) t = Template("{{ object }}") - c = Context({'object': NonAsciiRepr()}) + c = Context({"object": NonAsciiRepr()}) t.render(c) self.panel.process_response(self.request, self.response) self.panel.generate_stats(self.request, self.response) - self.assertIn('nôt åscíì', self.panel.content) + self.assertIn("nôt åscíì", self.panel.content) def test_insert_content(self): """ @@ -56,14 +55,14 @@ def test_insert_content(self): not the process_response. """ t = Template("{{ object }}") - c = Context({'object': NonAsciiRepr()}) + c = Context({"object": NonAsciiRepr()}) t.render(c) self.panel.process_response(self.request, self.response) # ensure the panel does not have content yet. - self.assertNotIn('nôt åscíì', self.panel.content) + self.assertNotIn("nôt åscíì", self.panel.content) self.panel.generate_stats(self.request, self.response) # ensure the panel renders correctly. - self.assertIn('nôt åscíì', self.panel.content) + self.assertIn("nôt åscíì", self.panel.content) self.assertValidHTML(self.panel.content) def test_custom_context_processor(self): @@ -73,26 +72,27 @@ def test_custom_context_processor(self): t.render(c) self.panel.process_response(self.request, self.response) self.panel.generate_stats(self.request, self.response) - self.assertIn('tests.panels.test_template.context_processor', self.panel.content) + self.assertIn( + "tests.panels.test_template.context_processor", self.panel.content + ) def test_disabled(self): - config = { - 'DISABLE_PANELS': {'debug_toolbar.panels.templates.TemplatesPanel'} - } + config = {"DISABLE_PANELS": {"debug_toolbar.panels.templates.TemplatesPanel"}} self.assertTrue(self.panel.enabled) with self.settings(DEBUG_TOOLBAR_CONFIG=config): self.assertFalse(self.panel.enabled) -@override_settings(DEBUG=True, - DEBUG_TOOLBAR_PANELS=['debug_toolbar.panels.templates.TemplatesPanel']) +@override_settings( + DEBUG=True, DEBUG_TOOLBAR_PANELS=["debug_toolbar.panels.templates.TemplatesPanel"] +) class JinjaTemplateTestCase(TestCase): def test_django_jinja2(self): - r = self.client.get('/regular_jinja/foobar/') - self.assertContains(r, 'Test for foobar (Jinja)') - self.assertContains(r, '

Templates (1 rendered)

') - self.assertContains(r, 'jinja2/basic.jinja') + r = self.client.get("/regular_jinja/foobar/") + self.assertContains(r, "Test for foobar (Jinja)") + self.assertContains(r, "

Templates (1 rendered)

") + self.assertContains(r, "jinja2/basic.jinja") def context_processor(request): - return {'content': 'set by processor'} + return {"content": "set by processor"} diff --git a/tests/panels/test_versions.py b/tests/panels/test_versions.py index d6541a953..ef9f02469 100644 --- a/tests/panels/test_versions.py +++ b/tests/panels/test_versions.py @@ -6,47 +6,41 @@ from ..base import BaseTestCase -version_info_t = namedtuple('version_info_t', ( - 'major', 'minor', 'micro', 'releaselevel', 'serial', -)) +version_info_t = namedtuple( + "version_info_t", ("major", "minor", "micro", "releaselevel", "serial") +) class VersionsPanelTestCase(BaseTestCase): - def setUp(self): super(VersionsPanelTestCase, self).setUp() - self.panel = self.toolbar.get_panel_by_id('VersionsPanel') + self.panel = self.toolbar.get_panel_by_id("VersionsPanel") def test_app_version_from_get_version_fn(self): - class FakeApp: def get_version(self): - return version_info_t(1, 2, 3, '', '') + return version_info_t(1, 2, 3, "", "") - self.assertEqual(self.panel.get_app_version(FakeApp()), '1.2.3') + self.assertEqual(self.panel.get_app_version(FakeApp()), "1.2.3") def test_incompatible_app_version_fn(self): - class FakeApp: - def get_version(self, some_other_arg): # This should be ignored by the get_version_from_app - return version_info_t(0, 0, 0, '', '') + return version_info_t(0, 0, 0, "", "") - VERSION = version_info_t(1, 2, 3, '', '') + VERSION = version_info_t(1, 2, 3, "", "") - self.assertEqual(self.panel.get_app_version(FakeApp()), '1.2.3') + self.assertEqual(self.panel.get_app_version(FakeApp()), "1.2.3") def test_app_version_from_VERSION(self): - class FakeApp: - VERSION = version_info_t(1, 2, 3, '', '') + VERSION = version_info_t(1, 2, 3, "", "") - self.assertEqual(self.panel.get_app_version(FakeApp()), '1.2.3') + self.assertEqual(self.panel.get_app_version(FakeApp()), "1.2.3") def test_app_version_from_underscore_version(self): - class FakeApp: - __version__ = version_info_t(1, 2, 3, '', '') + __version__ = version_info_t(1, 2, 3, "", "") - self.assertEqual(self.panel.get_app_version(FakeApp()), '1.2.3') + self.assertEqual(self.panel.get_app_version(FakeApp()), "1.2.3") diff --git a/tests/settings.py b/tests/settings.py index 0a3f1d773..e4d865f03 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -7,112 +7,101 @@ # Quick-start development settings - unsuitable for production -SECRET_KEY = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890' +SECRET_KEY = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890" -INTERNAL_IPS = ['127.0.0.1'] +INTERNAL_IPS = ["127.0.0.1"] -LOGGING_CONFIG = None # avoids spurious output in tests +LOGGING_CONFIG = None # avoids spurious output in tests # Application definition INSTALLED_APPS = [ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'debug_toolbar', - 'django_jinja', - 'tests', + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "debug_toolbar", + "django_jinja", + "tests", ] -MEDIA_URL = '/media/' # Avoids https://code.djangoproject.com/ticket/21451 +MEDIA_URL = "/media/" # Avoids https://code.djangoproject.com/ticket/21451 MIDDLEWARE = [ - 'debug_toolbar.middleware.DebugToolbarMiddleware', - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', + "debug_toolbar.middleware.DebugToolbarMiddleware", + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", ] -ROOT_URLCONF = 'tests.urls' +ROOT_URLCONF = "tests.urls" TEMPLATES = [ { - 'NAME': 'jinja2', - 'BACKEND': 'django_jinja.backend.Jinja2', - 'APP_DIRS': True, - 'DIRS': [os.path.join(BASE_DIR, 'tests', 'templates', 'jinja2')], + "NAME": "jinja2", + "BACKEND": "django_jinja.backend.Jinja2", + "APP_DIRS": True, + "DIRS": [os.path.join(BASE_DIR, "tests", "templates", "jinja2")], }, { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', - ], + "BACKEND": "django.template.backends.django.DjangoTemplates", + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ] }, }, ] -STATIC_ROOT = os.path.join(BASE_DIR, 'tests', 'static') +STATIC_ROOT = os.path.join(BASE_DIR, "tests", "static") -STATIC_URL = '/static/' +STATIC_URL = "/static/" STATICFILES_DIRS = [ - os.path.join(BASE_DIR, 'tests', 'additional_static'), - ("prefix", os.path.join(BASE_DIR, 'tests', 'additional_static')), + os.path.join(BASE_DIR, "tests", "additional_static"), + ("prefix", os.path.join(BASE_DIR, "tests", "additional_static")), ] # Cache and database CACHES = { - 'default': { - 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', - }, - 'second': { - 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', - }, + "default": {"BACKEND": "django.core.cache.backends.locmem.LocMemCache"}, + "second": {"BACKEND": "django.core.cache.backends.locmem.LocMemCache"}, } if os.environ.get("DJANGO_DATABASE_ENGINE") == "postgresql": DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.postgresql', - 'NAME': 'debug-toolbar', - } + "default": {"ENGINE": "django.db.backends.postgresql", "NAME": "debug-toolbar"} } -elif os.environ.get('DJANGO_DATABASE_ENGINE') == 'mysql': +elif os.environ.get("DJANGO_DATABASE_ENGINE") == "mysql": # % mysql - # mysql> CREATE USER 'debug_toolbar'@'localhost' IDENTIFIED BY ''; - # mysql> GRANT ALL PRIVILEGES ON debug_toolbar.* TO 'test_debug_toolbar'@'localhost'; + # CREATE USER 'debug_toolbar'@'localhost' IDENTIFIED BY ''; + # GRANT ALL PRIVILEGES ON debug_toolbar.* TO 'test_debug_toolbar'@'localhost'; DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.mysql', - 'NAME': 'debug_toolbar', - 'USER': 'debug_toolbar', + "default": { + "ENGINE": "django.db.backends.mysql", + "NAME": "debug_toolbar", + "USER": "debug_toolbar", } } else: - DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - } - } + DATABASES = {"default": {"ENGINE": "django.db.backends.sqlite3"}} # Debug Toolbar configuration DEBUG_TOOLBAR_CONFIG = { # Django's test client sets wsgi.multiprocess to True inappropriately - 'RENDER_PANELS': False, + "RENDER_PANELS": False } diff --git a/tests/test_integration.py b/tests/test_integration.py index ae191b403..e440f6ae0 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -32,7 +32,6 @@ @override_settings(DEBUG=True) class DebugToolbarTestCase(BaseTestCase): - def test_show_toolbar(self): self.assertTrue(show_toolbar(self.request)) @@ -47,41 +46,43 @@ def test_show_toolbar_INTERNAL_IPS(self): def _resolve_stats(self, path): # takes stats from Request panel self.request.path = path - panel = self.toolbar.get_panel_by_id('RequestPanel') + panel = self.toolbar.get_panel_by_id("RequestPanel") panel.process_request(self.request) panel.process_response(self.request, self.response) panel.generate_stats(self.request, self.response) return panel.get_stats() def test_url_resolving_positional(self): - stats = self._resolve_stats('/resolving1/a/b/') - self.assertEqual(stats['view_urlname'], 'positional-resolving') - self.assertEqual(stats['view_func'], 'tests.views.resolving_view') - self.assertEqual(stats['view_args'], ('a', 'b')) - self.assertEqual(stats['view_kwargs'], {}) + stats = self._resolve_stats("/resolving1/a/b/") + self.assertEqual(stats["view_urlname"], "positional-resolving") + self.assertEqual(stats["view_func"], "tests.views.resolving_view") + self.assertEqual(stats["view_args"], ("a", "b")) + self.assertEqual(stats["view_kwargs"], {}) def test_url_resolving_named(self): - stats = self._resolve_stats('/resolving2/a/b/') - self.assertEqual(stats['view_args'], ()) - self.assertEqual(stats['view_kwargs'], {'arg1': 'a', 'arg2': 'b'}) + stats = self._resolve_stats("/resolving2/a/b/") + self.assertEqual(stats["view_args"], ()) + self.assertEqual(stats["view_kwargs"], {"arg1": "a", "arg2": "b"}) def test_url_resolving_mixed(self): - stats = self._resolve_stats('/resolving3/a/') - self.assertEqual(stats['view_args'], ('a',)) - self.assertEqual(stats['view_kwargs'], {'arg2': 'default'}) + stats = self._resolve_stats("/resolving3/a/") + self.assertEqual(stats["view_args"], ("a",)) + self.assertEqual(stats["view_kwargs"], {"arg2": "default"}) def test_url_resolving_bad(self): - stats = self._resolve_stats('/non-existing-url/') - self.assertEqual(stats['view_urlname'], 'None') - self.assertEqual(stats['view_args'], 'None') - self.assertEqual(stats['view_kwargs'], 'None') - self.assertEqual(stats['view_func'], '') + stats = self._resolve_stats("/non-existing-url/") + self.assertEqual(stats["view_urlname"], "None") + self.assertEqual(stats["view_args"], "None") + self.assertEqual(stats["view_kwargs"], "None") + self.assertEqual(stats["view_func"], "") # Django doesn't guarantee that process_request, process_view and # process_response always get called in this order. def test_middleware_view_only(self): - DebugToolbarMiddleware().process_view(self.request, regular_view, ('title',), {}) + DebugToolbarMiddleware().process_view( + self.request, regular_view, ("title",), {} + ) def test_middleware_response_only(self): DebugToolbarMiddleware().process_response(self.request, self.response) @@ -90,169 +91,177 @@ def test_middleware_response_insertion(self): resp = regular_view(self.request, "İ") DebugToolbarMiddleware().process_response(self.request, resp) # check toolbar insertion before "" - self.assertContains(resp, '\n') + self.assertContains(resp, "\n") def test_cache_page(self): - self.client.get('/cached_view/') - self.assertEqual( - len(self.toolbar.get_panel_by_id('CachePanel').calls), 3) - self.client.get('/cached_view/') - self.assertEqual( - len(self.toolbar.get_panel_by_id('CachePanel').calls), 5) + self.client.get("/cached_view/") + self.assertEqual(len(self.toolbar.get_panel_by_id("CachePanel").calls), 3) + self.client.get("/cached_view/") + self.assertEqual(len(self.toolbar.get_panel_by_id("CachePanel").calls), 5) @override_settings(DEBUG=True) class DebugToolbarIntegrationTestCase(TestCase): - def test_middleware(self): - response = self.client.get('/execute_sql/') + response = self.client.get("/execute_sql/") self.assertEqual(response.status_code, 200) - @override_settings(DEFAULT_CHARSET='iso-8859-1') + @override_settings(DEFAULT_CHARSET="iso-8859-1") def test_non_utf8_charset(self): - response = self.client.get('/regular/ASCII/') - self.assertContains(response, 'ASCII') # template - self.assertContains(response, 'djDebug') # toolbar + response = self.client.get("/regular/ASCII/") + self.assertContains(response, "ASCII") # template + self.assertContains(response, "djDebug") # toolbar - response = self.client.get('/regular/LÀTÍN/') - self.assertContains(response, 'LÀTÍN') # template - self.assertContains(response, 'djDebug') # toolbar + response = self.client.get("/regular/LÀTÍN/") + self.assertContains(response, "LÀTÍN") # template + self.assertContains(response, "djDebug") # toolbar def test_html5_validation(self): - response = self.client.get('/regular/HTML5/') + response = self.client.get("/regular/HTML5/") parser = html5lib.HTMLParser() content = response.content parser.parse(content) if parser.errors: - default_msg = ['Content is invalid HTML:'] - lines = content.split(b'\n') + default_msg = ["Content is invalid HTML:"] + lines = content.split(b"\n") for position, errorcode, datavars in parser.errors: - default_msg.append(' %s' % html5lib.constants.E[errorcode] % datavars) - default_msg.append(' %r' % lines[position[0] - 1]) - msg = self._formatMessage(None, '\n'.join(default_msg)) + default_msg.append(" %s" % html5lib.constants.E[errorcode] % datavars) + default_msg.append(" %r" % lines[position[0] - 1]) + msg = self._formatMessage(None, "\n".join(default_msg)) raise self.failureException(msg) def test_render_panel_checks_show_toolbar(self): toolbar = DebugToolbar(None) toolbar.store() - url = '/__debug__/render_panel/' - data = {'store_id': toolbar.store_id, 'panel_id': 'VersionsPanel'} + url = "/__debug__/render_panel/" + data = {"store_id": toolbar.store_id, "panel_id": "VersionsPanel"} response = self.client.get(url, data) self.assertEqual(response.status_code, 200) - response = self.client.get(url, data, HTTP_X_REQUESTED_WITH='XMLHttpRequest') + response = self.client.get(url, data, HTTP_X_REQUESTED_WITH="XMLHttpRequest") self.assertEqual(response.status_code, 200) with self.settings(INTERNAL_IPS=[]): response = self.client.get(url, data) self.assertEqual(response.status_code, 404) - response = self.client.get(url, data, HTTP_X_REQUESTED_WITH='XMLHttpRequest') + response = self.client.get( + url, data, HTTP_X_REQUESTED_WITH="XMLHttpRequest" + ) self.assertEqual(response.status_code, 404) def test_template_source_checks_show_toolbar(self): - template = get_template('basic.html') - url = '/__debug__/template_source/' + template = get_template("basic.html") + url = "/__debug__/template_source/" data = { - 'template': template.template.name, - 'template_origin': signing.dumps(template.template.origin.name) + "template": template.template.name, + "template_origin": signing.dumps(template.template.origin.name), } response = self.client.get(url, data) self.assertEqual(response.status_code, 200) - response = self.client.get(url, data, HTTP_X_REQUESTED_WITH='XMLHttpRequest') + response = self.client.get(url, data, HTTP_X_REQUESTED_WITH="XMLHttpRequest") self.assertEqual(response.status_code, 200) with self.settings(INTERNAL_IPS=[]): response = self.client.get(url, data) self.assertEqual(response.status_code, 404) - response = self.client.get(url, data, HTTP_X_REQUESTED_WITH='XMLHttpRequest') + response = self.client.get( + url, data, HTTP_X_REQUESTED_WITH="XMLHttpRequest" + ) self.assertEqual(response.status_code, 404) def test_sql_select_checks_show_toolbar(self): - url = '/__debug__/sql_select/' + url = "/__debug__/sql_select/" data = { - 'sql': 'SELECT * FROM auth_user', - 'raw_sql': 'SELECT * FROM auth_user', - 'params': '{}', - 'alias': 'default', - 'duration': '0', - 'hash': '6e12daa636b8c9a8be993307135458f90a877606', + "sql": "SELECT * FROM auth_user", + "raw_sql": "SELECT * FROM auth_user", + "params": "{}", + "alias": "default", + "duration": "0", + "hash": "6e12daa636b8c9a8be993307135458f90a877606", } response = self.client.post(url, data) self.assertEqual(response.status_code, 200) - response = self.client.post(url, data, HTTP_X_REQUESTED_WITH='XMLHttpRequest') + response = self.client.post(url, data, HTTP_X_REQUESTED_WITH="XMLHttpRequest") self.assertEqual(response.status_code, 200) with self.settings(INTERNAL_IPS=[]): response = self.client.post(url, data) self.assertEqual(response.status_code, 404) - response = self.client.post(url, data, HTTP_X_REQUESTED_WITH='XMLHttpRequest') + response = self.client.post( + url, data, HTTP_X_REQUESTED_WITH="XMLHttpRequest" + ) self.assertEqual(response.status_code, 404) def test_sql_explain_checks_show_toolbar(self): - url = '/__debug__/sql_explain/' + url = "/__debug__/sql_explain/" data = { - 'sql': 'SELECT * FROM auth_user', - 'raw_sql': 'SELECT * FROM auth_user', - 'params': '{}', - 'alias': 'default', - 'duration': '0', - 'hash': '6e12daa636b8c9a8be993307135458f90a877606', + "sql": "SELECT * FROM auth_user", + "raw_sql": "SELECT * FROM auth_user", + "params": "{}", + "alias": "default", + "duration": "0", + "hash": "6e12daa636b8c9a8be993307135458f90a877606", } response = self.client.post(url, data) self.assertEqual(response.status_code, 200) - response = self.client.post(url, data, HTTP_X_REQUESTED_WITH='XMLHttpRequest') + response = self.client.post(url, data, HTTP_X_REQUESTED_WITH="XMLHttpRequest") self.assertEqual(response.status_code, 200) with self.settings(INTERNAL_IPS=[]): response = self.client.post(url, data) self.assertEqual(response.status_code, 404) - response = self.client.post(url, data, HTTP_X_REQUESTED_WITH='XMLHttpRequest') + response = self.client.post( + url, data, HTTP_X_REQUESTED_WITH="XMLHttpRequest" + ) self.assertEqual(response.status_code, 404) def test_sql_profile_checks_show_toolbar(self): - url = '/__debug__/sql_profile/' + url = "/__debug__/sql_profile/" data = { - 'sql': 'SELECT * FROM auth_user', - 'raw_sql': 'SELECT * FROM auth_user', - 'params': '{}', - 'alias': 'default', - 'duration': '0', - 'hash': '6e12daa636b8c9a8be993307135458f90a877606', + "sql": "SELECT * FROM auth_user", + "raw_sql": "SELECT * FROM auth_user", + "params": "{}", + "alias": "default", + "duration": "0", + "hash": "6e12daa636b8c9a8be993307135458f90a877606", } response = self.client.post(url, data) self.assertEqual(response.status_code, 200) - response = self.client.post(url, data, HTTP_X_REQUESTED_WITH='XMLHttpRequest') + response = self.client.post(url, data, HTTP_X_REQUESTED_WITH="XMLHttpRequest") self.assertEqual(response.status_code, 200) with self.settings(INTERNAL_IPS=[]): response = self.client.post(url, data) self.assertEqual(response.status_code, 404) - response = self.client.post(url, data, HTTP_X_REQUESTED_WITH='XMLHttpRequest') + response = self.client.post( + url, data, HTTP_X_REQUESTED_WITH="XMLHttpRequest" + ) self.assertEqual(response.status_code, 404) - @override_settings(DEBUG_TOOLBAR_CONFIG={'RENDER_PANELS': True}) + @override_settings(DEBUG_TOOLBAR_CONFIG={"RENDER_PANELS": True}) def test_data_store_id_not_rendered_when_none(self): - url = '/regular/basic/' + url = "/regular/basic/" response = self.client.get(url) self.assertIn(b'id="djDebug"', response.content) - self.assertNotIn(b'data-store-id', response.content) + self.assertNotIn(b"data-store-id", response.content) def test_view_returns_template_response(self): - response = self.client.get('/template_response/basic/') + response = self.client.get("/template_response/basic/") self.assertEqual(response.status_code, 200) - @override_settings(DEBUG_TOOLBAR_CONFIG={'DISABLE_PANELS': set()}) + @override_settings(DEBUG_TOOLBAR_CONFIG={"DISABLE_PANELS": set()}) def test_incercept_redirects(self): - response = self.client.get('/redirect/') + response = self.client.get("/redirect/") self.assertEqual(response.status_code, 200) # Link to LOCATION header. self.assertIn(b'href="/regular/redirect/"', response.content) @unittest.skipIf(webdriver is None, "selenium isn't installed") -@unittest.skipUnless('DJANGO_SELENIUM_TESTS' in os.environ, "selenium tests not requested") +@unittest.skipUnless( + "DJANGO_SELENIUM_TESTS" in os.environ, "selenium tests not requested" +) @override_settings(DEBUG=True) class DebugToolbarLiveTestCase(StaticLiveServerTestCase): - @classmethod def setUpClass(cls): super(DebugToolbarLiveTestCase, cls).setUpClass() @@ -264,95 +273,112 @@ def tearDownClass(cls): super(DebugToolbarLiveTestCase, cls).tearDownClass() def test_basic(self): - self.selenium.get(self.live_server_url + '/regular/basic/') - version_panel = self.selenium.find_element_by_id('VersionsPanel') + self.selenium.get(self.live_server_url + "/regular/basic/") + version_panel = self.selenium.find_element_by_id("VersionsPanel") # Versions panel isn't loaded with self.assertRaises(NoSuchElementException): - version_panel.find_element_by_tag_name('table') + version_panel.find_element_by_tag_name("table") # Click to show the versions panel - self.selenium.find_element_by_class_name('VersionsPanel').click() + self.selenium.find_element_by_class_name("VersionsPanel").click() # Version panel loads table = WebDriverWait(self.selenium, timeout=10).until( - lambda selenium: version_panel.find_element_by_tag_name('table')) + lambda selenium: version_panel.find_element_by_tag_name("table") + ) self.assertIn("Name", table.text) self.assertIn("Version", table.text) - @override_settings(DEBUG_TOOLBAR_CONFIG={ - 'DISABLE_PANELS': - {'debug_toolbar.panels.redirects.RedirectsPanel'} - }) + @override_settings( + DEBUG_TOOLBAR_CONFIG={ + "DISABLE_PANELS": {"debug_toolbar.panels.redirects.RedirectsPanel"} + } + ) def test_basic_jinja(self): - self.selenium.get(self.live_server_url + '/regular_jinja/basic') - template_panel = self.selenium.find_element_by_id('TemplatesPanel') + self.selenium.get(self.live_server_url + "/regular_jinja/basic") + template_panel = self.selenium.find_element_by_id("TemplatesPanel") # Click to show the template panel - self.selenium.find_element_by_class_name('TemplatesPanel').click() + self.selenium.find_element_by_class_name("TemplatesPanel").click() - self.assertIn('Templates (1 rendered)', template_panel.text) - self.assertIn('jinja2/basic.jinja', template_panel.text) + self.assertIn("Templates (1 rendered)", template_panel.text) + self.assertIn("jinja2/basic.jinja", template_panel.text) - @override_settings(DEBUG_TOOLBAR_CONFIG={'RESULTS_CACHE_SIZE': 0}) + @override_settings(DEBUG_TOOLBAR_CONFIG={"RESULTS_CACHE_SIZE": 0}) def test_expired_store(self): - self.selenium.get(self.live_server_url + '/regular/basic/') - version_panel = self.selenium.find_element_by_id('VersionsPanel') + self.selenium.get(self.live_server_url + "/regular/basic/") + version_panel = self.selenium.find_element_by_id("VersionsPanel") # Click to show the version panel - self.selenium.find_element_by_class_name('VersionsPanel').click() + self.selenium.find_element_by_class_name("VersionsPanel").click() # Version panel doesn't loads error = WebDriverWait(self.selenium, timeout=10).until( - lambda selenium: version_panel.find_element_by_tag_name('p')) + lambda selenium: version_panel.find_element_by_tag_name("p") + ) self.assertIn("Data for this panel isn't available anymore.", error.text) - @override_settings(DEBUG=True, TEMPLATES=[{ - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'OPTIONS': {'loaders': [( - 'django.template.loaders.cached.Loader', ( - 'django.template.loaders.filesystem.Loader', - 'django.template.loaders.app_directories.Loader', - ) - )]}, - }]) + @override_settings( + DEBUG=True, + TEMPLATES=[ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "OPTIONS": { + "loaders": [ + ( + "django.template.loaders.cached.Loader", + ( + "django.template.loaders.filesystem.Loader", + "django.template.loaders.app_directories.Loader", + ), + ) + ] + }, + } + ], + ) def test_django_cached_template_loader(self): - self.selenium.get(self.live_server_url + '/regular/basic/') - version_panel = self.selenium.find_element_by_id('TemplatesPanel') + self.selenium.get(self.live_server_url + "/regular/basic/") + version_panel = self.selenium.find_element_by_id("TemplatesPanel") # Click to show the versions panel - self.selenium.find_element_by_class_name('TemplatesPanel').click() + self.selenium.find_element_by_class_name("TemplatesPanel").click() # Version panel loads trigger = WebDriverWait(self.selenium, timeout=10).until( - lambda selenium: version_panel.find_element_by_css_selector( - '.remoteCall')) + lambda selenium: version_panel.find_element_by_css_selector(".remoteCall") + ) trigger.click() # Verify the code is displayed WebDriverWait(self.selenium, timeout=10).until( lambda selenium: self.selenium.find_element_by_css_selector( - '#djDebugWindow code')) + "#djDebugWindow code" + ) + ) @override_settings(DEBUG=True) class DebugToolbarSystemChecksTestCase(BaseTestCase): @override_settings( MIDDLEWARE=[ - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.middleware.gzip.GZipMiddleware', - 'debug_toolbar.middleware.DebugToolbarMiddleware', + "django.contrib.messages.middleware.MessageMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.middleware.gzip.GZipMiddleware", + "debug_toolbar.middleware.DebugToolbarMiddleware", ] ) def test_check_good_configuration(self): messages = run_checks() self.assertEqual(messages, []) - @override_settings(MIDDLEWARE=[ - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - ]) + @override_settings( + MIDDLEWARE=[ + "django.contrib.messages.middleware.MessageMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + ] + ) def test_check_missing_middleware_error(self): messages = run_checks() self.assertEqual( @@ -363,17 +389,17 @@ def test_check_missing_middleware_error(self): "missing from MIDDLEWARE.", hint="Add debug_toolbar.middleware.DebugToolbarMiddleware " "to MIDDLEWARE.", - id='debug_toolbar.W001', - ), - ] + id="debug_toolbar.W001", + ) + ], ) @override_settings( MIDDLEWARE=[ - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'debug_toolbar.middleware.DebugToolbarMiddleware', - 'django.middleware.gzip.GZipMiddleware', + "django.contrib.messages.middleware.MessageMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "debug_toolbar.middleware.DebugToolbarMiddleware", + "django.middleware.gzip.GZipMiddleware", ] ) def test_check_gzip_middleware_error(self): @@ -388,7 +414,7 @@ def test_check_gzip_middleware_error(self): hint="Move debug_toolbar.middleware.DebugToolbarMiddleware " "to after django.middleware.gzip.GZipMiddleware in " "MIDDLEWARE.", - id='debug_toolbar.W003', - ), - ] + id="debug_toolbar.W003", + ) + ], ) diff --git a/tests/test_utils.py b/tests/test_utils.py index e623a5762..c761eaa9a 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -6,19 +6,20 @@ class GetNameFromObjTestCase(unittest.TestCase): - def test_func(self): def x(): return 1 + res = get_name_from_obj(x) - self.assertEqual(res, 'tests.test_utils.x') + self.assertEqual(res, "tests.test_utils.x") def test_lambda(self): res = get_name_from_obj(lambda: 1) - self.assertEqual(res, 'tests.test_utils.') + self.assertEqual(res, "tests.test_utils.") def test_class(self): class A: pass + res = get_name_from_obj(A) - self.assertEqual(res, 'tests.test_utils.A') + self.assertEqual(res, "tests.test_utils.A") diff --git a/tests/urls.py b/tests/urls.py index 9cee7e501..152059cc7 100644 --- a/tests/urls.py +++ b/tests/urls.py @@ -10,16 +10,16 @@ from .models import NonAsciiRepr urlpatterns = [ - url(r'^resolving1/(.+)/(.+)/$', views.resolving_view, name='positional-resolving'), - url(r'^resolving2/(?P.+)/(?P.+)/$', views.resolving_view), - url(r'^resolving3/(.+)/$', views.resolving_view, {'arg2': 'default'}), - url(r'^regular/(?P.*)/$', views.regular_view), - url(r'^template_response/(?P.*)/$', views.template_response_view), - url(r'^regular_jinja/(?P.*)/$', views.regular_jinjia_view), - url(r'^non_ascii_request/$', views.regular_view, {'title': NonAsciiRepr()}), - url(r'^new_user/$', views.new_user), - url(r'^execute_sql/$', views.execute_sql), - url(r'^cached_view/$', views.cached_view), - url(r'^redirect/$', views.redirect_view), - url(r'^__debug__/', include(debug_toolbar.urls)), + url(r"^resolving1/(.+)/(.+)/$", views.resolving_view, name="positional-resolving"), + url(r"^resolving2/(?P.+)/(?P.+)/$", views.resolving_view), + url(r"^resolving3/(.+)/$", views.resolving_view, {"arg2": "default"}), + url(r"^regular/(?P.*)/$", views.regular_view), + url(r"^template_response/(?P.*)/$", views.template_response_view), + url(r"^regular_jinja/(?P.*)/$", views.regular_jinjia_view), + url(r"^non_ascii_request/$", views.regular_view, {"title": NonAsciiRepr()}), + url(r"^new_user/$", views.new_user), + url(r"^execute_sql/$", views.execute_sql), + url(r"^cached_view/$", views.cached_view), + url(r"^redirect/$", views.redirect_view), + url(r"^__debug__/", include(debug_toolbar.urls)), ] diff --git a/tests/views.py b/tests/views.py index 1811394da..01b1cf30b 100644 --- a/tests/views.py +++ b/tests/views.py @@ -15,16 +15,16 @@ def execute_sql(request): def regular_view(request, title): - return render(request, 'basic.html', {'title': title}) + return render(request, "basic.html", {"title": title}) def template_response_view(request, title): - return TemplateResponse(request, 'basic.html', {'title': title}) + return TemplateResponse(request, "basic.html", {"title": title}) -def new_user(request, username='joe'): +def new_user(request, username="joe"): User.objects.create_user(username=username) - return render(request, 'basic.html', {'title': 'new user'}) + return render(request, "basic.html", {"title": "new user"}) def resolving_view(request, arg1, arg2): @@ -38,13 +38,13 @@ def cached_view(request): def regular_jinjia_view(request, title): - return render(request, 'jinja2/basic.jinja', {'title': title}) + return render(request, "jinja2/basic.jinja", {"title": title}) def listcomp_view(request): lst = [i for i in range(50000) if i % 2 == 0] - return render(request, 'basic.html', {'title': 'List comprehension', 'lst': lst}) + return render(request, "basic.html", {"title": "List comprehension", "lst": lst}) def redirect_view(request): - return HttpResponseRedirect('/regular/redirect/') + return HttpResponseRedirect("/regular/redirect/") diff --git a/tox.ini b/tox.ini index c50bc353b..b2c323e7d 100644 --- a/tox.ini +++ b/tox.ini @@ -6,7 +6,7 @@ envlist = py{35,36,37}-djmaster postgresql, flake8, - isort, + style, readme [testenv] @@ -46,17 +46,24 @@ commands = make coverage TEST_ARGS='{posargs:tests}' basepython = python3 commands = make flake8 deps = flake8 +skip_install = true -[testenv:isort] +[testenv:style] basepython = python3 -commands = make isort_check_only -deps = isort +commands = make style_check +deps = + black + flake8 + isort +skip_install = true [testenv:jshint] basepython = python3 commands = make jshint +skip_install = true [testenv:readme] basepython = python3 commands = python setup.py check -r -s deps = readme_renderer +skip_install = true From 6f772a45de80c459166ed7ac7e9fd41bc0063973 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sun, 18 Nov 2018 18:20:12 -0800 Subject: [PATCH 5/6] simplified logic and fixed test --- debug_toolbar/panels/sql/tracking.py | 2 -- tests/panels/test_sql.py | 5 +++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/debug_toolbar/panels/sql/tracking.py b/debug_toolbar/panels/sql/tracking.py index 69e7ed8e0..c9b84cb30 100644 --- a/debug_toolbar/panels/sql/tracking.py +++ b/debug_toolbar/panels/sql/tracking.py @@ -92,8 +92,6 @@ 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 e17be15bd..90d0d3852 100644 --- a/tests/panels/test_sql.py +++ b/tests/panels/test_sql.py @@ -123,14 +123,15 @@ def test_binary_param_force_text(self): with connection.cursor() as cursor: cursor.execute("SELECT * FROM auth_user WHERE username = %s", [b"\xff"]) + self.panel.process_response(self.request, self.response) + self.panel.generate_stats(self.request, self.response) + 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") def test_raw_query_param_conversion(self): From efc91807b258555b7e4e9c9713cd1caf3dc753a2 Mon Sep 17 00:00:00 2001 From: dbowd Date: Tue, 27 Nov 2018 10:38:10 -0800 Subject: [PATCH 6/6] Update test_sql.py --- tests/panels/test_sql.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/panels/test_sql.py b/tests/panels/test_sql.py index 90d0d3852..4092cfbc1 100644 --- a/tests/panels/test_sql.py +++ b/tests/panels/test_sql.py @@ -132,7 +132,6 @@ def test_binary_param_force_text(self): "SELECT * FROM auth_user WHERE username = '\ufffd'", ) - @unittest.skipUnless(connection.vendor != "sqlite", "Test invalid for SQLite") def test_raw_query_param_conversion(self): self.assertEqual(len(self.panel._queries), 0)