Clean up views

This commit is contained in:
Jeremy Stretch 2024-01-31 12:44:46 -05:00
parent 64340ab33b
commit c273536921
3 changed files with 95 additions and 132 deletions

View File

@ -28,13 +28,13 @@ urlpatterns = (
# Background Tasks # Background Tasks
path('background-queues/', views.BackgroundQueueListView.as_view(), name='background_queue_list'), path('background-queues/', views.BackgroundQueueListView.as_view(), name='background_queue_list'),
path('background-queues/<int:queue_index>/<str:status>/', views.BackgroundTaskListView.as_view(), name='background_task_list'), path('background-queues/<int:queue_index>/<str:status>/', views.BackgroundTaskListView.as_view(), name='background_task_list'),
path('background-tasks/<str:job_id>/', views.BackgroundTaskDetailView.as_view(), name='background_task'), path('background-tasks/<str:job_id>/', views.BackgroundTaskView.as_view(), name='background_task'),
path('background-tasks/<str:job_id>/delete/', views.BackgroundTaskDeleteView.as_view(), name='background_task_delete'), path('background-tasks/<str:job_id>/delete/', views.BackgroundTaskDeleteView.as_view(), name='background_task_delete'),
path('background-tasks/<str:job_id>/requeue/', views.BackgroundTaskRequeueView.as_view(), name='background_task_requeue'), path('background-tasks/<str:job_id>/requeue/', views.BackgroundTaskRequeueView.as_view(), name='background_task_requeue'),
path('background-tasks/<str:job_id>/enqueue/', views.BackgroundTaskEnqueueView.as_view(), name='background_task_enqueue'), path('background-tasks/<str:job_id>/enqueue/', views.BackgroundTaskEnqueueView.as_view(), name='background_task_enqueue'),
path('background-tasks/<str:job_id>/stop/', views.BackgroundTaskStopView.as_view(), name='background_task_stop'), path('background-tasks/<str:job_id>/stop/', views.BackgroundTaskStopView.as_view(), name='background_task_stop'),
path('background-workers/<int:queue_index>/', views.WorkerListView.as_view(), name='worker_list'), path('background-workers/<int:queue_index>/', views.WorkerListView.as_view(), name='worker_list'),
path('background-worker/<str:key>/', views.WorkerDetailView.as_view(), name='worker'), path('background-workers/<str:key>/', views.WorkerView.as_view(), name='worker'),
# Config revisions # Config revisions
path('config-revisions/', views.ConfigRevisionListView.as_view(), name='configrevision_list'), path('config-revisions/', views.ConfigRevisionListView.as_view(), name='configrevision_list'),

View File

@ -4,31 +4,26 @@ from django.contrib import messages
from django.contrib.auth.mixins import UserPassesTestMixin from django.contrib.auth.mixins import UserPassesTestMixin
from django.core.cache import cache from django.core.cache import cache
from django.http import HttpResponseForbidden, Http404 from django.http import HttpResponseForbidden, Http404
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views.generic import View
from django_rq.queues import get_queue_by_index, get_redis_connection from django_rq.queues import get_queue_by_index, get_redis_connection
from django_rq.settings import QUEUES_MAP, QUEUES_LIST from django_rq.settings import QUEUES_MAP, QUEUES_LIST
from django_rq.utils import get_jobs, get_scheduler_statistics, get_statistics, stop_jobs from django_rq.utils import get_jobs, get_statistics, stop_jobs
from django.shortcuts import get_object_or_404, redirect, render from rq import requeue_job
from django.views.generic import View from rq.exceptions import NoSuchJobError
from rq.job import Job as RQ_Job, JobStatus as RQJobStatus
from rq.registry import (
DeferredJobRegistry, FailedJobRegistry, FinishedJobRegistry, ScheduledJobRegistry, StartedJobRegistry,
)
from rq.worker import Worker
from rq.worker_registration import clean_worker_registry
from netbox.config import get_config, PARAMS from netbox.config import get_config, PARAMS
from netbox.views import generic from netbox.views import generic
from netbox.views.generic.base import BaseObjectView from netbox.views.generic.base import BaseObjectView
from netbox.views.generic.mixins import ActionsMixin, TableMixin from netbox.views.generic.mixins import TableMixin
from rq import requeue_job
from rq.exceptions import NoSuchJobError
from rq.job import Job as RQ_Job, JobStatus
from rq.registry import (
DeferredJobRegistry,
FailedJobRegistry,
FinishedJobRegistry,
ScheduledJobRegistry,
StartedJobRegistry,
)
from rq.worker import Worker
from rq.worker_registration import clean_worker_registry
from utilities.forms import ConfirmationForm from utilities.forms import ConfirmationForm
from utilities.utils import count_related from utilities.utils import count_related
from utilities.views import ContentTypePermissionRequiredMixin, register_model_view from utilities.views import ContentTypePermissionRequiredMixin, register_model_view
@ -256,88 +251,78 @@ class ConfigRevisionRestoreView(ContentTypePermissionRequiredMixin, View):
return redirect(candidate_config.get_absolute_url()) return redirect(candidate_config.get_absolute_url())
# #
# Background Tasks (RQ) # Background Tasks (RQ)
# #
class BaseRQView(UserPassesTestMixin, View):
class BaseTaskListView(UserPassesTestMixin, TableMixin, View):
def test_func(self): def test_func(self):
return self.request.user.is_staff return self.request.user.is_staff
class BackgroundQueueListView(UserPassesTestMixin, View): class BackgroundQueueListView(TableMixin, BaseRQView):
table = tables.BackgroundQueueTable
def test_func(self):
return self.request.user.is_staff
def get(self, request): def get(self, request):
table = tables.BackgroundQueueTable(get_statistics(run_maintenance_tasks=True)["queues"], user=request.user) data = get_statistics(run_maintenance_tasks=True)["queues"]
table.configure(request) table = self.get_table(data, request, bulk_actions=False)
return render(request, 'core/rq_queue_list.html', { return render(request, 'core/rq_queue_list.html', {
'table': table, 'table': table,
}) })
class BackgroundTaskListView(BaseTaskListView): class BackgroundTaskListView(TableMixin, BaseRQView):
table = tables.BackgroundTaskTable table = tables.BackgroundTaskTable
def get_table_data(self, request, queue, status): def get_table_data(self, request, queue, status):
registry = None
jobs = [] jobs = []
if status == 'queued': # Call get_jobs() to returned queued tasks
if queue.count > 0: if status == RQJobStatus.QUEUED:
jobs = queue.get_jobs() return queue.get_jobs()
elif status == 'started':
registry = StartedJobRegistry(queue.name, queue.connection)
elif status == 'deferred':
registry = DeferredJobRegistry(queue.name, queue.connection)
elif status == 'finished':
registry = FinishedJobRegistry(queue.name, queue.connection)
elif status == 'failed':
registry = FailedJobRegistry(queue.name, queue.connection)
elif status == 'scheduled':
registry = ScheduledJobRegistry(queue.name, queue.connection)
if status != 'queued': # For other statuses, determine the registry to list (or raise a 404 for invalid statuses)
job_ids = registry.get_job_ids() try:
if status != 'deferred': registry_cls = {
jobs = get_jobs(queue, job_ids, registry) RQJobStatus.STARTED: StartedJobRegistry,
else: RQJobStatus.DEFERRED: DeferredJobRegistry,
# deferred jobs require special handling RQJobStatus.FINISHED: FinishedJobRegistry,
job_ids = registry.get_job_ids() RQJobStatus.FAILED: FailedJobRegistry,
RQJobStatus.SCHEDULED: ScheduledJobRegistry,
}[status]
except KeyError:
raise Http404
registry = registry_cls(queue.name, queue.connection)
for job_id in job_ids: job_ids = registry.get_job_ids()
try: if status != RQJobStatus.DEFERRED:
jobs.append(RQ_Job.fetch(job_id, connection=queue.connection, serializer=queue.serializer)) jobs = get_jobs(queue, job_ids, registry)
except NoSuchJobError: else:
pass # Deferred jobs require special handling
for job_id in job_ids:
try:
jobs.append(RQ_Job.fetch(job_id, connection=queue.connection, serializer=queue.serializer))
except NoSuchJobError:
pass
if jobs and status == 'scheduled': if jobs and status == RQJobStatus.SCHEDULED:
for job in jobs: for job in jobs:
job.scheduled_at = registry.get_scheduled_time(job) job.scheduled_at = registry.get_scheduled_time(job)
return jobs return jobs
def get(self, request, queue_index, status): def get(self, request, queue_index, status):
queue = get_queue_by_index(queue_index) queue = get_queue_by_index(queue_index)
data = self.get_table_data(request, queue, status) data = self.get_table_data(request, queue, status)
table = self.get_table(data, request, False) table = self.get_table(data, request, False)
# If this is an HTMX request, return only the rendered table HTML # If this is an HTMX request, return only the rendered table HTML
if request.htmx: if request.htmx:
if request.htmx.target != 'object_list':
table.embedded = True
# Hide selection checkboxes
if 'pk' in table.base_columns:
table.columns.hide('pk')
return render(request, 'htmx/table.html', { return render(request, 'htmx/table.html', {
'table': table, 'table': table,
'queue': queue,
'status': status,
}) })
return render(request, 'core/rq_task_list.html', { return render(request, 'core/rq_task_list.html', {
@ -347,46 +332,7 @@ class BackgroundTaskListView(BaseTaskListView):
}) })
class WorkerListView(BaseTaskListView): class BackgroundTaskView(BaseRQView):
table = tables.WorkerTable
def test_func(self):
return self.request.user.is_staff
def get_table_data(self, request, queue):
clean_worker_registry(queue)
all_workers = Worker.all(queue.connection)
workers = [worker for worker in all_workers if queue.name in worker.queue_names()]
return workers
def get(self, request, queue_index):
queue = get_queue_by_index(queue_index)
data = self.get_table_data(request, queue)
table = self.get_table(data, request, False)
# If this is an HTMX request, return only the rendered table HTML
if request.htmx:
if request.htmx.target != 'object_list':
table.embedded = True
# Hide selection checkboxes
if 'pk' in table.base_columns:
table.columns.hide('pk')
return render(request, 'htmx/table.html', {
'table': table,
'queue': queue,
})
return render(request, 'core/rq_worker_list.html', {
'table': table,
'queue': queue,
})
class BackgroundTaskDetailView(UserPassesTestMixin, View):
def test_func(self):
return self.request.user.is_staff
def get(self, request, job_id): def get(self, request, job_id):
# all the RQ queues should use the same connection # all the RQ queues should use the same connection
@ -413,10 +359,7 @@ class BackgroundTaskDetailView(UserPassesTestMixin, View):
}) })
class BackgroundTaskDeleteView(UserPassesTestMixin, View): class BackgroundTaskDeleteView(BaseRQView):
def test_func(self):
return self.request.user.is_staff
def get(self, request, job_id): def get(self, request, job_id):
if not request.htmx: if not request.htmx:
@ -455,10 +398,7 @@ class BackgroundTaskDeleteView(UserPassesTestMixin, View):
return redirect(reverse('core:background_queue_list')) return redirect(reverse('core:background_queue_list'))
class BackgroundTaskRequeueView(UserPassesTestMixin, View): class BackgroundTaskRequeueView(BaseRQView):
def test_func(self):
return self.request.user.is_staff
def get(self, request, job_id): def get(self, request, job_id):
# all the RQ queues should use the same connection # all the RQ queues should use the same connection
@ -476,10 +416,7 @@ class BackgroundTaskRequeueView(UserPassesTestMixin, View):
return redirect(reverse('core:background_task', args=[job_id])) return redirect(reverse('core:background_task', args=[job_id]))
class BackgroundTaskEnqueueView(UserPassesTestMixin, View): class BackgroundTaskEnqueueView(BaseRQView):
def test_func(self):
return self.request.user.is_staff
def get(self, request, job_id): def get(self, request, job_id):
# all the RQ queues should use the same connection # all the RQ queues should use the same connection
@ -500,13 +437,13 @@ class BackgroundTaskEnqueueView(UserPassesTestMixin, View):
queue.enqueue_job(job) queue.enqueue_job(job)
# Remove job from correct registry if needed # Remove job from correct registry if needed
if job.get_status() == JobStatus.DEFERRED: if job.get_status() == RQJobStatus.DEFERRED:
registry = DeferredJobRegistry(queue.name, queue.connection) registry = DeferredJobRegistry(queue.name, queue.connection)
registry.remove(job) registry.remove(job)
elif job.get_status() == JobStatus.FINISHED: elif job.get_status() == RQJobStatus.FINISHED:
registry = FinishedJobRegistry(queue.name, queue.connection) registry = FinishedJobRegistry(queue.name, queue.connection)
registry.remove(job) registry.remove(job)
elif job.get_status() == JobStatus.SCHEDULED: elif job.get_status() == RQJobStatus.SCHEDULED:
registry = ScheduledJobRegistry(queue.name, queue.connection) registry = ScheduledJobRegistry(queue.name, queue.connection)
registry.remove(job) registry.remove(job)
@ -514,10 +451,7 @@ class BackgroundTaskEnqueueView(UserPassesTestMixin, View):
return redirect(reverse('core:background_task', args=[job_id])) return redirect(reverse('core:background_task', args=[job_id]))
class BackgroundTaskStopView(UserPassesTestMixin, View): class BackgroundTaskStopView(BaseRQView):
def test_func(self):
return self.request.user.is_staff
def get(self, request, job_id): def get(self, request, job_id):
# all the RQ queues should use the same connection # all the RQ queues should use the same connection
@ -539,10 +473,40 @@ class BackgroundTaskStopView(UserPassesTestMixin, View):
return redirect(reverse('core:background_task', args=[job_id])) return redirect(reverse('core:background_task', args=[job_id]))
class WorkerDetailView(UserPassesTestMixin, View): class WorkerListView(TableMixin, BaseRQView):
table = tables.WorkerTable
def test_func(self): def get_table_data(self, request, queue):
return self.request.user.is_staff clean_worker_registry(queue)
all_workers = Worker.all(queue.connection)
workers = [worker for worker in all_workers if queue.name in worker.queue_names()]
return workers
def get(self, request, queue_index):
queue = get_queue_by_index(queue_index)
data = self.get_table_data(request, queue)
table = self.get_table(data, request, False)
# If this is an HTMX request, return only the rendered table HTML
if request.htmx:
if request.htmx.target != 'object_list':
table.embedded = True
# Hide selection checkboxes
if 'pk' in table.base_columns:
table.columns.hide('pk')
return render(request, 'htmx/table.html', {
'table': table,
'queue': queue,
})
return render(request, 'core/rq_worker_list.html', {
'table': table,
'queue': queue,
})
class WorkerView(BaseRQView):
def get(self, request, key): def get(self, request, key):
# all the RQ queues should use the same connection # all the RQ queues should use the same connection

View File

@ -4,8 +4,7 @@
{% load render_table from django_tables2 %} {% load render_table from django_tables2 %}
{% block breadcrumbs %} {% block breadcrumbs %}
<li class="breadcrumb-item"><a href="{% url 'core:background_queue_list' %}">{% trans 'Background Tasks' %}</a></li> <li class="breadcrumb-item"><a href="{% url 'core:background_queue_list' %}">{% trans 'Background Queues' %}</a></li>
{# <li class="breadcrumb-item"><a href="{% url 'core:background_task_list' queue_index=queue_index status=job.get_status %}">{{ queue.name }}</a></li> #}
{% endblock breadcrumbs %} {% endblock breadcrumbs %}
{% block title %}{% trans "Worker Info" %} {{ job.id }}{% endblock %} {% block title %}{% trans "Worker Info" %} {{ job.id }}{% endblock %}
@ -46,7 +45,7 @@
</tr> </tr>
<tr> <tr>
<th scope="row">{% trans "State" %}</th> <th scope="row">{% trans "State" %}</th>
<td>{{ worker.get_state|placeholder }}</td> <td>{{ worker.get_state|bettertitle|placeholder }}</td>
</tr> </tr>
<tr> <tr>
<th scope="row">{% trans "Birth" %}</th> <th scope="row">{% trans "Birth" %}</th>
@ -73,8 +72,8 @@
<td>{{ worker.failed_job_count }}</td> <td>{{ worker.failed_job_count }}</td>
</tr> </tr>
<tr> <tr>
<th scope="row">{% trans "Total working time (seconds)" %}</th> <th scope="row">{% trans "Total working time" %}</th>
<td>{{ total_working_time }}</td> <td>{{ total_working_time }} {% trans "seconds" %}</td>
</tr> </tr>
</table> </table>
</div> </div>