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

399 lines
13 KiB
Python
Raw Normal View History

from django.contrib.contenttypes.models import ContentType
2019-08-08 21:33:20 -04:00
from django.http import Http404
from django.shortcuts import get_object_or_404
from django_rq.queues import get_connection
from rest_framework import status
from rest_framework.decorators import action
2017-09-26 17:31:16 -04:00
from rest_framework.exceptions import PermissionDenied
from rest_framework.generics import RetrieveUpdateDestroyAPIView
from rest_framework.renderers import JSONRenderer
2017-09-21 16:32:05 -04:00
from rest_framework.response import Response
from rest_framework.routers import APIRootView
from rest_framework.viewsets import ReadOnlyModelViewSet, ViewSet
from rq import Worker
2016-03-01 11:23:03 -05:00
from core.choices import JobStatusChoices
from core.models import Job
2021-04-29 15:59:11 -04:00
from extras import filtersets
from extras.models import *
from extras.reports import get_module_and_report, run_report
from extras.scripts import get_module_and_script, run_script
from netbox.api.authentication import IsAuthenticatedOrLoginNotRequired
from netbox.api.features import SyncedDataMixin
from netbox.api.metadata import ContentTypeMetadata
from netbox.api.renderers import TextRenderer
2022-03-09 11:09:06 -05:00
from netbox.api.viewsets import NetBoxModelViewSet
from utilities.exceptions import RQWorkerNotRunningException
from utilities.utils import copy_safe_request, count_related
2017-03-08 16:12:14 -05:00
from . import serializers
from .mixins import ConfigTemplateRenderMixin
2016-03-01 11:23:03 -05:00
class ExtrasRootView(APIRootView):
"""
Extras API root view
"""
def get_view_name(self):
return 'Extras'
#
# Webhooks
#
2022-03-09 11:09:06 -05:00
class WebhookViewSet(NetBoxModelViewSet):
metadata_class = ContentTypeMetadata
queryset = Webhook.objects.all()
serializer_class = serializers.WebhookSerializer
2021-04-29 15:59:11 -04:00
filterset_class = filtersets.WebhookFilterSet
#
# Custom fields
#
2022-03-09 11:09:06 -05:00
class CustomFieldViewSet(NetBoxModelViewSet):
metadata_class = ContentTypeMetadata
queryset = CustomField.objects.all()
serializer_class = serializers.CustomFieldSerializer
2021-04-29 15:59:11 -04:00
filterset_class = filtersets.CustomFieldFilterSet
#
# Custom links
#
2022-03-09 11:09:06 -05:00
class CustomLinkViewSet(NetBoxModelViewSet):
metadata_class = ContentTypeMetadata
queryset = CustomLink.objects.all()
serializer_class = serializers.CustomLinkSerializer
2021-04-29 15:59:11 -04:00
filterset_class = filtersets.CustomLinkFilterSet
#
# Export templates
#
class ExportTemplateViewSet(SyncedDataMixin, NetBoxModelViewSet):
metadata_class = ContentTypeMetadata
queryset = ExportTemplate.objects.prefetch_related('data_source', 'data_file')
serializer_class = serializers.ExportTemplateSerializer
2021-04-29 15:59:11 -04:00
filterset_class = filtersets.ExportTemplateFilterSet
#
# Saved filters
#
class SavedFilterViewSet(NetBoxModelViewSet):
metadata_class = ContentTypeMetadata
queryset = SavedFilter.objects.all()
serializer_class = serializers.SavedFilterSerializer
filterset_class = filtersets.SavedFilterFilterSet
2018-05-22 12:46:14 -04:00
#
# Tags
#
2022-03-09 11:09:06 -05:00
class TagViewSet(NetBoxModelViewSet):
2020-07-23 14:23:50 -04:00
queryset = Tag.objects.annotate(
tagged_items=count_related(TaggedItem, 'tag')
)
2018-05-22 12:46:14 -04:00
serializer_class = serializers.TagSerializer
2021-04-29 15:59:11 -04:00
filterset_class = filtersets.TagFilterSet
2018-05-22 12:46:14 -04:00
#
# Image attachments
#
2022-03-09 11:09:06 -05:00
class ImageAttachmentViewSet(NetBoxModelViewSet):
metadata_class = ContentTypeMetadata
queryset = ImageAttachment.objects.all()
serializer_class = serializers.ImageAttachmentSerializer
2021-04-29 15:59:11 -04:00
filterset_class = filtersets.ImageAttachmentFilterSet
#
# Journal entries
#
2022-03-09 11:09:06 -05:00
class JournalEntryViewSet(NetBoxModelViewSet):
metadata_class = ContentTypeMetadata
queryset = JournalEntry.objects.all()
serializer_class = serializers.JournalEntrySerializer
2021-04-29 15:59:11 -04:00
filterset_class = filtersets.JournalEntryFilterSet
2018-06-27 16:02:34 -04:00
#
# Config contexts
#
class ConfigContextViewSet(SyncedDataMixin, NetBoxModelViewSet):
queryset = ConfigContext.objects.prefetch_related(
'regions', 'site_groups', 'sites', 'locations', 'roles', 'platforms', 'tenant_groups', 'tenants', 'data_source',
'data_file',
)
2018-06-27 16:02:34 -04:00
serializer_class = serializers.ConfigContextSerializer
2021-04-29 15:59:11 -04:00
filterset_class = filtersets.ConfigContextFilterSet
2018-06-27 16:02:34 -04:00
#
# Config templates
#
class ConfigTemplateViewSet(SyncedDataMixin, ConfigTemplateRenderMixin, NetBoxModelViewSet):
queryset = ConfigTemplate.objects.prefetch_related('data_source', 'data_file')
serializer_class = serializers.ConfigTemplateSerializer
filterset_class = filtersets.ConfigTemplateFilterSet
@action(detail=True, methods=['post'], renderer_classes=[JSONRenderer, TextRenderer])
def render(self, request, pk):
"""
Render a ConfigTemplate using the context data provided (if any). If the client requests "text/plain" data,
return the raw rendered content, rather than serialized JSON.
"""
configtemplate = self.get_object()
context = request.data
return self.render_configtemplate(request, configtemplate, context)
#
# Reports
#
2017-09-21 16:32:05 -04:00
class ReportViewSet(ViewSet):
permission_classes = [IsAuthenticatedOrLoginNotRequired]
2017-09-21 16:32:05 -04:00
_ignore_model_permissions = True
schema = None
2017-09-25 17:27:58 -04:00
lookup_value_regex = '[^/]+' # Allow dots
2017-09-21 16:32:05 -04:00
def _get_report(self, pk):
try:
module_name, report_name = pk.split('.', maxsplit=1)
except ValueError:
2017-09-26 16:36:43 -04:00
raise Http404
2017-09-21 16:32:05 -04:00
module, report = get_module_and_report(module_name, report_name)
2017-09-26 16:36:43 -04:00
if report is None:
raise Http404
return module, report
2017-09-26 16:36:43 -04:00
def list(self, request):
"""
Compile all reports and their related results (if any). Result data is deferred in the list view.
"""
results = {
job.name: job
for job in Job.objects.filter(
object_type=ContentType.objects.get(app_label='extras', model='reportmodule'),
status__in=JobStatusChoices.TERMINAL_STATE_CHOICES
2022-03-24 13:42:07 +01:00
).order_by('name', '-created').distinct('name').defer('data')
}
2017-09-26 16:36:43 -04:00
report_list = []
for report_module in ReportModule.objects.restrict(request.user):
report_list.extend([report() for report in report_module.reports.values()])
2017-09-26 16:36:43 -04:00
# Attach Job objects to each report (if any)
for report in report_list:
report.result = results.get(report.name, None)
2017-09-26 16:36:43 -04:00
serializer = serializers.ReportSerializer(report_list, many=True, context={
'request': request,
})
2017-09-25 17:27:58 -04:00
return Response(serializer.data)
def retrieve(self, request, pk):
2017-09-26 16:36:43 -04:00
"""
Retrieve a single Report identified as "<module>.<report>".
"""
module, report = self._get_report(pk)
2017-09-25 17:27:58 -04:00
# Retrieve the Report and Job, if any.
object_type = ContentType.objects.get(app_label='extras', model='reportmodule')
report.result = Job.objects.filter(
object_type=object_type,
name=report.name,
status__in=JobStatusChoices.TERMINAL_STATE_CHOICES
).first()
serializer = serializers.ReportDetailSerializer(report, context={
'request': request
})
2017-09-25 17:27:58 -04:00
return Response(serializer.data)
@action(detail=True, methods=['post'])
2017-09-25 17:27:58 -04:00
def run(self, request, pk):
2017-09-26 16:36:43 -04:00
"""
Run a Report identified as "<module>.<script>" and return the pending Job as the result
2017-09-26 16:36:43 -04:00
"""
2017-09-26 17:31:16 -04:00
# Check that the user has permission to run reports.
if not request.user.has_perm('extras.run_report'):
2017-09-26 17:31:16 -04:00
raise PermissionDenied("This user does not have permission to run reports.")
# Check that at least one RQ worker is running
if not Worker.count(get_connection('default')):
raise RQWorkerNotRunningException()
# Retrieve and run the Report. This will create a new Job.
module, report_cls = self._get_report(pk)
report = report_cls()
input_serializer = serializers.ReportInputSerializer(
data=request.data,
context={'report': report}
)
2017-09-21 16:32:05 -04:00
2022-10-20 21:36:43 +02:00
if input_serializer.is_valid():
report.result = Job.enqueue(
2022-10-20 21:36:43 +02:00
run_report,
instance=module,
name=report.class_name,
user=request.user,
2022-10-20 21:36:43 +02:00
job_timeout=report.job_timeout,
schedule_at=input_serializer.validated_data.get('schedule_at'),
interval=input_serializer.validated_data.get('interval')
2022-10-20 21:36:43 +02:00
)
serializer = serializers.ReportDetailSerializer(report, context={'request': request})
return Response(serializer.data)
return Response(input_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
2017-09-21 16:32:05 -04:00
#
# Scripts
#
class ScriptViewSet(ViewSet):
permission_classes = [IsAuthenticatedOrLoginNotRequired]
_ignore_model_permissions = True
schema = None
lookup_value_regex = '[^/]+' # Allow dots
def _get_script(self, pk):
try:
module_name, script_name = pk.split('.', maxsplit=1)
except ValueError:
raise Http404
module, script = get_module_and_script(module_name, script_name)
if script is None:
raise Http404
return module, script
def list(self, request):
results = {
job.name: job
for job in Job.objects.filter(
object_type=ContentType.objects.get(app_label='extras', model='scriptmodule'),
status__in=JobStatusChoices.TERMINAL_STATE_CHOICES
2022-03-24 13:42:07 +01:00
).order_by('name', '-created').distinct('name').defer('data')
}
script_list = []
for script_module in ScriptModule.objects.restrict(request.user):
script_list.extend(script_module.scripts.values())
# Attach Job objects to each script (if any)
for script in script_list:
script.result = results.get(script.class_name, None)
serializer = serializers.ScriptSerializer(script_list, many=True, context={'request': request})
return Response(serializer.data)
def retrieve(self, request, pk):
module, script = self._get_script(pk)
object_type = ContentType.objects.get(app_label='extras', model='scriptmodule')
script.result = Job.objects.filter(
object_type=object_type,
name=script.class_name,
status__in=JobStatusChoices.TERMINAL_STATE_CHOICES
).first()
serializer = serializers.ScriptDetailSerializer(script, context={'request': request})
return Response(serializer.data)
def post(self, request, pk):
"""
Run a Script identified as "<module>.<script>" and return the pending Job as the result
"""
if not request.user.has_perm('extras.run_script'):
raise PermissionDenied("This user does not have permission to run scripts.")
module, script = self._get_script(pk)
input_serializer = serializers.ScriptInputSerializer(
data=request.data,
context={'script': script}
)
# Check that at least one RQ worker is running
if not Worker.count(get_connection('default')):
raise RQWorkerNotRunningException()
2019-10-29 16:17:59 -04:00
if input_serializer.is_valid():
script.result = Job.enqueue(
run_script,
instance=module,
name=script.class_name,
user=request.user,
data=input_serializer.data['data'],
request=copy_safe_request(request),
commit=input_serializer.data['commit'],
job_timeout=script.job_timeout,
schedule_at=input_serializer.validated_data.get('schedule_at'),
interval=input_serializer.validated_data.get('interval')
)
2020-06-29 14:34:42 -04:00
serializer = serializers.ScriptDetailSerializer(script, context={'request': request})
return Response(serializer.data)
2019-10-29 16:17:59 -04:00
return Response(input_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
2018-06-19 14:57:03 -04:00
#
# Change logging
#
class ObjectChangeViewSet(ReadOnlyModelViewSet):
"""
Retrieve a list of recent changes.
"""
metadata_class = ContentTypeMetadata
queryset = ObjectChange.objects.valid_models().prefetch_related('user')
2018-06-19 14:57:03 -04:00
serializer_class = serializers.ObjectChangeSerializer
2021-04-29 15:59:11 -04:00
filterset_class = filtersets.ObjectChangeFilterSet
#
# ContentTypes
#
class ContentTypeViewSet(ReadOnlyModelViewSet):
"""
Read-only list of ContentTypes. Limit results to ContentTypes pertinent to NetBox objects.
"""
permission_classes = [IsAuthenticatedOrLoginNotRequired]
queryset = ContentType.objects.order_by('app_label', 'model')
serializer_class = serializers.ContentTypeSerializer
2021-04-29 15:59:11 -04:00
filterset_class = filtersets.ContentTypeFilterSet
#
# User dashboard
#
class DashboardView(RetrieveUpdateDestroyAPIView):
queryset = Dashboard.objects.all()
serializer_class = serializers.DashboardSerializer
def get_object(self):
return Dashboard.objects.filter(user=self.request.user).first()