Skip to content

Commit

Permalink
SIANXKE-342: Deactivate periodic task after end time
Browse files Browse the repository at this point in the history
  • Loading branch information
galipnik authored and Gabriel Lipnik committed Oct 29, 2023
1 parent 27ebaad commit 995af86
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def handle_tick(self):
p_task.max_number_of_executions is not None
and self.number_of_corresponding_single_tasks(p_task)
>= p_task.max_number_of_executions
):
) or (p_task.end_time is not None and p_task.end_time < dt):
p_task.is_active = False
break

Expand Down
30 changes: 30 additions & 0 deletions django_future_tasks/migrations/0006_periodicfuturetask_end_time.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Generated by Django 3.2.22 on 2023-10-20 15:42

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("django_future_tasks", "0005_periodicfuturetask_max_number_of_executions"),
]

operations = [
migrations.AddField(
model_name="periodicfuturetask",
name="end_time",
field=models.DateTimeField(
blank=True, null=True, verbose_name="Executions until"
),
),
migrations.AddConstraint(
model_name="periodicfuturetask",
constraint=models.CheckConstraint(
check=models.Q(
("end_time__isnull", True),
("max_number_of_executions__isnull", True),
_connector="OR",
),
name="not_both_not_null",
),
),
]
42 changes: 38 additions & 4 deletions django_future_tasks/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@
)
from cronfield.models import CronField
from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import models
from django.db.models import JSONField
from django.db.models import JSONField, Q
from django.utils.dateformat import format
from django.utils.timezone import utc
from django.utils.translation import gettext_lazy as _


Expand Down Expand Up @@ -86,6 +88,7 @@ class PeriodicFutureTask(models.Model):
max_number_of_executions = models.IntegerField(
_("Maximal number of executions"), null=True, blank=True
)
end_time = models.DateTimeField(_("Executions until"), null=True, blank=True)
__original_is_active = None
last_task_creation = models.DateTimeField(
_("Last single task creation"),
Expand All @@ -102,10 +105,18 @@ def next_planned_execution(self):
return None
else:
now = datetime.datetime.now()
return format(
croniter.croniter(self.cron_string, now).get_next(datetime.datetime),
settings.DATETIME_FORMAT,
next_planned_execution = utc.localize(
croniter.croniter(self.cron_string, now).get_next(datetime.datetime)
)
if self.end_time is not None and self.end_time < utc.localize(
croniter.croniter(self.cron_string, now).get_next(datetime.datetime)
):
return None
else:
return format(
next_planned_execution,
settings.DATETIME_FORMAT,
)

def cron_humnan_readable(self):
descriptor = ExpressionDescriptor(
Expand All @@ -125,8 +136,31 @@ def save(
if self.is_active and not self.__original_is_active:
self.last_task_creation = datetime.datetime.now()

self.clean()
super().save()
self.__original_is_active = self.is_active

def __str__(self):
return f"{self.periodic_task_id} ({self.cron_string})"

def clean(self):
if self.end_time is not None and self.max_number_of_executions is not None:
raise ValidationError(
{
"end_time": _(
"Cannot be set together with maximal number of executions, at least one must be empty."
),
"max_number_of_executions": _(
"Cannot be set together with execution end time, at least one must be empty."
),
}
)

class Meta:
constraints = [
models.CheckConstraint(
check=Q(end_time__isnull=True)
| Q(max_number_of_executions__isnull=True),
name="not_both_not_null",
)
]
42 changes: 40 additions & 2 deletions tests/testapp/tests/test_periodic_future_tasks.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
import time
from datetime import timedelta

from django.core.exceptions import ValidationError
from django.test import TransactionTestCase
from django.utils import timezone

from django_future_tasks.models import FutureTask, PeriodicFutureTask
from tests.core import settings
from tests.testapp.mixins import PopulatePeriodicTaskCommandMixin

SLEEP_TIME = 1.5
SLEEP_TIME = 1.8


class TestPeriodicFutureTasks(PopulatePeriodicTaskCommandMixin, TransactionTestCase):
def setUp(self):
super().setUp()

self.original_last_task_creation = timezone.now() - timedelta(hours=2)
now = timezone.now()
self.original_last_task_creation = now - timedelta(hours=2)

self.task_active = PeriodicFutureTask.objects.create(
periodic_task_id="periodic task",
Expand Down Expand Up @@ -45,6 +47,24 @@ def setUp(self):
p_task.last_task_creation = self.original_last_task_creation
p_task.save()

end_time = now - timedelta(hours=1)
self.task_with_end_time = PeriodicFutureTask.objects.create(
periodic_task_id="periodic task with end time",
type=settings.FUTURE_TASK_TYPE_ONE,
cron_string="42 * * * *",
end_time=end_time,
)

p_task = PeriodicFutureTask.objects.get(pk=self.task_with_end_time.pk)
p_task.last_task_creation = self.original_last_task_creation
p_task.save()

self.task_for_validation_test = PeriodicFutureTask.objects.create(
periodic_task_id="periodic task for validation test",
type=settings.FUTURE_TASK_TYPE_ONE,
cron_string="42 * * * *",
)

def test_periodic_future_task_populate_active_task(self):
p_task = PeriodicFutureTask.objects.get(pk=self.task_active.pk)

Expand Down Expand Up @@ -96,3 +116,21 @@ def test_periodic_future_task_max_one_exection(self):
FutureTask.objects.filter(periodic_parent_task_id=p_task.pk).count(), 1
)
self.assertFalse(p_task.is_active)

def test_periodic_future_task_end_time(self):
p_task = PeriodicFutureTask.objects.get(pk=self.task_with_end_time.pk)

# Make sure that task population has been processed.
time.sleep(SLEEP_TIME)

p_task.refresh_from_db()
self.assertEqual(
FutureTask.objects.filter(periodic_parent_task_id=p_task.pk).count(), 1
)
self.assertFalse(p_task.is_active)

def test_end_time_and_max_number_of_executions_validation(self):
p_task = PeriodicFutureTask.objects.get(pk=self.task_for_validation_test.pk)
p_task.max_number_of_executions = 42
p_task.end_time = timezone.now()
self.assertRaises(ValidationError, p_task.save)

0 comments on commit 995af86

Please sign in to comment.