Procházet zdrojové kódy

#11558: Disable sync button if RQ worker not running

jeremystretch před 2 roky
rodič
revize
08bdb54cb4

+ 3 - 3
netbox/core/models/data.py

@@ -16,7 +16,6 @@ from django.utils.translation import gettext as _
 
 from extras.models import JobResult
 from netbox.models import PrimaryModel
-from netbox.models.features import ChangeLoggingMixin
 from netbox.registry import registry
 from utilities.files import sha256_hash
 from utilities.querysets import RestrictedQuerySet
@@ -116,6 +115,7 @@ class DataSource(PrimaryModel):
         """
         # Set the status to "syncing"
         self.status = DataSourceStatusChoices.QUEUED
+        DataSource.objects.filter(pk=self.pk).update(status=self.status)
 
         # Enqueue a sync job
         job_result = JobResult.enqueue_job(
@@ -137,8 +137,8 @@ class DataSource(PrimaryModel):
         """
         Create/update/delete child DataFiles as necessary to synchronize with the remote source.
         """
-        if not self.ready_for_sync:
-            raise SyncError(f"Cannot initiate sync; data source not ready/enabled")
+        if self.status == DataSourceStatusChoices.SYNCING:
+            raise SyncError(f"Cannot initiate sync; syncing already in progress.")
 
         # Emit the pre_sync signal
         pre_sync.send(sender=self.__class__, instance=self)

+ 5 - 0
netbox/core/views.py

@@ -3,6 +3,7 @@ from django.shortcuts import get_object_or_404, redirect
 
 from netbox.views import generic
 from netbox.views.generic.base import BaseObjectView
+from utilities.rqworker import get_queue_for_model, get_workers_for_queue
 from utilities.utils import count_related
 from utilities.views import register_model_view
 from . import filtersets, forms, tables
@@ -31,7 +32,11 @@ class DataSourceView(generic.ObjectView):
             (DataFile.objects.restrict(request.user, 'view').filter(source=instance), 'source_id'),
         )
 
+        queue_name = get_queue_for_model(DataSource)
+        sync_enabled = bool(get_workers_for_queue(queue_name))
+
         return {
+            'sync_enabled': sync_enabled,
             'related_models': related_models,
         }
 

+ 2 - 1
netbox/extras/models/models.py

@@ -30,6 +30,7 @@ from netbox.models.features import (
     TagsMixin, WebhooksMixin,
 )
 from utilities.querysets import RestrictedQuerySet
+from utilities.rqworker import get_queue_for_model
 from utilities.utils import render_jinja2
 
 __all__ = (
@@ -730,7 +731,7 @@ class JobResult(models.Model):
             schedule_at: Schedule the job to be executed at the passed date and time
             interval: Recurrence interval (in minutes)
         """
-        rq_queue_name = get_config().QUEUE_MAPPINGS.get(obj_type.model, RQ_QUEUE_DEFAULT)
+        rq_queue_name = get_queue_for_model(obj_type.model)
         queue = django_rq.get_queue(rq_queue_name)
         status = JobResultStatusChoices.STATUS_SCHEDULED if schedule_at else JobResultStatusChoices.STATUS_PENDING
         job_result: JobResult = JobResult.objects.create(

+ 3 - 4
netbox/extras/views.py

@@ -6,14 +6,13 @@ from django.http import Http404, HttpResponseBadRequest, HttpResponseForbidden,
 from django.shortcuts import get_object_or_404, redirect, render
 from django.urls import reverse
 from django.views.generic import View
-from django_rq.queues import get_connection
-from rq import Worker
 
 from extras.dashboard.forms import DashboardWidgetAddForm, DashboardWidgetForm
 from extras.dashboard.utils import get_widget_class
 from netbox.views import generic
 from utilities.forms import ConfirmationForm, get_field_value
 from utilities.htmx import is_htmx
+from utilities.rqworker import get_workers_for_queue
 from utilities.templatetags.builtins.filters import render_markdown
 from utilities.utils import copy_safe_request, count_related, get_viewname, normalize_querydict, shallow_compare_dict
 from utilities.views import ContentTypePermissionRequiredMixin, register_model_view
@@ -863,7 +862,7 @@ class ReportView(ContentTypePermissionRequiredMixin, View):
         if form.is_valid():
 
             # Allow execution only if RQ worker process is running
-            if not Worker.count(get_connection('default')):
+            if not get_workers_for_queue('default'):
                 messages.error(request, "Unable to run report: RQ worker process not running.")
                 return render(request, 'extras/report.html', {
                     'report': report,
@@ -994,7 +993,7 @@ class ScriptView(ContentTypePermissionRequiredMixin, GetScriptMixin, View):
         form = script.as_form(request.POST, request.FILES)
 
         # Allow execution only if RQ worker process is running
-        if not Worker.count(get_connection('default')):
+        if not get_workers_for_queue('default'):
             messages.error(request, "Unable to run script: RQ worker process not running.")
 
         elif form.is_valid():

+ 6 - 4
netbox/templates/core/datasource.html

@@ -6,7 +6,7 @@
 
 {% block extra_controls %}
   {% if perms.core.sync_datasource %}
-    {% if object.ready_for_sync %}
+    {% if sync_enabled and object.ready_for_sync %}
       <form action="{% url 'core:datasource_sync' pk=object.pk %}" method="post">
         {% csrf_token %}
         <button type="submit" class="btn btn-sm btn-primary">
@@ -14,9 +14,11 @@
         </button>
       </form>
     {% else %}
-      <button class="btn btn-sm btn-primary" disabled>
-        <i class="mdi mdi-sync" aria-hidden="true"></i> Sync
-      </button>
+      <span class="inline-block" tabindex="0" data-bs-toggle="tooltip" data-bs-delay="100" data-bs-placement="bottom" title="Unable to sync: No RQ worker running">
+        <button class="btn btn-sm btn-primary" disabled>
+          <i class="mdi mdi-sync" aria-hidden="true"></i> Sync
+        </button>
+      </span>
     {% endif %}
   {% endif %}
 {% endblock %}

+ 24 - 0
netbox/utilities/rqworker.py

@@ -0,0 +1,24 @@
+from django_rq.queues import get_connection
+from rq import Worker
+
+from netbox.config import get_config
+from netbox.constants import RQ_QUEUE_DEFAULT
+
+__all__ = (
+    'get_queue_for_model',
+    'get_workers_for_queue',
+)
+
+
+def get_queue_for_model(model):
+    """
+    Return the configured queue name for jobs associated with the given model.
+    """
+    return get_config().QUEUE_MAPPINGS.get(model, RQ_QUEUE_DEFAULT)
+
+
+def get_workers_for_queue(queue_name):
+    """
+    Returns True if a worker process is currently servicing the specified queue.
+    """
+    return Worker.count(get_connection(queue_name))