From f52f266c691fd8bb80b21ab2a983118b914c04fa Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sun, 24 Nov 2013 17:13:11 +0100 Subject: Clean up and document the Panel API. --- debug_toolbar/panels/__init__.py | 141 +++++++++++++++++++++++++++++++-------- docs/conf.py | 3 + docs/panels.rst | 40 +++++++++++ 3 files changed, 155 insertions(+), 29 deletions(-) diff --git a/debug_toolbar/panels/__init__.py b/debug_toolbar/panels/__init__.py index d1a90d0..39c9d80 100644 --- a/debug_toolbar/panels/__init__.py +++ b/debug_toolbar/panels/__init__.py @@ -9,28 +9,17 @@ class Panel(object): """ Base class for panels. """ - # name = 'Base' - # template = 'debug_toolbar/panels/base.html' - - # If content returns something, set to True in subclass - has_content = False # We'll maintain a local context instance so we can expose our template - # context variables to panels which need them: + # context variables to panels which need them. (But see issue #450.) context = {} - # Panel methods + # Private panel methods def __init__(self, toolbar, context={}): self.toolbar = toolbar self.context.update(context) - def content(self): - if self.has_content: - context = self.context.copy() - context.update(self.get_stats()) - return render_to_string(self.template, context) - @property def panel_id(self): return self.__class__.__name__ @@ -39,52 +28,146 @@ class Panel(object): def enabled(self): return self.toolbar.request.COOKIES.get('djdt' + self.panel_id, 'on') == 'on' - # URLs for panel-specific views - - @classmethod - def get_urls(cls): - return [] - - # Titles and subtitles + # Titles and content + @property def nav_title(self): - """Title showing in sidebar""" - raise NotImplementedError + """ + Title shown in the side bar. Defaults to :attr:`title`. + """ + return self.title + @property def nav_subtitle(self): - """Subtitle showing under title in sidebar""" + """ + Subtitle shown in the side bar. Defaults to the empty string. + """ return '' + @property + def has_content(self): + """ + ``True`` if the panel can be displayed in full screen, ``False`` if + it's only shown in the side bar. Defaults to ``True``. + """ + return True + + @property def title(self): - """Title showing in panel""" + """ + Title shown in the panel when it's displayed in full screen. + + Mandatory, unless the panel sets :attr:`has_content` to ``False``. + """ + raise NotImplementedError + + @property + def template(self): + """ + Template used to render :attr:`content`. + + Mandatory, unless the panel sets :attr:`has_content` to ``False`` or + overrides `attr`:content`. + """ raise NotImplementedError + @property + def content(self): + """ + Content of the panel when it's displayed in full screen. + + By default this renders the template defined by :attr:`template`. + Statistics stored with :meth:`record_stats` are available in the + template's context. + """ + if self.has_content: + context = self.context.copy() + context.update(self.get_stats()) + return render_to_string(self.template, context) + + # URLs for panel-specific views + + @classmethod + def get_urls(cls): + """ + Return URLpatterns, if the panel has its own views. + """ + return [] + # Enable and disable (expensive) instrumentation, must be idempotent def enable_instrumentation(self): - pass + """ + Enable instrumentation to gather data for this panel. + + This usually means monkey-patching (!) or registering signal + receivers. Any instrumentation with a non-negligible effect on + performance should be installed by this method rather than at import + time. + + Unless the toolbar or this panel is disabled, this method will be + called early in :class:`DebugToolbarMiddleware.process_request`. It + should be idempotent. + """ def disable_instrumentation(self): - pass + """ + Disable instrumentation to gather data for this panel. + + This is the opposite of :meth:`enable_instrumentation`. + + Unless the toolbar or this panel is disabled, this method will be + called late in :class:`DebugToolbarMiddleware.process_response`. It + should be idempotent. + """ # Store and retrieve stats (shared between panels for no good reason) def record_stats(self, stats): + """ + Store data gathered by the panel. ``stats`` is a :class:`dict`. + + Each call to ``record_stats`` updates the statistics dictionary. + """ self.toolbar.stats.setdefault(self.panel_id, {}).update(stats) def get_stats(self): + """ + Access data stored by the panel. Returns a :class:`dict`. + """ return self.toolbar.stats.get(self.panel_id, {}) # Standard middleware methods def process_request(self, request): - pass + """ + Like process_request_ in Django's middleware. + + Write panel logic related to the request there. Save data with + :meth:`record_stats`. + + .. _process_request: https://docs.djangoproject.com/en/stable/topics/http/middleware/#process-request + """ def process_view(self, request, view_func, view_args, view_kwargs): - pass + """ + Like process_view_ in Django's middleware. + + Write panel logic related to the view there. Save data with + :meth:`record_stats`. + + .. _process_view: https://docs.djangoproject.com/en/stable/topics/http/middleware/#process-request + """ def process_response(self, request, response): - pass + """ + Like process_response_ in Django's middleware. + + Write panel logic related to the response there. Post-process data + gathered while the view executed. Save data with :meth:`record_stats`. + + .. _process_response: https://docs.djangoproject.com/en/stable/topics/http/middleware/#process-request + """ # Backward-compatibility for 1.0, remove in 2.0. diff --git a/docs/conf.py b/docs/conf.py index c2fcade..abde59b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -15,6 +15,9 @@ import sys import os +os.environ['DJANGO_SETTINGS_MODULE'] = 'example.settings' +sys.path.append(os.path.dirname(os.path.dirname(__file__))) + # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. diff --git a/docs/panels.rst b/docs/panels.rst index 39ddf7f..c766780 100644 --- a/docs/panels.rst +++ b/docs/panels.rst @@ -212,3 +212,43 @@ URL: https://github.com/playfire/django-debug-toolbar-user-panel Path: ``debug_toolbar_user_panel.panels.UserPanel`` Easily switch between logged in users, see properties of current user. + +API for third-party panels +-------------------------- + +Third-party panels must subclass :class:`~debug_toolbar.panels.Panel`, +according to the public API described below. Unless noted otherwise, all +methods are optional. + +Panels can ship their own templates, static files and views. They're no public +CSS or JavaScript API at this time, but they can assume jQuery is available. + +.. autoclass:: debug_toolbar.panels.Panel(*args, **kwargs) + + .. autoattribute:: debug_toolbar.panels.Panel.nav_title + + .. autoattribute:: debug_toolbar.panels.Panel.nav_subtitle + + .. autoattribute:: debug_toolbar.panels.Panel.has_content + + .. autoattribute:: debug_toolbar.panels.Panel.title + + .. autoattribute:: debug_toolbar.panels.Panel.template + + .. autoattribute:: debug_toolbar.panels.Panel.content + + .. automethod:: debug_toolbar.panels.Panel.get_urls + + .. automethod:: debug_toolbar.panels.Panel.enable_instrumentation + + .. automethod:: debug_toolbar.panels.Panel.disable_instrumentation + + .. automethod:: debug_toolbar.panels.Panel.record_stats + + .. automethod:: debug_toolbar.panels.Panel.get_stats + + .. automethod:: debug_toolbar.panels.Panel.process_request + + .. automethod:: debug_toolbar.panels.Panel.process_view + + .. automethod:: debug_toolbar.panels.Panel.process_response -- cgit v1.2.3