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