diff --git a/docs/plugins/development/dashboard-widgets.md b/docs/plugins/development/dashboard-widgets.md new file mode 100644 index 000000000..af100f258 --- /dev/null +++ b/docs/plugins/development/dashboard-widgets.md @@ -0,0 +1,52 @@ +# Dashboard Widgets + +!!! note "Introduced in v3.5" + Support for custom dashboard widgets was introduced in NetBox v3.5. + +Each NetBox user can customize his or her personal dashboard by adding and removing widgets and by manipulating the size and position of each. Plugins can register their own dashboard widgets to complement those already available natively. + +## The DashboardWidget Class + +All dashboard widgets must inherit from NetBox's `DashboardWidget` base class. Subclasses must provide a `render()` method, and may override the base class' default characteristics. + +Widgets which require configuration by a user must also include a `ConfigForm` child class. This form is used to render the user configuration options for the widget. + +::: extras.dashboard.widgets.DashboardWidget + +## Widget Registration + +To register a dashboard widget for use in NetBox, import the `register_widget()` decorator and use it to wrap each `DashboardWidget` subclass: + +```python +from extras.dashboard.widgets import DashboardWidget, register_widget + +@register_widget +class MyWidget1(DashboardWidget): + ... + +@register_widget +class MyWidget2(DashboardWidget): + ... +``` + +## Example + +```python +from django import forms +from extras.dashboard.utils import register_widget +from extras.dashboard.widgets import DashboardWidget + + +@register_widget +class ReminderWidget(DashboardWidget): + default_title = 'Reminder' + description = 'Add a virtual sticky note' + + class ConfigForm(forms.Form): + content = forms.CharField( + widget=forms.Textarea() + ) + + def render(self, request): + return self.config.get('content') +``` diff --git a/mkdocs.yml b/mkdocs.yml index 996890b11..bf4251e0e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -137,6 +137,7 @@ nav: - REST API: 'plugins/development/rest-api.md' - GraphQL API: 'plugins/development/graphql-api.md' - Background Tasks: 'plugins/development/background-tasks.md' + - Dashboard Widgets: 'plugins/development/dashboard-widgets.md' - Staged Changes: 'plugins/development/staged-changes.md' - Exceptions: 'plugins/development/exceptions.md' - Administration: diff --git a/netbox/extras/dashboard/widgets.py b/netbox/extras/dashboard/widgets.py index 89e72d999..8c7bdc57b 100644 --- a/netbox/extras/dashboard/widgets.py +++ b/netbox/extras/dashboard/widgets.py @@ -37,13 +37,26 @@ def get_content_type_labels(): class DashboardWidget: + """ + Base class for custom dashboard widgets. + + Attributes: + description: A brief, user-friendly description of the widget's function + default_title: The string to show for the widget's title when none has been specified. + default_config: Default configuration parameters, as a dictionary mapping + width: The widget's default width (1 to 12) + height: The widget's default height; the number of rows it consumes + """ + description = None default_title = None default_config = {} - description = None width = 4 height = 3 - class ConfigForm(forms.Form): + class ConfigForm(BootstrapMixin, forms.Form): + """ + The widget's configuration form. + """ pass def __init__(self, id=None, title=None, color=None, config=None, width=None, height=None, x=None, y=None): @@ -67,6 +80,12 @@ class DashboardWidget: self.y = grid_item.get('y') def render(self, request): + """ + This method is called to render the widget's content. + + Params: + request: The current request + """ raise NotImplementedError(f"{self.__class__} must define a render() method.") @property @@ -87,7 +106,7 @@ class NoteWidget(DashboardWidget): default_title = _('Note') description = _('Display some arbitrary custom content. Markdown is supported.') - class ConfigForm(BootstrapMixin, forms.Form): + class ConfigForm(DashboardWidget.ConfigForm): content = forms.CharField( widget=forms.Textarea() ) @@ -102,7 +121,7 @@ class ObjectCountsWidget(DashboardWidget): description = _('Display a set of NetBox models and the number of objects created for each type.') template_name = 'extras/dashboard/widgets/objectcounts.html' - class ConfigForm(BootstrapMixin, forms.Form): + class ConfigForm(DashboardWidget.ConfigForm): models = forms.MultipleChoiceField( choices=get_content_type_labels ) @@ -132,7 +151,7 @@ class ObjectListWidget(DashboardWidget): width = 12 height = 4 - class ConfigForm(BootstrapMixin, forms.Form): + class ConfigForm(DashboardWidget.ConfigForm): model = forms.ChoiceField( choices=get_content_type_labels ) @@ -177,7 +196,7 @@ class RSSFeedWidget(DashboardWidget): width = 6 height = 4 - class ConfigForm(BootstrapMixin, forms.Form): + class ConfigForm(DashboardWidget.ConfigForm): feed_url = forms.URLField( label=_('Feed URL') )