From c960b2fdf7164d95629de9fca5298ad6d5ba9053 Mon Sep 17 00:00:00 2001 From: tschilling Date: Mon, 6 Jul 2015 16:53:03 -0400 Subject: [PATCH 1/5] Create generate_stats, an optional method similar to process_response. generate_stats is not always called. It will only be called when the toolbar is going to insert itself into the response. The method process_response will always be called and should be used for panels that need to interrupt the request such as the RedirectsPanel. This update is to address issue #517. --- debug_toolbar/middleware.py | 4 ++++ debug_toolbar/panels/__init__.py | 20 +++++++++++++++++++- debug_toolbar/panels/cache.py | 2 +- debug_toolbar/panels/headers.py | 2 +- debug_toolbar/panels/logging.py | 2 +- debug_toolbar/panels/profiling.py | 2 +- debug_toolbar/panels/request.py | 2 +- debug_toolbar/panels/settings.py | 2 +- debug_toolbar/panels/signals.py | 2 +- debug_toolbar/panels/sql/panel.py | 2 +- debug_toolbar/panels/staticfiles.py | 2 +- debug_toolbar/panels/templates/panel.py | 2 +- debug_toolbar/panels/timer.py | 2 +- debug_toolbar/panels/versions.py | 2 +- tests/panels/test_cache.py | 13 +++++++++++++ tests/panels/test_logging.py | 18 ++++++++++++++++++ tests/panels/test_profiling.py | 14 ++++++++++++++ tests/panels/test_redirects.py | 11 +++++++++++ tests/panels/test_request.py | 15 +++++++++++++++ tests/panels/test_sql.py | 14 ++++++++++++++ tests/panels/test_staticfiles.py | 16 ++++++++++++++++ tests/panels/test_template.py | 17 +++++++++++++++++ tests/test_integration.py | 1 + 23 files changed, 154 insertions(+), 13 deletions(-) diff --git a/debug_toolbar/middleware.py b/debug_toolbar/middleware.py index 60c521ff6..939bb67c4 100644 --- a/debug_toolbar/middleware.py +++ b/debug_toolbar/middleware.py @@ -126,6 +126,10 @@ def process_response(self, request, response): # When the body ends with a newline, there's two trailing groups. bits.append(''.join(m[0] for m in matches if m[1] == '')) if len(bits) > 1: + # When the toolbar will be inserted for sure, generate the stats. + for panel in reversed(toolbar.enabled_panels): + panel.generate_stats(request, response) + bits[-2] += toolbar.render_toolbar() response.content = insert_before.join(bits) if response.get('Content-Length', None): diff --git a/debug_toolbar/panels/__init__.py b/debug_toolbar/panels/__init__.py index 76febd2ff..9f2c3c39a 100644 --- a/debug_toolbar/panels/__init__.py +++ b/debug_toolbar/panels/__init__.py @@ -167,10 +167,28 @@ def process_view(self, request, view_func, view_args, view_kwargs): def process_response(self, request, response): """ - Like process_response in Django's middleware. + Like process_response in Django's middleware. This is similar to + :meth:`generate_stats`, but will be executed on every request. It + should be used when either the logic needs to be executed on every + request or it needs to change the response entirely, such as + :class:`RedirectsPanel`. Write panel logic related to the response there. Post-process data gathered while the view executed. Save data with :meth:`record_stats`. + + Return a response to overwrite the existing response. + """ + + def generate_stats(self, request, response): + """ + Similar to :meth:`process_response`, but may not be executed on every + request. This will only be called if the toolbar will be inserted into + the request. + + Write panel logic related to the response there. Post-process data + gathered while the view executed. Save data with :meth:`record_stats`. + + Does not return a value. """ diff --git a/debug_toolbar/panels/cache.py b/debug_toolbar/panels/cache.py index 6e799dee0..87b753e94 100644 --- a/debug_toolbar/panels/cache.py +++ b/debug_toolbar/panels/cache.py @@ -244,7 +244,7 @@ def disable_instrumentation(self): middleware_cache.caches = original_caches cache.get_cache = original_get_cache - def process_response(self, request, response): + def generate_stats(self, request, response): self.record_stats({ 'total_calls': len(self.calls), 'calls': self.calls, diff --git a/debug_toolbar/panels/headers.py b/debug_toolbar/panels/headers.py index 48c9b9e41..0af226594 100644 --- a/debug_toolbar/panels/headers.py +++ b/debug_toolbar/panels/headers.py @@ -47,7 +47,7 @@ def process_request(self, request): 'environ': self.environ, }) - def process_response(self, request, response): + def generate_stats(self, request, response): self.response_headers = OrderedDict(sorted(response.items())) self.record_stats({ 'response_headers': self.response_headers, diff --git a/debug_toolbar/panels/logging.py b/debug_toolbar/panels/logging.py index b451019cd..41601055a 100644 --- a/debug_toolbar/panels/logging.py +++ b/debug_toolbar/panels/logging.py @@ -74,7 +74,7 @@ def nav_subtitle(self): def process_request(self, request): collector.clear_collection() - def process_response(self, request, response): + def generate_stats(self, request, response): records = collector.get_collection() self._records[threading.currentThread()] = records collector.clear_collection() diff --git a/debug_toolbar/panels/profiling.py b/debug_toolbar/panels/profiling.py index 8ee3d6f78..d452f199b 100644 --- a/debug_toolbar/panels/profiling.py +++ b/debug_toolbar/panels/profiling.py @@ -142,7 +142,7 @@ def add_node(self, func_list, func, max_depth, cum_time=0.1): func.has_subfuncs = True self.add_node(func_list, subfunc, max_depth, cum_time=cum_time) - def process_response(self, request, response): + def generate_stats(self, request, response): if not hasattr(self, 'profiler'): return None # Could be delayed until the panel content is requested (perf. optim.) diff --git a/debug_toolbar/panels/request.py b/debug_toolbar/panels/request.py index b5caa5230..0aecda296 100644 --- a/debug_toolbar/panels/request.py +++ b/debug_toolbar/panels/request.py @@ -25,7 +25,7 @@ def nav_subtitle(self): view_func = self.get_stats().get('view_func', '') return view_func.rsplit('.', 1)[-1] - def process_response(self, request, response): + 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)], diff --git a/debug_toolbar/panels/settings.py b/debug_toolbar/panels/settings.py index 54afd23e5..64d73361f 100644 --- a/debug_toolbar/panels/settings.py +++ b/debug_toolbar/panels/settings.py @@ -19,7 +19,7 @@ class SettingsPanel(Panel): def title(self): return _("Settings from %s") % settings.SETTINGS_MODULE - def process_response(self, request, response): + def generate_stats(self, request, response): 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 3ec54d838..90bfbe474 100644 --- a/debug_toolbar/panels/signals.py +++ b/debug_toolbar/panels/signals.py @@ -58,7 +58,7 @@ def signals(self): signals[signal_name] = getattr(signals_mod, signal_name) return signals - def process_response(self, request, response): + def generate_stats(self, request, response): signals = [] for name, signal in sorted(self.signals.items(), key=lambda x: x[0]): if signal is None: diff --git a/debug_toolbar/panels/sql/panel.py b/debug_toolbar/panels/sql/panel.py index 07b184810..2ab21ad13 100644 --- a/debug_toolbar/panels/sql/panel.py +++ b/debug_toolbar/panels/sql/panel.py @@ -136,7 +136,7 @@ def disable_instrumentation(self): for connection in connections.all(): unwrap_cursor(connection) - def process_response(self, request, response): + def generate_stats(self, request, response): colors = contrasting_color_generator() trace_colors = defaultdict(lambda: next(colors)) query_duplicates = defaultdict(lambda: defaultdict(int)) diff --git a/debug_toolbar/panels/staticfiles.py b/debug_toolbar/panels/staticfiles.py index 93dbd7494..dd6d67050 100644 --- a/debug_toolbar/panels/staticfiles.py +++ b/debug_toolbar/panels/staticfiles.py @@ -113,7 +113,7 @@ def nav_subtitle(self): def process_request(self, request): collector.clear_collection() - def process_response(self, request, response): + def generate_stats(self, request, response): used_paths = collector.get_collection() self._paths[threading.currentThread()] = used_paths diff --git a/debug_toolbar/panels/templates/panel.py b/debug_toolbar/panels/templates/panel.py index 1f763e20c..fb2fc0064 100644 --- a/debug_toolbar/panels/templates/panel.py +++ b/debug_toolbar/panels/templates/panel.py @@ -195,7 +195,7 @@ def enable_instrumentation(self): def disable_instrumentation(self): template_rendered.disconnect(self._store_template_info) - def process_response(self, request, response): + def generate_stats(self, request, response): template_context = [] for template_data in self.templates: info = {} diff --git a/debug_toolbar/panels/timer.py b/debug_toolbar/panels/timer.py index 741f57af6..b9fd1c784 100644 --- a/debug_toolbar/panels/timer.py +++ b/debug_toolbar/panels/timer.py @@ -52,7 +52,7 @@ def process_request(self, request): if self.has_content: self._start_rusage = resource.getrusage(resource.RUSAGE_SELF) - def process_response(self, request, response): + def generate_stats(self, request, response): stats = {} if hasattr(self, '_start_time'): stats['total_time'] = (time.time() - self._start_time) * 1000 diff --git a/debug_toolbar/panels/versions.py b/debug_toolbar/panels/versions.py index f1ea56f0a..f56f19c34 100644 --- a/debug_toolbar/panels/versions.py +++ b/debug_toolbar/panels/versions.py @@ -22,7 +22,7 @@ def nav_subtitle(self): template = 'debug_toolbar/panels/versions.html' - def process_response(self, request, response): + def generate_stats(self, request, response): versions = [ ('Python', '%d.%d.%d' % sys.version_info[:3]), ('Django', self.get_app_version(django)), diff --git a/tests/panels/test_cache.py b/tests/panels/test_cache.py index a32d3e39a..750974bdf 100644 --- a/tests/panels/test_cache.py +++ b/tests/panels/test_cache.py @@ -46,3 +46,16 @@ def test_recording_get_cache(self): default_cache.set('foo', 'bar') second_cache.get('foo') self.assertEqual(len(self.panel.calls), 2) + + def test_insert_content(self): + """ + Test that the panel only inserts content after generate_stats and + not the process_response. + """ + 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.panel.generate_stats(self.request, self.response) + # ensure the panel renders correctly. + self.assertIn('café', self.panel.content) diff --git a/tests/panels/test_logging.py b/tests/panels/test_logging.py index 306481941..3322e9cb0 100644 --- a/tests/panels/test_logging.py +++ b/tests/panels/test_logging.py @@ -1,3 +1,5 @@ +# coding: utf-8 + from __future__ import absolute_import, unicode_literals import logging @@ -24,6 +26,7 @@ def test_happy_case(self): 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'] self.assertEqual(1, len(records)) @@ -34,12 +37,26 @@ def test_formatting(self): 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'] self.assertEqual(1, len(records)) 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.panel.process_response(self.request, self.response) + # ensure the panel does not have content yet. + 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) + def test_failing_formatting(self): class BadClass(object): def __str__(self): @@ -49,6 +66,7 @@ def __str__(self): 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'] self.assertEqual(1, len(records)) diff --git a/tests/panels/test_profiling.py b/tests/panels/test_profiling.py index 2731daa6b..6ede65082 100644 --- a/tests/panels/test_profiling.py +++ b/tests/panels/test_profiling.py @@ -23,9 +23,23 @@ def setUp(self): def test_regular_view(self): 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) + 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_response(self.request, self.response) + # ensure the panel does not have content yet. + 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) + @override_settings(DEBUG=True, DEBUG_TOOLBAR_PANELS=['debug_toolbar.panels.profiling.ProfilingPanel']) diff --git a/tests/panels/test_redirects.py b/tests/panels/test_redirects.py index 5f40dd9b2..463059e75 100644 --- a/tests/panels/test_redirects.py +++ b/tests/panels/test_redirects.py @@ -58,3 +58,14 @@ def test_unknown_status_code_with_reason(self): redirect['Location'] = 'http://somewhere/else/' response = self.panel.process_response(self.request, redirect) self.assertContains(response, '369 Look Ma!') + + def test_insert_content(self): + """ + Test that the panel only inserts content after generate_stats and + not the process_response. + """ + redirect = HttpResponse(status=304) + response = self.panel.process_response(self.request, redirect) + self.assertIsNotNone(response) + response = self.panel.generate_stats(self.request, redirect) + self.assertIsNone(response) diff --git a/tests/panels/test_request.py b/tests/panels/test_request.py index 1c60ec708..452086f88 100644 --- a/tests/panels/test_request.py +++ b/tests/panels/test_request.py @@ -19,6 +19,7 @@ def test_non_ascii_session(self): 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) @@ -30,4 +31,18 @@ def test_object_with_non_ascii_repr_in_request_params(self): 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) + + 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.panel.process_response(self.request, self.response) + # ensure the panel does not have content yet. + 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) diff --git a/tests/panels/test_sql.py b/tests/panels/test_sql.py index 80ac105e7..748f06e42 100644 --- a/tests/panels/test_sql.py +++ b/tests/panels/test_sql.py @@ -64,10 +64,24 @@ def test_non_ascii_query(self): 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) + 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'))) + self.panel.process_response(self.request, self.response) + # ensure the panel does not have content yet. + 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) + @unittest.skipUnless(connection.vendor == 'postgresql', 'Test valid only on PostgreSQL') def test_erroneous_query(self): diff --git a/tests/panels/test_staticfiles.py b/tests/panels/test_staticfiles.py index be4f26705..4002087ac 100644 --- a/tests/panels/test_staticfiles.py +++ b/tests/panels/test_staticfiles.py @@ -16,6 +16,7 @@ def setUp(self): 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.' @@ -26,3 +27,18 @@ def test_default_case(self): ['django.contrib.admin', 'debug_toolbar']) self.assertEqual(self.panel.get_staticfiles_dirs(), finders.FileSystemFinder().locations) + + def test_insert_content(self): + """ + Test that the panel only inserts content after generate_stats and + not the process_response. + """ + 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.panel.generate_stats(self.request, self.response) + # ensure the panel renders correctly. + self.assertIn('django.contrib.staticfiles.finders.' + 'AppDirectoriesFinder', self.panel.content) diff --git a/tests/panels/test_template.py b/tests/panels/test_template.py index 7279783e5..cdf12f9ca 100644 --- a/tests/panels/test_template.py +++ b/tests/panels/test_template.py @@ -48,6 +48,22 @@ def test_object_with_non_ascii_repr_in_context(self): 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) + + def test_insert_content(self): + """ + Test that the panel only inserts content after generate_stats and + not the process_response. + """ + t = Template("{{ object }}") + 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.panel.generate_stats(self.request, self.response) + # ensure the panel renders correctly. self.assertIn('nôt åscíì', self.panel.content) def test_custom_context_processor(self): @@ -56,6 +72,7 @@ def test_custom_context_processor(self): c = RequestContext(self.request, processors=[context_processor]) 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) def test_disabled(self): diff --git a/tests/test_integration.py b/tests/test_integration.py index 009a09f1e..7643e241f 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -45,6 +45,7 @@ def _resolve_stats(self, path): 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): From fec2fc4e91bcc3ccd1d338a80a8b8ec62d875bb5 Mon Sep 17 00:00:00 2001 From: tschilling Date: Mon, 6 Jul 2015 16:53:27 -0400 Subject: [PATCH 2/5] Update the translatable strings. --- debug_toolbar/locale/en/LC_MESSAGES/django.po | 36 ++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/debug_toolbar/locale/en/LC_MESSAGES/django.po b/debug_toolbar/locale/en/LC_MESSAGES/django.po index baebc7019..69e0d02b7 100644 --- a/debug_toolbar/locale/en/LC_MESSAGES/django.po +++ b/debug_toolbar/locale/en/LC_MESSAGES/django.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: Django Debug Toolbar\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-05-27 19:40-0400\n" +"POT-Creation-Date: 2015-07-06 16:50-0400\n" "PO-Revision-Date: 2012-03-31 20:10+0000\n" "Last-Translator: \n" "Language-Team: \n" @@ -20,18 +20,18 @@ msgstr "" msgid "Debug Toolbar" msgstr "" -#: panels/cache.py:197 +#: panels/cache.py:209 msgid "Cache" msgstr "" -#: panels/cache.py:202 +#: panels/cache.py:214 #, python-format msgid "%(cache_calls)d call in %(time).2fms" msgid_plural "%(cache_calls)d calls in %(time).2fms" msgstr[0] "" msgstr[1] "" -#: panels/cache.py:210 +#: panels/cache.py:222 #, python-format msgid "Cache calls from %(count)d backend" msgid_plural "Cache calls from %(count)d backends" @@ -292,7 +292,7 @@ msgid "Calls" msgstr "" #: templates/debug_toolbar/panels/cache.html:43 -#: templates/debug_toolbar/panels/sql.html:20 +#: templates/debug_toolbar/panels/sql.html:23 msgid "Time (ms)" msgstr "" @@ -466,36 +466,46 @@ msgid_plural "%(num)s queries" msgstr[0] "" msgstr[1] "" -#: templates/debug_toolbar/panels/sql.html:18 +#: templates/debug_toolbar/panels/sql.html:9 +#, python-format +msgid "including %(dupes)s duplicates" +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:21 msgid "Query" msgstr "" -#: templates/debug_toolbar/panels/sql.html:19 +#: templates/debug_toolbar/panels/sql.html:22 #: templates/debug_toolbar/panels/timer.html:36 msgid "Timeline" msgstr "" -#: templates/debug_toolbar/panels/sql.html:21 +#: templates/debug_toolbar/panels/sql.html:24 msgid "Action" msgstr "" -#: templates/debug_toolbar/panels/sql.html:64 +#: templates/debug_toolbar/panels/sql.html:39 +#, python-format +msgid "Duplicated %(dupes)s times." +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:71 msgid "Connection:" msgstr "" -#: templates/debug_toolbar/panels/sql.html:66 +#: templates/debug_toolbar/panels/sql.html:73 msgid "Isolation level:" msgstr "" -#: templates/debug_toolbar/panels/sql.html:69 +#: templates/debug_toolbar/panels/sql.html:76 msgid "Transaction status:" msgstr "" -#: templates/debug_toolbar/panels/sql.html:83 +#: templates/debug_toolbar/panels/sql.html:90 msgid "(unknown)" msgstr "" -#: templates/debug_toolbar/panels/sql.html:92 +#: templates/debug_toolbar/panels/sql.html:99 msgid "No SQL queries were recorded during this request." msgstr "" From 7d899940bca1555c2022eb04804c7672a17b9311 Mon Sep 17 00:00:00 2001 From: tschilling Date: Tue, 7 Jul 2015 08:39:53 -0400 Subject: [PATCH 3/5] Ignore the call to the profilers disable method in the ProfilingPanel. While this hasn't caused any issues with the actual usage of the toolbar, it has caused an unexpected error within the unit tests. This is because the dictionary returned by stats is converted into a list. The order of this list isn't consistent, while the logic expects it to which gave it the random feel to it. Ignoring the call to the disable should cause the view function to be the only function with zero callers. --- debug_toolbar/panels/profiling.py | 7 ++++++- tests/panels/test_profiling.py | 4 ---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/debug_toolbar/panels/profiling.py b/debug_toolbar/panels/profiling.py index d452f199b..6ff205bae 100644 --- a/debug_toolbar/panels/profiling.py +++ b/debug_toolbar/panels/profiling.py @@ -10,6 +10,11 @@ from colorsys import hsv_to_rgb import os +# 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 = "" + class DjangoDebugToolbarStats(Stats): __root = None @@ -17,7 +22,7 @@ class DjangoDebugToolbarStats(Stats): def get_root_func(self): if self.__root is None: for func, (cc, nc, tt, ct, callers) in self.stats.items(): - if len(callers) == 0: + if len(callers) == 0 and INVALID_PROFILER_FUNC not in func: self.__root = func break return self.__root diff --git a/tests/panels/test_profiling.py b/tests/panels/test_profiling.py index 6ede65082..18387a631 100644 --- a/tests/panels/test_profiling.py +++ b/tests/panels/test_profiling.py @@ -7,7 +7,6 @@ from ..base import BaseTestCase from ..views import regular_view -from debug_toolbar.compat import unittest @override_settings(DEBUG_TOOLBAR_PANELS=['debug_toolbar.panels.profiling.ProfilingPanel']) @@ -17,9 +16,6 @@ def setUp(self): super(ProfilingPanelTestCase, self).setUp() self.panel = self.toolbar.get_panel_by_id('ProfilingPanel') - # This test fails randomly for a reason I don't understand. - - @unittest.expectedFailure def test_regular_view(self): self.panel.process_view(self.request, regular_view, ('profiling',), {}) self.panel.process_response(self.request, self.response) From abeebe09461369b8e40b956fe7c2bd67bb1cd8e1 Mon Sep 17 00:00:00 2001 From: tschilling Date: Mon, 13 Jul 2015 21:39:19 -0400 Subject: [PATCH 4/5] Improve documentation around generate_stats. Added a line for the 1.4 release which will contain this new method. Revised the sphinx method references for generate_stats and process_response to actually link to each other. Following the existing format to link to process_response lead to a link being generated to Django's actual process_response method on the middleware. I had to specifically mention the toolbar's process_response and then updated the generate_stats reference to be consistent between the two methods. --- debug_toolbar/panels/__init__.py | 15 ++++++++------- debug_toolbar/panels/profiling.py | 2 +- docs/changes.rst | 16 ++++++++++++++++ docs/panels.rst | 2 ++ 4 files changed, 27 insertions(+), 8 deletions(-) diff --git a/debug_toolbar/panels/__init__.py b/debug_toolbar/panels/__init__.py index 9f2c3c39a..bf1f63327 100644 --- a/debug_toolbar/panels/__init__.py +++ b/debug_toolbar/panels/__init__.py @@ -168,10 +168,10 @@ def process_view(self, request, view_func, view_args, view_kwargs): def process_response(self, request, response): """ Like process_response in Django's middleware. This is similar to - :meth:`generate_stats`, but will be executed on every request. It - should be used when either the logic needs to be executed on every - request or it needs to change the response entirely, such as - :class:`RedirectsPanel`. + :meth:`generate_stats `, + but will be executed on every request. It should be used when either + the logic needs to be executed on every request or it needs to change + the response entirely, such as :class:`RedirectsPanel`. Write panel logic related to the response there. Post-process data gathered while the view executed. Save data with :meth:`record_stats`. @@ -181,9 +181,10 @@ def process_response(self, request, response): def generate_stats(self, request, response): """ - Similar to :meth:`process_response`, but may not be executed on every - request. This will only be called if the toolbar will be inserted into - the request. + Similar to :meth:`process_response + `, + but may not be executed on every request. This will only be called if + the toolbar will be inserted into the request. Write panel logic related to the response there. Post-process data gathered while the view executed. Save data with :meth:`record_stats`. diff --git a/debug_toolbar/panels/profiling.py b/debug_toolbar/panels/profiling.py index 6ff205bae..33eee0d41 100644 --- a/debug_toolbar/panels/profiling.py +++ b/debug_toolbar/panels/profiling.py @@ -13,7 +13,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 = "" +INVALID_PROFILER_FUNC = '_lsprof.Profiler' class DjangoDebugToolbarStats(Stats): diff --git a/docs/changes.rst b/docs/changes.rst index fb1b90d8e..065a88907 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -1,6 +1,22 @@ Change log ========== +1.4 +--- + +New features +~~~~~~~~~~~~ + +* New panel method :meth:`debug_toolbar.panels.Panel.generate_stats` allows panels + to only record stats when the toolbar is going to be inserted into the + response. + +Bugfixes +~~~~~~~~ + +* Response time for requests of projects with numerous media files has + been improved. + 1.3 --- diff --git a/docs/panels.rst b/docs/panels.rst index 7707a9a65..71023f049 100644 --- a/docs/panels.rst +++ b/docs/panels.rst @@ -315,6 +315,8 @@ CSS API at this time. .. automethod:: debug_toolbar.panels.Panel.process_response + .. automethod:: debug_toolbar.panels.Panel.generate_stats + .. _javascript-api: JavaScript API From 8cfa70e1aa1c8436db2bb0e6609853bf14956729 Mon Sep 17 00:00:00 2001 From: tschilling Date: Sat, 18 Jul 2015 09:30:45 -0400 Subject: [PATCH 5/5] Convert the ProfilingPanels check for the profiler into a function. The func variable is a tuple, so each element needs to be checked for the profiler. --- debug_toolbar/panels/profiling.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/debug_toolbar/panels/profiling.py b/debug_toolbar/panels/profiling.py index 33eee0d41..707a8245b 100644 --- a/debug_toolbar/panels/profiling.py +++ b/debug_toolbar/panels/profiling.py @@ -1,5 +1,6 @@ from __future__ import absolute_import, division, unicode_literals +from django.utils import six from django.utils.translation import ugettext_lazy as _ from django.utils.safestring import mark_safe from debug_toolbar.panels import Panel @@ -16,13 +17,23 @@ INVALID_PROFILER_FUNC = '_lsprof.Profiler' +def contains_profiler(func_tuple): + """Helper function that checks to see if the tuple contains + the INVALID_PROFILE_FUNC in any string value of the tuple.""" + has_profiler = False + for value in func_tuple: + if isinstance(value, six.string_types): + has_profiler |= INVALID_PROFILER_FUNC in value + return has_profiler + + class DjangoDebugToolbarStats(Stats): __root = None def get_root_func(self): if self.__root is None: for func, (cc, nc, tt, ct, callers) in self.stats.items(): - if len(callers) == 0 and INVALID_PROFILER_FUNC not in func: + if len(callers) == 0 and not contains_profiler(func): self.__root = func break return self.__root