views.py 7.3 KB

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