diff --git a/netbox/extras/choices.py b/netbox/extras/choices.py index 123fd2cd4..fe0f0eed5 100644 --- a/netbox/extras/choices.py +++ b/netbox/extras/choices.py @@ -139,6 +139,7 @@ class LogLevelChoices(ChoiceSet): class JobResultStatusChoices(ChoiceSet): STATUS_PENDING = 'pending' + STATUS_SCHEDULED = 'pending' STATUS_RUNNING = 'running' STATUS_COMPLETED = 'completed' STATUS_ERRORED = 'errored' @@ -146,6 +147,7 @@ class JobResultStatusChoices(ChoiceSet): CHOICES = ( (STATUS_PENDING, 'Pending'), + (STATUS_SCHEDULED, 'Pending'), (STATUS_RUNNING, 'Running'), (STATUS_COMPLETED, 'Completed'), (STATUS_ERRORED, 'Errored'), diff --git a/netbox/extras/filtersets.py b/netbox/extras/filtersets.py index df0af3541..25f1a04c9 100644 --- a/netbox/extras/filtersets.py +++ b/netbox/extras/filtersets.py @@ -16,6 +16,7 @@ __all__ = ( 'ConfigContextFilterSet', 'ContentTypeFilterSet', 'CustomFieldFilterSet', + 'JobResultFilterSet', 'CustomLinkFilterSet', 'ExportTemplateFilterSet', 'ImageAttachmentFilterSet', @@ -86,6 +87,26 @@ class CustomFieldFilterSet(BaseFilterSet): Q(description__icontains=value) ) +class JobResultFilterSet(BaseFilterSet): + q = django_filters.CharFilter( + method='search', + label='Search', + ) + + # TODO: Add filters + + class Meta: + model = JobResult + fields = [ + 'id', 'name', 'obj_type', 'created', 'completed', 'user', 'status' + ] + + def search(self, queryset, name, value): + if not value.strip(): + return queryset + return queryset.filter( + Q(name__icontains=value) + ) class CustomLinkFilterSet(BaseFilterSet): q = django_filters.CharFilter( diff --git a/netbox/extras/forms/filtersets.py b/netbox/extras/forms/filtersets.py index 526d47013..2e715d9a7 100644 --- a/netbox/extras/forms/filtersets.py +++ b/netbox/extras/forms/filtersets.py @@ -19,6 +19,7 @@ from virtualization.models import Cluster, ClusterGroup, ClusterType __all__ = ( 'ConfigContextFilterForm', 'CustomFieldFilterForm', + 'JobResultFilterForm', 'CustomLinkFilterForm', 'ExportTemplateFilterForm', 'JournalEntryFilterForm', @@ -65,6 +66,13 @@ class CustomFieldFilterForm(FilterForm): ) +class JobResultFilterForm(FilterForm): + fieldsets = ( + (None, ('q',)), + #('Attributes', ('type', 'content_type_id', 'group_name', 'weight', 'required', 'ui_visibility')), + ) + + class CustomLinkFilterForm(FilterForm): fieldsets = ( (None, ('q',)), diff --git a/netbox/extras/models/models.py b/netbox/extras/models/models.py index 100766d53..574b51f70 100644 --- a/netbox/extras/models/models.py +++ b/netbox/extras/models/models.py @@ -509,12 +509,18 @@ class JobResult(models.Model): unique=True ) + objects = RestrictedQuerySet.as_manager() + class Meta: ordering = ['obj_type', 'name', '-created'] def __str__(self): return str(self.job_id) + def get_absolute_url(self): + # TODO: Fix this to point the right place + return reverse('virtualization:clustertype', args=[self.pk]) + @property def duration(self): if not self.completed: @@ -546,7 +552,7 @@ class JobResult(models.Model): args: additional args passed to the callable kwargs: additional kargs passed to the callable """ - job_result = cls.objects.create( + job_result: JobResult = cls.objects.create( name=name, obj_type=obj_type, user=user, @@ -556,6 +562,9 @@ class JobResult(models.Model): queue = django_rq.get_queue("default") if schedule_at := kwargs.pop("schedule_at", None): + job_result.status = JobResultStatusChoices.STATUS_SCHEDULED + job_result.save() + queue.enqueue_at(schedule_at, func, job_id=str(job_result.job_id), job_result=job_result, **kwargs) else: queue.enqueue(func, job_id=str(job_result.job_id), job_result=job_result, **kwargs) diff --git a/netbox/extras/tables/tables.py b/netbox/extras/tables/tables.py index 1df5c9487..60e500da6 100644 --- a/netbox/extras/tables/tables.py +++ b/netbox/extras/tables/tables.py @@ -8,6 +8,7 @@ from .template_code import * __all__ = ( 'ConfigContextTable', 'CustomFieldTable', + 'JobResultTable', 'CustomLinkTable', 'ExportTemplateTable', 'JournalEntryTable', @@ -39,6 +40,30 @@ class CustomFieldTable(NetBoxTable): default_columns = ('pk', 'name', 'content_types', 'label', 'group_name', 'type', 'required', 'description') +# +# Custom fields +# + +class JobResultTable(NetBoxTable): + name = tables.Column( + linkify=True + ) + #obj_type = columns.ContentTypesColumn() + required = columns.BooleanColumn() + ui_visibility = columns.ChoiceFieldColumn(verbose_name="UI visibility") + + actions = columns.ActionsColumn( + actions=() # TODO: Delete + ) + + class Meta(NetBoxTable.Meta): + model = JobResult + fields = ( + 'pk', 'id', 'name', 'obj_type', 'created', 'completed', 'user', 'status', 'job_id', + ) + default_columns = ('pk', 'id', 'name', 'obj_type', 'created', 'completed', 'user', 'status', 'job_id') + + # # Custom links # diff --git a/netbox/extras/urls.py b/netbox/extras/urls.py index ced3bd4b9..66d60133f 100644 --- a/netbox/extras/urls.py +++ b/netbox/extras/urls.py @@ -103,6 +103,10 @@ urlpatterns = [ path('reports/results//', views.ReportResultView.as_view(), name='report_result'), re_path(r'^reports/(?P.([^.]+)).(?P.(.+))/', views.ReportView.as_view(), name='report'), + # Job results + path('job-results/', views.JobResultListView.as_view(), name='jobresult_view'), + # path('custom-fields//', views.CustomFieldView.as_view(), name='customfield'), + # Scripts path('scripts/', views.ScriptListView.as_view(), name='script_list'), path('scripts/results//', views.ScriptResultView.as_view(), name='script_result'), diff --git a/netbox/extras/views.py b/netbox/extras/views.py index 21bffdff3..bf122955e 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -775,3 +775,14 @@ class ScriptResultView(ContentTypePermissionRequiredMixin, GetScriptMixin, View) 'result': result, 'class_name': script.__class__.__name__ }) + + +# +# Job results +# + +class JobResultListView(generic.ObjectListView): + queryset = JobResult.objects.all() + filterset = filtersets.JobResultFilterSet + filterset_form = forms.JobResultFilterForm + table = tables.JobResultTable \ No newline at end of file