diff --git a/netbox/core/api/views.py b/netbox/core/api/views.py index 24d2e8d1f..98da3bb11 100644 --- a/netbox/core/api/views.py +++ b/netbox/core/api/views.py @@ -1,7 +1,14 @@ +from django.contrib.contenttypes.models import ContentType +from django.shortcuts import get_object_or_404 + +from rest_framework.decorators import action +from rest_framework.exceptions import PermissionDenied +from rest_framework.response import Response from rest_framework.routers import APIRootView from core import filtersets from core.models import * +from extras.models import JobResult from netbox.api.viewsets import NetBoxModelViewSet from utilities.utils import count_related from . import serializers @@ -26,6 +33,20 @@ class DataSourceViewSet(NetBoxModelViewSet): serializer_class = serializers.DataSourceSerializer filterset_class = filtersets.DataSourceFilterSet + @action(detail=True, methods=['post']) + def sync(self, request, pk): + """ + Enqueue a job to synchronize the DataSource. + """ + if not request.user.has_perm('extras.sync_datasource'): + raise PermissionDenied("Syncing data sources requires the core.sync_datasource permission.") + + datasource = get_object_or_404(DataSource, pk=pk) + datasource.enqueue_sync_job(request) + serializer = serializers.DataSourceSerializer(datasource, context={'request': request}) + + return Response(serializer.data) + class DataFileViewSet(NetBoxModelViewSet): queryset = DataFile.objects.defer('data').prefetch_related('source') diff --git a/netbox/core/models/data.py b/netbox/core/models/data.py index e9dd44366..853bdfdee 100644 --- a/netbox/core/models/data.py +++ b/netbox/core/models/data.py @@ -5,11 +5,14 @@ import tempfile from fnmatch import fnmatchcase from urllib.parse import quote, urlunparse, urlparse +from django.contrib.contenttypes.models import ContentType from django.db import models from django.urls import reverse from django.utils import timezone +from django.utils.module_loading import import_string from django.utils.translation import gettext as _ +from extras.models import JobResult from netbox.models import ChangeLoggedModel from utilities.files import sha256_hash from utilities.querysets import RestrictedQuerySet @@ -88,6 +91,30 @@ class DataSource(ChangeLoggedModel): def get_status_color(self): return DataSourceStatusChoices.colors.get(self.status) + @property + def ready_for_sync(self): + return self.enabled and self.status not in ( + DataSourceStatusChoices.QUEUED, + DataSourceStatusChoices.SYNCING + ) + + def enqueue_sync_job(self, request): + """ + Enqueue a background job to synchronize the DataSource by calling sync(). + """ + # Set the status to "syncing" + self.status = DataSourceStatusChoices.QUEUED + + # Enqueue a sync job + job_result = JobResult.enqueue_job( + import_string('core.jobs.sync_datasource'), + name=self.name, + obj_type=ContentType.objects.get_for_model(DataSource), + user=request.user, + ) + + return job_result + def sync(self): """ Create/update/delete child DataFiles as necessary to synchronize with the remote source. diff --git a/netbox/core/views.py b/netbox/core/views.py index a7e25e4d1..519577c0e 100644 --- a/netbox/core/views.py +++ b/netbox/core/views.py @@ -44,18 +44,7 @@ class DataSourceSyncView(BaseObjectView): def post(self, request, pk): datasource = get_object_or_404(self.queryset, pk=pk) - - # Set the status to "syncing" - datasource.status = DataSourceStatusChoices.QUEUED - datasource.save() - - # Enqueue a sync job - job_result = JobResult.enqueue_job( - jobs.sync_datasource, - name=datasource.name, - obj_type=ContentType.objects.get_for_model(DataSource), - user=request.user, - ) + job_result = datasource.enqueue_sync_job(request) messages.success(request, f"Queued job #{job_result.pk} to sync {datasource}") return redirect(datasource.get_absolute_url()) diff --git a/netbox/templates/core/datasource.html b/netbox/templates/core/datasource.html index 38a5311cb..ccf202420 100644 --- a/netbox/templates/core/datasource.html +++ b/netbox/templates/core/datasource.html @@ -6,7 +6,7 @@ {% block extra_controls %} {% if perms.core.sync_datasource %} - {% if object.enabled %} + {% if object.ready_for_sync %}