From 5506b97724fb8444adc043a78b5ece589dcf8005 Mon Sep 17 00:00:00 2001 From: Matthew Egan Date: Tue, 9 Oct 2018 21:05:38 -0400 Subject: [PATCH 1/2] add seconds option to interval_units with tests (cherry picked from commit 83cc4328fddc90a5ef1ad41d3679aa5ee5a3b879) --- .gitignore | 4 ++++ .../migrations/0006_auto_20181010_0102.py | 18 ++++++++++++++++++ scheduler/models.py | 1 + scheduler/tests.py | 6 ++++++ 4 files changed, 29 insertions(+) create mode 100644 scheduler/migrations/0006_auto_20181010_0102.py diff --git a/.gitignore b/.gitignore index 5401004..f7bb23f 100644 --- a/.gitignore +++ b/.gitignore @@ -66,3 +66,7 @@ target/ # SQLite *.sqlite3 + +# Pipenv +Pipfile +Pipfile.lock \ No newline at end of file diff --git a/scheduler/migrations/0006_auto_20181010_0102.py b/scheduler/migrations/0006_auto_20181010_0102.py new file mode 100644 index 0000000..5719c59 --- /dev/null +++ b/scheduler/migrations/0006_auto_20181010_0102.py @@ -0,0 +1,18 @@ +# Generated by Django 2.1.2 on 2018-10-10 01:02 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('scheduler', '0005_added_result_ttl'), + ] + + operations = [ + migrations.AlterField( + model_name='repeatablejob', + name='interval_unit', + field=models.CharField(choices=[('seconds', 'seconds'), ('minutes', 'minutes'), ('hours', 'hours'), ('days', 'days'), ('weeks', 'weeks')], default='hours', max_length=12, verbose_name='interval unit'), + ), + ] diff --git a/scheduler/models.py b/scheduler/models.py index 88924ab..40b5018 100644 --- a/scheduler/models.py +++ b/scheduler/models.py @@ -147,6 +147,7 @@ class Meta: class RepeatableJob(ScheduledTimeMixin, BaseJob): UNITS = Choices( + ('seconds', _('seconds')), ('minutes', _('minutes')), ('hours', _('hours')), ('days', _('days')), diff --git a/scheduler/tests.py b/scheduler/tests.py index fe1bc65..300aa82 100644 --- a/scheduler/tests.py +++ b/scheduler/tests.py @@ -233,6 +233,12 @@ def test_interval_seconds_minutes(self): job.interval_unit = 'minutes' self.assertEqual(900.0, job.interval_seconds()) + def test_interval_seconds_seconds(self): + job = RepeatableJob() + job.interval = 15 + job.interval_unit = 'seconds' + self.assertEqual(15.0, job.interval_seconds()) + def test_repeatable_schedule(self): job = self.JobClassFactory() job.id = 1 From 70b2928f21b18631283c771f92e758043b45cef9 Mon Sep 17 00:00:00 2001 From: tom-price Date: Sun, 26 May 2019 18:15:06 -0400 Subject: [PATCH 2/2] Proposed handling of intervals in seconds. Rejects when a job's interval is lower than a queue's interval. Rejects when a job's interval is not a multiple of a queue's interval. This seemed to be the most restrictive approach from which things can be loosened up. Added a settings parameter DJANGO_RQ_SCHEDULER_INTERVAL that can be used to change the schedular interval. Default is 60 matching DJANGO_RQ's default. Renamed migration adding seconds to be more descriptive. --- ...0102.py => 0006_seconds_interval_units.py} | 0 scheduler/models.py | 19 ++++++++++- scheduler/tests.py | 33 +++++++++++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) rename scheduler/migrations/{0006_auto_20181010_0102.py => 0006_seconds_interval_units.py} (100%) diff --git a/scheduler/migrations/0006_auto_20181010_0102.py b/scheduler/migrations/0006_seconds_interval_units.py similarity index 100% rename from scheduler/migrations/0006_auto_20181010_0102.py rename to scheduler/migrations/0006_seconds_interval_units.py diff --git a/scheduler/models.py b/scheduler/models.py index 40b5018..073cfde 100644 --- a/scheduler/models.py +++ b/scheduler/models.py @@ -16,6 +16,9 @@ from model_utils.models import TimeStampedModel +RQ_SCHEDULER_INTERVAL = getattr(settings, "DJANGO_RQ_SCHEDULER_INTERVAL", 60) + + @python_2_unicode_compatible class BaseJob(TimeStampedModel): @@ -90,7 +93,7 @@ def delete(self, **kwargs): super(BaseJob, self).delete(**kwargs) def scheduler(self): - return django_rq.get_scheduler(self.queue) + return django_rq.get_scheduler(self.queue, interval=RQ_SCHEDULER_INTERVAL) def is_schedulable(self): if self.job_id: @@ -160,6 +163,20 @@ class RepeatableJob(ScheduledTimeMixin, BaseJob): ) repeat = models.PositiveIntegerField(_('repeat'), blank=True, null=True) + def clean(self): + super(RepeatableJob, self).clean() + self.clean_interval_unit() + + def clean_interval_unit(self): + if RQ_SCHEDULER_INTERVAL > self.interval_seconds(): + raise ValidationError({ + 'interval_unit': ValidationError( + _("Job interval is set lower than {} queue's interval.".format(self.queue)), code='invalid') + }) + if self.interval_seconds() % RQ_SCHEDULER_INTERVAL: + raise ValidationError(_("Job interval is not a multiple of rq_scheduler's interval frequency: {}s".format( + RQ_SCHEDULER_INTERVAL)), code='invalid') + def interval_display(self): return '{} {}'.format(self.interval, self.get_interval_unit_display()) diff --git a/scheduler/tests.py b/scheduler/tests.py index 300aa82..924cc74 100644 --- a/scheduler/tests.py +++ b/scheduler/tests.py @@ -209,6 +209,39 @@ class TestRepeatableJob(TestScheduledJob): JobClass = RepeatableJob JobClassFactory = RepeatableJobFactory + def test_clean(self): + job = self.JobClass() + job.queue = list(settings.RQ_QUEUES)[0] + job.callable = 'scheduler.tests.test_job' + job.interval = 1 + assert job.clean() is None + + def test_clean_seconds(self): + job = self.JobClass() + job.queue = list(settings.RQ_QUEUES)[0] + job.callable = 'scheduler.tests.test_job' + job.interval = 60 + job.interval_unit = 'seconds' + assert job.clean() is None + + def test_clean_too_frequent(self): + job = self.JobClass() + job.queue = list(settings.RQ_QUEUES)[0] + job.callable = 'scheduler.tests.test_job' + job.interval = 30 + job.interval_unit = 'seconds' + with self.assertRaises(ValidationError): + job.clean_interval_unit() + + def test_clean_not_multiple(self): + job = self.JobClass() + job.queue = list(settings.RQ_QUEUES)[0] + job.callable = 'scheduler.tests.test_job' + job.interval = 121 + job.interval_unit = 'seconds' + with self.assertRaises(ValidationError): + job.clean_interval_unit() + def test_interval_seconds_weeks(self): job = RepeatableJob() job.interval = 2