1from django.template.loader import render_to_string 2 3from debug_toolbar import settings as dt_settings 4from debug_toolbar.utils import get_name_from_obj 5 6 7class Panel: 8 """ 9 Base class for panels. 10 """ 11 12 def __init__(self, toolbar, get_response): 13 self.toolbar = toolbar 14 self.get_response = get_response 15 16 # Private panel properties 17 18 @property 19 def panel_id(self): 20 return self.__class__.__name__ 21 22 @property 23 def enabled(self): 24 # Check to see if settings has a default value for it 25 disabled_panels = dt_settings.get_config()["DISABLE_PANELS"] 26 panel_path = get_name_from_obj(self) 27 # Some panels such as the SQLPanel and TemplatesPanel exist in a 28 # panel module, but can be disabled without panel in the path. 29 # For that reason, replace .panel. in the path and check for that 30 # value in the disabled panels as well. 31 disable_panel = ( 32 panel_path in disabled_panels 33 or panel_path.replace(".panel.", ".") in disabled_panels 34 ) 35 if disable_panel: 36 default = "off" 37 else: 38 default = "on" 39 # The user's cookies should override the default value 40 return self.toolbar.request.COOKIES.get("djdt" + self.panel_id, default) == "on" 41 42 # Titles and content 43 44 @property 45 def nav_title(self): 46 """ 47 Title shown in the side bar. Defaults to :attr:`title`. 48 """ 49 return self.title 50 51 @property 52 def nav_subtitle(self): 53 """ 54 Subtitle shown in the side bar. Defaults to the empty string. 55 """ 56 return "" 57 58 @property 59 def has_content(self): 60 """ 61 ``True`` if the panel can be displayed in full screen, ``False`` if 62 it's only shown in the side bar. Defaults to ``True``. 63 """ 64 return True 65 66 @property 67 def is_historical(self): 68 """ 69 Panel supports rendering historical values. 70 71 Defaults to :attr:`has_content`. 72 """ 73 return self.has_content 74 75 @property 76 def title(self): 77 """ 78 Title shown in the panel when it's displayed in full screen. 79 80 Mandatory, unless the panel sets :attr:`has_content` to ``False``. 81 """ 82 raise NotImplementedError 83 84 @property 85 def template(self): 86 """ 87 Template used to render :attr:`content`. 88 89 Mandatory, unless the panel sets :attr:`has_content` to ``False`` or 90 overrides `attr`:content`. 91 """ 92 raise NotImplementedError 93 94 @property 95 def content(self): 96 """ 97 Content of the panel when it's displayed in full screen. 98 99 By default this renders the template defined by :attr:`template`. 100 Statistics stored with :meth:`record_stats` are available in the 101 template's context. 102 """ 103 if self.has_content: 104 return render_to_string(self.template, self.get_stats()) 105 106 @property 107 def scripts(self): 108 """ 109 Scripts used by the HTML content of the panel when it's displayed. 110 111 When a panel is rendered on the frontend, the ``djdt.panel.render`` 112 JavaScript event will be dispatched. The scripts can listen for 113 this event to support dynamic functionality. 114 """ 115 return [] 116 117 # URLs for panel-specific views 118 119 @classmethod 120 def get_urls(cls): 121 """ 122 Return URLpatterns, if the panel has its own views. 123 """ 124 return [] 125 126 # Enable and disable (expensive) instrumentation, must be idempotent 127 128 def enable_instrumentation(self): 129 """ 130 Enable instrumentation to gather data for this panel. 131 132 This usually means monkey-patching (!) or registering signal 133 receivers. Any instrumentation with a non-negligible effect on 134 performance should be installed by this method rather than at import 135 time. 136 137 Unless the toolbar or this panel is disabled, this method will be 138 called early in ``DebugToolbarMiddleware``. It should be idempotent. 139 """ 140 141 def disable_instrumentation(self): 142 """ 143 Disable instrumentation to gather data for this panel. 144 145 This is the opposite of :meth:`enable_instrumentation`. 146 147 Unless the toolbar or this panel is disabled, this method will be 148 called late in the middleware. It should be idempotent. 149 """ 150 151 # Store and retrieve stats (shared between panels for no good reason) 152 153 def record_stats(self, stats): 154 """ 155 Store data gathered by the panel. ``stats`` is a :class:`dict`. 156 157 Each call to ``record_stats`` updates the statistics dictionary. 158 """ 159 self.toolbar.stats.setdefault(self.panel_id, {}).update(stats) 160 161 def get_stats(self): 162 """ 163 Access data stored by the panel. Returns a :class:`dict`. 164 """ 165 return self.toolbar.stats.get(self.panel_id, {}) 166 167 def record_server_timing(self, key, title, value): 168 """ 169 Store data gathered by the panel. ``stats`` is a :class:`dict`. 170 171 Each call to ``record_stats`` updates the statistics dictionary. 172 """ 173 data = {key: {"title": title, "value": value}} 174 self.toolbar.server_timing_stats.setdefault(self.panel_id, {}).update(data) 175 176 def get_server_timing_stats(self): 177 """ 178 Access data stored by the panel. Returns a :class:`dict`. 179 """ 180 return self.toolbar.server_timing_stats.get(self.panel_id, {}) 181 182 # Standard middleware methods 183 184 def process_request(self, request): 185 """ 186 Like __call__ in Django's middleware. 187 188 Write panel logic related to the request there. Save data with 189 :meth:`record_stats`. 190 191 Return the existing response or overwrite it. 192 """ 193 return self.get_response(request) 194 195 def generate_stats(self, request, response): 196 """ 197 Called after :meth:`process_request 198 <debug_toolbar.panels.Panel.process_request>`, but may not be executed 199 on every request. This will only be called if the toolbar will be 200 inserted into the request. 201 202 Write panel logic related to the response there. Post-process data 203 gathered while the view executed. Save data with :meth:`record_stats`. 204 205 Does not return a value. 206 """ 207 208 def generate_server_timing(self, request, response): 209 """ 210 Similar to :meth:`generate_stats 211 <debug_toolbar.panels.Panel.generate_stats>`, 212 213 Generate stats for Server Timing https://w3c.github.io/server-timing/ 214 215 Does not return a value. 216 """ 217 218 @classmethod 219 def run_checks(cls): 220 """ 221 Check that the integration is configured correctly for the panel. 222 223 This will be called as a part of the Django checks system when the 224 application is being setup. 225 226 Return a list of :class: `django.core.checks.CheckMessage` instances. 227 """ 228 return [] 229