The REST API enables powerful integrations with other systems that exchange data with NetBox.
NetBox's API is built on the Django REST Framework (DRF). DRF is not part of Django itself, but it works very well with Django models and querysets. In this step, we will extend NetBox's REST API to serve our plugin models.
🟦 Note: If you skipped the previous step, run git checkout step09-filter-sets (if you cloned the repository netbox-plugin-demo).
Our API code will live in an api/ package inside netbox_access_lists/.
Create the directory and an __init__.py file:
cd netbox_access_lists/
mkdir api
touch api/__init__.pySerializers are somewhat analogous to forms. A form renders HTML and validates user input from the UI. A serializer defines how a model is represented in the API (usually as JSON), and how input data from the API is validated and converted into Python objects.
We will create one serializer per model.
Begin by creating serializers.py in the api/ directory:
touch api/serializers.pyAt the top of this file, import:
- NetBox's
NetBoxModelSerializerbase class - DRF's
serializersmodule (we will use it for fields likeHyperlinkedIdentityField) - NetBox's
PrefixSerializerso we can include nested prefix data - our plugin models
from ipam.api.serializers import PrefixSerializer
from netbox.api.serializers import NetBoxModelSerializer
from rest_framework import serializers
from ..models import AccessList, AccessListRuleCreate a serializer for AccessList by subclassing NetBoxModelSerializer.
Much like a model form, we define a Meta class that tells the serializer which model it is for, and which fields to include.
Start with the URL field.
Every serializer should include a read only url field that points to the API endpoint for this object.
We create this using DRF's HyperlinkedIdentityField.
class AccessListSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='plugins-api:netbox_access_lists-api:accesslist-detail'
)The view_name here is the name of the API view that will serve a single access list object.
We will create it later when we register our API viewsets and URLs.
Next, remember the rule_count column we added for the table in Step 3. We can expose that value in the API too.
This is a computed field, so we make it read-only, and we will annotate it in the view queryset.
rule_count = serializers.IntegerField(read_only=True)Now add the Meta class:
class Meta:
model = AccessList
fields = (
'id',
'url',
'display',
'name',
'default_action',
'rule_count',
'comments',
'tags',
'custom_fields',
'created',
'last_updated',
)A quick overview of some common fields:
idis the primary key. This is a must-have for every serializer.displayis provided byNetBoxModelSerializer. It is read-only, and returns a human-friendly string representation of the object.tagsandcustom_fieldsare provided by NetBox's model and serializer helpers.createdandlast_updatedcome fromNetBoxModeland are read-only.
🟢 Tip: The order in fields is the order you will see in the API response. Many NetBox serializers put tags, custom_fields, created, and last_updated near the end.
Now create a serializer for AccessListRule.
This will look similar, but it includes several foreign keys and list fields.
Start with the URL field again:
class AccessListRuleSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='plugins-api:netbox_access_lists-api:accesslistrule-detail'
)Now define the Meta class. Make sure we include description, since it is part of the model and useful for API consumers.
class Meta:
model = AccessListRule
fields = (
'id',
'url',
'display',
'access_list',
'index',
'protocol',
'source_prefix',
'source_ports',
'destination_prefix',
'destination_ports',
'action',
'description',
'comments',
'tags',
'custom_fields',
'created',
'last_updated',
)By default, serializers often represent related objects by primary key only. That works, but it forces clients to make extra requests just to display basic info like a name.
NetBox supports nested representations for related objects, which usually include an id, a url, and a display value.
This is very convenient for both humans and integrations.
For source_prefix and destination_prefix, we can use NetBox's nested prefix serializer:
class AccessListRuleSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='plugins-api:netbox_access_lists-api:accesslistrule-detail',
)
source_prefix = PrefixSerializer(nested=True, required=False, allow_null=True)
destination_prefix = PrefixSerializer(nested=True, required=False, allow_null=True)We also want a nested representation for access_list.
To do that, we will make sure our AccessListSerializer defines brief_fields, and then reuse it with nested=True.
NetBoxModelSerializer supports nested mode using brief_fields.
These fields define what should be included when the serializer is used with nested=True.
Update both serializer Meta classes to include brief_fields:
class AccessListSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='plugins-api:netbox_access_lists-api:accesslist-detail',
)
rule_count = serializers.IntegerField(read_only=True)
class Meta:
model = AccessList
fields = (
'id',
'url',
'display',
'name',
'default_action',
'rule_count',
'comments',
'tags',
'custom_fields',
'created',
'last_updated',
)
brief_fields = ('id', 'url', 'display', 'name')
class AccessListRuleSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='plugins-api:netbox_access_lists-api:accesslistrule-detail',
)
access_list = AccessListSerializer(nested=True)
source_prefix = PrefixSerializer(nested=True, required=False, allow_null=True)
destination_prefix = PrefixSerializer(nested=True, required=False, allow_null=True)
class Meta:
model = AccessListRule
fields = (
'id',
'url',
'display',
'access_list',
'index',
'protocol',
'source_prefix',
'source_ports',
'destination_prefix',
'destination_ports',
'action',
'description',
'comments',
'tags',
'custom_fields',
'created',
'last_updated',
)
brief_fields = ('id', 'url', 'display', 'index')🟢 Tip: It is good practice to include id, url, and display in brief_fields.
For reference, your plugin project should now include api/serializers.py:
.
├── netbox_access_lists
│ ├── api
│ │ ├── __init__.py
│ │ └── serializers.py
│ ├── choices.py
│ ├── filtersets.py
│ ├── forms.py
│ ├── __init__.py
│ ├── migrations
│ │ ├── 0001_initial.py
│ │ └── __init__.py
│ ├── models.py
│ ├── navigation.py
│ ├── tables.py
│ ├── templates
│ │ └── netbox_access_lists
│ │ ├── accesslist.html
│ │ └── accesslistrule.html
│ ├── urls.py
│ └── views.py
├── pyproject.toml
└── README.md
Next, we need API views. In the UI, we created multiple views per model (detail, list, edit, delete). For the REST API, DRF uses viewsets. A viewset is a single class that can handle the common API operations for a model, like listing objects, retrieving one object, creating, updating, and deleting.
Create api/views.py:
touch api/views.pyAdd the imports below:
from django.db.models import Count
from netbox.api.viewsets import NetBoxModelViewSet
from .. import filtersets, models
from .serializers import AccessListSerializer, AccessListRuleSerializerCreate a viewset for access lists. We set:
querysetto the objects we want to exposeserializer_classto the serializer we created earlier
We also annotate rule_count so the serializer field is populated.
class AccessListViewSet(NetBoxModelViewSet):
queryset = models.AccessList.objects.annotate(rule_count=Count('rules')).prefetch_related('tags')
serializer_class = AccessListSerializerNow create a viewset for rules.
Here we also connect the filter set from Step 9 using filterset_class.
This is what allows the list view endpoint to support query filtering.
For performance, we load foreign key relationships using select_related, and tags using prefetch_related.
class AccessListRuleViewSet(NetBoxModelViewSet):
queryset = models.AccessListRule.objects.select_related(
'access_list', 'source_prefix', 'destination_prefix'
).prefetch_related('tags')
serializer_class = AccessListRuleSerializer
filterset_class = filtersets.AccessListRuleFilterSetFinally, we need to expose these viewsets under API URLs.
This works a bit differently from UI URLs.
Instead of defining many path() entries, we register each viewset with a router.
Create api/urls.py:
touch api/urls.pyAdd the imports:
from netbox.api.routers import NetBoxRouter
from . import viewsSet app_name.
NetBox uses this namespace to resolve view names, including the view_name values we used in our serializers.
app_name = 'netbox_access_lists'Now create a router and register each viewset:
router = NetBoxRouter()
router.register('access-lists', views.AccessListViewSet)
router.register('access-list-rules', views.AccessListRuleViewSet)
urlpatterns = router.urls🟢 Tip: The base URL for plugin API endpoints is determined by the base_url in your plugin config (Step 1). In our case, the API root is under /api/plugins/access-lists/.
With all REST API components in place, you should be able to browse the plugin API endpoints.
While logged into NetBox, open:
http://localhost:8000/api/plugins/access-lists/
You should see the available endpoints. Clicking on an endpoint will show a list view, and from there you can click through to individual objects.
🟦 Note: If the REST API endpoints do not load, restart the development server (manage.py runserver) and refresh the page.
For reference, your plugin project should now include the full api/ directory:
.
├── netbox_access_lists
│ ├── api
│ │ ├── __init__.py
│ │ ├── serializers.py
│ │ ├── urls.py
│ │ └── views.py
│ ├── choices.py
│ ├── filtersets.py
│ ├── forms.py
│ ├── __init__.py
│ ├── migrations
│ │ ├── 0001_initial.py
│ │ └── __init__.py
│ ├── models.py
│ ├── navigation.py
│ ├── tables.py
│ ├── templates
│ │ └── netbox_access_lists
│ │ ├── accesslist.html
│ │ └── accesslistrule.html
│ ├── urls.py
│ └── views.py
├── pyproject.toml
└── README.md

