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}'每个模型默认包含一个由数据库自动生成的自增数值主键,可通过pk或id引用。
注意(命名规范):模型类名应遵循 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):
- 提供功能运行所需的字段、方法和属性——例如
ChangeLoggingMixin自动追加created/last_updated时间戳,CustomFieldsMixin增加custom_field_dataJSON 字段,TagsMixin通过NetBoxTaggableManagerField提供tags管理属性(见 netbox/netbox/models/features.py); - 将模型注册为“使用这些功能”——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),其组合了:BookmarksMixin、ChangeLoggingMixin、CloningMixin、CustomFieldsMixin、CustomLinksMixin、CustomValidationMixin、ExportTemplatesMixin、JournalingMixin、NotificationsMixin、TagsMixin与EventRulesMixin。同时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模块中出现的其他类(如ImageAttachmentsMixin、SyncedDataMixin、NotificationsMixin等——尽管核心模型在用)目前不支持被插件使用,插件应只依赖文档公开承诺的 API。
内置扩展模型类:PrimaryModel、OrganizationalModel 与 NestedGroupModel
除了NetBoxModel基类,NetBox 还额外提供了三类开箱即用的模型基类,方便插件按对象性质选择。它们均在 NetBox v4.5 中纳入插件 API,定义于 netbox/netbox/models/init.py:
PrimaryModel(主模型)
适用于绝大多数“真实对象”类型。它在NetBoxModel的基础上扩展了description与comments字段,并引入所有权(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来实现层级对象。
数据库迁移:从makemigrations到migrate
模型定义完成后,需要为其生成数据库模式迁移。迁移文件本质上是指导 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)建议:最好在插件
PluginConfig的ready()方法中执行功能注册,确保应用加载完成、模型可用之后再注册判定逻辑。
注册完成后,该自定义功能即与 NetBox 原生功能一样,可被has_feature()、get_model_features()等查询,并能在 UI/API 层作为该模型的特性被识别和展示。
ChoiceSet:为模型字段定义可动态配置的选项
对于需要从预定义列表中选择一个或多个值的模型字段,NetBox 提供了ChoiceSet工具类,可替代 Django 原生 choices 元组,带来两项增强能力:动态配置与颜色标记(模型级消费者可通过get_FOO_color()获取颜色映射,见 netbox/utilities/choices.py)。
定义 ChoiceSet
为模型字段定义选项时,继承ChoiceSet并定义一个名为CHOICES的元组/列表,每个成员是二元素或三元素元组:
- 数据库值(value)
- 人类可读标签(label)
- 分配的颜色(可选,color)
约定:建议将每个数据库值声明为类上的常量,并在
CHOICES成员中引用这些常量,这样可以在类外部引用这些值(例如作为字段默认值)。此约定非强制。
动态配置:key与FIELD_CHOICES
NetBox 中部分模型字段的选项可由管理员配置(例如 Site 模型status字段的默认选项可以被替换或补充)。要为某个ChoiceSet子类启用动态配置,需将其key定义为“模型.字段”形式的字符串:
from utilities.choices import ChoiceSet class StatusChoices(ChoiceSet): key = 'MyModel.status'随后,NetBox 管理员可通过FIELD_CHOICES配置参数扩展或替换该选项集的默认值。my_plugin中MyModel的status字段被引用为:
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),仅供参考