views.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. from django.contrib.contenttypes.models import ContentType
  2. from django.db.models import Count
  3. from django.http import Http404, HttpResponse
  4. from django.shortcuts import get_object_or_404
  5. from rest_framework.decorators import action
  6. from rest_framework.exceptions import PermissionDenied
  7. from rest_framework.response import Response
  8. from rest_framework.viewsets import ReadOnlyModelViewSet, ViewSet
  9. from taggit.models import Tag
  10. from extras import filters
  11. from extras.models import (
  12. ConfigContext, CustomField, ExportTemplate, Graph, ImageAttachment, ObjectChange, ReportResult, TopologyMap,
  13. )
  14. from extras.reports import get_report, get_reports
  15. from utilities.api import FieldChoicesViewSet, IsAuthenticatedOrLoginNotRequired, ModelViewSet
  16. from . import serializers
  17. #
  18. # Field choices
  19. #
  20. class ExtrasFieldChoicesViewSet(FieldChoicesViewSet):
  21. fields = (
  22. (CustomField, ['type']),
  23. (Graph, ['type']),
  24. )
  25. #
  26. # Custom fields
  27. #
  28. class CustomFieldModelViewSet(ModelViewSet):
  29. """
  30. Include the applicable set of CustomFields in the ModelViewSet context.
  31. """
  32. def get_serializer_context(self):
  33. # Gather all custom fields for the model
  34. content_type = ContentType.objects.get_for_model(self.queryset.model)
  35. custom_fields = content_type.custom_fields.prefetch_related('choices')
  36. # Cache all relevant CustomFieldChoices. This saves us from having to do a lookup per select field per object.
  37. custom_field_choices = {}
  38. for field in custom_fields:
  39. for cfc in field.choices.all():
  40. custom_field_choices[cfc.id] = cfc.value
  41. custom_field_choices = custom_field_choices
  42. context = super(CustomFieldModelViewSet, self).get_serializer_context()
  43. context.update({
  44. 'custom_fields': custom_fields,
  45. 'custom_field_choices': custom_field_choices,
  46. })
  47. return context
  48. def get_queryset(self):
  49. # Prefetch custom field values
  50. return super(CustomFieldModelViewSet, self).get_queryset().prefetch_related('custom_field_values__field')
  51. #
  52. # Graphs
  53. #
  54. class GraphViewSet(ModelViewSet):
  55. queryset = Graph.objects.all()
  56. serializer_class = serializers.GraphSerializer
  57. filterset_class = filters.GraphFilter
  58. #
  59. # Export templates
  60. #
  61. class ExportTemplateViewSet(ModelViewSet):
  62. queryset = ExportTemplate.objects.all()
  63. serializer_class = serializers.ExportTemplateSerializer
  64. filterset_class = filters.ExportTemplateFilter
  65. #
  66. # Topology maps
  67. #
  68. class TopologyMapViewSet(ModelViewSet):
  69. queryset = TopologyMap.objects.select_related('site')
  70. serializer_class = serializers.TopologyMapSerializer
  71. filterset_class = filters.TopologyMapFilter
  72. @action(detail=True)
  73. def render(self, request, pk):
  74. tmap = get_object_or_404(TopologyMap, pk=pk)
  75. img_format = 'png'
  76. try:
  77. data = tmap.render(img_format=img_format)
  78. except Exception:
  79. return HttpResponse(
  80. "There was an error generating the requested graph. Ensure that the GraphViz executables have been "
  81. "installed correctly."
  82. )
  83. response = HttpResponse(data, content_type='image/{}'.format(img_format))
  84. response['Content-Disposition'] = 'inline; filename="{}.{}"'.format(tmap.slug, img_format)
  85. return response
  86. #
  87. # Tags
  88. #
  89. class TagViewSet(ModelViewSet):
  90. queryset = Tag.objects.annotate(tagged_items=Count('taggit_taggeditem_items'))
  91. serializer_class = serializers.TagSerializer
  92. filterset_class = filters.TagFilter
  93. #
  94. # Image attachments
  95. #
  96. class ImageAttachmentViewSet(ModelViewSet):
  97. queryset = ImageAttachment.objects.all()
  98. serializer_class = serializers.ImageAttachmentSerializer
  99. #
  100. # Config contexts
  101. #
  102. class ConfigContextViewSet(ModelViewSet):
  103. queryset = ConfigContext.objects.prefetch_related(
  104. 'regions', 'sites', 'roles', 'platforms', 'tenant_groups', 'tenants',
  105. )
  106. serializer_class = serializers.ConfigContextSerializer
  107. filterset_class = filters.ConfigContextFilter
  108. #
  109. # Reports
  110. #
  111. class ReportViewSet(ViewSet):
  112. permission_classes = [IsAuthenticatedOrLoginNotRequired]
  113. _ignore_model_permissions = True
  114. exclude_from_schema = True
  115. lookup_value_regex = '[^/]+' # Allow dots
  116. def _retrieve_report(self, pk):
  117. # Read the PK as "<module>.<report>"
  118. if '.' not in pk:
  119. raise Http404
  120. module_name, report_name = pk.split('.', 1)
  121. # Raise a 404 on an invalid Report module/name
  122. report = get_report(module_name, report_name)
  123. if report is None:
  124. raise Http404
  125. return report
  126. def list(self, request):
  127. """
  128. Compile all reports and their related results (if any). Result data is deferred in the list view.
  129. """
  130. report_list = []
  131. # Iterate through all available Reports.
  132. for module_name, reports in get_reports():
  133. for report in reports:
  134. # Attach the relevant ReportResult (if any) to each Report.
  135. report.result = ReportResult.objects.filter(report=report.full_name).defer('data').first()
  136. report_list.append(report)
  137. serializer = serializers.ReportSerializer(report_list, many=True, context={
  138. 'request': request,
  139. })
  140. return Response(serializer.data)
  141. def retrieve(self, request, pk):
  142. """
  143. Retrieve a single Report identified as "<module>.<report>".
  144. """
  145. # Retrieve the Report and ReportResult, if any.
  146. report = self._retrieve_report(pk)
  147. report.result = ReportResult.objects.filter(report=report.full_name).first()
  148. serializer = serializers.ReportDetailSerializer(report)
  149. return Response(serializer.data)
  150. @action(detail=True, methods=['post'])
  151. def run(self, request, pk):
  152. """
  153. Run a Report and create a new ReportResult, overwriting any previous result for the Report.
  154. """
  155. # Check that the user has permission to run reports.
  156. if not request.user.has_perm('extras.add_reportresult'):
  157. raise PermissionDenied("This user does not have permission to run reports.")
  158. # Retrieve and run the Report. This will create a new ReportResult.
  159. report = self._retrieve_report(pk)
  160. report.run()
  161. serializer = serializers.ReportDetailSerializer(report)
  162. return Response(serializer.data)
  163. #
  164. # Change logging
  165. #
  166. class ObjectChangeViewSet(ReadOnlyModelViewSet):
  167. """
  168. Retrieve a list of recent changes.
  169. """
  170. queryset = ObjectChange.objects.select_related('user')
  171. serializer_class = serializers.ObjectChangeSerializer
  172. filterset_class = filters.ObjectChangeFilter