views.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  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
  5. from rest_framework import status
  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.choices import JobResultStatusChoices
  12. from extras.models import (
  13. ConfigContext, CustomFieldChoice, ExportTemplate, Graph, ImageAttachment, ObjectChange, JobResult, Tag,
  14. )
  15. from extras.reports import get_report, get_reports
  16. from extras.scripts import get_script, get_scripts, run_script
  17. from utilities.api import IsAuthenticatedOrLoginNotRequired, ModelViewSet
  18. from . import serializers
  19. #
  20. # Custom field choices
  21. #
  22. class CustomFieldChoicesViewSet(ViewSet):
  23. """
  24. """
  25. permission_classes = [IsAuthenticatedOrLoginNotRequired]
  26. def __init__(self, *args, **kwargs):
  27. super(CustomFieldChoicesViewSet, self).__init__(*args, **kwargs)
  28. self._fields = OrderedDict()
  29. for cfc in CustomFieldChoice.objects.all():
  30. self._fields.setdefault(cfc.field.name, {})
  31. self._fields[cfc.field.name][cfc.value] = cfc.pk
  32. def list(self, request):
  33. return Response(self._fields)
  34. def retrieve(self, request, pk):
  35. if pk not in self._fields:
  36. raise Http404
  37. return Response(self._fields[pk])
  38. def get_view_name(self):
  39. return "Custom Field choices"
  40. #
  41. # Custom fields
  42. #
  43. class CustomFieldModelViewSet(ModelViewSet):
  44. """
  45. Include the applicable set of CustomFields in the ModelViewSet context.
  46. """
  47. def get_serializer_context(self):
  48. # Gather all custom fields for the model
  49. content_type = ContentType.objects.get_for_model(self.queryset.model)
  50. custom_fields = content_type.custom_fields.prefetch_related('choices')
  51. # Cache all relevant CustomFieldChoices. This saves us from having to do a lookup per select field per object.
  52. custom_field_choices = {}
  53. for field in custom_fields:
  54. for cfc in field.choices.all():
  55. custom_field_choices[cfc.id] = cfc.value
  56. custom_field_choices = custom_field_choices
  57. context = super().get_serializer_context()
  58. context.update({
  59. 'custom_fields': custom_fields,
  60. 'custom_field_choices': custom_field_choices,
  61. })
  62. return context
  63. def get_queryset(self):
  64. # Prefetch custom field values
  65. return super().get_queryset().prefetch_related('custom_field_values__field')
  66. #
  67. # Graphs
  68. #
  69. class GraphViewSet(ModelViewSet):
  70. queryset = Graph.objects.all()
  71. serializer_class = serializers.GraphSerializer
  72. filterset_class = filters.GraphFilterSet
  73. #
  74. # Export templates
  75. #
  76. class ExportTemplateViewSet(ModelViewSet):
  77. queryset = ExportTemplate.objects.all()
  78. serializer_class = serializers.ExportTemplateSerializer
  79. filterset_class = filters.ExportTemplateFilterSet
  80. #
  81. # Tags
  82. #
  83. class TagViewSet(ModelViewSet):
  84. queryset = Tag.restricted.annotate(
  85. tagged_items=Count('extras_taggeditem_items', distinct=True)
  86. )
  87. serializer_class = serializers.TagSerializer
  88. filterset_class = filters.TagFilterSet
  89. #
  90. # Image attachments
  91. #
  92. class ImageAttachmentViewSet(ModelViewSet):
  93. queryset = ImageAttachment.objects.all()
  94. serializer_class = serializers.ImageAttachmentSerializer
  95. #
  96. # Config contexts
  97. #
  98. class ConfigContextViewSet(ModelViewSet):
  99. queryset = ConfigContext.objects.prefetch_related(
  100. 'regions', 'sites', 'roles', 'platforms', 'tenant_groups', 'tenants',
  101. )
  102. serializer_class = serializers.ConfigContextSerializer
  103. filterset_class = filters.ConfigContextFilterSet
  104. #
  105. # Reports
  106. #
  107. class ReportViewSet(ViewSet):
  108. permission_classes = [IsAuthenticatedOrLoginNotRequired]
  109. _ignore_model_permissions = True
  110. exclude_from_schema = True
  111. lookup_value_regex = '[^/]+' # Allow dots
  112. def _retrieve_report(self, pk):
  113. # Read the PK as "<module>.<report>"
  114. if '.' not in pk:
  115. raise Http404
  116. module_name, report_name = pk.split('.', 1)
  117. # Raise a 404 on an invalid Report module/name
  118. report = get_report(module_name, report_name)
  119. if report is None:
  120. raise Http404
  121. return report
  122. def list(self, request):
  123. """
  124. Compile all reports and their related results (if any). Result data is deferred in the list view.
  125. """
  126. report_list = []
  127. report_content_type = ContentType.objects.get(app_label='extras', model='report')
  128. results = {
  129. r.name: r
  130. for r in JobResult.objects.filter(
  131. obj_type=report_content_type,
  132. status__in=JobResultStatusChoices.TERMINAL_STATE_CHOICES
  133. ).defer('data')
  134. }
  135. # Iterate through all available Reports.
  136. for module_name, reports in get_reports():
  137. for report in reports:
  138. # Attach the relevant JobResult (if any) to each Report.
  139. report.result = results.get(report.full_name, None)
  140. report_list.append(report)
  141. serializer = serializers.ReportSerializer(report_list, many=True, context={
  142. 'request': request,
  143. })
  144. return Response(serializer.data)
  145. def retrieve(self, request, pk):
  146. """
  147. Retrieve a single Report identified as "<module>.<report>".
  148. """
  149. # Retrieve the Report and JobResult, if any.
  150. report = self._retrieve_report(pk)
  151. report_content_type = ContentType.objects.get(app_label='extras', model='report')
  152. report.result = JobResult.objects.filter(
  153. obj_type=report_content_type,
  154. name=report.full_name,
  155. status__in=JobResultStatusChoices.TERMINAL_STATE_CHOICES
  156. ).first()
  157. serializer = serializers.ReportDetailSerializer(report, context={
  158. 'request': request
  159. })
  160. return Response(serializer.data)
  161. @action(detail=True, methods=['post'])
  162. def run(self, request, pk):
  163. """
  164. Run a Report identified as "<module>.<script>" and return the pending JobResult as the result
  165. """
  166. # Check that the user has permission to run reports.
  167. if not request.user.has_perm('extras.add_reportresult'):
  168. raise PermissionDenied("This user does not have permission to run reports.")
  169. # Retrieve and run the Report. This will create a new JobResult.
  170. report = self._retrieve_report(pk)
  171. report_content_type = ContentType.objects.get(app_label='extras', model='report')
  172. job_result = JobResult.enqueue_job(
  173. run_report,
  174. report.full_name,
  175. report_content_type,
  176. request.user
  177. )
  178. report.result = job_result
  179. serializer = serializers.ReportDetailSerializer(report)
  180. return Response(serializer.data)
  181. #
  182. # Scripts
  183. #
  184. class ScriptViewSet(ViewSet):
  185. permission_classes = [IsAuthenticatedOrLoginNotRequired]
  186. _ignore_model_permissions = True
  187. exclude_from_schema = True
  188. lookup_value_regex = '[^/]+' # Allow dots
  189. def _get_script(self, pk):
  190. module_name, script_name = pk.split('.')
  191. script = get_script(module_name, script_name)
  192. if script is None:
  193. raise Http404
  194. return script
  195. def list(self, request):
  196. script_content_type = ContentType.objects.get(app_label='extras', model='script')
  197. results = {
  198. r.name: r
  199. for r in JobResult.objects.filter(
  200. obj_type=script_content_type,
  201. status__in=JobResultStatusChoices.TERMINAL_STATE_CHOICES
  202. ).defer('data').order_by('created')
  203. }
  204. flat_list = []
  205. for script_list in get_scripts().values():
  206. flat_list.extend(script_list.values())
  207. # Attach JobResult objects to each script (if any)
  208. for script in flat_list:
  209. script.result = results.get(script.full_name, None)
  210. serializer = serializers.ScriptSerializer(flat_list, many=True, context={'request': request})
  211. return Response(serializer.data)
  212. def retrieve(self, request, pk):
  213. script = self._get_script(pk)
  214. script_content_type = ContentType.objects.get(app_label='extras', model='script')
  215. script.result = JobResult.objects.filter(
  216. obj_type=script_content_type,
  217. name=script.full_name,
  218. status__in=JobResultStatusChoices.TERMINAL_STATE_CHOICES
  219. ).first()
  220. serializer = serializers.ScriptDetailSerializer(script, context={'request': request})
  221. return Response(serializer.data)
  222. def post(self, request, pk):
  223. """
  224. Run a Script identified as "<module>.<script>" and return the pending JobResult as the result
  225. """
  226. script = self._get_script(pk)()
  227. input_serializer = serializers.ScriptInputSerializer(data=request.data)
  228. if input_serializer.is_valid():
  229. data = input_serializer.data['data']
  230. commit = input_serializer.data['commit']
  231. script_content_type = ContentType.objects.get(app_label='extras', model='script')
  232. job_result = JobResult.enqueue_job(
  233. run_script,
  234. script.full_name,
  235. script_content_type,
  236. request.user,
  237. data=form.cleaned_data,
  238. request=copy_safe_request(request),
  239. commit=commit
  240. )
  241. script.result = job_result
  242. serializer = serializers.ScriptDetailSerializer(script)
  243. return Response(serializer.data)
  244. return Response(input_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
  245. #
  246. # Change logging
  247. #
  248. class ObjectChangeViewSet(ReadOnlyModelViewSet):
  249. """
  250. Retrieve a list of recent changes.
  251. """
  252. queryset = ObjectChange.objects.prefetch_related('user')
  253. serializer_class = serializers.ObjectChangeSerializer
  254. filterset_class = filters.ObjectChangeFilterSet
  255. #
  256. # Job Results
  257. #
  258. class JobResultViewSet(ReadOnlyModelViewSet):
  259. """
  260. Retrieve a list of job results
  261. """
  262. queryset = JobResult.objects.prefetch_related('user')
  263. serializer_class = serializers.JobResultSerializer
  264. filterset_class = filters.JobResultFilterSet