NetBox 自定义字段(Custom Fields)完全指南:从建模、配置到 REST/GraphQL API 与生命周期管理
【免费下载链接】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 中的自定义字段(Custom Fields)为每个模型提供了高度灵活的元数据扩展能力:你可以在不改动核心数据库表结构的前提下,为 Site、Device、Prefix 等对象挂接任意类型的附加属性(如内部工单号、业务标签、维护窗口),并让这些字段自动融入 Web UI、表单、过滤器、导出模板以及 REST/GraphQL API。本文以 NetBox 官方文档《Custom Fields》为骨架,结合当前仓库中的模型、选项与配置源码,系统讲解自定义字段的类型体系、创建与校验规则、字段生命周期状态机,以及它们在 Jinja2 模板与两类 API 中的实际用法,帮助你在生产环境中安全、高效地落地自定义字段。
自定义字段的定位:为什么需要它
NetBox 中每个模型在数据库里都是一张独立的表,模型的每个属性对应表中的一个列。例如站点存储在dcim_site表中,包含name、facility、physical_address等列。随着 NetBox 的发展,新属性会不断被加入并扩展这些表。
但有些用户需要记录的属性相当"小众",把它们写进 NetBox 核心数据库模式并不合理。例如:你的组织希望把每台设备与内部支持系统中的工单号关联起来——这对 NetBox 是合理用法,但不足以让每个 NetBox 安装都为它内置一个字段。此时就可以创建一个自定义字段来承载这类数据。
从存储实现上看,自定义字段值以 JSON 形式直接保存在对象旁边:启用自定义字段支持的模型都带有一个custom_field_dataJSON 字段,见 netbox/netbox/models/features.py 中的CustomFieldsMixin。这种设计免去了检索对象时编写复杂关联查询的麻烦——数据随对象存储、随对象读取。
自定义字段的类型体系
在 Web 界面中通过Customization > Custom Fields创建自定义字段。NetBox 支持 13 种字段类型,源码中定义于 netbox/extras/choices.py 的CustomFieldTypeChoices,其语义如下:
| 类型 | 存储值 | 说明 |
|---|---|---|
| Text | 字符串 | 自由文本,面向单行使用 |
| Text (long) | 字符串 | 任意长度文本,支持 Markdown 渲染 |
| Integer | 整数 | 正数或负数的整数 |
| Decimal | 小数 | 固定精度小数(4 位小数) |
| Boolean | 布尔 | True / False |
| Date | 日期 | ISO 8601 格式(YYYY-MM-DD) |
| Date & time | 日期时间 | ISO 8601 格式(YYYY-MM-DD HH:MM:SS) |
| URL | 字符串 | 在 Web UI 中以链接呈现;取值受ALLOWED_URL_SCHEMES允许的 scheme 限制;未带 scheme 的值(如example.com)默认视为https并按绝对 URL 存储(https://example.com) |
| JSON | 任意 JSON | 以 JSON 格式存储的任意数据 |
| Selection | 字符串 | 从预定义选项中单选 |
| Multiple selection | 字符串列表 | 支持多选的选项字段 |
| Object | 对象主键 | 由object_type指定的单个 NetBox 对象 |
| Multiple objects | 对象主键列表 | 由object_type指定的一个或多个 NetBox 对象 |
其中 URL 字段在源码中由LaxURLField(assume_scheme='https', ...)实现(见 netbox/extras/models/customfields.py),ALLOWED_URL_SCHEMES的默认值为file, ftp, ftps, http, https, irc, mailto, sftp, ssh, tel, telnet, tftp, vnc, xmpp(见 netbox/netbox/config/parameters.py)。
创建自定义字段:基本属性
每个自定义字段必须有一个名称(name),它应当是数据库友好的字符串(如tps_report),且只能包含字母数字字符和下划线。源码中的正则校验为^[a-z0-9_]+$,并且额外禁止出现双下划线__(见 netbox/extras/models/customfields.py),名称在全局范围内必须唯一。
创建时还需要关注以下属性:
- Label(标签):面向用户的可读名称(如 "TPS report"),显示在 Web 表单上;未提供时界面直接使用 name。
- Weight(权重):必填。权重越高的字段在表单中排得越靠下,默认值为 100。模型默认排序为
['group_name', 'weight', 'name'](见 netbox/extras/models/customfields.py)。 - Description(描述):若提供,会显示在表单中该字段的下方(源码中会以 Markdown 渲染为
help_text)。 - Required(必填):标记后,创建新对象或保存既有对象时强制用户提供值。
- Default(默认值):可为字段设置默认值;布尔字段用
"true"/"false",选择字段必须使用某个选项的精确值。 - Object types(对象类型):一个自定义字段必须被指派到一个或多个模型。创建后,字段会自动出现在这些模型的 Web UI 与 REST API 中。注意并非所有模型都支持自定义字段。
默认值与数据回填行为
(注意:NetBox v4.6.8 起行为有变化)为了提升创建自定义字段的性能,空字段值不再被预置(pre-provisioned)。
- 除非字段设置了默认值,否则创建自定义字段不会向已存在的对象写入任何值。从未被赋值的对象对字段不存储任何内容,在 Web UI、REST API、GraphQL API 与导出中均报告为"无值",与显式存储
null完全一致。 - 这一点只有在直接查询底层
custom_field_dataJSON 时才重要(例如在自定义脚本中)。字段的 key 在赋值之前并不存在于对象数据中,因此请用obj.cf['field_name']或obj.custom_field_data.get('field_name')读取,而不要直接下标访问。 - 与之相反,设置默认值时,会在字段创建的那一刻把该值写入所有既有对象,使其立即可被过滤。但如果字段已存在后再补设默认值,不会回填:无值的对象会继续保持"无值",直到下次保存。
从源码看,回填通过populate_initial_data()完成:它使用jsonb_set仅为尚未持有该字段 key的对象写入默认值(~Q(custom_field_data__has_key=self.name)),因此操作是幂等的(见 netbox/extras/models/customfields.py)。
字段状态(Field Status):生命周期状态机
(注意:该行为在 NetBox v4.7.0 引入)创建带默认值的自定义字段、以及删除自定义字段,都需要重写字段所作用对象已存储的数据。当字段被指派给大量对象时,这项工作无法在一次请求内完成,于是会被交给后台任务执行,字段随之报告自身状态:
| 状态 | 含义 |
|---|---|
| Active | 字段已上线,可正常使用 |
| Provisioning | 正在把字段的默认值写入既有对象 |
| Deleting | 正在从既有对象中移除字段数据 |
是否需要后台任务,取决于该字段全部关联对象类型的对象总数,并对照配置参数BULK_UPDATE_CHUNK_SIZE判断——而不是看这些对象中有多少真正持有字段值。因此,删除一个指派到大型表的字段,即使字段完全没有数据也会被延迟处理:NetBox 无法在不扫描整张表的情况下统计持有值的对象数,而扫描整表正是这个阈值要避免的开销。
BULK_UPDATE_CHUNK_SIZE的默认值为 5000,必须是正整数或None(见 netbox/netbox/settings.py)。源码中的_exceeds_inline_limit()通过"探测"而非COUNT(*)统计行数——只数比上限多一个主键即停止,保证在千万行表上与万行表开销相同(见 netbox/extras/models/customfields.py)。
状态机带来的实际约束:
- 字段只有在Active状态下才"存活"。Provisioning 或 Deleting 期间,字段不会出现在对象、表单、过滤器或两类 API 中,其存储数据只由负责它的任务读写;任务完成后字段才上线(或彻底消失)。
- 期间新创建的对象不受影响——处于 Provisioning 的字段仍会为新对象提供默认值。源码中
get_defaults_for_model()特意把 Provisioning 字段包含进来(DATA_STATUSES = (STATUS_ACTIVE, STATUS_PROVISIONING)),因为回填任务只覆盖字段创建前已存在的对象(见 netbox/extras/models/customfields.py)。 - 非 Active 字段在任务运行期间不可修改(配置不能在被任务重写数据时变动),包括继续指派对象类型、或撤销已有对象类型——此类改动会被拒绝,直到字段重新上线。此约束在
CustomField.clean()中强制实现(见 netbox/extras/models/customfields.py)。 - 待删除的字段在数据清除完成前会一直占用其名称,因此无法用旧值仍残留在对象上的名称新建字段,也不能把既有字段重命名到该名称。
- 这些操作要求有正在运行的后台工作进程
rqworker。字段若因没有 worker 或任务失败而中途搁置,将一直保持待处理状态,直到任务运行完成。 - 无论处于何种状态,字段始终可以被删除。删除一个已待删除的字段会重新排队一个清除任务;处于 Provisioning 的字段则没有等价的应用内重试手段:需要从后台队列(Admin > System > Background Tasks,需 staff 账号)重新入队其任务,或者删除后重建。
注意:从自定义字段撤销对象类型,仍然会立即从这些对象上移除字段数据,并且在非常大的表上仍受请求超时限制;重命名字段同理。
过滤逻辑(Filtering)
过滤逻辑控制按自定义字段过滤对象时的值匹配方式:
- Loose(宽松,默认):部分值匹配。例如精确过滤字符串 "red" 只匹配值 "red",而宽松过滤会匹配 "red"、"red-orange" 甚至 "bored"。
- Exact(精确):给定字符串必须与字段值完全匹配。
- Disabled(禁用):完全禁用按该字段过滤。
对应选项定义于CustomFieldFilterLogicChoices(见 netbox/extras/choices.py)。从源码to_filter()可以看到,宽松匹配在底层映射为icontains(不区分大小写的子串查询),且字段被包装进missing_key_aware_filter_factory,使取反查询能正确匹配那些根本不携带该字段 key 的对象(见 netbox/extras/models/customfields.py)。
分组(Grouping)
相关自定义字段可以在 UI 内分组:给它们赋予相同的组名(group name)。当某个对象类型至少有一个字段定义了分组时,这些字段会显示在对象视图的自定义字段面板中对应分组标题之下;组名必须完全一致,否则每个都会显示为独立标题。
注意该参数对 API 中的自定义字段数据表示没有任何影响。模型层面的排序索引为(group_name, weight, name),即按组名、权重、名称顺序组织展示(见 netbox/extras/models/customfields.py)。
可见性与可编辑性(Visibility & Editing)
创建字段时可控制其在 NetBox UI 中的显示与编辑条件。显示控制有三个选项:
- Always(始终,默认):查看对象时总是包含该字段。
- If Set(已设置时):仅当对象已定义该字段值时显示。
- Hidden(隐藏):UI 中永不显示,推荐用于不面向人工用户的字段。
编辑控制也有三个选项:
- Yes(是,默认):编辑对象时可修改字段值。
- No(否):编辑对象时字段仅作展示,不可修改。
- Hidden(隐藏):编辑对象时不显示该字段。
选项定义见CustomFieldUIVisibleChoices与CustomFieldUIEditableChoices(见 netbox/extras/choices.py)。注意:该设置对 REST 与 GraphQL API 没有影响——自定义字段数据经 API 始终可用。UI 不可编辑的字段在表单层通过field.disabled = True实现只读(见 netbox/extras/models/customfields.py)。
值校验(Validation)
NetBox 对自定义字段值提供有限的自定义校验,按字段类型区分:
| 字段类型 | 校验规则 |
|---|---|
| Text | 正则表达式(可选) |
| Integer | 最小值 / 最大值(可选) |
| Decimal | 最小值 / 最大值(可选) |
| Selection | 必须精确匹配预定义选项之一 |
| JSON | 必须符合定义的 JSON schema(若有) |
这些规则在CustomField.clean()中做了类型绑定约束:最小/最大值仅可用于数值字段(validation_minimum/validation_maximum,最多 16 位、4 位小数);正则仅可用于 Text、Long text 与 URL 字段(validation_regex,如^[A-Z]{3}$可将值限定为恰好三个大写字母);JSON schema 仅可用于 JSON 字段(validation_schema);唯一性约束不能用于布尔字段(见 netbox/extras/models/customfields.py)。实际取值校验则在validate()中逐类型执行(见 netbox/extras/models/customfields.py)。
选择字段(Custom Selection Fields)
每个选择字段必须指定一个包含至少两个选项的选项集(choice set),选项以逗号分隔列表形式给出。
- 若为选择字段指定默认值,它必须精确匹配其中一个选项。
- 多选字段的值总是返回列表,即使只选了一个值。
从源码看,选项集(CustomFieldChoiceSet)还支持可选的基础选项集——内置了 IATA(机场代码)、ISO 3166(国家代码)、UN/LOCODE(地点代码)三套预置选项(见 netbox/extras/choices.py),以及字母序排序、选项配色等扩展能力(见 netbox/extras/models/customfields.py)。当从选项集中移除某个仍被对象引用的选项时,保存会被拒绝,以保证数据完整性。
对象字段(Custom Object Fields)
Object / Multi-object 类型字段以某个 NetBox 对象(或多个对象)作为字段"值"。这类字段必须定义object_type,它决定了字段实例指向的对象类型。
默认情况下,对象选择字段的下拉框会列出该类型全部对象。可以在 Related Object Filter 字段中以 JSON 形式提供query_params字典,把候选对象过滤为仅包含特定值的那部分。query_params的更多说明见自定义脚本文档中的 ObjectVar 一节。源码中该过滤条件会被透传给DynamicModelChoiceField/DynamicModelMultipleChoiceField的query_params参数,并在渲染时拼接到对象选择 API 请求中(见 netbox/extras/models/customfields.py)。
在模板中使用自定义字段
NetBox 的若干特性(如导出模板、Webhook)使用 Jinja2 模板。为了方便,支持自定义字段的对象都通过cf属性暴露字段数据——这比直接读取custom_field_data字段更简洁。例如 Site 模型上一个名为foo123的自定义字段,在实例上可写作:
{{ site.cf.foo123 }}该属性在源码中实现于CustomFieldsMixin.cf:它为实例的每个自定义字段返回"字段名 → 反序列化值"的映射字典(见 netbox/netbox/models/features.py)。反序列化负责把 JSON 中的存储值还原为对应类型的 Python 对象,例如把日期字符串还原为date、把 Object 字段的主键还原为实际对象(见 netbox/extras/models/customfields.py)。
自定义字段与 REST API
通过 REST API 检索对象时,其全部自定义数据都会包含在custom_fields属性中。下面是一个定义了两个自定义字段的站点对象的部分输出:
{ "id": 123, "url": "http://localhost:8000/api/dcim/sites/123/", "name": "Raleigh 42", ... "custom_fields": { "deployed": "2018-06-19", "site_code": "US-NC-RAL42" }, ...Selection 与 Multiple selection 字段会以对象形式返回,同时携带存储值与面向人的标签,与 NetBox 内置选项字段的约定一致:
"custom_fields": { "site_type": { "value": "datacenter", "label": "Data Center" }, "regions": [ { "value": "us-east", "label": "US East" }, { "value": "us-west", "label": "US West" } ] }, ...写入或修改这些值只需包含嵌套 JSON 数据,例如:
{ "name": "New Site", "slug": "new-site", "custom_fields": { "deployed": "2019-03-24" } }与内置选项字段一致,选择型自定义字段在写入时应传入原始值(如"site_type": "datacenter"),而不是读取时返回的{value, label}对象。选择值的{value, label}解析在源码中由resolve_selection_value()统一实现,REST 与 GraphQL 共用,以保证两边表示一致(见 netbox/extras/models/customfields.py)。
自定义字段与 GraphQL API
GraphQL API 的custom_fields字段同样会把 Selection 与 Multiple selection 值解析为{value, label}表示,与 REST API 完全一致。换句话说,通过 GraphQL 读取和过滤自定义字段时,其行为约定与 REST API 保持对称,你可以把上面 REST 一节中关于custom_fields嵌套对象的约定直接套用到 GraphQL 查询中。
运维与性能要点
- 大表操作必须依赖
rqworker:创建带默认值的字段、删除指派到大量对象的字段,都可能转入后台任务(Provisioning / Deleting 状态)。请确保 background worker 正常运行,否则字段会停留在待处理状态。 - 任务失败后的恢复:处于 Deleting 的字段可再次删除以重新排队清除任务;处于 Provisioning 的字段需从Admin > System > Background Tasks重新入队任务(需 staff 账号),或删除后重建。
- 修改被锁定的字段:非 Active 字段在任务运行期间不可编辑、不可增删对象类型,需等待其回到 Active。
- 名称占用:待删除字段会占用其名称,直至数据清除完成,避免新字段"继承"旧数据。
- 写入与回填的幂等性:
populate_initial_data()与remove_stale_data()均只改写实际持有(或不持有)字段 key 的行,且分批提交(每批不超过BULK_UPDATE_CHUNK_SIZE行),既避免超大 JSONB 更新语句触发数据库语句超时,也让任务可安全重试(见 netbox/extras/models/customfields.py)。
总结
自定义字段是 NetBox 数据建模中最灵活、最常用的扩展机制:13 种字段类型覆盖了从纯文本到对象引用的绝大多数附加数据需求;JSON 就近存储的架构让读写都无需复杂关联;v4.7.0 引入的字段状态机则让大表上的默认值回填与字段删除变得安全可控。无论你是想给设备挂接工单号、给站点补充业务分组,还是通过 REST/GraphQL API 与自动化系统交换元数据,都可以按照本文的配置路径、校验规则与 API 约定直接落地。更深入的字段模型定义、选项集管理与校验实现,可继续查阅 customfields.py 模型源码、choices.py 选项定义 以及自定义字段模型文档。
【免费下载链接】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),仅供参考