Netbox Plugin Setup Tutorial
Netbox Plugin Setup Tutorial
Install NetBox
Plugin development requires a local installation of NetBox. If you don't already have NetBox
installed, please consult the installation instructions.
Be sure to enable debugging in your NetBox configuration by setting DEBUG = True. This will
ensure that static assets can be served by the development server and return complete
tracebacks whenever there's a server error.
Warning: This guide requires NetBox v3.4 or later. Attempting to use an earlier NetBox release
will not work.
• First, we'll create a subdirectory to store our plugin's Python code, along with an
__init__.py file to define the PluginConfig.
$ mkdir netbox-plugin-demo/
$ cd netbox-plugin-demo/
$ mkdir netbox_access_lists
$ touch netbox_access_lists/__init__.py
• Next, open __init__.py in the text editor and Create the PluginConfig Class
class NetBoxAccessListsConfig(PluginConfig):
name = 'netbox_access_lists'
verbose_name = 'Netbox Access Lists'
description = 'Manage simple ACLs in Netbox'
version = 0.2
base_url = 'access-lists'
min_version = "3.7.0"
max_version = "3.7.99"
config = NetBoxAccessListsConfig
setup(
name='netbox-access-lists',
version='0.2.0',
description='An example of Netbox Plugin',
install_requires=[],
packages=find_packages(),
include_package_data=True,
zip_safe=False,
)
• Configure NetBox
Over in the NetBox installation path, open netbox/netbox/configuration.py and look for
the PLUGINS parameter;
# configuration.py
PLUGINS = [
'netbox_access_lists',
]
Save the file and run the NetBox development server (if not already running):
pip install -r requirements.txt
At the top of the file, Import Django’s module library and NetboxModel class.
Fields
• name (CharField): The name of the access list. Maximum length is 100
characters.
• default_action (CharField): The default action for the access list, selected from
predefined choices (ActionChoices). Maximum length is 30 characters.
• comments (TextField): Optional comments or notes related to the access list.
Meta
Methods
class AccessList(NetBoxModel):
name = models.CharField(
max_length=100
The AccessListRule model represents a rule within an access list. It includes detailed
attributes for defining network access rules.
Fields
Meta
Methods
• __str__(): Returns a string representation of the rule, including the access list
and index.
• get_protocol_color(): Retrieves the color associated with the protocol choice
from ProtocolChoices.
• get_action_color(): Retrieves the color associated with the action choice from
ActionChoices.
• get_absolute_url(): Generates the URL for viewing the access list rule detail
page.
class AccessListRule(NetBoxModel):
access_list = models.ForeignKey(
to=AccessList,
on_delete=models.CASCADE,
related_name='rules'
)
class ActionChoices(ChoiceSet):
key = 'AccessListRule.action'
PERMIT = 'permit',
DENY = 'deny',
REJECT = 'reject',
B. Create a class for Protocol choices:
class ProtocolChoices(ChoiceSet):
TCP = 'tcp',
UDP = 'udp',
ICMP = 'icmp',
Excellent! We can now create access lists and rules in the database. The next few steps
will work on exposing this functionality in the NetBox user interface.
3. Creating Tables
We will create tables for AccessList and AccessListRule models, and also a bulk edit table
for AccessListRule.
$ cd netbox_access_lists/
$ edit tables.py
Start by importing necessary modules:
import django_tables2 as tables
from netbox.tables import NetBoxTable, ChoiceFieldColumn
from .models import AccessList, AccessListRule
from django.utils.translation import gettext as _
4. Creating Forms
Set Up the Forms Directory
Navigate to netbox_access_lists directory and create a new directory for
forms
$ cd netbox_access_lists/
$ mkdir forms
The model_forms.py file defines forms for handling data related to the AccessList and
AccessListRule models in the Django application.
It utilizes NetBoxModelForm for form creation and includes dynamic choice fields
for better flexibility.
AccessListForm
A form for creating and updating AccessList instances. It includes fields for the name,
default action, and comments, along with tags for additional categorization
class AccessListForm(NetBoxModelForm):
comment = CommentField()
Components
AccessListRuleForm
A form for creating and updating AccessListRule instances. It includes fields for access list
selection, network prefixes, protocol choices, and actions.
class AccessListRuleForm(NetBoxModelForm):
access_list = DynamicModelChoiceField(queryset=AccessList.objects.all())
By default, Django will create a "static" foreign key field for related objects. This renders
as a dropdown list that's pre-populated with all available objects. As you can imagine, in
a NetBox instance with many thousands of objects this can get rather unwieldy.
To avoid this, NetBox provides the DynamicModelChoiceField class. This renders foreign
key fields using a special dynamic widget backed by NetBox's REST API. This avoids the
overhead imposed by the static field, and allows the user to conveniently search for the
desired object.
🟢 Tip: The DynamicModelMultipleChoiceField class is also available for many-to-many
fields, which support the assignment of multiple objects.
We'll use DynamicModelChoiceField for the three foreign key fields in our form:
access_list, source_prefix, and destination_prefix. First, we must import the field class,
as well as the models of the related objects. AccessList is already imported, so we just
need to import Prefix from NetBox's ipam app. The beginning of forms.py should now
look like this:
Components
This documentation provides details about two bulk import forms used in the plugin for
handling AccessList and AccessListRule models. These forms are part of the NetBox
integration and are designed for importing data from CSV files into the application's
database.
AccessListBulkImportForm
The AccessListBulkImportForm is used to import bulk data into the AccessList model.
This form allows users to upload CSV files containing details of access lists, which are
then processed and stored in the database.
• Imports:
class AccessListBulkImportForm(NetBoxModelImportForm):
class Meta:
Meta Information:
• model: Specifies that this form is for the AccessList model.
• fields: Lists the fields that can be imported via the form:
• id: The unique identifier of the access list.
• name: The name of the access list.
• default_action: The default action associated with the access list.
• comments: Any additional comments related to the access list.
AccessListRuleBulkImportForm
Imports:
class AccessListRuleBulkImportForm(NetBoxModelImportForm):
action = CSVChoiceField(
label=_('Action'),
choices=ActionChoices,
help_text=_('Action')
)
Fields:
• action: CSVChoiceField
▪ label: 'Action'
▪ choices: Choices for the action field, imported from ActionChoices.
▪ help_text: 'Action'
• access_list: CSVModelChoiceField
▪ label: 'Access List'
▪ queryset: Fetches all AccessList objects.
▪ to_field_name: 'name'
▪ help_text: 'Access List'
• source_prefix: CSVModelChoiceField
▪ queryset: Fetches all Prefix objects.
▪ required: False
▪ to_field_name: 'prefix'
▪ help_text: 'Source Prefix'
• destination_prefix: CSVModelChoiceField
▪ queryset: Fetches all Prefix objects.
▪ required: False
▪ to_field_name: 'prefix'
▪ help_text: 'Destination Prefix'
❖ Next we’ll create bulk_edit.py file
This documentation provides details about two bulk edit forms used in the Plugin for
handling AccessList and AccessListRule models. These forms are used to edit multiple
records at once and are part of the NetBox integration.
AccessListBulkEditForm
The AccessListBulkEditForm allows users to edit multiple AccessList records at once. This
form is used to apply changes to the comments field of the access lists in bulk.
Imports:
class AccessListBulkEditForm(NetBoxModelBulkEditForm):
comments = CommentField()
AccessListRuleBulkEditForm
Imports:
class AccessListRuleBulkEditForm(NetBoxModelBulkEditForm):
fieldsets = (
( None,
(
'action',
'protocol', ),),)
Fields:
• action: forms.ChoiceField
▪ label: 'Action'
▪ choices: Choices for the action field, imported from ActionChoices.
▪ required: False (indicates that this field is optional)
• protocol: forms.ChoiceField
▪ label: 'Protocol'
▪ choices: Choices for the protocol field, imported from ProtocolChoices.
▪ required: False (indicates that this field is optional)
Meta Information:
a. fieldsets: Defines the layout of the form fields in the bulk edit interface. This
specifies that action and protocol should be grouped together.
b. model: Specifies that this form is for the AccessListRule model.
5. Views.py
The views.py file defines various views for managing AccessList and AccessListRule
objects in the Django application. These views include detail views, list views, edit views,
delete views, and bulk operations. Each view is associated with a specific form, table,
and filterset to handle CRUD operations efficiently.
Imports
from netbox.views import generic
from . import filtersets
from . import models, tables
from django.db.models import Count
from utilities.views import register_model_view
from .forms.model_forms import AccessListForm, AccessListRuleForm
from .forms.bulk_edit import AccessListRuleBulkEditForm, AccessListBulkEditForm
from .forms.bulk_import import AccessListBulkImportForm, AccessListRuleBulkImportForm
from .forms.filtersets import *
AccessListView
A view for displaying the details of a AccessList instance, including a table of related
AccessListRule instances.
@register_model_view(models.AccessList)
class AccessListView(generic.ObjectView):
queryset = models.AccessList.objects.all()
Components
AccessListListView
A view for listing all AccessList instances with an annotated rule count.
class AccessListListView(generic.ObjectListView):
queryset = AccessList.objects.annotate(
rule_count=Count('rules')
)
Components
AccessListEditView
AccessListDeleteView
AccessListsBulkImportView
AccessListBulkDeleteView
AccessListBulkEditView
AccessListRuleView
Components
Lists all AccessListRule instances with filtering and table display capabilities.
class AccessListRuleListView(generic.ObjectListView):
queryset = models.AccessListRule.objects.all()
Components
AccessListRuleEditView
Components
AccessListRuleDeleteView
@register_model_view(models.AccessListRule, "delete")
class AccessListRuleDeleteView(generic.ObjectDeleteView):
Components
AccessListRuleImportView
class AccessListRuleImportView(generic.BulkImportView):
queryset = models.AccessListRule.objects.all()
AccessListRuleBulkEditView
Components
AccessListRuleBulkDeleteView
class AccessListRuleBulkDeleteView(generic.BulkDeleteView):
queryset = models.AccessListRule.objects.all()
Components
In the netbox_access_lists/ directory, create urls.py. This will define our view URLs.
Imports
from django.urls import path, include
from . import views
from utilities.urls import get_model_urls
app_name = "netbox_access_lists"
URL Patterns
urlpatterns = [
# Access lists
path('access-lists/', views.AccessListListView.as_view(), name='accesslist_list'),
path('access-lists/add/', views.AccessListEditView.as_view(), name='accesslist_add'),
path('access-lists/import/', views.AccessListsBulkImportView.as_view(), name='accesslist_import'),
path('access-lists/edit/', views.AccessListBulkEditView.as_view(), name='accesslist_bulk_edit'),
path('access-lists/delete/', views.AccessListBulkDeleteView.as_view(), name='accesslist_bulk_delete'),
path("access-lists/<int:pk>/", include(get_model_urls(app_name, "accesslist"))),
Now for the moment of truth: Has all our work thus far yielded functional UI views?
Check that the development server is running, then open a browser and navigate to
http://localhost:8000/plugins/access-lists/access-lists/. You should see the access list list
view.
6. Templates
NetBox looks for templates within the templates/ directory (if it exists) within the plugin
root. Within this directory, create a subdirectory bearing the name of the plugin:
$ cd
netbox_access_lists /
$ mkdir - p
templates /netbox_access_lists /
Although we need to create our own template, NetBox has done much of the work for
us, and provides a generic template that we can easily extend. At the top of the file, add
an extends tag:
{% extends 'generic/object.html' %}
{% load render_table from django_tables2 %}
{% block content %}
<div class="row mb-3">
<div class="col col-md-6">
<div class="card">
<h5 class="card-header">Access List</h5>
Let's take a look at our new template! Navigate to the list view again (at
http://localhost:8000/plugins/access-lists/access-lists/), and follow the link through to a
particular access list. You should see something like the image below.
🟦 Note: If NetBox complains that the template still does not exist, you may need to
manually restart the development server (manage.py runserver).
This is nice, but it would be handy to include the access list's assigned rules on the page
as well.
@register_model_view(models.AccessList)
class AccessListView(generic.ObjectView):
queryset = models.AccessList.objects.all()
def get_extra_context(self, request, instance):
table = tables.AccessListRuleTable(instance.rules.all())
table.configure(request)
return {
'rules_table': table,
}
Then, immediately above the {% endblock content %} line at the end of the file, insert
the following template code:
<div class="row">
<div class="col col-md-12">
<div class="card">
<h5 class="card-header">Rules</h5>
<div class="card-body table-responsive">
{% render_table rules_table %}
</div>
</div>
</div>
After refreshing the access list view in the browser, you should now see the rules table
at the bottom of the page.
Create the AccessListRule Template
Speaking of rules, let's not forget about our AccessListRule model: It needs a
template too. Create accesslistrule.html alongside our first template:
$ edit templates/netbox_access_lists/accesslistrule.html
{% extends 'generic/object.html' %}
{% block content %}
<div class="row mb-3">
<div class="col col-md-6">
<div class="card">
<h5 class="card-header">Access List Rule</h5>
<div class="card-body">
<table class="table table-hover attr-table">
<tr>
<th scope="row">Access List</th>
<td>
<a href="{{ object.access_list.get_absolute_url }}">{{ object.access_list }}</a>
</td>
</tr>
<tr>
Feel free to experiment with different layouts and content before proceeding with the
next step.
7. Navigation
So far, we've been manually entering URLs to access our plugin's views. This obviously
will not suffice for regular use, so let's see about adding some links to NetBox's
navigation menu.
$ cd netbox_access_lists/
$ edit navigation.py
This document explains the configuration of menu items and buttons for the
netbox_access_lists plugin within the NetBox application. The code sets up navigation
menus and buttons for managing Access Lists and Access List Rules.
Components
1. Imports
2. Buttons:
3. Permissions:
accesslist_buttons = [
PluginMenuButton(
link='plugins:netbox_access_lists:accesslist_add',
PluginMenuButton(
link='plugins:netbox_access_lists:accesslist_import',
accesslistrule_buttons = [
PluginMenuButton(
link='plugins:netbox_access_lists:accesslistrule_add',
PluginMenuButton(
link='plugins:netbox_access_lists:accesslistrule_import',
4. Menu Items:
menu_items = (
PluginMenuItem(
link='plugins:netbox_access_lists:accesslist_list',
PluginMenuItem(
link='plugins:netbox_access_lists:accesslistrule_list',
8. Filter Sets:
Filters enable users to request only a specific subset of objects matching a query; when
filtering the sites list by status or region, for instance. NetBox employs the django-filters
library to build and apply filter sets for models. We can create filter sets to enable this
same functionality for our plugin as well.
$ cd netbox_access_lists/
$ touch filtersets.py
AccessListFilterSet
• Purpose: This filter set is used to filter and search through access lists.
• Fields Available for Filtering:
o id: The unique identifier of the access list.
o name: The name of the access list.
• Custom Search: Allows searching within the name field of the access lists to find
lists that contain the search term.
AccessListRuleFilterSet
• Purpose: This filter set allows you to filter and search through access list rules.
• Fields Available for Filtering:
o id: The unique identifier of the rule.
o access_list: The specific access list to which the rule belongs.
o index: The position or order of the rule within the access list.
o protocol: The protocol used by the rule.
o action: The action taken by the rule (e.g., permit or deny).
• Custom Search: Allows searching within the description field of the rules to find
rules that contain the search term.
AccessListRuleFilterForm
• Purpose: Provides a user interface for filtering access list rules based on various
criteria.
• Fields:
• access_list: Dropdown menu to select an access list for filtering.
• index: Field to specify the rule’s index number.
• protocol: Dropdown menu to select one or multiple protocols.
• action: Dropdown menu to select the action (permit/deny) associated
with the rule.
AccessListFilterForm
• Purpose: Provides a user interface for filtering access lists based on criteria.
• Fields:
• name: Field to enter the name of the access list for filtering.
• action: Dropdown menu to select an action associated with the access
list.
9. REST API
The REST API enables powerful integration with other systems which exchange data with
NetBox. It is powered by the Django REST Framework (DRF), which is not a
componentof Django itself. In this tutorial, we'll see how we can extend NetBox's REST
API to serve our plugin.
Our API code will live in the api/ directory under netbox_access_lists/. Let's go ahead and
create that as well as an __init__.py file now:
Structure:
├── __init__.py
├── nested_serializers.py
├── serializers.py
├── urls.py
└── views.py
Imports
from rest_framework import serializers
from netbox.api.serializers import NetBoxModelSerializer
from ..models import AccessList, AccessListRule
from ipam.api.serializers import NestedPrefixSerializer
from .nested_serializers import NestedAccessListSerializer
Create AccessListSerializer
Create AccessListRuleSerializer
• Key Fields:
o url: Hyperlink to the rule's detail view.
o access_list, source_prefix, destination_prefix: Nested serializers for
related objects.
• Includes: Fields like id, index, protocol, source_prefix, and
destination_prefix.
Then, create two nested serializer classes, one for each of our plugin's models. Each of
these will have a url field and Meta child class like the regular serializers, however the
Meta.fields attribute for each is limited to a bare minimum of fields: id, url, display, and
a supplementary human-friendly identifier. Add these in serializers.py above the regular
serializers (because we need to define NestedAccessListSerializer before we can
reference it).
AccessListViewSet
AccessListRuleViewSet
• Filterset: Utilizes AccessListRuleFilterSet to provide filtering options for the
AccessListRule model.
Next, we'll define an app_name. This will be used to resolve API view names for
our plugin.
app_name = 'netbox_access_list'
Then, we create a NetBoxRouter instance and register each view with it using
our desired URL. These are the endpoints that will be available under
/api/plugins/access-lists/.
router = NetBoxRouter()
router.register('access-lists', views.AccessListViewSet)
router.register('access-list-rules', views.AccessListRuleViewSet)
Finally, we expose the router's urls attribute as urlpatterns so that it will be
detected by the plugins framework.
urlpatterns = router.urls
10. GraphQL API
In addition to its REST API, NetBox also features a GraphQL API. This can be used
to conveniently request arbitrary collections of data about NetBox objects.
NetBox's GraphQL API is built using the Graphene and graphene-django library.
Structure
cd graphql/
ls
__init__.py __pycache__ schema.py types.py
access_list_rule = ObjectField(AccessListRuleType)
access_list_rule_list = ObjectListField(AccessListRuleType)
schema = AccessListQuery
$ cd netbox_access_lists/
$ edit search.py
AccessListIndex
AccessListRuleIndex
@register_search
class AccessListIndex(SearchIndex):
With our indexers now registered, we can run the reindex management command
to index any existing objects. (New objects created from this point forward will be
registered automatically upon creation.)
This completes the plugin development tutorial. Well done! Now you're all set to make a plugin
of your own!