views.py 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. from django.http import Http404, HttpResponse
  2. from django.shortcuts import get_object_or_404
  3. from django.utils.translation import gettext_lazy as _
  4. from django_rq.queues import get_redis_connection
  5. from django_rq.settings import QUEUES_LIST
  6. from django_rq.utils import get_statistics
  7. from drf_spectacular.types import OpenApiTypes
  8. from drf_spectacular.utils import OpenApiParameter, extend_schema
  9. from rest_framework import viewsets
  10. from rest_framework.decorators import action
  11. from rest_framework.exceptions import PermissionDenied
  12. from rest_framework.response import Response
  13. from rest_framework.routers import APIRootView
  14. from rest_framework.viewsets import ReadOnlyModelViewSet
  15. from rq.job import Job as RQ_Job
  16. from rq.worker import Worker
  17. from core import filtersets
  18. from core.jobs import SyncDataSourceJob
  19. from core.models import *
  20. from core.utils import delete_rq_job, enqueue_rq_job, get_rq_jobs, requeue_rq_job, stop_rq_job
  21. from netbox.api.authentication import IsAuthenticatedOrLoginNotRequired
  22. from netbox.api.metadata import ContentTypeMetadata
  23. from netbox.api.pagination import LimitOffsetListPagination
  24. from netbox.api.viewsets import NetBoxModelViewSet, NetBoxReadOnlyModelViewSet
  25. from utilities.api import IsSuperuser
  26. from . import serializers
  27. class CoreRootView(APIRootView):
  28. """
  29. Core API root view
  30. """
  31. def get_view_name(self):
  32. return 'Core'
  33. class DataSourceViewSet(NetBoxModelViewSet):
  34. queryset = DataSource.objects.all()
  35. serializer_class = serializers.DataSourceSerializer
  36. filterset_class = filtersets.DataSourceFilterSet
  37. @action(detail=True, methods=['post'])
  38. def sync(self, request, pk):
  39. """
  40. Enqueue a job to synchronize the DataSource.
  41. """
  42. datasource = get_object_or_404(DataSource, pk=pk)
  43. if not request.user.has_perm('core.sync_datasource', obj=datasource):
  44. raise PermissionDenied(_("This user does not have permission to synchronize this data source."))
  45. # Enqueue the sync job
  46. SyncDataSourceJob.enqueue(instance=datasource, user=request.user)
  47. serializer = serializers.DataSourceSerializer(datasource, context={'request': request})
  48. return Response(serializer.data)
  49. class DataFileViewSet(NetBoxReadOnlyModelViewSet):
  50. queryset = DataFile.objects.defer('data')
  51. serializer_class = serializers.DataFileSerializer
  52. filterset_class = filtersets.DataFileFilterSet
  53. class JobViewSet(ReadOnlyModelViewSet):
  54. """
  55. Retrieve a list of job results
  56. """
  57. queryset = Job.objects.all()
  58. serializer_class = serializers.JobSerializer
  59. filterset_class = filtersets.JobFilterSet
  60. class ObjectChangeViewSet(ReadOnlyModelViewSet):
  61. """
  62. Retrieve a list of recent changes.
  63. """
  64. metadata_class = ContentTypeMetadata
  65. serializer_class = serializers.ObjectChangeSerializer
  66. filterset_class = filtersets.ObjectChangeFilterSet
  67. def get_queryset(self):
  68. return ObjectChange.objects.valid_models()
  69. class ObjectTypeViewSet(ReadOnlyModelViewSet):
  70. """
  71. Read-only list of ObjectTypes.
  72. """
  73. permission_classes = [IsAuthenticatedOrLoginNotRequired]
  74. queryset = ObjectType.objects.order_by('app_label', 'model')
  75. serializer_class = serializers.ObjectTypeSerializer
  76. filterset_class = filtersets.ObjectTypeFilterSet
  77. class BaseRQViewSet(viewsets.ViewSet):
  78. """
  79. Base class for RQ view sets. Provides a list() method. Subclasses must implement get_data().
  80. """
  81. permission_classes = [IsSuperuser]
  82. serializer_class = None
  83. def get_data(self):
  84. raise NotImplementedError()
  85. @extend_schema(responses={200: OpenApiTypes.OBJECT})
  86. def list(self, request):
  87. data = self.get_data()
  88. paginator = LimitOffsetListPagination()
  89. data = paginator.paginate_list(data, request)
  90. serializer = self.serializer_class(data, many=True, context={'request': request})
  91. return paginator.get_paginated_response(serializer.data)
  92. def get_serializer(self, *args, **kwargs):
  93. """
  94. Return the serializer instance that should be used for validating and
  95. deserializing input and for serializing output.
  96. """
  97. serializer_class = self.get_serializer_class()
  98. kwargs['context'] = self.get_serializer_context()
  99. return serializer_class(*args, **kwargs)
  100. def get_serializer_class(self):
  101. """
  102. Return the class to use for the serializer.
  103. """
  104. return self.serializer_class
  105. def get_serializer_context(self):
  106. """
  107. Extra context provided to the serializer class.
  108. """
  109. return {
  110. 'request': self.request,
  111. 'format': self.format_kwarg,
  112. 'view': self,
  113. }
  114. class BackgroundQueueViewSet(BaseRQViewSet):
  115. """
  116. Retrieve a list of RQ Queues.
  117. Note: Queue names are not URL safe, so not returning a detail view.
  118. """
  119. serializer_class = serializers.BackgroundQueueSerializer
  120. lookup_field = 'name'
  121. lookup_value_regex = r'[\w.@+-]+'
  122. def get_view_name(self):
  123. return 'Background Queues'
  124. def get_data(self):
  125. return get_statistics(run_maintenance_tasks=True)['queues']
  126. @extend_schema(
  127. operation_id='core_background_queues_retrieve_by_name',
  128. parameters=[OpenApiParameter(name='name', type=OpenApiTypes.STR, location=OpenApiParameter.PATH)],
  129. responses={200: OpenApiTypes.OBJECT},
  130. )
  131. def retrieve(self, request, name):
  132. data = self.get_data()
  133. if not data:
  134. raise Http404
  135. for queue in data:
  136. if queue['name'] == name:
  137. serializer = self.serializer_class(queue, context={'request': request})
  138. return Response(serializer.data)
  139. raise Http404
  140. class BackgroundWorkerViewSet(BaseRQViewSet):
  141. """
  142. Retrieve a list of RQ Workers.
  143. """
  144. serializer_class = serializers.BackgroundWorkerSerializer
  145. lookup_field = 'name'
  146. def get_view_name(self):
  147. return 'Background Workers'
  148. def get_data(self):
  149. config = QUEUES_LIST[0]
  150. return Worker.all(get_redis_connection(config['connection_config']))
  151. @extend_schema(
  152. operation_id='core_background_workers_retrieve_by_name',
  153. parameters=[OpenApiParameter(name='name', type=OpenApiTypes.STR, location=OpenApiParameter.PATH)],
  154. responses={200: OpenApiTypes.OBJECT},
  155. )
  156. def retrieve(self, request, name):
  157. # all the RQ queues should use the same connection
  158. config = QUEUES_LIST[0]
  159. workers = Worker.all(get_redis_connection(config['connection_config']))
  160. worker = next((item for item in workers if item.name == name), None)
  161. if not worker:
  162. raise Http404
  163. serializer = serializers.BackgroundWorkerSerializer(worker, context={'request': request})
  164. return Response(serializer.data)
  165. class BackgroundTaskViewSet(BaseRQViewSet):
  166. """
  167. Retrieve a list of RQ Tasks.
  168. """
  169. serializer_class = serializers.BackgroundTaskSerializer
  170. lookup_field = 'id'
  171. def get_view_name(self):
  172. return 'Background Tasks'
  173. def get_data(self):
  174. return get_rq_jobs()
  175. def get_task_from_id(self, task_id):
  176. config = QUEUES_LIST[0]
  177. task = RQ_Job.fetch(task_id, connection=get_redis_connection(config['connection_config']))
  178. if not task:
  179. raise Http404
  180. return task
  181. @extend_schema(
  182. operation_id='core_background_tasks_retrieve_by_id',
  183. parameters=[OpenApiParameter(name='id', type=OpenApiTypes.STR, location=OpenApiParameter.PATH)],
  184. responses={200: OpenApiTypes.OBJECT},
  185. )
  186. def retrieve(self, request, id):
  187. """
  188. Retrieve the details of the specified RQ Task.
  189. """
  190. task = self.get_task_from_id(id)
  191. serializer = self.serializer_class(task, context={'request': request})
  192. return Response(serializer.data)
  193. @extend_schema(parameters=[OpenApiParameter(name='id', type=OpenApiTypes.STR, location=OpenApiParameter.PATH)])
  194. @action(methods=['POST'], detail=True)
  195. def delete(self, request, id):
  196. """
  197. Delete the specified RQ Task.
  198. """
  199. delete_rq_job(id)
  200. return HttpResponse(status=200)
  201. @extend_schema(parameters=[OpenApiParameter(name='id', type=OpenApiTypes.STR, location=OpenApiParameter.PATH)])
  202. @action(methods=['POST'], detail=True)
  203. def requeue(self, request, id):
  204. """
  205. Requeues the specified RQ Task.
  206. """
  207. requeue_rq_job(id)
  208. return HttpResponse(status=200)
  209. @extend_schema(parameters=[OpenApiParameter(name='id', type=OpenApiTypes.STR, location=OpenApiParameter.PATH)])
  210. @action(methods=['POST'], detail=True)
  211. def enqueue(self, request, id):
  212. """
  213. Enqueues the specified RQ Task.
  214. """
  215. enqueue_rq_job(id)
  216. return HttpResponse(status=200)
  217. @extend_schema(parameters=[OpenApiParameter(name='id', type=OpenApiTypes.STR, location=OpenApiParameter.PATH)])
  218. @action(methods=['POST'], detail=True)
  219. def stop(self, request, id):
  220. """
  221. Stops the specified RQ Task.
  222. """
  223. stopped_jobs = stop_rq_job(id)
  224. if len(stopped_jobs) == 1:
  225. return HttpResponse(status=200)
  226. else:
  227. return HttpResponse(status=204)