diff --git a/django_q/cluster.py b/django_q/cluster.py index 2a3ec1c..364e1a7 100644 --- a/django_q/cluster.py +++ b/django_q/cluster.py @@ -442,14 +442,21 @@ def scheduler(list_key=Conf.Q_LIST): next_run = next_run.replace(years=+1) s.next_run = next_run.datetime s.repeats += -1 - else: - s.repeats = 0 # send it to the cluster kwargs['list_key'] = list_key s.task = tasks.async(s.func, *args, **kwargs) + # log it if not s.task: logger.error(_('{} failed to create a task from schedule {} [{}]').format(current_process().name, s.id), s.func) else: logger.info(_('{} created a task from schedule {} [{}]').format(current_process().name, s.id, s.func)) + # default behavior is to delete a ONCE schedule + if s.schedule_type == s.ONCE: + if s.repeats < 0: + s.delete() + return + # but not if it has a positive repeats + s.repeats = 0 + # save the schedule s.save() diff --git a/django_q/tests/test_scheduler.py b/django_q/tests/test_scheduler.py index 2a9473b..95e90c4 100644 --- a/django_q/tests/test_scheduler.py +++ b/django_q/tests/test_scheduler.py @@ -1,6 +1,8 @@ from multiprocessing import Queue, Event, Value import pytest +import arrow +from django.utils import timezone from django_q.conf import redis_client from django_q.cluster import pusher, worker, monitor, scheduler @@ -45,10 +47,30 @@ def test_scheduler(r): assert schedule.repeats == 0 assert schedule.last_run() is not None assert schedule.success() is True + assert schedule.next_run < arrow.get(timezone.now()).replace(hours=+1) task = fetch(schedule.task) assert task is not None assert task.success is True assert task.result < 0 + # Once schedule with delete + once_schedule = create_schedule('django_q.tests.tasks.word_multiply', + 2, + word='django', + schedule_type=Schedule.ONCE, + repeats=-1, + hook='django_q.tests.tasks.result' + ) + assert hasattr(once_schedule, 'pk') is True + # negative repeats + always_schedule = create_schedule('django_q.tests.tasks.word_multiply', + 2, + word='django', + schedule_type=Schedule.DAILY, + repeats=-1, + hook='django_q.tests.tasks.result' + ) + assert hasattr(always_schedule, 'pk') is True + # All other types for t in Schedule.TYPE: schedule = create_schedule('django_q.tests.tasks.word_multiply', 2, @@ -59,4 +81,7 @@ def test_scheduler(r): ) assert schedule is not None assert schedule.last_run() is None - scheduler() + scheduler(list_key=list_key) + scheduler(list_key=list_key) + # ONCE schedule should be deleted + assert Schedule.objects.filter(pk=once_schedule.pk).exists() is False diff --git a/docs/admin.rst b/docs/admin.rst index 9fc7964..329875d 100644 --- a/docs/admin.rst +++ b/docs/admin.rst @@ -41,12 +41,14 @@ In this case you would set the schedule type to :attr:`Schedule.HOURLY` and the When you set repeats to `-1` the schedule will continue indefinitely and the repeats will still count down. This can be used as an indicator of how many times the schedule has been executed. -An exception to this are schedules of type :attr:`Schedule.ONCE`. Repeats are ignored by this schedule type and it will always reset it zero after execution. +An exception to this are schedules of type :attr:`Schedule.ONCE`. Negative repeats for this schedule type will cause it to be deleted from the database. +This behavior is useful if you have many delayed actions which you do not necessarily need a result for. A positive number will keep the ONCE schedule, but it will not run again. .. note:: To run a `Once` schedule again, change the repeats to something other than `0`. Set a new run time before you do this or let it execute immediately. + Next run ~~~~~~~~ diff --git a/docs/schedules.rst b/docs/schedules.rst index 576f391..abca5fd 100644 --- a/docs/schedules.rst +++ b/docs/schedules.rst @@ -116,7 +116,9 @@ Reference .. py:attribute:: ONCE - `'O'` the schedule will only run once. Repeats are ignored and set to `0` after execution. + `'O'` the schedule will only run once. + If it has a negative :attr:`repeats` it will be deleted after it has run. + If you want to keep the result, set :attr:`repeats` to a positive number. .. py:attribute:: HOURLY