netbox-community / netbox-plugin-tutorial Goto Github PK
View Code? Open in Web Editor NEWA tutorial on building custom plugins for NetBox v3.2+
A tutorial on building custom plugins for NetBox v3.2+
Thank you Jeremy for a great tutorial. I have built a plugin following the plugin tutorial everything works fine however upon trying to put the models so that it could initiate the webhook because i need to use changes to the models to make some changes outside netbox its not working.
I went on to install the plugin (netbox-access-lists) on my server and i tried to get a webhook response from the plugin to no avail. I am getting a repsonse though using built in models like 'dcim.devices' as shown in the image below
What aspects should i change in order to get a response from the webhook using the tutorial as the case study
cookiecutter-netbox-plugin was recently released to provide a convenient scaffolding for new NetBox plugins. We should rewrite the first step of the tutorial to use it, to minimize the amount of time the reader must spend on boilerplate setup.
After following the instructions about 45 times. Each time resulting with the same issue. After restarting netbox, I'm seeing the error in the logs. "Unable to import plugin netbox_access_lists" and it fails to start.
Somethings not installing or adding correctly. etc.
Hello, everyone
I'm trying to develop netbox plugin while refering to this tutorial.
However, when step 5, I stucked because netbox rule.
Therefore, I would like to add comment for this netbox rule explicitly.
Add button
is not shown in model list view if URL name with `_'.
when refering Test the Views
ex) when model name is VLANGroupSet
good: vlangroupset_add
bad: vlan_group_set_add
Maybe this rule is related in /netbox/utilities/utils.py
(I cloud not find truth information, sorry)
It would be useful as a new plugin author to expand the plugin tutorial to include steps around creating and managing custom permissions for a plugin.
NetBox 3.2.0 was just released and has a number of extension improvements.
Furthermore, a NetBox 3.2 plugin tutorial was released
NetBox v3.4 introduced official support for plugins to register their own top-level navigation menus (FR #9071).
The guide intentionally links to the documentation for NetBox's feature
branch. This will need to be updated to stable
once v3.2.0 is released.
NetBox v3.4 introduced a new global search engine, with the ability for plugins to register their own models (FR #8927).
It would be useful as a new plugin author to include tutorial steps on adding and utilising configuration options. (It took me a little while to reverse engineer how to access plugin config options within my code).
I just started with the tutorial as described in Step 1 and have successfully installed "my" plugin.
Step 2 starts with the following note:
Note: If you skipped the previous step, run git checkout step01-initial-setup.
Step 1 ends with the following tip
Tip: You can check your work at the end of each step in the tutorial by running a git diff against the corresponding branch. For example, at the end of step one, run git diff step01-initial-setup to compare your work with the completed step. This will help identify any tasks you might have missed.
Executing this last command in the location where I ended up after installing the pug, I get
fatal: ambiguous argument 'step01-initial-setup': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'
Executing the git command from the beginning of step 2 I get
error: The following untracked working tree files would be overwritten by checkout:
README.md
netbox_access_lists/__init__.py
setup.py
Please move or remove them before you switch branches.
Aborting
My git-foo is pretty poor, so I am lost on what to do next. Step 2 suggests that there should have been a git command executed at the end of step 1, but I am sure that a diff is not the one.
What did I miss?
Thanks!
In the guide the EditView forms are defined as:
class AccessListEditView(generic.ObjectEditView):
queryset = models.AccessList.objects.all()
form = forms.AccessListForm
class AccessListRuleEditView(generic.ObjectEditView):
queryset = models.AccessListRule.objects.all()
form = forms.AccessListRuleForm
Trying to render the pages an error occur:
TypeError at /plugins/access-lists/access-lists/add/
'NoneType' object is not callable
Request Method: GET
Request URL: http://127.0.0.1:8000/plugins/access-lists/access-lists/add/
Django Version: 4.0.3
Exception Type: TypeError
Exception Value:
'NoneType' object is not callable
Exception Location: /opt/netbox/netbox/netbox/views/generic/object_views.py, line 342, in get
Python Executable: /opt/netbox/venv/bin/python3
Python Version: 3.8.10
Python Path:
['/opt/netbox/netbox',
'/usr/lib/python38.zip',
'/usr/lib/python3.8',
'/usr/lib/python3.8/lib-dynload',
'/opt/netbox/venv/lib/python3.8/site-packages',
'/home/network/netbox-plugin-demo']
Server time: Tue, 05 Apr 2022 12:43:58 +0000
Checking the inherited class generic.ObjectEditView
it seems the variable name for model form should be model_form
Updating the two views in views.py
as shown here seems to fix the issue:
class AccessListEditView(generic.ObjectEditView):
queryset = models.AccessList.objects.all()
- form = forms.AccessListForm
+ model_form = forms.AccessListForm
class AccessListRuleEditView(generic.ObjectEditView):
queryset = models.AccessListRule.objects.all()
- form = forms.AccessListRuleForm
+ model_form = forms.AccessListRuleForm
Environment
Python: 3.8.10
Code: v3.2-beta2 (got from sudo git clone -b v3.2-beta2 --depth 1 https://github.com/netbox-community/netbox.git .
)
Cloning the netbox-plugin-tutorial
doing a checkout on the last step to try out the functionality.
When applying the migrations with python netbox/manage.py migrate
produces the following error.
File "/home/network/netbox-plugin-demo/netbox_access_lists/api/urls.py", line 1, in <module>
from netbox.api.routers import NetBoxRouter
ImportError: cannot import name 'NetBoxRouter' from 'netbox.api.routers' (/opt/netbox/netbox/netbox/api/routers.py)
Looking at the contents of netbox/netbox/netbox/api/routers.py
in v3.2-beta2 it does not seem to have a NetBoxRouter class
from collections import OrderedDict
from rest_framework.routers import DefaultRouter
class OrderedDefaultRouter(DefaultRouter):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Extend the list view mappings to support the DELETE operation
self.routes[0].mapping.update({
'put': 'bulk_update',
'patch': 'bulk_partial_update',
'delete': 'bulk_destroy',
})
def get_api_root_view(self, api_urls=None):
"""
Wrap DRF's DefaultRouter to return an alphabetized list of endpoints.
"""
api_root_dict = OrderedDict()
list_name = self.routes[0].name
for prefix, viewset, basename in sorted(self.registry, key=lambda x: x[0]):
api_root_dict[prefix] = list_name.format(basename=basename)
return self.APIRootView.as_view(api_root_dict=api_root_dict)```
Hello everyone,
I'm trying to debug a new plugin I'm writing for Netbox. But I'm currently stuck at the error. 'netbox_redfish' is not a registered namespace inside 'plugins'
There is a stack trace but it isn't pointing to the line of code wich is causing the error.
Code can be found here: https://github.com/accelleran/netbox_redfish
It would be useful as a new plugin author to expand the plugin tutorial to include steps around creating and managing custom permissions for a plugin.
Near the bottom of the page for Step5 there is a section Test the Views that contains the following URL: http://localhost:8000/plugins/netbox-access-lists/access-lists/
Trying to open the URL provides the following error:
Using the URLconf defined in netbox.urls, Django tried these URL patterns, in this order:
[name='home']
search/ [name='search']
login/ [name='login']
logout/ [name='logout']
oauth/
circuits/
dcim/
extras/
ipam/
tenancy/
user/
virtualization/
wireless/
api/ [name='api-root']
api/circuits/
api/dcim/
api/extras/
api/ipam/
api/tenancy/
api/users/
api/virtualization/
api/wireless/
api/status/ [name='api-status']
api/docs/ [name='api_docs']
api/redoc/ [name='api_redocs']
^api/swagger(?P<format>.json|.yaml)$ [name='schema_swagger']
graphql/ [name='graphql']
media/<path:path>
media-failure/ [name='media_failure']
plugins/ access-lists/
api/plugins/
admin/background-tasks/
admin/plugins/
admin/
__debug__/
The current path, plugins/netbox-access-lists/access-lists/, didn’t match any of these.
When I run python3 setup.py develop I get the message:
running develop
/usr/lib/python3/dist-packages/setuptools/command/easy_install.py:158: EasyInstallDeprecationWarning: easy_install command is deprecated. Use build and pip and other standards-based tools.
warnings.warn(
/usr/lib/python3/dist-packages/setuptools/command/install.py:34: SetuptoolsDeprecationWarning: setup.py install is deprecated. Use build and pip and other standards-based tools.
warnings.warn(
/usr/lib/python3/dist-packages/pkg_resources/__init__.py:116: PkgResourcesDeprecationWarning: 0.1.43ubuntu1 is an invalid version and will not be supported in a future release
warnings.warn(
/usr/lib/python3/dist-packages/pkg_resources/__init__.py:116: PkgResourcesDeprecationWarning: 1.1build1 is an invalid version and will not be supported in a future release
warnings.warn(
running egg_info
writing netbox_end_of_support.egg-info/PKG-INFO
writing dependency_links to netbox_end_of_support.egg-info/dependency_links.txt
writing top-level names to netbox_end_of_support.egg-info/top_level.txt
reading manifest file 'netbox_end_of_support.egg-info/SOURCES.txt'
writing manifest file 'netbox_end_of_support.egg-info/SOURCES.txt'
running build_ext
Creating /usr/local/lib/python3.8/dist-packages/netbox-end-of-support.egg-link (link to .)
netbox-end-of-support 0.1 is already the active version in easy-install.pth
Installed /opt/netbox-3.4.4/plugins
Processing dependencies for netbox-end-of-support==0.1
Finished processing dependencies for netbox-end-of-support==0.1
When running the command mange.py run server. I get the message:
Unable to import plugin netbox-end-of-support: Module not found. Check that the plugin module has been installed within the correct Python environment.
What am I doing wrong
I've followed the tutorial, started a very basic plugin (just 1 model in models.py for now) from scratch and for the life of me I can't get the API of the plugin to load. If I put an obvious syntax error into urls.py, automatic reloading or manually restarting the server doesn't give an error. Opening netbox, no error. Trying to access the API URL just returns a 404 (or rather the debug version of that) that the route doesn't exist, which matches the behaviour I am seeing.
The plugin itself works fine. I must be missing something obvious, but after looking at all the files I don't see any difference apart from naming to the tutorial or existing plugins with an API. And yet... The API directory has just the name "api", I even checked that....
Has anyone ever seen this before and can help? If necessary I can add some files here, but I am not sure which ones would be relevant.
Thanks!
Following the tutorial and creating the ChoiceSet as a tuple results in the following error when extending via FIELD_CHOICES
AttributeError: 'tuple' object has no attribute 'extend'
The tutorial has the following
class ActionChoices(ChoiceSet):
key = 'AccessListRule.action'
CHOICES = (
('permit', 'Permit', 'green'),
('deny', 'Deny', 'red'),
('reject', 'Reject (Reset)', 'orange'),
)
It should be
class ActionChoices(ChoiceSet):
key = 'AccessListRule.action'
CHOICES = [
('permit', 'Permit', 'green'),
('deny', 'Deny', 'red'),
('reject', 'Reject (Reset)', 'orange'),
]
Hello, I have one error when I open plugin in my netbox . Is it compatible with v3.6.4 Netbox ?
Server Error
There was a problem with your request. Please contact an administrator.
The complete exception is provided below:
<class 'ValueError'>
Cannot query "rpawela": Must be "SavedTopology" instance.
Python version: 3.9.2
NetBox version: 3.6.4
Plugins:
netbox_bgp: 0.11.0
netbox_dns: 0.20.2
netbox_interface_sync: 0.2.0
netbox_qrcode: 0.0.10
netbox_topology_views: 3.8.1
nextbox_ui_plugin: 0.13.0
If further assistance is required, please post to the NetBox discussion forum on GitHub.
File "/usr/lib/python3.8/threading.py", line 932, in _bootstrap_inner
self.run()
File "/usr/lib/python3.8/threading.py", line 870, in run
self._target(*self._args, **self._kwargs)
File "/usr/local/lib/python3.8/dist-packages/django/utils/autoreload.py", line 64, in wrapper
fn(*args, **kwargs)
File "/usr/local/lib/python3.8/dist-packages/django/core/management/commands/runserver.py", line 133, in inner_run
self.check(display_num_errors=True)
File "/usr/local/lib/python3.8/dist-packages/django/core/management/base.py", line 485, in check
all_issues = checks.run_checks(
File "/usr/local/lib/python3.8/dist-packages/django/core/checks/registry.py", line 88, in run_checks
new_errors = check(app_configs=app_configs, databases=databases)
File "/usr/local/lib/python3.8/dist-packages/django/core/checks/urls.py", line 42, in check_url_namespaces_unique
all_namespaces = _load_all_namespaces(resolver)
File "/usr/local/lib/python3.8/dist-packages/django/core/checks/urls.py", line 61, in _load_all_namespaces
url_patterns = getattr(resolver, "url_patterns", [])
File "/usr/local/lib/python3.8/dist-packages/django/utils/functional.py", line 57, in get
res = instance.dict[self.name] = self.func(instance)
File "/usr/local/lib/python3.8/dist-packages/django/urls/resolvers.py", line 715, in url_patterns
patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
File "/usr/local/lib/python3.8/dist-packages/django/utils/functional.py", line 57, in get
res = instance.dict[self.name] = self.func(instance)
File "/usr/local/lib/python3.8/dist-packages/django/urls/resolvers.py", line 708, in urlconf_module
return import_module(self.urlconf_name)
File "/usr/lib/python3.8/importlib/init.py", line 127, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "", line 1014, in _gcd_import
File "", line 991, in _find_and_load
File "", line 975, in _find_and_load_unlocked
File "", line 671, in _load_unlocked
File "", line 848, in exec_module
File "", line 219, in _call_with_frames_removed
File "/opt/netbox-3.6.6/netbox/netbox/urls.py", line 9, in
from extras.plugins.urls import plugin_admin_patterns, plugin_patterns, plugin_api_patterns
File "/opt/netbox-3.6.6/netbox/extras/plugins/urls.py", line 31, in
urlpatterns = import_string(f"{plugin_path}.urls.urlpatterns")
File "/usr/local/lib/python3.8/dist-packages/django/utils/module_loading.py", line 30, in import_string
return cached_import(module_path, class_name)
File "/usr/local/lib/python3.8/dist-packages/django/utils/module_loading.py", line 15, in cached_import
module = import_module(module_path)
File "/usr/lib/python3.8/importlib/init.py", line 127, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "/home/vayu/netbox-plugins/netbox_access_lists/urls.py", line 13, in
path('access-lists/int:pk/changelog/', ObjectChangeLogView.as_view(), name='accesslist_changelog', kwargs={
NameError: name 'ObjectChangeLogView' is not defined
This is the error I am facing while running the views
When defining the AccessListListView and AccessListRuleListView in views.py
class AccessListListView(generic.ObjectListView):
queryset = models.AccessList.objects.annotate(
rule_count=Count('rules')
)
table = tables.AccessListTable
class AccessListRuleListView(generic.ObjectListView):
queryset = models.AccessListRule.objects.all()
table = tables.AccessListRuleTable
filterset = filtersets.AccessListRuleFilterSet
filterset_form = forms.AccessListRuleFilterForm
If we do not define actions
it seem they default to actions present in netbox/views/generic/bulk_views.py
class ObjectListView
which requires to have all of the following: actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete')
.
Since for the demo example does not define urls and views for 'import' , 'bulk_edit', 'bulk_delete' the rendering of the page fails with Error:
Reverse for 'None' not found. 'None' is not a valid view function or pattern name.
1 <a href="{% url import_url %}" type="button" class="btn btn-sm btn-info">
2 <i class="mdi mdi-upload"></i> Import
3 </a>
4
Editing the two Class views in views.py
to add the actions
variable seems to fix the issue (but please verify if this is even a proper fix)
class AccessListListView(generic.ObjectListView):
queryset = models.AccessList.objects.annotate(
rule_count=Count('rules')
)
+ actions = ('add', 'export')
table = tables.AccessListTable
class AccessListRuleListView(generic.ObjectListView):
queryset = models.AccessListRule.objects.all()
+ actions = ('add', 'export')
table = tables.AccessListRuleTable
filterset = filtersets.AccessListRuleFilterSet
filterset_form = forms.AccessListRuleFilterForm
There is an error in the documentation step05_views.md. It says "models.AccessListTable" instead of "tables.AccessListTable"
class AccessListListView(generic.ObjectListView):
queryset = models.AccessList.objects.all()
table = models.AccessListTable
It is then correct in the next code example:
from django.db.models import Count
# ...
class AccessListListView(generic.ObjectListView):
queryset = models.AccessList.objects.annotate(
rule_count=Count('rules')
)
table = tables.AccessListTable
In the tutorial is missing creating a MANIFEST.in
file.
When missing without proper lines
recursive-include <plugin_folder>/templates *
it causes the django.template.exceptions.TemplateDoesNotExist: <plugin_name>/<templates>.html
when installing within a netbox.
In the current configuration (show bellow) using the AccessListForm and AccessListRuleForm forms to add new objects makes it mandatory to use tags
field. Also the Tags should load automatically similar how the source and destination prefix work on the AccessListRuleForm form.
class AccessListForm(NetBoxModelForm):
comments = CommentField()
class Meta:
model = AccessList
fields = ('name', 'default_action', 'comments', 'tags')
class AccessListRuleForm(NetBoxModelForm):
access_list = DynamicModelChoiceField(
queryset=AccessList.objects.all()
)
source_prefix = DynamicModelChoiceField(
queryset=Prefix.objects.all()
)
destination_prefix = DynamicModelChoiceField(
queryset=Prefix.objects.all()
)
class Meta:
model = AccessListRule
fields = (
'access_list', 'index', 'description', 'source_prefix', 'source_ports', 'destination_prefix',
'destination_ports', 'protocol', 'action', 'tags',
)
To get away from mandatory tags
field and to make tags
dynamically load and support multiple tags on the created object we should change the the Forms.
changes:
-from utilities.forms.fields import CommentField, DynamicModelChoiceField
+from utilities.forms.fields import CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField
class AccessListForm(NetBoxModelForm):
comments = CommentField()
+ tags = DynamicModelMultipleChoiceField(
+ queryset=Tag.objects.all(),
+ required=False
+ )
+
class Meta:
model = AccessList
fields = ('name', 'default_action', 'comments', 'tags')
class AccessListRuleForm(NetBoxModelForm):
access_list = DynamicModelChoiceField(
queryset=AccessList.objects.all()
)
source_prefix = DynamicModelChoiceField(
queryset=Prefix.objects.all()
)
destination_prefix = DynamicModelChoiceField(
queryset=Prefix.objects.all()
)
+ tags = DynamicModelMultipleChoiceField(
+ queryset=Tag.objects.all(),
+ required=False
+ )
class Meta:
model = AccessListRule
fields = (
'access_list', 'index', 'description', 'source_prefix', 'source_ports', 'destination_prefix',
'destination_ports', 'protocol', 'action', 'tags',
)
This tutorial needs some updates to bring it fully into line with the plugin API changes introduced in NetBox 4.0.
As a starting point, tutorial/step01-initial-setup.md
still instructs the author to import from extras.plugins
, instead of the new netbox.plugins
package.
Refer to Migrating Your Plugin to NetBox 4.0 in the main NetBox docs for the full set of needed changes.
NetBox v3.4 introduced the ability for plugins to register custom views that appear as tabs on core model views (FR #9072).
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.