""" Debug Toolbar middleware """ from __future__ import absolute_import, unicode_literals import re import threading from django.conf import settings from django.utils import six from django.utils.deprecation import MiddlewareMixin from django.utils.encoding import force_text from django.utils.lru_cache import lru_cache from django.utils.module_loading import import_string from debug_toolbar import settings as dt_settings from debug_toolbar.toolbar import DebugToolbar _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: return False return bool(settings.DEBUG) @lru_cache() 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'] if isinstance(func_or_path, six.string_types): return import_string(func_or_path) else: return func_or_path 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): # Decide whether the toolbar is active for this request. show_toolbar = get_show_toolbar() if not show_toolbar(request): return # Don't render the toolbar during AJAX requests. if request.is_ajax(): return toolbar = DebugToolbar(request) self.__class__.debug_toolbars[threading.current_thread().ident] = toolbar # Activate instrumentation ie. monkey-patch. for panel in toolbar.enabled_panels: panel.enable_instrumentation() # Run process_request methods of panels like Django middleware. response = None for panel in toolbar.enabled_panels: response = panel.process_request(request) if response: break return response def process_view(self, request, view_func, view_args, view_kwargs): toolbar = self.__class__.debug_toolbars.get(threading.current_thread().ident) if not toolbar: return # Run process_view methods of panels like Django middleware. response = None for panel in toolbar.enabled_panels: response = panel.process_view(request, view_func, view_args, view_kwargs) if response: break return response def process_response(self, request, response): toolbar = self.__class__.debug_toolbars.pop(threading.current_thread().ident, None) if not toolbar: return response # Run process_response methods of panels like Django middleware. for panel in reversed(toolbar.enabled_panels): new_response = panel.process_response(request, response) if new_response: response = new_response # Deactivate instrumentation ie. monkey-unpatch. This must run # regardless of the response. Keep 'return' clauses below. # (NB: Django's model for middleware doesn't guarantee anything.) for panel in reversed(toolbar.enabled_panels): 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)): 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) # Insert the toolbar in the response. content = force_text(response.content, encoding=response.charset) 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: # When the toolbar will be inserted for sure, generate the stats. for panel in reversed(toolbar.enabled_panels): panel.generate_stats(request, response) panel.generate_server_timing(request, response) 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) return response @staticmethod def generate_server_timing_header(response, panels): data = [] for panel in panels: stats = panel.get_server_timing_stats() if not stats: continue 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'))) if data: response['Server-Timing'] = ', '.join(data) return response