1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

added admin and api views for listing all plugins, and refactored urls import

This commit is contained in:
John Anderson
2020-03-20 20:10:02 -04:00
parent e220c38b97
commit 4e84e8048f
10 changed files with 242 additions and 39 deletions

View File

@ -19,6 +19,7 @@ class PluginConfig(AppConfig):
"""
# Plugin metadata
author = ''
author_email = ''
description = ''
version = ''
@ -182,7 +183,7 @@ def register_nav_menu_links():
default_app_config = getattr(module, 'default_app_config')
module, app_config = default_app_config.rsplit('.', 1)
app_config = getattr(importlib.import_module(module), app_config)
section_name = app_config.NetBoxPluginMeta.name
section_name = getattr(app_config, 'verbose_name', app_config.name)
if not isinstance(response, list):
response = [response]

View File

@ -0,0 +1,53 @@
import importlib
from django.apps import apps
from django.conf import settings
from django.conf.urls import include
from django.core.exceptions import ImproperlyConfigured
from django.urls import path
from django.utils.module_loading import import_string
from . import views
# Plugins
plugin_patterns = []
plugin_api_patterns = []
for plugin in settings.PLUGINS:
app = apps.get_app_config(plugin)
url_slug = getattr(app, 'url_slug') or app.label
# Check if the plugin specifies any URLs
try:
urlpatterns = import_string(f"{plugin}.urls.urlpatterns")
except ImportError:
# No urls defined
urlpatterns = None
if urlpatterns:
plugin_patterns.append(
path(f"{url_slug}/", include((urlpatterns, app.label)))
)
# Check if the plugin specifies any API URLs
try:
urlpatterns = import_string(f"{plugin}.api.urls.urlpatterns")
app_name = import_string(f"{plugin}.api.urls.app_name")
except ImportError:
# No urls defined
urlpatterns = None
if urlpatterns:
plugin_api_patterns.append(
path(f"{url_slug}/", include((urlpatterns, app_name)))
)
# Plugin list admin view
admin_plugin_patterns = [
path('', views.installed_plugins_admin_view, name='plugins_list')
]
# Plugin list API view
plugin_api_patterns += [
path('', views.PluginsAPIRootView.as_view(), name='api-root'),
path('installed-plugins/', views.InstalledPluginsAPIView.as_view(), name='plugins-list')
]

View File

@ -0,0 +1,95 @@
from collections import OrderedDict
from django.apps import apps
from django.conf import settings
from django.contrib import admin
from django.contrib.admin.views.decorators import staff_member_required
from django.urls.exceptions import NoReverseMatch
from django.utils.module_loading import import_string
from django.shortcuts import render
from django.views.generic import View
from rest_framework import authentication, permissions, routers
from rest_framework.response import Response
from rest_framework.reverse import reverse
from rest_framework.views import APIView
@staff_member_required
def installed_plugins_admin_view(request):
"""
Admin view for listing all installed plugins
"""
context_data = {
'plugins': [apps.get_app_config(plugin) for plugin in settings.PLUGINS]
}
return render(request, 'extras/admin/plugins_list.html', context_data)
class InstalledPluginsAPIView(APIView):
"""
API view for listing all installed plugins
"""
permission_classes = [permissions.IsAdminUser]
_ignore_model_permissions = True
exclude_from_schema = True
swagger_schema = None
def get_view_name(self):
return "Installed Plugins"
@staticmethod
def _get_plugin_data(plugin_app_config):
return {
'name': plugin_app_config.verbose_name,
'package': plugin_app_config.name,
'author': plugin_app_config.author,
'author_email': plugin_app_config.author_email,
'description': plugin_app_config.description,
'verison': plugin_app_config.version
}
def get(self, request, format=None):
return Response([self._get_plugin_data(apps.get_app_config(plugin)) for plugin in settings.PLUGINS])
class PluginsAPIRootView(APIView):
_ignore_model_permissions = True
exclude_from_schema = True
swagger_schema = None
def get_view_name(self):
return "Plugins"
@staticmethod
def _get_plugin_entry(plugin, app_config, request, format):
try:
api_app_name = import_string(f"{plugin}.api.urls.app_name")
except (ImportError, ModuleNotFoundError):
# Plugin does not expose an API
return None
try:
entry = (getattr(app_config, 'url_slug', app_config.label), reverse(
f"plugins-api:{api_app_name}:api-root",
request=request,
format=format
))
except NoReverseMatch:
# The plugin does not include an api-root
entry = None
return entry
def get(self, request, format=None):
entries = []
for plugin in settings.PLUGINS:
app_config = apps.get_app_config(plugin)
entry = self._get_plugin_entry(plugin, app_config, request, format)
if entry is not None:
entries.append(entry)
return Response(OrderedDict((
('installed-plugins', reverse('plugins-api:plugins-list', request=request, format=format)),
*entries
)))

View File

@ -11,7 +11,7 @@ class NetBoxAdminSite(AdminSite):
site_header = 'NetBox Administration'
site_title = 'NetBox'
site_url = '/{}'.format(settings.BASE_PATH)
index_template = 'django_rq/index.html'
index_template = 'admin/index.html'
admin_site = NetBoxAdminSite(name='admin')

View File

@ -638,13 +638,13 @@ if PAGINATE_COUNT not in PER_PAGE_DEFAULTS:
# Plugins
#
PLUGINS = []
PLUGINS = set()
if PLUGINS_ENABLED:
for entry_point in iter_entry_points(group='netbox_plugins', name=None):
plugin = entry_point.module_name
app_config = entry_point.load()
PLUGINS.append(plugin)
PLUGINS.add(plugin)
INSTALLED_APPS.append(plugin)
# Check version constraints
@ -688,4 +688,7 @@ if PLUGINS_ENABLED:
raise ImproperlyConfigured(f"Plugin {plugin} caching_config is invalid!")
if app != plugin:
raise ImproperlyConfigured(f"Plugin {plugin} may not modify caching config for another app!")
else:
# Apply the default config like all other core apps
plugin_cacheops = {f"{plugin}.*": {'ops': 'all'}}
CACHEOPS.update(plugin_cacheops)

View File

@ -8,7 +8,7 @@ from django.views.static import serve
from drf_yasg import openapi
from drf_yasg.views import get_schema_view
from extras.plugins import PluginConfig
from extras.plugins.urls import admin_plugin_patterns, plugin_patterns, plugin_api_patterns
from netbox.views import APIRootView, HomeView, StaticMediaFailureView, SearchView
from users.views import LoginView, LogoutView
from .admin import admin_site
@ -70,42 +70,12 @@ _patterns = [
# Errors
path('media-failure/', StaticMediaFailureView.as_view(), name='media_failure'),
# Plugins
path('plugins/', include((plugin_patterns, 'plugins'))),
path('api/plugins/', include((plugin_api_patterns, 'plugins-api'))),
path('admin/plugins/installed-plugins/', include(admin_plugin_patterns))
]
# Plugins
plugin_patterns = []
plugin_api_patterns = []
for app in apps.get_app_configs():
# Loop over all apps look for installed plugins
if isinstance(app, PluginConfig):
# Check if the plugin specifies any URLs
if importlib.util.find_spec('{}.urls'.format(app.name)):
urls = importlib.import_module('{}.urls'.format(app.name))
url_slug = getattr(app, 'url_slug') or app.label
if hasattr(urls, 'urlpatterns'):
# Mount URLs at `<url_slug>/<path>`
plugin_patterns.append(
path('{}/'.format(url_slug), include((urls.urlpatterns, app.label)))
)
# Check if the plugin specifies any API URLs
if importlib.util.find_spec('{}.api'.format(app.name)):
if importlib.util.find_spec('{}.api.urls'.format(app.name)):
urls = importlib.import_module('{}.api.urls'.format(app.name))
if hasattr(urls, 'urlpatterns'):
url_slug = getattr(app, 'url_slug') or app.label
# Mount URLs at `<url_slug>/<path>`
plugin_api_patterns.append(
path('{}/'.format(url_slug), include((urls.urlpatterns, app.label)))
)
# Mount all plugin URLs within the `plugins` namespace
_patterns.append(
path('plugins/', include((plugin_patterns, 'plugins')))
)
# Mount all plugin API URLs within the `plugins-api` namespace
_patterns.append(
path('api/plugins/', include((plugin_api_patterns, 'plugins-api')))
)
if settings.DEBUG:
import debug_toolbar

View File

@ -341,6 +341,7 @@ class APIRootView(APIView):
('dcim', reverse('dcim-api:api-root', request=request, format=format)),
('extras', reverse('extras-api:api-root', request=request, format=format)),
('ipam', reverse('ipam-api:api-root', request=request, format=format)),
('plugins', reverse('plugins-api:api-root', request=request, format=format)),
('secrets', reverse('secrets-api:api-root', request=request, format=format)),
('tenancy', reverse('tenancy-api:api-root', request=request, format=format)),
('virtualization', reverse('virtualization-api:api-root', request=request, format=format)),

View File

@ -0,0 +1,6 @@
{% extends "django_rq/index.html" %}
{% block sidebar %}
{{ block.super }}
{% include 'extras/admin/plugins_index.html' %}
{% endblock %}

View File

@ -0,0 +1,14 @@
<div id="django-rq">
<div class="module">
<table>
<caption>Plugins</caption>
<tbody>
<tr>
<th>
<a href = "{% url 'plugins_list' %}">Installed plugins</a>
</th>
</tr>
</tbody>
</table>
</div>
</div>

View File

@ -0,0 +1,60 @@
{% extends "admin/base_site.html" %}
{% block title %}Installed Plugins {{ block.super }}{% endblock %}
{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="{% url 'admin:index' %}">Home</a> &rsaquo;
<a href="{% url 'plugins_list' %}">Installed Plugins</a>
</div>
{% endblock %}
{% block content_title %}<h1>Installed Plugins{{ queue.name }}</h1>{% endblock %}
{% block content %}
<div id="content-main">
<div class="module" id="changelist">
<div class="results">
<table id="result_list">
<thead>
<tr>
<th><div class = 'text'><span>Name</span></div></th>
<th><div class = 'text'><span>Package Name</span></div></th>
<th><div class = 'text'><span>Author</span></div></th>
<th><div class = 'text'><span>Author Email</span></div></th>
<th><div class = 'text'><span>Description</span></div></th>
<th><div class = 'text'><span>Version</span></div></th>
</tr>
</thead>
<tbody>
{% for plugin in plugins %}
<tr class = "{% cycle 'row1' 'row2' %}">
<td>
{{ plugin.verbose_name }}
</td>
<td>
{{ plugin.name }}
</td>
<td>
{{ plugin.author }}
</td>
<td>
{{ plugin.author_email }}
</td>
<td>
{{ plugin.description }}
</td>
<td>
{{ plugin.version }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}