Browse Source

Extended reports API

Jeremy Stretch 7 years ago
parent
commit
d35a2b0faa

+ 31 - 1
netbox/extras/api/serializers.py

@@ -7,7 +7,7 @@ from rest_framework import serializers
 from dcim.api.serializers import NestedDeviceSerializer, NestedRackSerializer, NestedSiteSerializer
 from dcim.models import Device, Rack, Site
 from extras.models import (
-    ACTION_CHOICES, ExportTemplate, Graph, GRAPH_TYPE_CHOICES, ImageAttachment, TopologyMap, UserAction,
+    ACTION_CHOICES, ExportTemplate, Graph, GRAPH_TYPE_CHOICES, ImageAttachment, ReportResult, TopologyMap, UserAction,
 )
 from users.api.serializers import NestedUserSerializer
 from utilities.api import ChoiceFieldSerializer, ContentTypeFieldSerializer, ValidatedModelSerializer
@@ -128,6 +128,36 @@ class WritableImageAttachmentSerializer(ValidatedModelSerializer):
 
 
 #
+# Reports
+#
+
+class ReportResultSerializer(serializers.ModelSerializer):
+
+    class Meta:
+        model = ReportResult
+        fields = ['created', 'user', 'failed', 'data']
+
+
+class NestedReportResultSerializer(serializers.ModelSerializer):
+
+    class Meta:
+        model = ReportResult
+        fields = ['created', 'user', 'failed']
+
+
+class ReportSerializer(serializers.Serializer):
+    module = serializers.CharField(max_length=255)
+    name = serializers.CharField(max_length=255)
+    description = serializers.CharField(max_length=255, required=False)
+    test_methods = serializers.ListField(child=serializers.CharField(max_length=255))
+    result = NestedReportResultSerializer()
+
+
+class ReportDetailSerializer(ReportSerializer):
+    result = ReportResultSerializer()
+
+
+#
 # User actions
 #
 

+ 65 - 13
netbox/extras/api/views.py

@@ -1,17 +1,16 @@
 from __future__ import unicode_literals
-from collections import OrderedDict
 
 from rest_framework.decorators import detail_route
 from rest_framework.response import Response
 from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet, ViewSet
 
 from django.contrib.contenttypes.models import ContentType
-from django.http import HttpResponse
+from django.http import Http404, HttpResponse
 from django.shortcuts import get_object_or_404
 
 from extras import filters
-from extras.models import ExportTemplate, Graph, ImageAttachment, TopologyMap, UserAction
-from extras.reports import get_reports
+from extras.models import ExportTemplate, Graph, ImageAttachment, ReportResult, TopologyMap, UserAction
+from extras.reports import get_report, get_reports
 from utilities.api import WritableSerializerMixin
 from . import serializers
 
@@ -94,23 +93,76 @@ class ImageAttachmentViewSet(WritableSerializerMixin, ModelViewSet):
 class ReportViewSet(ViewSet):
     _ignore_model_permissions = True
     exclude_from_schema = True
+    lookup_value_regex = '[^/]+'  # Allow dots
 
     def list(self, request):
 
-        ret_list = []
+        # Compile all reports
+        report_list = []
         for module_name, reports in get_reports():
             for report_name, report_cls in reports:
-                report = OrderedDict((
-                    ('module', module_name),
-                    ('name', report_name),
-                    ('description', report_cls.description),
-                    ('test_methods', report_cls().test_methods),
-                ))
-                ret_list.append(report)
+                data = {
+                    'module': module_name,
+                    'name': report_name,
+                    'description': report_cls.description,
+                    'test_methods': report_cls().test_methods,
+                    'result': None,
+                }
+                try:
+                    result = ReportResult.objects.defer('data').get(report='{}.{}'.format(module_name, report_name))
+                    data['result'] = result
+                except ReportResult.DoesNotExist:
+                    pass
+                report_list.append(data)
+
+        serializer = serializers.ReportSerializer(report_list, many=True, context={'request': request})
+
+        return Response(serializer.data)
+
+    def retrieve(self, request, pk):
+
+        # Retrieve report by <module>.<report>
+        if '.' not in pk:
+            raise Http404
+        module_name, report_name = pk.split('.', 1)
+        report_cls = get_report(module_name, report_name)
+        data = {
+            'module': module_name,
+            'name': report_name,
+            'description': report_cls.description,
+            'test_methods': report_cls().test_methods,
+            'result': None,
+        }
+
+        # Attach report result
+        try:
+            result = ReportResult.objects.get(report='{}.{}'.format(module_name, report_name))
+            data['result'] = result
+        except ReportResult.DoesNotExist:
+            pass
+
+        serializer = serializers.ReportDetailSerializer(data)
+
+        return Response(serializer.data)
+
+    @detail_route()
+    def run(self, request, pk):
+
+        # Retrieve report by <module>.<report>
+        if '.' not in pk:
+            raise Http404
+        module_name, report_name = pk.split('.', 1)
+        report_cls = get_report(module_name, report_name)
 
-        return Response(ret_list)
+        # Run the report
+        report = report_cls()
+        result = report.run()
 
+        # Save the ReportResult
+        ReportResult.objects.filter(report=pk).delete()
+        ReportResult(report=pk, failed=report.failed, data=result).save()
 
+        return Response('Report completed.')
 
 
 class RecentActivityViewSet(ReadOnlyModelViewSet):

+ 3 - 3
netbox/extras/management/commands/runreport.py

@@ -30,15 +30,15 @@ class Command(BaseCommand):
                         "[{:%H:%M:%S}] Running {}.{}...".format(timezone.now(), module_name, report_name)
                     )
                     report = report_cls()
-                    results = report.run()
+                    result = report.run()
 
                     # Record the results
                     ReportResult.objects.filter(report=report_name_full).delete()
-                    ReportResult(report=report_name_full, failed=report.failed, data=results).save()
+                    ReportResult(report=report_name_full, failed=report.failed, data=result).save()
 
                     # Report on success/failure
                     status = self.style.ERROR('FAILED') if report.failed else self.style.SUCCESS('SUCCESS')
-                    for test_name, attrs in results.items():
+                    for test_name, attrs in result.items():
                         self.stdout.write(
                             "\t{}: {} success, {} info, {} warning, {} failed".format(
                                 test_name, attrs['success'], attrs['info'], attrs['warning'], attrs['failed']

+ 8 - 0
netbox/extras/reports.py

@@ -18,6 +18,14 @@ def is_report(obj):
     return False
 
 
+def get_report(module_name, report_name):
+    """
+    Return a specific report from within a module.
+    """
+    module = importlib.import_module('reports.{}'.format(module_name))
+    return getattr(module, report_name)
+
+
 def get_reports():
     """
     Compile a list of all reports available across all modules in the reports path. Returns a list of tuples: