mcpField
N
netbox-plugin-development

netbox-plugin-development

github.com/netboxlabs開発2147 回インストール2 週間前に更新
リポジトリ

netbox-plugin-development について

NetBox のカスタムモデル、ビュー、API、UI を構築するための開発スキルです。NetBox 4.5~4.7 を対象とし、リリースごとに変わる API を最新の資料で確認する方針を示します。

主な機能

  1. 01プラグイン構成と登録設定
  2. 02モデル、ビュー、REST API のパターン
  3. 03フォーム、テーブル、フィルターの指針
  4. 04ナビゲーションとテンプレート拡張
  5. 05テストとパッケージ化のガイド

活用シーン

  1. 01NetBox プラグインの初期構成
  2. 02NetBox の版に合わせたプラグイン更新
  3. 03プラグインへのカスタム API と UI の追加

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/netboxlabs/skills --skill netbox-plugin-development

インストール後、Claude Code で/netbox-plugin-developmentを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
forms-tables-filtersets.md
model-patterns.md
netbox-4.7-migration.md
packaging.md
testing-guide.md
views-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コミュニティプラグインのパターン

カスタムモデル、ビュー、API、UI 要素を追加して NetBox を拡張するプラグインを構築します。 対象: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 (名前/スラッグ/説明/コメント。名前とスラッグの一意性を強制)は、プラグイン API の一部になりました。 モデルがこれらのパターンに当てはまる場合に使用してください。

主な注意点:

  • プラグイン間の FK の衝突を避けるため、related_name に %(app_label)s_ プレフィックスを使用します
  • ForeignKey フィールドでは、コアモデルを文字列('dcim.Device')で参照します
  • 必ず get_absolute_url() を定義します — plugins:<plugin_name>:<model_name> パターンに従います
  • 権限の形式:netbox_myplugin.view_accesslist、.add_accesslist など

ベースクラスの階層、ミックスイン、選択肢のパターン、マイグレーションのヒントについては、references/model-patterns.md を参照してください。


3. ビュー

標準的な CRUD ビューには @register_model_view デコレーターを使用します。

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. テンプレート拡張

2つの方法があります。タブベースの拡張には 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)

フィクスチャ、ビューのテスト、フォームのテスト、CI のセットアップについては、 references/testing-guide.md を参照してください。


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 変数を指している必要があります。dependencies に netbox を絶対に記載しないでください。NetBox は 4.7 以降 PyPI で公開されているため、pip が実際の NetBox と並べて venv に2つ目の NetBox をインストールしてしまいます。 代わりに、PluginConfig の min_version/max_version で互換性を宣言してください。

バージョン管理戦略、PyPI への公開、バージョン互換性マトリクスについては、 references/packaging.md を参照してください。


10. よくある落とし穴

  1. config は __init__.py のモジュールレベルに置く必要があります。関数内に置いてはいけません
  2. FK の related_name が衝突する場合 — 複数のプラグインが同じコアモデルを対象とする場合は、%(app_label)s_ をプレフィックスとして付けます
  3. URL 名前空間 — ビュー:plugins:<plugin>:<view>、API:plugins-api:<plugin>-api:<basename>-detail
  4. filterset に search() がない — これがないと ?q= は機能しません
  5. TemplateExtension の models — クラスではなく 'app.model' 文字列のリストである必要があります。None はグローバルを意味します
  6. __init__.py での重いインポート — シグナルと遅延インポートには ready() を使用します(4.5.2 で修正されましたが、現在もベストプラクティスです)
  7. max_version が厳しすぎる — パッチを許可するには '4.5.0' ではなく '4.7.99'(または使用する最上位マイナーバージョンの .99)を使用します
  8. GraphQL は 4.0 以降 Strawberry を使用します — Graphene のパターンは機能しません
  9. @register_filterset(4.5 以降)— これがないと、ルックアップ修飾子が UI に表示されません
  10. 権限 — 形式は <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 では、ブランチに安全なバックグラウンド書き込みと、config-context の omit 最適化が追加されました。
  • 削除: 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)、Django の EMAIL_* 設定、NetBox の querystring タグ(Django のタグを使用し、request は不要です: {% querystring page=1 %})。完全なチェックリストは netbox-4.7-migration を参照してください。
  • 変更: custom_fields/get_for_model() はリストを返すようになりました。django-tables2 v3(querystring_replace、RelatedLinkColumn はありません)。filterset テストミックスインの名前を *TestMixin に変更しました。検索インデックスの更新は on-commit まで延期されます(テストでは 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 フィルターと config-template コンテキスト(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)— シリアライザーの検索を制御するリゾルバーを登録できます。
  • セキュリティ: ExportTemplate/ConfigTemplate の environment_params における RCE(CVE-2026-29514)は 4.6.1 で修正されました。許可リストにより 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 をサポート

コメント