Merge pull request #1026 from tcely/patch-6

Add a start link to scheduled tasks
This commit is contained in:
meeb
2025-05-09 00:43:20 +10:00
committed by GitHub
5 changed files with 156 additions and 11 deletions

View File

@@ -44,10 +44,19 @@ class ResetTasksForm(forms.Form):
pass
class ScheduleTaskForm(forms.Form):
when = forms.DateTimeField(
label=_('When the task should run'),
required=True,
)
class ConfirmDeleteMediaServerForm(forms.Form):
pass
_media_server_type_label = 'Jellyfin'
class JellyfinMediaServerForm(forms.Form):

View File

@@ -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 %}

View File

@@ -43,11 +43,16 @@
</p>
<div class="collection">
{% for task in errors %}
<a href="{% url task.url pk=task.instance.pk %}" class="collection-item error-text">
<i class="fas fa-exclamation-triangle"></i> <strong>{{ task }}</strong>, attempted {{ task.attempts }} time{{ task.attempts|pluralize }}<br>
Error: &quot;{{ task.error_message }}&quot;<br>
<div class="collection-item error-text">
<a href="{% url task.url pk=task.instance.pk %}" class="error-text">
<i class="fas fa-exclamation-triangle"></i> <strong>{{ task }}</strong>, attempted {{ task.attempts }} time{{ task.attempts|pluralize }}<br>
Error: &quot;{{ task.error_message }}&quot;<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>
</a>
<a href="{% url 'sync:run-task' pk=task.pk %}" class="error-text">
<i class="fas fa-undo"></i>
</a>
</div>
{% empty %}
<span class="collection-item no-items"><i class="fas fa-info-circle"></i> There are no tasks with errors on this page.</span>
{% endfor %}
@@ -66,11 +71,17 @@
</p>
<div class="collection">
{% for task in scheduled %}
<a href="{% url task.url pk=task.instance.pk %}" class="collection-item">
<i class="fas fa-stopwatch"></i> <strong>{{ task }}</strong><br>
{% 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-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 %}
</a>
<div class="collection-item">
<a href="{% url task.url pk=task.instance.pk %}">
<i class="fas fa-hourglass-start"></i> <strong>{{ task }}</strong><br>
{% if task.instance.index_schedule and task.repeat > 0 %}Scheduled to run {{ task.instance.get_index_schedule_display|lower }}.<br>{% endif %}
<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 %}
<span class="collection-item no-items"><i class="fas fa-info-circle"></i> There are no scheduled tasks on this page.</span>
{% endfor %}

View File

@@ -3,7 +3,7 @@ from .views import (DashboardView, SourcesView, ValidateSourceView, AddSourceVie
SourceView, UpdateSourceView, DeleteSourceView, MediaView,
MediaThumbView, MediaItemView, MediaRedownloadView, MediaSkipView,
MediaEnableView, MediaContent, TasksView, CompletedTasksView, ResetTasks,
MediaServersView, AddMediaServerView, MediaServerView,
TaskScheduleView, MediaServersView, AddMediaServerView, MediaServerView,
DeleteMediaServerView, UpdateMediaServerView)
@@ -122,6 +122,18 @@ urlpatterns = [
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(
'tasks-completed',
CompletedTasksView.as_view(),

View File

@@ -20,11 +20,12 @@ from django.utils.text import slugify
from django.utils._os import safe_join
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from common.timestamp import timestamp_to_datetime
from common.utils import append_uri_params
from background_task.models import Task, CompletedTask
from .models import Source, Media, MediaServer
from .forms import (ValidateSourceForm, ConfirmDeleteSourceForm, RedownloadMediaForm,
SkipMediaForm, EnableMediaForm, ResetTasksForm,
SkipMediaForm, EnableMediaForm, ResetTasksForm, ScheduleTaskForm,
ConfirmDeleteMediaServerForm)
from .utils import validate_url, delete_file, multi_key_sort, mkdir_p
from .tasks import (map_task_to_instance, get_error_message,
@@ -1005,6 +1006,84 @@ class ResetTasks(FormView):
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):
'''
List of media servers which have been added.