diff --git a/django_q/models.py b/django_q/models.py index 69dd5bb..5cc534f 100644 --- a/django_q/models.py +++ b/django_q/models.py @@ -302,6 +302,12 @@ class Schedule(models.Model): def __str__(self): return self.func + def save(self, *args, **kwargs): + if self.pk is None and self.schedule_type == self.CRON: + self.next_run = self.calculate_next_run() + + return super().save(*args, **kwargs) + success.boolean = True success.short_description = _("success") last_run.allow_tags = True diff --git a/django_q/tests/test_scheduler.py b/django_q/tests/test_scheduler.py index edecf53..fcad6f8 100644 --- a/django_q/tests/test_scheduler.py +++ b/django_q/tests/test_scheduler.py @@ -473,6 +473,47 @@ def test_scheduler_atomic_must_specify_the_database_based_on_router_redirection( mocked_db.atomic.assert_called_with(using="default") +@pytest.mark.django_db +def test_schedule_save_sets_next_run_for_cron(): + """Ensure Schedule.save() sets next_run correctly for CRON schedules.""" + cron_expression = "0 12 * * *" # Executes dialy at 12pm + schedule = Schedule( + func="math.sqrt", + schedule_type=Schedule.CRON, + cron=cron_expression, + ) + + assert schedule.next_run is not None + assert schedule.pk is None + + initial_next_run = schedule.next_run + schedule.save() + + # After save, next_run must be recalculated based on the CRON expression + assert schedule.next_run > initial_next_run + + +@pytest.mark.django_db +def test_schedule_save_direct_db(broker): + """Ensure Schedule.save() updates next_run correctly when created directly in DB.""" + cron_expression = "0 12 * * *" # Executes dialy at 12pm + + # Creating schedule directly in database + schedule = Schedule.objects.create( + func="math.sqrt", + schedule_type=Schedule.CRON, + cron=cron_expression, + ) + + # Next run must be defined after execution time + assert schedule.next_run is not None + assert schedule.next_run > timezone.now() + + scheduler(broker) + + assert broker.queue_size() == 0 + + def test_localtime(): assert not is_naive(localtime())