NetBox 插件开发指南:数据库模型(Database Models)的创建、NetBox 功能集成与 ChoiceSet 用法
2026/9/21 18:41:52 网站建设 项目流程

NetBox 插件开发指南:数据库模型(Database Models)的创建、NetBox 功能集成与 ChoiceSet 用法

【免费下载链接】netboxThe premier source of truth powering network automation. Open source under Apache 2. Try NetBox Cloud free: https://netboxlabs.com/products/free-netbox-cloud/项目地址: https://gitcode.com/gh_mirrors/ne/netbox

NetBox 插件允许在核心对象之外引入全新的数据模型,以支撑自定义业务对象(如专有的资产类型、工单状态等)。本文以官方文档 docs/plugins/development/models.md 为骨架,系统讲解如何在插件中定义 Django 模型、通过继承NetBoxModel及其功能 Mixin 启用标签、自定义字段、变更日志、事件规则等 NetBox 原生能力,并深入剖析数据库迁移、register_model_feature()自定义功能注册以及ChoiceSet动态选项配置的底层实现。读完本文,你将能够独立编写一个可被 NetBox 完整识别、支持核心特性的插件数据模型。

插件中的 Django 模型:从models.py开始

插件本质上是与 NetBox 一同安装的独立 Django 应用,因此插件引入新对象类型的最直接方式就是定义 Django 模型。模型是数据库表的 Python 表示,其属性对应表中的列;通过 Django 查询 可以创建、修改和删除模型实例。所有模型都必须定义在名为models.py的文件中,这是 Django 自动发现模型类(进而生成迁移、注册到管理后台)的约定。

一个最基础的双字段示例(来自原文档,可直接复制运行):

from django.db import models class MyModel(models.Model): foo = models.CharField(max_length=50) bar = models.CharField(max_length=50) def __str__(self): return f'{self.foo} {self.bar}'

每个模型默认包含一个由数据库自动生成的自增数值主键,可通过pkid引用。

注意(命名规范):模型类名应遵循 PEP8 的 CapWords 风格(首字母大写的驼峰式,不要使用下划线)。模型名中若出现下划线会导致权限系统出现问题,因为 NetBox 的权限机制会基于模型名解析动作权限。

启用 NetBox 功能:继承NetBoxModel

仅仅定义一个普通的models.Model只能获得 Django 提供的基础能力。若希望插件模型能复用 NetBox 的标签(tags)、自定义字段(custom fields)、事件规则(event rules)、自定义链接、变更日志、书签、通知、导出模板等功能,必须继承 NetBox 提供的NetBoxModel基类。从源码看,该基类承担两项关键职责(见 netbox/netbox/models/init.py 及 netbox/netbox/models/features.py):

  1. 提供功能运行所需的字段、方法和属性——例如ChangeLoggingMixin自动追加created/last_updated时间戳,CustomFieldsMixin增加custom_field_dataJSON 字段,TagsMixin通过NetBoxTaggableManagerField提供tags管理属性(见 netbox/netbox/models/features.py);
  2. 将模型注册为“使用这些功能”——NetBox 的register_models()会依据模型继承的 Mixin 自动注册相应功能视图(如变更日志页、标签页),见 netbox/netbox/models/features.py。

定义方式非常简单:

# models.py from django.db import models from netbox.models import NetBoxModel class MyModel(NetBoxModel): foo = models.CharField() ...

NetBoxModel的完整功能集合定义在NetBoxFeatureSet中(见 netbox/netbox/models/init.py),其组合了:BookmarksMixinChangeLoggingMixinCloningMixinCustomFieldsMixinCustomLinksMixinCustomValidationMixinExportTemplatesMixinJournalingMixinNotificationsMixinTagsMixinEventRulesMixin。同时NetBoxModel继承自BaseModel(见 netbox/netbox/models/init.py),后者对 Django 默认行为做了重要增强:

  • 默认管理器替换为RestrictedQuerySet.as_manager(),使对象查询自动受权限过滤;
  • clean()save()会校验 GenericForeignKey 字段,并把“可空且唯一”的 CharField 空字符串强制转为None,避免 PostgreSQL 将空串视为重复值而触发唯一性冲突。

NetBoxModel属性:docs_url_netbox_private

NetBoxModel还提供了两个可供插件覆盖/使用的属性:

  • docs_url:指定该模型文档的访问 URL。默认返回/static/docs/models/<app_label>/<model_name>/(见 netbox/netbox/models/init.py)。插件模型可覆盖此属性,例如指向插件自建在 ReadTheDocs 上的文档。
  • _netbox_private:默认情况下,插件模型会出现在“通用对象类型”列表中(例如创建自定义字段或某些仪表盘部件时的候选对象)。如果模型仅供“幕后使用”、不应暴露给终端用户,可将_netbox_private设为True,将其从通用对象类型列表中剔除。其判定逻辑位于model_is_public()(见 netbox/netbox/models/features.py):只有app_label属于核心应用或PluginConfig的应用,且未标记_netbox_private的模型才视为“公开可用”。

按需启用功能:使用独立 Mixin

如果只想启用上述功能的子集,NetBox 为每个功能提供了独立的“混合(mix-in)”类。定义模型时分别继承这些 Mixin 即可(注意此时仍需继承 Django 内置的Model类)。例如仅支持标签与导出模板:

# models.py from django.db import models from netbox.models.features import ExportTemplatesMixin, TagsMixin class MyModel(ExportTemplatesMixin, TagsMixin, models.Model): foo = models.CharField() ...

继承所有可用的 Mixin 在效果上等同于直接继承NetBoxModel。需要特别留意的是,官方文档明确给出警告:只有文档中列出的 Mixin 才受官方支持,features模块中出现的其他类(如ImageAttachmentsMixinSyncedDataMixinNotificationsMixin等——尽管核心模型在用)目前支持被插件使用,插件应只依赖文档公开承诺的 API。

内置扩展模型类:PrimaryModel、OrganizationalModel 与 NestedGroupModel

除了NetBoxModel基类,NetBox 还额外提供了三类开箱即用的模型基类,方便插件按对象性质选择。它们均在 NetBox v4.5 中纳入插件 API,定义于 netbox/netbox/models/init.py:

PrimaryModel(主模型)

适用于绝大多数“真实对象”类型。它在NetBoxModel的基础上扩展了descriptioncomments字段,并引入所有权(ownership)支持(通过OwnerMixin提供owner字段):

字段必填唯一说明
owner对象的所有者
description对象的人类可读描述(CharField,最长 200 字符)
comments通用备注(TextField

字段定义可对照 netbox/netbox/models/init.py 源码。

OrganizationalModel(组织模型)

用于主要承担“组织/归类其他对象”职能的对象类型(如设备角色、区域等)。字段如下(见 netbox/netbox/models/init.py):

字段必填唯一说明
name对象名称(CharField,最长 100 字符)
slug唯一的 URL 友好标识(SlugField
owner对象的所有者
description对象的人类可读描述

NestedGroupModel(嵌套分组模型)

用于可递归排列成层级结构的对象(如同 Region、Location),通过自引用的parent外键实现。其字段如下:

字段必填唯一说明
name对象名称
slug唯一的 URL 友好标识
parent将该对象嵌套于其下的同类型对象
owner对象的所有者
description对象的人类可读描述
comments通用备注

需要说明的是,当前仓库中NestedGroupModel仍是基于 django-mptt 的实现(见 netbox/netbox/models/init.py),但其源码注释明确指出:新代码(包括插件)应使用NestedLtreeGroupModel(基于 PostgreSQL ltree,见同文件 L231-L262),MPTT 版本仅为向后兼容而保留,将在未来版本中移除。因此新插件应优先使用NestedLtreeGroupModel来实现层级对象。

数据库迁移:从makemigrationsmigrate

模型定义完成后,需要为其生成数据库模式迁移。迁移文件本质上是指导 PostgreSQL 数据库创建新表或修改既有表的一组指令。多数情况下可用 Django 的makemigrations管理命令自动生成——前提是插件已安装并启用,否则 Django 无法找到该应用。

提示(开启开发者模式):NetBox 对makemigrations命令设有保护,防止普通用户误生成错误的模式迁移。插件开发时需在configuration.py中设置DEVELOPER=True才能使用该命令。

生成迁移:

$ ./manage.py makemigrations my_plugin Migrations for 'my_plugin': /home/jstretch/animal_sounds/my_plugin/migrations/0001_initial.py - Create model MyModel

然后应用迁移:

$ ./manage.py migrate my_plugin Operations to perform: Apply all migrations: my_plugin Running migrations: Applying my_plugin.0001_initial... OK

迁移目录migrations/是插件标准结构的一部分(参见 docs/plugins/development/index.md 中的插件结构示例)。更多迁移机制可参阅 Django 迁移文档。

功能 Mixin 参考(Feature Mixins Reference)

NetBox 为插件官方支持以下功能 Mixin(均可从netbox.models.features导入),插件可按需组合:

Mixin 类启用能力源码实现要点
BookmarksMixin用户书签通过GenericRelation关联extras.Bookmark
ChangeLoggingMixin变更日志追加created/last_updated字段,提供snapshot()to_objectchange(),配合ChangeLoggingMiddleware记录变更
CloningMixin对象克隆提供clone()方法,按clone_fields复制属性以预填充创建表单
ContactsMixin联系人分配通过ContactAssignment关联联系人,get_contacts()可继承父对象联系人
CustomLinksMixin自定义链接使模型可作为自定义链接的目标
CustomFieldsMixin自定义字段增加custom_field_dataJSON 字段及cf/custom_fields访问器,并在clean()/save()中校验、填充默认值
CustomValidationMixin自定义校验clean()中发送post_clean信号,供自定义校验规则挂钩
EventRulesMixin事件规则使模型可挂接事件规则(Webhook、自动执行脚本)
ExportTemplatesMixin导出模板使模型支持导出模板
JobsMixin任务结果关联core.Job,提供get_latest_jobs(),删除对象时分批清理关联任务
JournalingMixin对象日志通过GenericRelation关联extras.JournalEntry
TagsMixin标签提供NetBoxTaggableManager类型的tags字段,支持多个应用中同名模型的倒置访问器去冲突

以上各类的具体实现均位于 netbox/netbox/models/features.py。NetBox 核心正是通过如下方式在启动时把“功能名 → 判定函数”注册进registry['model_features'](见同文件 L705-L719):

register_model_feature('bookmarks', lambda model: issubclass(model, BookmarksMixin)) register_model_feature('custom_fields', lambda model: issubclass(model, CustomFieldsMixin)) register_model_feature('tags', lambda model: issubclass(model, TagsMixin)) # ... 其余功能同理

随后has_feature()/get_model_features()(见 netbox/netbox/models/features.py)在运行时查询模型支持的功能集合。

自定义模型功能:register_model_feature()

除了使用 NetBox 原生提供的模型功能,插件还可以注册自己的模型功能。这通过netbox.utils中的register_model_feature()函数完成(见 netbox/netbox/utils.py)。该函数接受两个参数:功能名称,以及一个接收模型类的可调用对象;该可调用对象必须返回布尔值,指示给定模型是否支持该功能。

从源码看,其实现会把name → func写入全局registry['model_features'];若同名功能已注册会抛出ValueError。函数既可作为装饰器使用:

@register_model_feature('foo') def supports_foo(model): # Your logic here

也可直接调用:

register_model_feature('foo', supports_foo)

建议:最好在插件PluginConfigready()方法中执行功能注册,确保应用加载完成、模型可用之后再注册判定逻辑。

注册完成后,该自定义功能即与 NetBox 原生功能一样,可被has_feature()get_model_features()等查询,并能在 UI/API 层作为该模型的特性被识别和展示。

ChoiceSet:为模型字段定义可动态配置的选项

对于需要从预定义列表中选择一个或多个值的模型字段,NetBox 提供了ChoiceSet工具类,可替代 Django 原生 choices 元组,带来两项增强能力:动态配置颜色标记(模型级消费者可通过get_FOO_color()获取颜色映射,见 netbox/utilities/choices.py)。

定义 ChoiceSet

为模型字段定义选项时,继承ChoiceSet并定义一个名为CHOICES的元组/列表,每个成员是二元素或三元素元组:

  1. 数据库值(value)
  2. 人类可读标签(label)
  3. 分配的颜色(可选,color)

约定:建议将每个数据库值声明为类上的常量,并在CHOICES成员中引用这些常量,这样可以在类外部引用这些值(例如作为字段默认值)。此约定非强制。

动态配置:keyFIELD_CHOICES

NetBox 中部分模型字段的选项可由管理员配置(例如 Site 模型status字段的默认选项可以被替换或补充)。要为某个ChoiceSet子类启用动态配置,需将其key定义为“模型.字段”形式的字符串:

from utilities.choices import ChoiceSet class StatusChoices(ChoiceSet): key = 'MyModel.status'

随后,NetBox 管理员可通过FIELD_CHOICES配置参数扩展或替换该选项集的默认值。my_pluginMyModelstatus字段被引用为:

FIELD_CHOICES = { 'my_plugin.MyModel.status': ( # Custom choices ) }

对照 docs/configuration/data-validation.md 中的说明:FIELD_CHOICES是“模型字段 → 选项列表”的字典;每个选项必须包含数据库值和标签,可选颜色;不带+后缀表示替换默认选项,带+后缀(如'my_plugin.MyModel.status+')表示追加到默认选项;字段标识符大小写不敏感。

从源码层面看(netbox/utilities/choices.py),ChoiceSetMeta元类在类创建时读取key,并以{app}.{key}为键去settings.FIELD_CHOICES中查找替换/扩展配置:找到replace_key则整体替换CHOICES,否则查找replace_key + '+'并将配置项追加到现有CHOICES。因此**CHOICES必须声明为可变的 list 而非 tuple**,否则动态扩展会失败(元类甚至会在key存在而CHOICES不是 list 时抛出ImproperlyConfigured)。

提示ChoiceSet还提供values()as_enum()类方法,可将选项集转为值列表或enum.Enum(见 netbox/utilities/choices.py),便于在代码中以类型安全方式引用选项值。

完整示例

my_plugin插件为例,先定义choices.py

# choices.py from utilities.choices import ChoiceSet class StatusChoices(ChoiceSet): key = 'MyModel.status' STATUS_FOO = 'foo' STATUS_BAR = 'bar' STATUS_BAZ = 'baz' CHOICES = [ (STATUS_FOO, 'Foo', 'red'), (STATUS_BAR, 'Bar', 'green'), (STATUS_BAZ, 'Baz', 'blue'), ]

再在models.py中引用:

# models.py from django.db import models from .choices import StatusChoices class MyModel(models.Model): status = models.CharField( max_length=50, choices=StatusChoices, default=StatusChoices.STATUS_FOO )

字段的choices参数直接传入StatusChoices类即可(ChoiceSet实现了__iter__,见 netbox/utilities/choices.py);默认值引用类常量,保证与CHOICES中的数据库值一致。之后管理员即可通过configuration.py中的FIELD_CHOICES动态调整my_plugin.MyModel.status的可用选项,而无需改动插件代码——这正是ChoiceSet相比原生 choices 元组的核心优势。

小结

插件模型开发的核心路径可以归纳为三步:定义模型 → 选择基类/Mixin 启用 NetBox 功能 → 生成并应用迁移。在此基础上,register_model_feature()允许插件扩展 NetBox 的模型功能注册表,ChoiceSet则让插件模型字段也能享受与核心模型一致的动态选项配置体验。建议新插件优先使用PrimaryModel/OrganizationalModel/NestedLtreeGroupModel等内置基类,并只依赖 docs/plugins/development/models.md 与 docs/plugins/development/index.md 所承诺的受支持 API,以确保在 NetBox 后续版本中的兼容性。

【免费下载链接】netboxThe premier source of truth powering network automation. Open source under Apache 2. Try NetBox Cloud free: https://netboxlabs.com/products/free-netbox-cloud/项目地址: https://gitcode.com/gh_mirrors/ne/netbox

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询