mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-25 01:48:38 -06:00
Work on job scheduling:
* Added JobResult form filtersets * Change housekeeping cleanup delete from `_raw_delete` to `delete` to make sure scheduled tasks are cancelled * Change default sort of JobResult table to -created * Added `delete` override to `JobResult` to remove scheduled tasks from RQ when a JobResult is deleted * Updated js/css dist files. Will need to be redone when develop is merged to feature.
This commit is contained in:
parent
53c8a48244
commit
679a9e839b
@ -141,7 +141,7 @@ class LogLevelChoices(ChoiceSet):
|
|||||||
class JobResultStatusChoices(ChoiceSet):
|
class JobResultStatusChoices(ChoiceSet):
|
||||||
|
|
||||||
STATUS_PENDING = 'pending'
|
STATUS_PENDING = 'pending'
|
||||||
STATUS_SCHEDULED = 'pending'
|
STATUS_SCHEDULED = 'scheduled'
|
||||||
STATUS_RUNNING = 'running'
|
STATUS_RUNNING = 'running'
|
||||||
STATUS_COMPLETED = 'completed'
|
STATUS_COMPLETED = 'completed'
|
||||||
STATUS_ERRORED = 'errored'
|
STATUS_ERRORED = 'errored'
|
||||||
@ -149,7 +149,7 @@ class JobResultStatusChoices(ChoiceSet):
|
|||||||
|
|
||||||
CHOICES = (
|
CHOICES = (
|
||||||
(STATUS_PENDING, 'Pending'),
|
(STATUS_PENDING, 'Pending'),
|
||||||
(STATUS_SCHEDULED, 'Pending'),
|
(STATUS_SCHEDULED, 'Scheduled'),
|
||||||
(STATUS_RUNNING, 'Running'),
|
(STATUS_RUNNING, 'Running'),
|
||||||
(STATUS_COMPLETED, 'Completed'),
|
(STATUS_COMPLETED, 'Completed'),
|
||||||
(STATUS_ERRORED, 'Errored'),
|
(STATUS_ERRORED, 'Errored'),
|
||||||
|
@ -87,6 +87,7 @@ class CustomFieldFilterSet(BaseFilterSet):
|
|||||||
Q(description__icontains=value)
|
Q(description__icontains=value)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class CustomLinkFilterSet(BaseFilterSet):
|
class CustomLinkFilterSet(BaseFilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
@ -434,8 +435,8 @@ class JobResultFilterSet(BaseFilterSet):
|
|||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
)
|
)
|
||||||
created = django_filters.DateTimeFilter()
|
created = django_filters.DateTimeFromToRangeFilter()
|
||||||
completed = django_filters.DateTimeFilter()
|
completed = django_filters.DateTimeFromToRangeFilter()
|
||||||
status = django_filters.MultipleChoiceFilter(
|
status = django_filters.MultipleChoiceFilter(
|
||||||
choices=JobResultStatusChoices,
|
choices=JobResultStatusChoices,
|
||||||
null_value=None
|
null_value=None
|
||||||
|
@ -69,7 +69,43 @@ class CustomFieldFilterForm(FilterForm):
|
|||||||
class JobResultFilterForm(FilterForm):
|
class JobResultFilterForm(FilterForm):
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q',)),
|
(None, ('q',)),
|
||||||
#('Attributes', ('type', 'content_type_id', 'group_name', 'weight', 'required', 'ui_visibility')),
|
('Attributes', ('obj_type', 'status')),
|
||||||
|
('Creation', ('created_before', 'created_after', 'completed_before', 'completed_after', 'user')),
|
||||||
|
)
|
||||||
|
|
||||||
|
obj_type = ContentTypeChoiceField(
|
||||||
|
label=_('Object Type'),
|
||||||
|
queryset=ContentType.objects.all(),
|
||||||
|
limit_choices_to=FeatureQuery('job_results'), # TODO: This doesn't actually work
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
status = MultipleChoiceField(
|
||||||
|
choices=JobResultStatusChoices,
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
created_after = forms.DateTimeField(
|
||||||
|
required=False,
|
||||||
|
widget=DateTimePicker()
|
||||||
|
)
|
||||||
|
created_before = forms.DateTimeField(
|
||||||
|
required=False,
|
||||||
|
widget=DateTimePicker()
|
||||||
|
)
|
||||||
|
completed_after = forms.DateTimeField(
|
||||||
|
required=False,
|
||||||
|
widget=DateTimePicker()
|
||||||
|
)
|
||||||
|
completed_before = forms.DateTimeField(
|
||||||
|
required=False,
|
||||||
|
widget=DateTimePicker()
|
||||||
|
)
|
||||||
|
user = DynamicModelMultipleChoiceField(
|
||||||
|
queryset=User.objects.all(),
|
||||||
|
required=False,
|
||||||
|
label=_('User'),
|
||||||
|
widget=APISelectMultiple(
|
||||||
|
api_url='/api/users/users/',
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,4 +13,4 @@ class ReportForm(BootstrapMixin, forms.Form):
|
|||||||
widget=DateTimePicker(),
|
widget=DateTimePicker(),
|
||||||
label="Schedule at",
|
label="Schedule at",
|
||||||
help_text="Schedule execution of report to a set time",
|
help_text="Schedule execution of report to a set time",
|
||||||
)
|
)
|
||||||
|
@ -81,7 +81,7 @@ class Command(BaseCommand):
|
|||||||
ending=""
|
ending=""
|
||||||
)
|
)
|
||||||
self.stdout.flush()
|
self.stdout.flush()
|
||||||
JobResult.objects.filter(created__lt=cutoff)._raw_delete(using=DEFAULT_DB_ALIAS)
|
JobResult.objects.filter(created__lt=cutoff).delete(using=DEFAULT_DB_ALIAS)
|
||||||
if options['verbosity']:
|
if options['verbosity']:
|
||||||
self.stdout.write("Done.", self.style.SUCCESS)
|
self.stdout.write("Done.", self.style.SUCCESS)
|
||||||
elif options['verbosity']:
|
elif options['verbosity']:
|
||||||
|
17
netbox/extras/migrations/0079_change_jobresult_order.py
Normal file
17
netbox/extras/migrations/0079_change_jobresult_order.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# Generated by Django 4.1.1 on 2022-10-09 18:37
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('extras', '0078_unique_constraints'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='jobresult',
|
||||||
|
options={'ordering': ['-created']},
|
||||||
|
),
|
||||||
|
]
|
@ -528,13 +528,22 @@ class JobResult(models.Model):
|
|||||||
objects = RestrictedQuerySet.as_manager()
|
objects = RestrictedQuerySet.as_manager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['obj_type', 'name', '-created']
|
ordering = ['-created']
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.job_id)
|
return str(self.job_id)
|
||||||
|
|
||||||
|
def delete(self, *args, **kwargs):
|
||||||
|
queue = django_rq.get_queue("default")
|
||||||
|
job = queue.fetch_job(str(self.job_id))
|
||||||
|
|
||||||
|
if job:
|
||||||
|
job.cancel()
|
||||||
|
|
||||||
|
return super().delete(*args, **kwargs)
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('extras:jobresult', args=[self.pk])
|
return reverse(f'extras:{self.obj_type.name}_result', args=[self.pk])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def duration(self):
|
def duration(self):
|
||||||
@ -579,7 +588,7 @@ class JobResult(models.Model):
|
|||||||
if schedule_at := kwargs.pop("schedule_at", None):
|
if schedule_at := kwargs.pop("schedule_at", None):
|
||||||
job_result.status = JobResultStatusChoices.STATUS_SCHEDULED
|
job_result.status = JobResultStatusChoices.STATUS_SCHEDULED
|
||||||
job_result.save()
|
job_result.save()
|
||||||
|
|
||||||
queue.enqueue_at(schedule_at, func, job_id=str(job_result.job_id), job_result=job_result, **kwargs)
|
queue.enqueue_at(schedule_at, func, job_id=str(job_result.job_id), job_result=job_result, **kwargs)
|
||||||
else:
|
else:
|
||||||
queue.enqueue(func, job_id=str(job_result.job_id), job_result=job_result, **kwargs)
|
queue.enqueue(func, job_id=str(job_result.job_id), job_result=job_result, **kwargs)
|
||||||
|
@ -56,9 +56,9 @@ class JobResultTable(NetBoxTable):
|
|||||||
class Meta(NetBoxTable.Meta):
|
class Meta(NetBoxTable.Meta):
|
||||||
model = JobResult
|
model = JobResult
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'id', 'name', 'obj_type', 'created', 'completed', 'user', 'status', 'job_id',
|
'pk', 'id', 'name', 'obj_type', 'job_id', 'created', 'completed', 'user', 'status',
|
||||||
)
|
)
|
||||||
default_columns = ('pk', 'id', 'name', 'obj_type', 'created', 'completed', 'user', 'status', 'job_id')
|
default_columns = ('pk', 'id', 'name', 'obj_type', 'status', 'created', 'completed', 'user',)
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -76,7 +76,6 @@ urlpatterns = [
|
|||||||
|
|
||||||
# Job results
|
# Job results
|
||||||
path('job-results/', views.JobResultListView.as_view(), name='jobresult_list'),
|
path('job-results/', views.JobResultListView.as_view(), name='jobresult_list'),
|
||||||
path('job-results/<int:pk>/', views.JobResultView.as_view(), name='jobresult'),
|
|
||||||
path('job-results/delete/', views.JobResultBulkDeleteView.as_view(), name='jobresult_bulk_delete'),
|
path('job-results/delete/', views.JobResultBulkDeleteView.as_view(), name='jobresult_bulk_delete'),
|
||||||
path('job-results/<int:pk>/delete/', views.JobResultDeleteView.as_view(), name='jobresult_delete'),
|
path('job-results/<int:pk>/delete/', views.JobResultDeleteView.as_view(), name='jobresult_delete'),
|
||||||
|
|
||||||
|
@ -815,6 +815,7 @@ class JobResultListView(generic.ObjectListView):
|
|||||||
table = tables.JobResultTable
|
table = tables.JobResultTable
|
||||||
actions = ('delete', 'bulk_delete', )
|
actions = ('delete', 'bulk_delete', )
|
||||||
|
|
||||||
|
|
||||||
class JobResultDeleteView(generic.ObjectDeleteView):
|
class JobResultDeleteView(generic.ObjectDeleteView):
|
||||||
queryset = JobResult.objects.all()
|
queryset = JobResult.objects.all()
|
||||||
|
|
||||||
@ -822,4 +823,4 @@ class JobResultDeleteView(generic.ObjectDeleteView):
|
|||||||
class JobResultBulkDeleteView(generic.BulkDeleteView):
|
class JobResultBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = JobResult.objects.all()
|
queryset = JobResult.objects.all()
|
||||||
filterset = filtersets.JobResultFilterSet
|
filterset = filtersets.JobResultFilterSet
|
||||||
table = tables.JobResultTable
|
table = tables.JobResultTable
|
||||||
|
BIN
netbox/project-static/dist/netbox-external.css
vendored
BIN
netbox/project-static/dist/netbox-external.css
vendored
Binary file not shown.
BIN
netbox/project-static/dist/netbox.js
vendored
BIN
netbox/project-static/dist/netbox.js
vendored
Binary file not shown.
BIN
netbox/project-static/dist/netbox.js.map
vendored
BIN
netbox/project-static/dist/netbox.js.map
vendored
Binary file not shown.
@ -1,60 +0,0 @@
|
|||||||
{% extends 'generic/object.html' %}
|
|
||||||
{% load helpers %}
|
|
||||||
{% load plugins %}
|
|
||||||
|
|
||||||
{% block title %}{{ object.name }} ({{object.job_id}}){% endblock %}
|
|
||||||
|
|
||||||
{# JobResult does not support add/edit controls #}
|
|
||||||
{% block controls %}{% endblock %}
|
|
||||||
{% block subtitle %}{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="row">
|
|
||||||
<div class="col col-md-6">
|
|
||||||
<div class="card">
|
|
||||||
<h5 class="card-header">
|
|
||||||
Tag
|
|
||||||
</h5>
|
|
||||||
<div class="card-body">
|
|
||||||
<table class="table table-hover panel-body attr-table">
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Name</th>
|
|
||||||
<td>
|
|
||||||
{{ object.name }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Created</th>
|
|
||||||
<td>
|
|
||||||
{{ object.created|annotated_date }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Completed</th>
|
|
||||||
<td>
|
|
||||||
{{ object.completed|annotated_date }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col col-md-6">
|
|
||||||
<div class="card">
|
|
||||||
<h5 class="card-header">
|
|
||||||
TODO
|
|
||||||
</h5>
|
|
||||||
<div class="card-body">
|
|
||||||
<table class="table table-hover panel-body attr-table">
|
|
||||||
TODO
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col col-md-12">
|
|
||||||
{% plugin_full_width_page object %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
Loading…
Reference in New Issue
Block a user