From 431b21b5fe53edda53acfb99daec2882c4ec9ee8 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Mon, 27 Mar 2023 21:33:47 -0400 Subject: [PATCH] Introduce ObjectJobsView --- netbox/core/jobs.py | 3 +- netbox/core/models/data.py | 6 +++ netbox/core/models/jobs.py | 2 +- netbox/netbox/models/features.py | 6 +++ netbox/netbox/views/generic/feature_views.py | 56 ++++++++++++++++++++ netbox/templates/core/object_jobs.html | 15 ++++++ 6 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 netbox/templates/core/object_jobs.html diff --git a/netbox/core/jobs.py b/netbox/core/jobs.py index fd31347e3..1c1dc4641 100644 --- a/netbox/core/jobs.py +++ b/netbox/core/jobs.py @@ -1,6 +1,5 @@ import logging -from .choices import JobStatusChoices from netbox.search.backends import search_backend from .choices import * from .exceptions import SyncError @@ -13,7 +12,7 @@ def sync_datasource(job_result, *args, **kwargs): """ Call sync() on a DataSource. """ - datasource = DataSource.objects.get(name=job_result.name) + datasource = DataSource.objects.get(pk=job_result.object_id) try: job_result.start() diff --git a/netbox/core/models/data.py b/netbox/core/models/data.py index 4f736c70d..7b5dc8c89 100644 --- a/netbox/core/models/data.py +++ b/netbox/core/models/data.py @@ -5,6 +5,7 @@ from fnmatch import fnmatchcase from urllib.parse import urlparse from django.conf import settings +from django.contrib.contenttypes.fields import GenericRelation from django.core.exceptions import ValidationError from django.core.validators import RegexValidator from django.db import models @@ -70,6 +71,11 @@ class DataSource(JobsMixin, PrimaryModel): null=True, editable=False ) + jobs = GenericRelation( + to='core.Job', + content_type_field='object_type', + object_id_field='object_id' + ) class Meta: ordering = ('name',) diff --git a/netbox/core/models/jobs.py b/netbox/core/models/jobs.py index 24257d3fb..1d5e693ee 100644 --- a/netbox/core/models/jobs.py +++ b/netbox/core/models/jobs.py @@ -162,7 +162,7 @@ class Job(models.Model): self.trigger_webhooks(event=EVENT_JOB_END) @classmethod - def enqueue(cls, func, instance, name=None, user=None, schedule_at=None, interval=None, **kwargs): + def enqueue(cls, func, instance, name='', user=None, schedule_at=None, interval=None, **kwargs): """ Create a Job instance and enqueue a job using the given callable diff --git a/netbox/netbox/models/features.py b/netbox/netbox/models/features.py index 861f82882..760dae132 100644 --- a/netbox/netbox/models/features.py +++ b/netbox/netbox/models/features.py @@ -455,6 +455,12 @@ def _register_features(sender, **kwargs): 'changelog', kwargs={'model': sender} )('netbox.views.generic.ObjectChangeLogView') + if issubclass(sender, JobsMixin): + register_model_view( + sender, + 'jobs', + kwargs={'model': sender} + )('netbox.views.generic.ObjectJobsView') if issubclass(sender, SyncedDataMixin): register_model_view( sender, diff --git a/netbox/netbox/views/generic/feature_views.py b/netbox/netbox/views/generic/feature_views.py index 8181b6d20..d78c8f754 100644 --- a/netbox/netbox/views/generic/feature_views.py +++ b/netbox/netbox/views/generic/feature_views.py @@ -6,6 +6,8 @@ from django.shortcuts import get_object_or_404, redirect, render from django.utils.translation import gettext as _ from django.views.generic import View +from core.models import Job +from core.tables import JobTable from extras import forms, tables from extras.models import * from utilities.permissions import get_permission_for_model @@ -15,6 +17,7 @@ from .base import BaseMultiObjectView __all__ = ( 'BulkSyncDataView', 'ObjectChangeLogView', + 'ObjectJobsView', 'ObjectJournalView', 'ObjectSyncDataView', ) @@ -134,6 +137,59 @@ class ObjectJournalView(View): }) +class ObjectJobsView(View): + """ + Render a list of all Job assigned to an object. For example: + + path('data-sources//jobs/', ObjectJobsView.as_view(), name='datasource_jobs', kwargs={'model': DataSource}), + + Attributes: + base_template: The name of the template to extend. If not provided, "{app}/{model}.html" will be used. + """ + base_template = None + tab = ViewTab( + label=_('Jobs'), + badge=lambda obj: obj.jobs.count(), + permission='core.view_job', + weight=11000 + ) + + def get_object(self, request, **kwargs): + return get_object_or_404(self.model.objects.restrict(request.user, 'view'), **kwargs) + + def get_jobs(self, instance): + object_type = ContentType.objects.get_for_model(instance) + return Job.objects.filter( + object_type=object_type, + object_id=instance.id + ) + + def get(self, request, model, **kwargs): + self.model = model + obj = self.get_object(request, **kwargs) + + # Gather all Jobs for this object + jobs = self.get_jobs(obj) + jobs_table = JobTable( + data=jobs, + orderable=False, + user=request.user + ) + jobs_table.configure(request) + + # Default to using "/.html" as the template, if it exists. Otherwise, + # fall back to using base.html. + if self.base_template is None: + self.base_template = f"{model._meta.app_label}/{model._meta.model_name}.html" + + return render(request, 'core/object_jobs.html', { + 'object': obj, + 'table': jobs_table, + 'base_template': self.base_template, + 'tab': self.tab, + }) + + class ObjectSyncDataView(View): def post(self, request, model, **kwargs): diff --git a/netbox/templates/core/object_jobs.html b/netbox/templates/core/object_jobs.html new file mode 100644 index 000000000..7d8c0a3b7 --- /dev/null +++ b/netbox/templates/core/object_jobs.html @@ -0,0 +1,15 @@ +{% extends base_template %} +{% load render_table from django_tables2 %} + +{% block content %} +
+
+
+
+ {% render_table table 'inc/table.html' %} + {% include 'inc/paginator.html' with paginator=table.paginator page=table.page %} +
+
+
+
+{% endblock %}