Merge pull request #1026 from tcely/patch-6
Add a start link to scheduled tasks
This commit is contained in:
5 files changed
+156
-11
No files matched your search
@@ -44,10 +44,19 @@ class ResetTasksForm(forms.Form):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ScheduleTaskForm(forms.Form):
|
||||||
|
|
||||||
|
when = forms.DateTimeField(
|
||||||
|
label=_('When the task should run'),
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ConfirmDeleteMediaServerForm(forms.Form):
|
class ConfirmDeleteMediaServerForm(forms.Form):
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
_media_server_type_label = 'Jellyfin'
|
_media_server_type_label = 'Jellyfin'
|
||||||
class JellyfinMediaServerForm(forms.Form):
|
class JellyfinMediaServerForm(forms.Form):
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block headtitle %}Schedule task{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row no-margin-bottom">
|
||||||
|
<div class="col s12">
|
||||||
|
<h1>Schedule task</h1>
|
||||||
|
<p>
|
||||||
|
If you don't want to wait for the existing schedule to be triggered,
|
||||||
|
you can use this to change when the task will be scheduled to run.
|
||||||
|
It is not guaranteed to run at any exact time, because when a task
|
||||||
|
requests to run and when a slot to execute it, in the appropriate
|
||||||
|
queue and with the priority level assigned, is dependent on how long
|
||||||
|
other tasks are taking to complete the assigned work.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
This will change the time that the task is requesting to be run
|
||||||
|
to the current time, plus some number of seconds.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<form method="post" action="{% url 'sync:run-task' pk=task.pk %}" class="col s12 simpleform">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% include 'simpleform.html' with form=form %}
|
||||||
|
<div class="row no-margin-bottom padding-top">
|
||||||
|
<div class="col s12">
|
||||||
|
<button class="btn" type="submit" name="action">Really schedule task <i class="far fa-clock"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -43,11 +43,16 @@
|
|||||||
</p>
|
</p>
|
||||||
<div class="collection">
|
<div class="collection">
|
||||||
{% for task in errors %}
|
{% for task in errors %}
|
||||||
<a href="{% url task.url pk=task.instance.pk %}" class="collection-item error-text">
|
<div class="collection-item error-text">
|
||||||
<i class="fas fa-exclamation-triangle"></i> <strong>{{ task }}</strong>, attempted {{ task.attempts }} time{{ task.attempts|pluralize }}<br>
|
<a href="{% url task.url pk=task.instance.pk %}" class="error-text">
|
||||||
Error: "{{ task.error_message }}"<br>
|
<i class="fas fa-exclamation-triangle"></i> <strong>{{ task }}</strong>, attempted {{ task.attempts }} time{{ task.attempts|pluralize }}<br>
|
||||||
|
Error: "{{ task.error_message }}"<br>
|
||||||
|
</a>
|
||||||
<i class="fas fa-history"></i> Task will be retried at <strong>{{ task.run_at|date:'Y-m-d H:i:s' }}</strong>
|
<i class="fas fa-history"></i> Task will be retried at <strong>{{ task.run_at|date:'Y-m-d H:i:s' }}</strong>
|
||||||
</a>
|
<a href="{% url 'sync:run-task' pk=task.pk %}" class="error-text">
|
||||||
|
<i class="fas fa-undo"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<span class="collection-item no-items"><i class="fas fa-info-circle"></i> There are no tasks with errors on this page.</span>
|
<span class="collection-item no-items"><i class="fas fa-info-circle"></i> There are no tasks with errors on this page.</span>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -66,11 +71,17 @@
|
|||||||
</p>
|
</p>
|
||||||
<div class="collection">
|
<div class="collection">
|
||||||
{% for task in scheduled %}
|
{% for task in scheduled %}
|
||||||
<a href="{% url task.url pk=task.instance.pk %}" class="collection-item">
|
<div class="collection-item">
|
||||||
<i class="fas fa-stopwatch"></i> <strong>{{ task }}</strong><br>
|
<a href="{% url task.url pk=task.instance.pk %}">
|
||||||
{% if task.instance.index_schedule and task.repeat > 0 %}Scheduled to run {{ task.instance.get_index_schedule_display|lower }}.<br>{% endif %}
|
<i class="fas fa-hourglass-start"></i> <strong>{{ task }}</strong><br>
|
||||||
<i class="fas fa-redo"></i> Task will run {% if task.run_now %}<strong>immediately</strong>{% else %}at <strong>{{ task.run_at|date:'Y-m-d H:i:s' }}</strong>{% endif %}
|
{% if task.instance.index_schedule and task.repeat > 0 %}Scheduled to run {{ task.instance.get_index_schedule_display|lower }}.<br>{% endif %}
|
||||||
</a>
|
<i class="far fa-clock"></i> Task will run {% if task.run_now %}<strong>immediately</strong>{% else %}at <strong>{{ task.run_at|date:'Y-m-d H:i:s' }}</strong>
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'sync:run-task' pk=task.pk %}">
|
||||||
|
<i class="far fa-play-circle"></i>
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<span class="collection-item no-items"><i class="fas fa-info-circle"></i> There are no scheduled tasks on this page.</span>
|
<span class="collection-item no-items"><i class="fas fa-info-circle"></i> There are no scheduled tasks on this page.</span>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
+13
-1
@@ -3,7 +3,7 @@ from .views import (DashboardView, SourcesView, ValidateSourceView, AddSourceVie
|
|||||||
SourceView, UpdateSourceView, DeleteSourceView, MediaView,
|
SourceView, UpdateSourceView, DeleteSourceView, MediaView,
|
||||||
MediaThumbView, MediaItemView, MediaRedownloadView, MediaSkipView,
|
MediaThumbView, MediaItemView, MediaRedownloadView, MediaSkipView,
|
||||||
MediaEnableView, MediaContent, TasksView, CompletedTasksView, ResetTasks,
|
MediaEnableView, MediaContent, TasksView, CompletedTasksView, ResetTasks,
|
||||||
MediaServersView, AddMediaServerView, MediaServerView,
|
TaskScheduleView, MediaServersView, AddMediaServerView, MediaServerView,
|
||||||
DeleteMediaServerView, UpdateMediaServerView)
|
DeleteMediaServerView, UpdateMediaServerView)
|
||||||
|
|
||||||
|
|
||||||
@@ -122,6 +122,18 @@ urlpatterns = [
|
|||||||
name='tasks',
|
name='tasks',
|
||||||
),
|
),
|
||||||
|
|
||||||
|
path(
|
||||||
|
'task/<int:pk>/schedule/now',
|
||||||
|
TaskScheduleView.as_view(),
|
||||||
|
name='run-task',
|
||||||
|
),
|
||||||
|
|
||||||
|
path(
|
||||||
|
'task/<int:pk>/schedule/<int:timestamp>',
|
||||||
|
TaskScheduleView.as_view(),
|
||||||
|
name='schedule-task',
|
||||||
|
),
|
||||||
|
|
||||||
path(
|
path(
|
||||||
'tasks-completed',
|
'tasks-completed',
|
||||||
CompletedTasksView.as_view(),
|
CompletedTasksView.as_view(),
|
||||||
|
|||||||
+80
-1
@@ -20,11 +20,12 @@ from django.utils.text import slugify
|
|||||||
from django.utils._os import safe_join
|
from django.utils._os import safe_join
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from common.timestamp import timestamp_to_datetime
|
||||||
from common.utils import append_uri_params
|
from common.utils import append_uri_params
|
||||||
from background_task.models import Task, CompletedTask
|
from background_task.models import Task, CompletedTask
|
||||||
from .models import Source, Media, MediaServer
|
from .models import Source, Media, MediaServer
|
||||||
from .forms import (ValidateSourceForm, ConfirmDeleteSourceForm, RedownloadMediaForm,
|
from .forms import (ValidateSourceForm, ConfirmDeleteSourceForm, RedownloadMediaForm,
|
||||||
SkipMediaForm, EnableMediaForm, ResetTasksForm,
|
SkipMediaForm, EnableMediaForm, ResetTasksForm, ScheduleTaskForm,
|
||||||
ConfirmDeleteMediaServerForm)
|
ConfirmDeleteMediaServerForm)
|
||||||
from .utils import validate_url, delete_file, multi_key_sort, mkdir_p
|
from .utils import validate_url, delete_file, multi_key_sort, mkdir_p
|
||||||
from .tasks import (map_task_to_instance, get_error_message,
|
from .tasks import (map_task_to_instance, get_error_message,
|
||||||
@@ -1005,6 +1006,84 @@ class ResetTasks(FormView):
|
|||||||
return append_uri_params(url, {'message': 'reset'})
|
return append_uri_params(url, {'message': 'reset'})
|
||||||
|
|
||||||
|
|
||||||
|
class TaskScheduleView(FormView, SingleObjectMixin):
|
||||||
|
'''
|
||||||
|
Confirm that the task should be re-scheduled.
|
||||||
|
'''
|
||||||
|
|
||||||
|
template_name = 'sync/task-schedule.html'
|
||||||
|
form_class = ScheduleTaskForm
|
||||||
|
model = Task
|
||||||
|
errors = dict(
|
||||||
|
invalid_when=_('The type ({}) was incorrect.'),
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.object = None
|
||||||
|
self.timestamp = None
|
||||||
|
self.when = None
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
self.object = self.get_object()
|
||||||
|
self.timestamp = kwargs.get('timestamp', '')
|
||||||
|
try:
|
||||||
|
self.timestamp = int(self.timestamp, 10)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
self.timestamp = None
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
self.when = timestamp_to_datetime(self.timestamp)
|
||||||
|
except AssertionError:
|
||||||
|
self.when = None
|
||||||
|
if self.when is None:
|
||||||
|
self.when = timezone.now()
|
||||||
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def get_initial(self):
|
||||||
|
initial = super().get_initial()
|
||||||
|
initial['when'] = self.when
|
||||||
|
return initial
|
||||||
|
|
||||||
|
def get_context_data(self, *args, **kwargs):
|
||||||
|
data = super().get_context_data(*args, **kwargs)
|
||||||
|
data['when'] = self.when
|
||||||
|
return data
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return append_uri_params(
|
||||||
|
reverse_lazy('sync:tasks'),
|
||||||
|
dict(
|
||||||
|
message='scheduled',
|
||||||
|
pk=str(self.object.pk),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
max_attempts = getattr(settings, 'MAX_ATTEMPTS', 15)
|
||||||
|
now = timezone.now()
|
||||||
|
when = form.cleaned_data.get('when')
|
||||||
|
|
||||||
|
if not isinstance(when, now.__class__):
|
||||||
|
form.add_error(
|
||||||
|
'when',
|
||||||
|
ValidationError(
|
||||||
|
errors['invalid_when'].format(
|
||||||
|
type(when),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
if form.errors:
|
||||||
|
return super().form_invalid(form)
|
||||||
|
|
||||||
|
self.object.attempts = max_attempts // 2
|
||||||
|
self.object.run_at = when
|
||||||
|
self.object.save()
|
||||||
|
|
||||||
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
class MediaServersView(ListView):
|
class MediaServersView(ListView):
|
||||||
'''
|
'''
|
||||||
List of media servers which have been added.
|
List of media servers which have been added.
|
||||||
|
|||||||
Reference in new issue
Block a user