Revised plugins documentation

This commit is contained in:
Jeremy Stretch 2020-03-26 11:09:20 -04:00
parent 59815ea53d
commit 68ef5dd2a4
3 changed files with 81 additions and 37 deletions

View File

@ -303,7 +303,19 @@ Determine how many objects to display per page within each list of objects.
Default: Empty Default: Empty
This parameter holds configuration settings for individual NetBox plugins. It is defined as a dictionary, with each key using the name of an installed plugin. The specific parameters supported are unique to each plugin: Reference the plugin's documentation to determine the supported parameters. This parameter holds configuration settings for individual NetBox plugins. It is defined as a dictionary, with each key using the name of an installed plugin. The specific parameters supported are unique to each plugin: Reference the plugin's documentation to determine the supported parameters. An example configuration is shown below:
```python
PLUGINS_CONFIG = {
'plugin1': {
'foo': 123,
'bar': True
},
'plugin2': {
'foo': 456,
},
}
```
Note that `PLUGINS_ENABLED` must be set to True for this to take effect. Note that `PLUGINS_ENABLED` must be set to True for this to take effect.

View File

@ -10,7 +10,7 @@ Plugins can do a lot, including:
* Establish their own REST API endpoints * Establish their own REST API endpoints
* Add custom request/response middleware * Add custom request/response middleware
However, keep in mind that each piece of functionality is entirely optional. For example, if your plugin merely adds a piece of middleware or an API endpoint, there's no need to define a new model. However, keep in mind that each piece of functionality is entirely optional. For example, if your plugin merely adds a piece of middleware or an API endpoint for existing data, there's no need to define any new models.
## Initial Setup ## Initial Setup
@ -35,9 +35,9 @@ plugin_name/
- setup.py - setup.py
``` ```
The top level is the project root, which is typically synonymous with the git repository. Within the root should exist several files: The top level is the project root. Immediately within the root should exist several items:
* `setup.py` - This is a standard Python installation script used to install the plugin package within the Python environment. * `setup.py` - This is a standard installation script used to install the plugin package within the Python environment.
* `README` - A brief introduction to your plugin, how to install and configure it, where to find help, and any other pertinent information. It is recommended to write README files using a markup language such as Markdown. * `README` - A brief introduction to your plugin, how to install and configure it, where to find help, and any other pertinent information. It is recommended to write README files using a markup language such as Markdown.
* The plugin source directory, with the same name as your plugin. * The plugin source directory, with the same name as your plugin.
@ -45,7 +45,7 @@ The plugin source directory contains all of the actual Python code and other res
### Create setup.py ### Create setup.py
`setup.py` is the [setup script](https://docs.python.org/3.6/distutils/setupscript.html) we'll use to install our plugin once it's finished. This script essentially just calls the setuptools library's `setup()` function to create a Python distribution package. We can pass a number of keyword arguments to information the package creation as well as to provide metadata about the plugin. An example `setup.py` is below: `setup.py` is the [setup script](https://docs.python.org/3.6/distutils/setupscript.html) we'll use to install our plugin once it's finished. The primary function of this script is to call the setuptools library's `setup()` function to create a Python distribution package. We can pass a number of keyword arguments to inform the package creation as well as to provide metadata about the plugin. An example `setup.py` is below:
```python ```python
from setuptools import find_packages, setup from setuptools import find_packages, setup
@ -67,13 +67,11 @@ setup(
) )
``` ```
Many of these are self-explanatory, but for more information, see the [setuptools documentation](https://setuptools.readthedocs.io/en/latest/setuptools.html). Many of these are self-explanatory, but for more information, see the [setuptools documentation](https://setuptools.readthedocs.io/en/latest/setuptools.html). The key requirement for a NetBox plugin is the presence of an entry point for `netbox_plugins` pointing to the `PluginConfig` subclass, which we'll create next.
The key requirement for a NetBox plugin is the presence of an entry point for `netbox_plugins` pointing to the `PluginConfig` subclass, which we'll define next.
### Define a PluginConfig ### Define a PluginConfig
The `PluginConfig` class is a NetBox-specific wrapper around Django's built-in [`AppConfig`](https://docs.djangoproject.com/en/stable/ref/applications/). It is used to declare NetBox plugin functionality within a Python package. Each plugin should provide its own subclass, defining its name, metadata, and default and required configuration parameters. An example is below: The `PluginConfig` class is a NetBox-specific wrapper around Django's built-in [`AppConfig`](https://docs.djangoproject.com/en/stable/ref/applications/) class. It is used to declare NetBox plugin functionality within a Python package. Each plugin should provide its own subclass, defining its name, metadata, and default and required configuration parameters. An example is below:
```python ```python
from extras.plugins import PluginConfig from extras.plugins import PluginConfig
@ -94,24 +92,24 @@ class AnimalSoundsConfig(PluginConfig):
#### PluginConfig Attributes #### PluginConfig Attributes
* `name` - Raw plugin name; same as the plugin's source directory * `name` - Raw plugin name; same as the plugin's source directory
* `verbose_name` - Human-friendly name for the plugin
* `version` - Current release ([semantic versioning](https://semver.org/) is encouraged)
* `description` - Brief description of the plugin's purpose
* `author_name` - Name of plugin's author * `author_name` - Name of plugin's author
* `author_email` - Author's public email address * `author_email` - Author's public email address
* `verbose_name` - Human-friendly name
* `version` - Plugin version
* `description` - Brief description of the plugin's purpose
* `base_url` - Base path to use for plugin URLs (optional). If not specified, the project's `name` will be used. * `base_url` - Base path to use for plugin URLs (optional). If not specified, the project's `name` will be used.
* `required_settings`: A list of configuration parameters that **must** be defined by the user * `required_settings`: A list of any configuration parameters that **must** be defined by the user
* `default_settings`: A dictionary of configuration parameter names and their default values * `default_settings`: A dictionary of configuration parameter names and their default values
* `min_version`: Minimum version of NetBox with which the plugin is compatible * `min_version`: Minimum version of NetBox with which the plugin is compatible
* `max_version`: Maximum version of NetBox with which the plugin is compatible * `max_version`: Maximum version of NetBox with which the plugin is compatible
* `middleware`: A list of middleware classes to append after NetBox's build-in middleware. * `middleware`: A list of middleware classes to append after NetBox's build-in middleware
* `caching_config`: Plugin-specific cache configuration * `caching_config`: Plugin-specific cache configuration
* `template_content`: The dotted path to the list of template content classes (default: `template_content.template_contnet`) * `template_content`: The dotted path to the list of template content classes (default: `template_content.template_contnet`)
* `menu_items`: The dotted path to the list of menu items provided by the plugin (default: `navigation.menu_items`) * `menu_items`: The dotted path to the list of menu items provided by the plugin (default: `navigation.menu_items`)
### Install the Plugin for Development ### Install the Plugin for Development
To ease development, it is recommended to go ahead and install the plugin at this point using setuptools' "develop" mode. This will create symbolic links within your Python environment to the plugin development directory. Call `setup.py` from the plugin's root directory with the `develop` argument (instead of `install`): To ease development, it is recommended to go ahead and install the plugin at this point using setuptools' `develop` mode. This will create symbolic links within your Python environment to the plugin development directory. Call `setup.py` from the plugin's root directory with the `develop` argument (instead of `install`):
```no-highlight ```no-highlight
$ python setup.py develop $ python setup.py develop
@ -119,9 +117,9 @@ $ python setup.py develop
## Database Models ## Database Models
If your plugin introduces a new type of object in NetBox, you'll probably want to create a Django model for it. A model is essentially a Python representation of a database table, with attributes that represent individual columns. Model instances can be created, manipulated, and deleted using the [Django ORM](https://docs.djangoproject.com/en/stable/topics/db/). Models are typically defined within a plugin's `models.py` file, though this is not a strict requirement. If your plugin introduces a new type of object in NetBox, you'll probably want to create a [Django model](https://docs.djangoproject.com/en/stable/topics/db/models/) for it. A model is essentially a Python representation of a database table, with attributes that represent individual columns. Model instances can be created, manipulated, and deleted using [queries](https://docs.djangoproject.com/en/stable/topics/db/queries/). Models must be defined within a file named `models.py`.
Below is a simple example `models.py` file showing a model with two character fields: Below is an example `models.py` file containing a model with two character fields:
```python ```python
from django.db import models from django.db import models
@ -134,7 +132,7 @@ class Animal(models.Model):
return self.name return self.name
``` ```
Once you have defined the model(s) for your plugin, you'll need to create the necessary database schema migrations as well. This can be done using the Django `makemigrations` management command. Once you have defined the model(s) for your plugin, you'll need to create the database schema migrations. A migration file is essentially a set of instructions for manipulating the PostgreSQL database to support your new model, or to alter existing models. Creating migrations can usually be done automatically using Django's `makemigrations` management command.
!!! note !!! note
A plugin must be installed before it can be used with Django management commands. If you skipped this step above, run `python setup.py develop` from the plugin's root directory. A plugin must be installed before it can be used with Django management commands. If you skipped this step above, run `python setup.py develop` from the plugin's root directory.
@ -146,7 +144,7 @@ Migrations for 'netbox_animal_sounds':
- Create model Animal - Create model Animal
``` ```
Once you're satisfied the migration is ready and all model changes have been accounted for, we can apply it locally with the `migrate` command: Next, we can apply the migration to the database with the `migrate` command:
```no-highlight ```no-highlight
$ ./manage.py migrate netbox_animal_sounds $ ./manage.py migrate netbox_animal_sounds
@ -156,17 +154,18 @@ Running migrations:
Applying netbox_animal_sounds.0001_initial... OK Applying netbox_animal_sounds.0001_initial... OK
``` ```
For more information on database migrations, see the [Django documentation](https://docs.djangoproject.com/en/stable/topics/migrations/). For more background on schema migrations, see the [Django documentation](https://docs.djangoproject.com/en/stable/topics/migrations/).
### Using the Django Admin Interface ### Using the Django Admin Interface
Plugins can optionally expose their models via Django's built-in [administrative interface](https://docs.djangoproject.com/en/stable/ref/contrib/admin/). This can greatly improve troubleshooting ability, particularly during development. An example `admin.py` file for the above model is shown below: Plugins can optionally expose their models via Django's built-in [administrative interface](https://docs.djangoproject.com/en/stable/ref/contrib/admin/). This can greatly improve troubleshooting ability, particularly during development. To expose a model, simply register it with Netbox's `admin_site` object. An example `admin.py` file for the above model is shown below:
```python ```python
from django.contrib import admin from django.contrib import admin
from netbox.admin import admin_site
from .models import Animal from .models import Animal
@admin.register(Animal) @admin.register(Animal, site=admin_site)
class AnimalAdmin(admin.ModelAdmin): class AnimalAdmin(admin.ModelAdmin):
list_display = ('name', 'sound') list_display = ('name', 'sound')
``` ```
@ -177,7 +176,7 @@ This will display the plugin and its model in the admin UI. Staff users can crea
## Views ## Views
If your plugin needs its own page or pages in the NetBox web UI, you'll need to define views. A view is a particular page tied to a URL within NetBox. Views are typically defined in `views.py`, and URL patterns in `urls.py`. As an example, let's write a view which displays a random animal and the sound it makes. First, we'll create the view in `views.py`: If your plugin needs its own page or pages in the NetBox web UI, you'll need to define views. A view is a particular page tied to a URL within NetBox, which renders content using a template. Views are typically defined in `views.py`, and URL patterns in `urls.py`. As an example, let's write a view which displays a random animal and the sound it makes. First, we'll create the view in `views.py`:
```python ```python
from django.shortcuts import render from django.shortcuts import render
@ -187,6 +186,7 @@ from .models import Animal
class RandomAnimalSoundView(View): class RandomAnimalSoundView(View):
def get(self, request): def get(self, request):
# Retrieve a random animal
animal = Animal.objects.order_by('?').first() animal = Animal.objects.order_by('?').first()
return render(request, 'netbox_animal_sounds/animal_sound.html', { return render(request, 'netbox_animal_sounds/animal_sound.html', {
@ -194,7 +194,7 @@ class RandomAnimalSoundView(View):
}) })
``` ```
This view retrieves a random animal from the database and and passes it as a context variable when rendering a template named `animal_sound.html`. To create this template, first create a directory named `templates/netbox_animal_sounds/` within the plugin root directory. We use the plugin's name as a subdirectory to guard against naming collisions with other plugins. Then, create `animal_sound.html`: This view retrieves a random animal from the database and and passes it as a context variable when rendering a template named `animal_sound.html`, which doesn't exist yet. To create this template, first create a directory named `templates/netbox_animal_sounds/` within the plugin source directory. (We use the plugin's name as a subdirectory to guard against naming collisions with other plugins.) Then, create `animal_sound.html`:
```jinja2 ```jinja2
{% extends '_base.html' %} {% extends '_base.html' %}
@ -208,10 +208,12 @@ This view retrieves a random animal from the database and and passes it as a con
{% endblock %} {% endblock %}
``` ```
The first line of the template instructs Django to extend the NetBox base template and inject our custom content within its `content` block.
!!! note !!! note
Django renders templates with its own custom [template language](https://docs.djangoproject.com/en/stable/topics/templates/#the-django-template-language). This is very similar to Jinja2, however there are some important differences to be aware of. Django renders templates with its own custom [template language](https://docs.djangoproject.com/en/stable/topics/templates/#the-django-template-language). This is very similar to Jinja2, however there are some important differences to be aware of.
Finally, to make the view accessible to users, we need to register a URL for it. We do this in `urls.py`: Finally, to make the view accessible to users, we need to register a URL for it. We do this in `urls.py` by defining a `urlpatterns` variable containing a list of paths.
```python ```python
from django.urls import path from django.urls import path
@ -222,6 +224,12 @@ urlpatterns = [
] ]
``` ```
A URL pattern has three components:
* `route` - The unique portion of the URL dedicated to this view
* `view` - The view itself
* `name` - A short name used to identify the URL path internally
This makes our view accessible at the URL `/plugins/animal-sounds/random-sound/`. (Remember, our `AnimalSoundsConfig` class sets our plugin's base URL to `animal-sounds`.) Viewing this URL should show the base NetBox template with our custom content inside it. This makes our view accessible at the URL `/plugins/animal-sounds/random-sound/`. (Remember, our `AnimalSoundsConfig` class sets our plugin's base URL to `animal-sounds`.) Viewing this URL should show the base NetBox template with our custom content inside it.
## REST API Endpoints ## REST API Endpoints
@ -273,7 +281,7 @@ With these three components in place, we can request `/api/plugins/animal-sounds
## Navigation Menu Items ## Navigation Menu Items
To make its views easily accessible to users, a plugin can inject items in NetBox's navigation menu. Menu items are added by defining a list of PluginNavMenuLink instances. By default, this should be a variable named `menu_items` in the file `navigations.py`. An example is shown below. To make its views easily accessible to users, a plugin can inject items in NetBox's navigation menu under the "Plugins" header. Menu items are added by defining a list of PluginNavMenuLink instances. By default, this should be a variable named `menu_items` in the file `navigation.py`. An example is shown below.
```python ```python
from extras.plugins import PluginNavMenuButton, PluginNavMenuLink from extras.plugins import PluginNavMenuButton, PluginNavMenuLink
@ -301,7 +309,7 @@ A `PluginNavMenuLink` has the following attributes:
A `PluginNavMenuButton` has the following attributes: A `PluginNavMenuButton` has the following attributes:
* `link` - The name of the URL path to which this menu item links * `link` - The name of the URL path to which this button links
* `title` - The tooltip text (displayed when the mouse hovers over the button) * `title` - The tooltip text (displayed when the mouse hovers over the button)
* `color` - Button color (one of the choices provided by `ButtonColorChoices`) * `color` - Button color (one of the choices provided by `ButtonColorChoices`)
* `icon_class` - Button icon CSS class * `icon_class` - Button icon CSS class
@ -321,7 +329,7 @@ Each of these methods must return HTML content suitable for inclusion in the obj
* `self.obj` - The object being viewed * `self.obj` - The object being viewed
* `self.context` - The current template context * `self.context` - The current template context
Additionally, a `render()` method is available for convenience. This method accepts the name of a template to render, and any additional context data. Its use is optional, however. Additionally, a `render()` method is available for convenience. This method accepts the name of a template to render, and any additional context data you want to pass. Its use is optional, however.
Declared subclasses should be gathered into a list or tuple for integration with NetBox. By default, NetBox looks for an iterable named `template_content` within a `template_content.py` file. (This can be overridden by setting `template_content` to a custom value on the plugin's PluginConfig.) An example is below. Declared subclasses should be gathered into a list or tuple for integration with NetBox. By default, NetBox looks for an iterable named `template_content` within a `template_content.py` file. (This can be overridden by setting `template_content` to a custom value on the plugin's PluginConfig.) An example is below.
@ -343,3 +351,20 @@ class AddRackAnimal(PluginTemplateContent):
template_content_classes = [AddSiteAnimal, AddRackAnimal] template_content_classes = [AddSiteAnimal, AddRackAnimal]
``` ```
## Caching Configuration
By default, all query operations within a plugin are cached. To change this, define a caching configuration under the PluginConfig class' `caching_config` attribute. An example configuration is below:
```python
class MyPluginConfig(PluginConfig):
...
caching_config = {
'my_plugin.foo': {
'ops': 'get',
'timeout': 60 * 15,
},
}
```
See the [django-cacheops](https://github.com/Suor/django-cacheops) documentation for more detail on configuring caching.

View File

@ -8,21 +8,21 @@ Plugins are supported on NetBox v2.8 and later.
The NetBox plugin architecture allows for the following: The NetBox plugin architecture allows for the following:
* **Add new data models.** A plugin can introduce one or more models to hold data. (A model is essentially a SQL table.) * **Add new data models.** A plugin can introduce one or more models to hold data. (A model is essentially a table in the SQL database.)
* **Add new URLs and views.** Plugins can register URLS under the `/plugins` root path to provide browsable views for users. * **Add new URLs and views.** Plugins can register URLs under the `/plugins` root path to provide browsable views for users.
* **Add content to existing model templates.** A template content class can be used to inject custom HTML content within the view of a core NetBox model. This content can appear in the left side, right side, or bottom of the page. * **Add content to existing model templates.** A template content class can be used to inject custom HTML content within the view of a core NetBox model. This content can appear in the left side, right side, or bottom of the page.
* **Add navigation menu items.** Each plugin can register new links in the navigation menu. Each link may have a set of buttons for specific actions, similar to the built-in navigation items. * **Add navigation menu items.** Each plugin can register new links in the navigation menu. Each link may have a set of buttons for specific actions, similar to the built-in navigation items.
* **Add custom middleware.** Custom Django middleware can be registered by each plugin. * **Add custom middleware.** Custom Django middleware can be registered by each plugin.
* **Declare configuration parameters.** Each plugin can define required, optional, and default configuration parameters within its unique namespace. * **Declare configuration parameters.** Each plugin can define required, optional, and default configuration parameters within its unique namespace. Plug configuration parameter are defined by the user under `PLUGINS_CONFIG` in `configuration.py`.
* **Limit installation by NetBox version.** A plugin can specify a minimum and/or maximum NetBox version with which it is compatible. * **Limit installation by NetBox version.** A plugin can specify a minimum and/or maximum NetBox version with which it is compatible.
## Limitations ## Limitations
Either by policy or by technical limitation, the interaction of plugins with NetBox core is restricted in certain ways. These include: Either by policy or by technical limitation, the interaction of plugins with NetBox core is restricted in certain ways. A plugin may not:
* **Modify core models.** Plugins may not alter, remove, or override core NetBox models in any way. This rule is in place to ensure the integrity of the core data model. * **Modify core models.** Plugins may not alter, remove, or override core NetBox models in any way. This rule is in place to ensure the integrity of the core data model.
* **Register URLs outside the `/plugins` root.`** All plugin URLs are restricted to this path to prevent name/path collisions. * **Register URLs outside the `/plugins` root.** All plugin URLs are restricted to this path to prevent path collisions with core or other plugins.
* **Override core templates.** The only avenue available for injecting content into core templates is the provided * **Override core templates.** Plugins can inject additional content where supported, but may not manipulate or remove core content.
* **Modify core settings.** A configuration registry is provided for plugins, however they cannot alter or delete the core configuration. * **Modify core settings.** A configuration registry is provided for plugins, however they cannot alter or delete the core configuration.
* **Disable core components.** Plugins are not permitted to disable or hide core NetBox components. * **Disable core components.** Plugins are not permitted to disable or hide core NetBox components.
@ -32,11 +32,18 @@ The instructions below detail the process for installing and enabling a NetBox p
### Install Package ### Install Package
TODO Download and install the plugin package per its installation instructions. Plugins published via PyPI are typically installed using pip. Be sure to install the plugin within NetBox's virtual environment.
```no-highlight
$ source /opt/netbox/venv/bin/activate
(venv) $ pip install <package>
```
Alternatively, you may wish to install the plugin manually by running `python setup.py install`. If you are developing a plugin and want to install it only temporarily, run `python setup.py develop` instead.
### Enable Plugins ### Enable Plugins
In `configuration.py`, set the `PLUGINS_ENABLED` parameter to True (if not already set): In `configuration.py`, ensure the `PLUGINS_ENABLED` parameter is set to True:
```python ```python
PLUGINS_ENABLED = True PLUGINS_ENABLED = True
@ -57,7 +64,7 @@ PLUGINS_CONFIG = {
### Restart WSGI Service ### Restart WSGI Service
Restart the WSGI service to detect the new plugin: Restart the WSGI service to load the new plugin:
```no-highlight ```no-highlight
# sudo systemctl restart netbox # sudo systemctl restart netbox