关于 netbox-plugin-development
为 NetBox 插件开发提供参考资料与实践指南,面向构建自定义模型、视图、API 和界面的开发者。内容覆盖 NetBox 4.5–4.7,并强调按版本核对不断变化的 API。
主要功能
- 01插件结构与注册配置
- 02模型、视图与 REST API 模式
- 03表单、表格与筛选器指南
- 04导航与模板扩展方法
- 05测试和打包建议
适用场景
- 01为 NetBox 插件搭建初始结构
- 02按 NetBox 版本更新插件实现
- 03为插件添加自定义 API 与界面
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ netboxlabs/ skills --skill netbox-plugin-development安装后在 Claude Code 中输入/netbox-plugin-development确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/forms-tables-filtersets.mdmodel-patterns.mdnetbox-4.7-migration.mdpackaging.mdtesting-guide.mdviews-and-api.md详细介绍
NetBox 插件开发
你对 NetBox 插件 API 的了解可能已经过时。 基类、混入类、模板标签和注册模式会随 NetBox 版本变化。请优先检索,而非依赖预训练知识。
检索来源
| 来源 | URL / 方法 | 用途 |
|---|---|---|
| 插件开发文档 | https://netboxlabs.com/docs/netbox/plugins/development/ | 插件框架参考 |
| NetBox 仓库 | https://github.com/netbox-community/netbox | 基类、混入类、当前 API |
| NetBox 发行说明 | https://netboxlabs.com/docs/netbox/release-notes/ | 破坏性变更、新插件功能 |
| 示例插件 | https://github.com/netbox-community | 社区插件模式 |
构建插件,为 NetBox 扩展自定义模型、视图、API 和 UI 元素。 目标版本:NetBox 4.5–4.7 / Python 3.12–3.14。请注意 Django 版本的变化:NetBox 4.5 运行于 Django 5.2,NetBox 4.6 运行于 Django 6.0,NetBox 4.7 运行于 Django 6.1——面向 4.6+ 时,代码、迁移和第三方依赖必须兼容 Django 6.0;面向 4.7+ 时,则必须兼容 Django 6.1。
REST API 客户端模式(分页、筛选、令牌)请参见 netbox-api-integration。
快速参考 — 关键导入
# Models
from netbox.models import NetBoxModel # base for all plugin models
from netbox.models import PrimaryModel # adds description + comments (4.5+)
from netbox.models import OrganizationalModel # name/slug/description/comments (4.5+)
# Views
from netbox.views.generic import (ObjectListView, ObjectView, ObjectEditView,
ObjectDeleteView, BulkImportView, BulkEditView, BulkDeleteView)
from utilities.views import register_model_view, ViewTab
# Forms
from netbox.forms import NetBoxModelForm, NetBoxModelBulkEditForm
from netbox.forms import NetBoxModelFilterSetForm, NetBoxModelImportForm
# Tables
from netbox.tables import NetBoxTable, columns
# FilterSets
from netbox.filtersets import NetBoxModelFilterSet
from utilities.filters import register_filterset # 4.5+: UI lookup modifiers
# REST API
from netbox.api.serializers import NetBoxModelSerializer
from netbox.api.viewsets import NetBoxModelViewSet
from netbox.api.routers import NetBoxRouter
# Navigation
from netbox.plugins import PluginMenu, PluginMenuItem, PluginMenuButton
1. 插件结构与设置
包布局
netbox_myplugin/
├── __init__.py # PluginConfig + config variable
├── version.py # __version__ = '1.0.0'
├── models.py # Django models (NetBoxModel subclasses)
├── views.py # UI views
├── tables.py # django-tables2 table classes
├── forms.py # Model, bulk edit, filter, import forms
├── filtersets.py # FilterSet classes
├── navigation.py # Menu items (auto-discovered)
├── search.py # Search indexes (auto-discovered)
├── graphql/ # Strawberry GraphQL schema
│ ├── schema.py # Query class (auto-discovered)
│ ├── types.py
│ └── filters.py
├── api/
│ ├── urls.py # NetBoxRouter config
│ ├── views.py # API ViewSets
│ └── serializers.py
├── templates/netbox_myplugin/
│ └── myplugin_model.html # Detail view templates
├── template_content.py # Template extensions (auto-discovered)
├── event_rules.py # EventRuleAction subclasses (4.7+, auto-discovered)
├── jinja_env.py # Config-template Jinja filters (4.7+, auto-discovered)
├── graphql_extensions.py # Fields/filters on core GraphQL types (4.7+, auto-discovered)
└── tests/
├── test_models.py
├── test_views.py
├── test_api.py
└── test_forms.py
PluginConfig(__init__.py)
from netbox.plugins import PluginConfig
from .version import __version__
class MyPluginConfig(PluginConfig):
name = 'netbox_myplugin'
verbose_name = 'My Plugin'
description = 'Adds custom functionality to NetBox'
version = __version__
author = 'Your Name'
author_email = '[email protected]'
base_url = 'myplugin' # URL prefix under /plugins/
min_version = '4.5.0'
max_version = '4.7.99' # span 4.5–4.7; use .99 to allow patch releases
default_settings = {'feature_x': True}
required_settings = []
def ready(self):
super().ready()
from . import signals # deferred imports to avoid circular deps
config = MyPluginConfig # MUST be module-level variable named 'config'
自动发现: NetBox 会自动发现 navigation.menu、navigation.menu_items、
template_content.template_extensions、search.indexes、graphql.schema,以及 (4.7+)
event_rules.event_rule_actions、jinja_env.filters、graphql_extensions.type_extensions/.filter_extensions。
2. 模型
继承 NetBoxModel 以获得完整功能支持(标签、自定义字段、变更日志、
书签、日志记录、导出模板、事件规则、通知)。
from django.db import models
from django.urls import reverse
from netbox.models import NetBoxModel
class AccessList(NetBoxModel):
name = models.CharField(max_length=100)
device = models.ForeignKey(
to='dcim.Device',
on_delete=models.CASCADE,
related_name='%(app_label)s_access_lists', # avoid FK collisions
)
type = models.CharField(max_length=50, choices=AccessListTypeChoices)
class Meta:
ordering = ['name']
unique_together = ('device', 'name')
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('plugins:netbox_myplugin:accesslist', args=[self.pk])
4.5+:
PrimaryModel(描述 + 评论)和OrganizationalModel(名称/slug/描述/评论,并强制名称和 slug 唯一)现已纳入插件 API。模型符合这些模式时,请使用它们。
注意事项:
- 在
related_name上使用%(app_label)s_前缀,以避免插件之间的 FK 冲突 - 在 ForeignKey 字段中以字符串形式引用核心模型(
'dcim.Device') - 始终定义
get_absolute_url()——遵循plugins:<plugin_name>:<model_name>模式 - 权限格式:
netbox_myplugin.view_accesslist、.add_accesslist等
有关基类层次结构、混入类、选项模式和迁移提示,请参见 references/model-patterns.md。
3. 视图
使用 @register_model_view 装饰器创建标准 CRUD 视图:
from netbox.views.generic import (
ObjectListView, ObjectView, ObjectEditView, ObjectDeleteView,
BulkImportView, BulkEditView, BulkDeleteView
)
from utilities.views import register_model_view, ViewTab
from .models import AccessList
from .tables import AccessListTable
from .forms import AccessListForm, AccessListFilterForm, AccessListBulkEditForm
from .filtersets import AccessListFilterSet
@register_model_view(AccessList, 'list')
class AccessListListView(ObjectListView):
queryset = AccessList.objects.all()
table = AccessListTable
filterset = AccessListFilterSet
filterset_form = AccessListFilterForm
@register_model_view(AccessList)
class AccessListView(ObjectView):
queryset = AccessList.objects.all()
@register_model_view(AccessList, 'edit')
class AccessListEditView(ObjectEditView):
queryset = AccessList.objects.all()
form = AccessListForm
@register_model_view(AccessList, 'delete')
class AccessListDeleteView(ObjectDeleteView):
queryset = AccessList.objects.all()
URL 配置(urls.py)
from netbox.views.generic import get_model_urls
from . import models
urlpatterns = get_model_urls('netbox_myplugin', models)
有关所有视图类、URL 模式、ViewTab(向核心对象视图添加标签页)、声明式布局 + 面包屑导航(4.7)、GraphQL 核心类型扩展(4.7)和自定义事件规则操作(4.7),请参见 references/views-and-api.md。
4. REST API
序列化器
from netbox.api.serializers import NetBoxModelSerializer
from ..models import AccessList
class AccessListSerializer(NetBoxModelSerializer):
class Meta:
model = AccessList
fields = ('id', 'url', 'display', 'name', 'device', 'type',
'tags', 'custom_fields', 'created', 'last_updated')
brief_fields = ('id', 'url', 'display', 'name') # for nested representation
ViewSet
from netbox.api.viewsets import NetBoxModelViewSet
from ..models import AccessList
from ..filtersets import AccessListFilterSet
from .serializers import AccessListSerializer
class AccessListViewSet(NetBoxModelViewSet):
queryset = AccessList.objects.all()
serializer_class = AccessListSerializer
filterset_class = AccessListFilterSet
路由器(api/urls.py)
from netbox.api.routers import NetBoxRouter
from . import views
router = NetBoxRouter()
router.register('access-lists', views.AccessListViewSet)
urlpatterns = router.urls
API URL 命名: plugins-api:netbox_myplugin-api:accesslist-detail
(这会影响序列化器中的 HyperlinkedIdentityField)。
有关 GraphQL、嵌套序列化器和高级 API 模式,请参见 references/views-and-api.md。
5. 表单、表格与 FilterSet
模型表单
from netbox.forms import NetBoxModelForm
from utilities.forms.fields import DynamicModelChoiceField
from utilities.forms.rendering import FieldSet
from dcim.models import Device
from .models import AccessList
class AccessListForm(NetBoxModelForm):
device = DynamicModelChoiceField(queryset=Device.objects.all())
fieldsets = (
FieldSet('name', 'device', 'type'),
FieldSet('tags', name='Tags'),
)
class Meta:
model = AccessList
fields = ('name', 'device', 'type', 'tags')
表格
import django_tables2 as tables
from netbox.tables import NetBoxTable, columns
class AccessListTable(NetBoxTable):
name = tables.Column(linkify=True) # no LinkColumn in netbox.tables.columns
device = tables.Column(linkify=True)
type = columns.ChoiceFieldColumn()
tags = columns.TagColumn()
class Meta(NetBoxTable.Meta):
model = AccessList
fields = ('pk', 'name', 'device', 'type', 'tags')
default_columns = ('name', 'device', 'type')
FilterSet
from netbox.filtersets import NetBoxModelFilterSet
from utilities.filters import register_filterset
from .models import AccessList
@register_filterset # 4.5+: enables lookup modifiers in UI
class AccessListFilterSet(NetBoxModelFilterSet):
class Meta:
model = AccessList
fields = ('name', 'device', 'type')
def search(self, queryset, name, value):
"""Required for ?q= parameter to work."""
return queryset.filter(name__icontains=value)
4.5+:要让查找修饰符(例如
name__ic、device_id__n)显示在 UI 筛选表单中,必须使用@register_filterset装饰器。
有关批量编辑/导入/筛选表单、GenericObjectChoiceField(4.7)和 django-tables2 v3 注意事项(4.7),请参见 references/forms-tables-filtersets.md。
6. 导航
# navigation.py — auto-discovered
from netbox.plugins import PluginMenu, PluginMenuItem, PluginMenuButton
menu = PluginMenu(
label='My Plugin',
icon_class='mdi mdi-shield-lock',
groups=(
('Access Control', (
PluginMenuItem(
link='plugins:netbox_myplugin:accesslist_list',
link_text='Access Lists',
permissions=['netbox_myplugin.view_accesslist'],
buttons=(
PluginMenuButton(
link='plugins:netbox_myplugin:accesslist_add',
title='Add',
icon_class='mdi mdi-plus-thick',
permissions=['netbox_myplugin.add_accesslist'],
),
),
),
)),
),
)
7. 模板扩展
有两种方式——对于基于标签页的扩展,使用 ViewTab(现代方式);对于向现有页面注入内容,使用 PluginTemplateExtension:
# template_content.py
from netbox.plugins import PluginTemplateExtension
class DeviceAccessInfo(PluginTemplateExtension):
models = ['dcim.device'] # list of strings, not model classes; None = global
def right_page(self):
return self.render('netbox_myplugin/inc/device_access.html', extra_context={
'access_lists': self.context['object'].accesslists.all()
})
template_extensions = [DeviceAccessInfo]
可用的方法:head()、navbar()、list_buttons()、buttons()、alerts()、
left_page()、right_page()、full_width_page()。
8. 测试
from django.test import TestCase
from utilities.testing import APITestCase
from .models import AccessList
class AccessListAPITest(APITestCase):
model = AccessList
def setUp(self):
self.device = Device.objects.create(name='test-device', ...)
self.access_list = AccessList.objects.create(
name='test-acl', device=self.device, type='standard'
)
def test_list(self):
url = reverse('plugins-api:netbox_myplugin-api:accesslist-list')
response = self.client.get(url, **self.header)
self.assertEqual(response.status_code, 200)
请参阅 references/testing-guide.md,了解夹具、视图测试、 表单测试和 CI 设置。
9. 打包
使用 pyproject.toml(现代方式)或 setup.py:
[project]
name = "netbox-myplugin"
version = "1.0.0"
dependencies = [] # never list "netbox" here — see note below
[project.entry-points."netbox.plugins"]
netbox_myplugin = "netbox_myplugin:config"
**注意:**入口点键必须与
PluginConfig.name匹配,并指向模块级的config变量。切勿将netbox列入dependencies:NetBox 自 4.7 起已发布到 PyPI,因此 pip 会在真实的 NetBox 旁边,将第二个 NetBox 安装到虚拟环境中——应改为在PluginConfig上通过min_version/max_version声明兼容性。
请参阅 references/packaging.md,了解版本管理策略、 发布到 PyPI 以及版本兼容性矩阵。
10. 常见问题
config必须定义在模块级别,位于__init__.py中,不能放在函数内部- 外键
related_name冲突——多个插件针对同一个核心模型时,使用%(app_label)s_作为前缀 - URL 命名空间——视图:
plugins:<plugin>:<view>,API:plugins-api:<plugin>-api:<basename>-detail - Filterset 缺少
search()——没有它,?q=将无法使用 - TemplateExtension 上的
models——必须是'app.model'字符串列表,不能是类;None表示全局 - 在
__init__.py中进行重量级导入——信号和延迟导入请使用ready()(已在 4.5.2 中修复,但仍是最佳实践) max_version过于严格——使用'4.7.99'(或你的最高次版本号加.99),而不是'4.5.0',以允许补丁版本- 自 4.0 起,GraphQL 使用 Strawberry——Graphene 模式将无法使用
@register_filterset(4.5+)——没有它,查找修饰符不会显示在 UI 中- 权限——格式为
<plugin_name>.view_<model>、<plugin_name>.add_<model>等。**自定义操作(4.6.0+):**通过模型的Meta.permissions声明额外操作;NetBox 会自动将它们注册为 ObjectPermission 表单中可选的操作(优先于临时权限检查)。
版本说明
NetBox 4.7(2026-09-02)
- PostgreSQL 15+(在 14 上系统检查会失败,包括运行
manage.py test时)和 Django 6.1;NetBox 已发布到 PyPI(不要在pyproject.toml中依赖netbox);建议使用 ≥4.7.3——4.7.0/4.7.1 记录了通过后台批量请求创建的明文 API 令牌,并将其写入作业结果;4.7.3 增加了分支安全的后台写入和配置上下文省略优化 - 已移除:
DEFAULT_ACTION_PERMISSIONS、旧版视图操作、registry['models'](改用ObjectType.objects.public())和registry['denormalized_fields']、OptionalLimitOffsetPagination(→NetBoxPagination)、ExpandableIPAddressField/expand_ipaddress_pattern()(→*IPNetwork*)、populate_custom_field_defaults()、OwnerMixin的反向关联(site_set)、DjangoEMAIL_*设置,以及 NetBox 的querystring标签(改用 Django 的标签,且不传入request:{% querystring page=1 %})——完整清单见 netbox-4.7-migration - 已更改:
custom_fields/get_for_model()返回列表;django-tables2 v3(使用querystring_replace,不再有RelatedLinkColumn);筛选集测试混入类更名为*TestMixin;搜索索引推迟到提交时执行(测试中使用captureOnCommitCallbacks(execute=True));插件视图集继承?background=true和逐对象批量错误处理 - 核心树结构从 MPTT 迁移到 ltree(Region、Location、Platform 等):无法筛选或排序
level;get_root()/get_family()/is_leaf_node()/move_to()/insert_at()均已移除。NestedGroupModel已弃用 →NestedLtreeGroupModel;在 4.7.0 上安装了 ltree 触发器的插件需要添加ReinstallLtreeTriggers迁移——参见 model-patterns - 新的插件扩展点(均需设置
min_version='4.7.0'):核心 GraphQL 类型/筛选器扩展(graphql_extensions.py)、自定义事件规则操作(EventRuleAction)、Jinja 筛选器和配置模板上下文(jinja_env.py、get_jinja_context())、布局中的面包屑、GenericObjectChoiceField、选项描述
NetBox 4.6(2026)
- Django 6.0(4.5 使用的是 5.2)——确保代码、迁移和依赖项兼容 Django 6.0;使用以下任何仅限 4.6 的 API 时,为插件设置
min_version='4.6.0' - 声明式视图布局 / 可复用 UI 组件——
from netbox.ui import layout(Layout/Row/Column);这是手写详情模板的现代替代方案(需要 NetBox ≥4.6.0) - 通过
Meta.permissions定义自定义模型操作 / 权限——在模型的Meta中声明;NetBox 会自动将其注册为可在 ObjectPermission 表单中选择的操作 get_serializer_for_model()的自定义序列化器解析器(4.6.2)——注册解析器以控制序列化器查找- **安全性:**4.6.1 修复了 ExportTemplate/ConfigTemplate
environment_params的 RCE 漏洞(CVE-2026-29514)——允许列表会阻止extensions/finalize/loader/bytecode_cache - 应避免使用的弃用项:
DEFAULT_ACTION_PERMISSIONS、旧版视图操作、内部注册表的models键、自定义querystring模板标签,以及OptionalLimitOffsetPagination
NetBox 4.5(2026-01-06)
- 需要 Python 3.12+(不再支持 3.10/3.11);Django 5.2
PrimaryModel、OrganizationalModel、NestedGroupModel现已加入插件 API@register_filterset装饰器可启用 UI 查找修饰符OwnerMixin可用于对象所有权- **重大变更:**GraphQL 筛选语法要求 ID/枚举使用查找修饰符
NetBox 4.4(2025-09-02)
- 后台作业日志记录:在作业方法中使用
self.logger.info() ObjectAction用于视图中的自定义单项 / 批量操作register_model_feature()用于插件注册自定义模型特性- 表格支持
ArrayColumn