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

313 lines
9.6 KiB
Python
Raw Normal View History

from collections import OrderedDict
from django.contrib.contenttypes.models import ContentType
2018-05-22 12:46:14 -04:00
from django.db.models import Count
2019-08-08 21:33:20 -04:00
from django.http import Http404
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
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
2017-03-08 16:12:14 -05:00
from extras import filters
from extras.choices import JobResultStatusChoices
from extras.models import ConfigContext, ExportTemplate, ImageAttachment, ObjectChange, JobResult, Tag
2020-07-03 11:55:04 -04:00
from extras.reports import get_report, get_reports, run_report
from extras.scripts import get_script, get_scripts, run_script
2020-03-12 10:48:17 -04:00
from utilities.api import IsAuthenticatedOrLoginNotRequired, ModelViewSet
from utilities.exceptions import RQWorkerNotRunningException
from utilities.metadata import ContentTypeMetadata
2020-06-29 14:34:42 -04:00
from utilities.utils import copy_safe_request
2017-03-08 16:12:14 -05:00
from . import serializers
2016-03-01 11:23:03 -05:00
class ExtrasRootView(APIRootView):
"""
Extras API root view
"""
def get_view_name(self):
return 'Extras'
#
# Custom fields
#
2017-01-24 17:12:16 -05:00
class CustomFieldModelViewSet(ModelViewSet):
2016-08-22 13:20:30 -04:00
"""
Include the applicable set of CustomFields in the ModelViewSet context.
2016-08-22 13:20:30 -04:00
"""
2017-01-24 17:12:16 -05:00
def get_serializer_context(self):
# Gather all custom fields for the model
content_type = ContentType.objects.get_for_model(self.queryset.model)
custom_fields = content_type.custom_fields.all()
2017-01-24 17:12:16 -05:00
context = super().get_serializer_context()
context.update({
2017-01-24 17:12:16 -05:00
'custom_fields': custom_fields,
})
return context
2017-01-24 17:12:16 -05:00
2016-08-22 13:20:30 -04:00
#
# Export templates
#
class ExportTemplateViewSet(ModelViewSet):
metadata_class = ContentTypeMetadata
queryset = ExportTemplate.objects.all()
serializer_class = serializers.ExportTemplateSerializer
2020-01-09 20:35:07 -05:00
filterset_class = filters.ExportTemplateFilterSet
2018-05-22 12:46:14 -04:00
#
# Tags
#
class TagViewSet(ModelViewSet):
2020-07-23 14:23:50 -04:00
queryset = Tag.objects.annotate(
tagged_items=Count('extras_taggeditem_items')
).order_by(*Tag._meta.ordering)
2018-05-22 12:46:14 -04:00
serializer_class = serializers.TagSerializer
2020-01-09 20:35:07 -05:00
filterset_class = filters.TagFilterSet
2018-05-22 12:46:14 -04:00
#
# Image attachments
#
class ImageAttachmentViewSet(ModelViewSet):
metadata_class = ContentTypeMetadata
queryset = ImageAttachment.objects.all()
serializer_class = serializers.ImageAttachmentSerializer
2018-06-27 16:02:34 -04:00
#
# Config contexts
#
class ConfigContextViewSet(ModelViewSet):
queryset = ConfigContext.objects.prefetch_related(
'regions', 'sites', 'roles', 'platforms', 'tenant_groups', 'tenants',
)
2018-06-27 16:02:34 -04:00
serializer_class = serializers.ConfigContextSerializer
2020-01-09 20:35:07 -05:00
filterset_class = filters.ConfigContextFilterSet
2018-06-27 16:02:34 -04:00
#
# 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
exclude_from_schema = True
2017-09-25 17:27:58 -04:00
lookup_value_regex = '[^/]+' # Allow dots
2017-09-21 16:32:05 -04:00
2017-09-26 16:36:43 -04:00
def _retrieve_report(self, pk):
# Read the PK as "<module>.<report>"
if '.' not in pk:
raise Http404
module_name, report_name = pk.split('.', 1)
2017-09-21 16:32:05 -04:00
2017-09-26 16:36:43 -04:00
# Raise a 404 on an invalid Report module/name
report = get_report(module_name, report_name)
if report is None:
raise Http404
return report
def list(self, request):
"""
Compile all reports and their related results (if any). Result data is deferred in the list view.
"""
2017-09-25 17:27:58 -04:00
report_list = []
report_content_type = ContentType.objects.get(app_label='extras', model='report')
results = {
r.name: r
for r in JobResult.objects.filter(
obj_type=report_content_type,
status__in=JobResultStatusChoices.TERMINAL_STATE_CHOICES
).defer('data')
}
2017-09-26 16:36:43 -04:00
# Iterate through all available Reports.
2017-09-21 16:32:05 -04:00
for module_name, reports in get_reports():
2017-09-26 16:36:43 -04:00
for report in reports:
# Attach the relevant JobResult (if any) to each Report.
report.result = results.get(report.full_name, None)
2017-09-26 16:36:43 -04:00
report_list.append(report)
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>".
"""
2017-09-25 17:27:58 -04:00
# Retrieve the Report and JobResult, if any.
2017-09-26 16:36:43 -04:00
report = self._retrieve_report(pk)
report_content_type = ContentType.objects.get(app_label='extras', model='report')
report.result = JobResult.objects.filter(
obj_type=report_content_type,
name=report.full_name,
status__in=JobResultStatusChoices.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 JobResult 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.
2020-07-06 11:15:20 -04:00
if not request.user.has_perm('extras.run_script'):
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 JobResult.
2017-09-26 16:36:43 -04:00
report = self._retrieve_report(pk)
report_content_type = ContentType.objects.get(app_label='extras', model='report')
job_result = JobResult.enqueue_job(
run_report,
report.full_name,
report_content_type,
request.user
)
report.result = job_result
2017-09-21 16:32:05 -04:00
2020-07-06 11:15:20 -04:00
serializer = serializers.ReportDetailSerializer(report, context={'request': request})
2017-09-26 16:36:43 -04:00
return Response(serializer.data)
2017-09-21 16:32:05 -04:00
#
# Scripts
#
class ScriptViewSet(ViewSet):
permission_classes = [IsAuthenticatedOrLoginNotRequired]
_ignore_model_permissions = True
exclude_from_schema = True
lookup_value_regex = '[^/]+' # Allow dots
def _get_script(self, pk):
module_name, script_name = pk.split('.')
script = get_script(module_name, script_name)
if script is None:
raise Http404
return script
def list(self, request):
script_content_type = ContentType.objects.get(app_label='extras', model='script')
results = {
r.name: r
for r in JobResult.objects.filter(
obj_type=script_content_type,
status__in=JobResultStatusChoices.TERMINAL_STATE_CHOICES
).defer('data').order_by('created')
}
flat_list = []
for script_list in get_scripts().values():
flat_list.extend(script_list.values())
# Attach JobResult objects to each script (if any)
for script in flat_list:
script.result = results.get(script.full_name, None)
serializer = serializers.ScriptSerializer(flat_list, many=True, context={'request': request})
return Response(serializer.data)
def retrieve(self, request, pk):
script = self._get_script(pk)
script_content_type = ContentType.objects.get(app_label='extras', model='script')
script.result = JobResult.objects.filter(
obj_type=script_content_type,
name=script.full_name,
status__in=JobResultStatusChoices.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 JobResult as the result
"""
script = self._get_script(pk)()
2019-10-29 16:17:59 -04:00
input_serializer = serializers.ScriptInputSerializer(data=request.data)
# 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():
data = input_serializer.data['data']
commit = input_serializer.data['commit']
script_content_type = ContentType.objects.get(app_label='extras', model='script')
job_result = JobResult.enqueue_job(
run_script,
script.full_name,
script_content_type,
request.user,
2020-06-29 14:34:42 -04:00
data=data,
request=copy_safe_request(request),
commit=commit
)
script.result = job_result
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.prefetch_related('user')
2018-06-19 14:57:03 -04:00
serializer_class = serializers.ObjectChangeSerializer
2020-01-09 20:35:07 -05:00
filterset_class = filters.ObjectChangeFilterSet
#
# Job Results
#
class JobResultViewSet(ReadOnlyModelViewSet):
"""
Retrieve a list of job results
"""
queryset = JobResult.objects.prefetch_related('user')
serializer_class = serializers.JobResultSerializer
filterset_class = filters.JobResultFilterSet