diff --git a/django_q/cluster.py b/django_q/cluster.py index ba4fa3e..0b2105f 100644 --- a/django_q/cluster.py +++ b/django_q/cluster.py @@ -440,7 +440,9 @@ def scheduler(list_key=Conf.Q_LIST): # set up the next run time if not s.schedule_type == s.ONCE: next_run = arrow.get(s.next_run) - if s.schedule_type == s.HOURLY: + if s.schedule_type == s.MINUTES: + next_run = next_run.replace(minutes=+(s.minutes or 1)) + elif s.schedule_type == s.HOURLY: next_run = next_run.replace(hours=+1) elif s.schedule_type == s.DAILY: next_run = next_run.replace(days=+1) diff --git a/django_q/migrations/0006_auto_20150805_1817.py b/django_q/migrations/0006_auto_20150805_1817.py new file mode 100644 index 0000000..0406f8c --- /dev/null +++ b/django_q/migrations/0006_auto_20150805_1817.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('django_q', '0005_auto_20150718_1506'), + ] + + operations = [ + migrations.AddField( + model_name='schedule', + name='minutes', + field=models.PositiveSmallIntegerField(help_text='Number of minutes for the Minutes type', blank=True, null=True), + ), + migrations.AlterField( + model_name='schedule', + name='schedule_type', + field=models.CharField(max_length=1, choices=[('O', 'Once'), ('I', 'Minutes'), ('H', 'Hourly'), ('D', 'Daily'), ('W', 'Weekly'), ('M', 'Monthly'), ('Q', 'Quarterly'), ('Y', 'Yearly')], default='O', verbose_name='Schedule Type'), + ), + ] diff --git a/django_q/models.py b/django_q/models.py index c7e398c..9645313 100644 --- a/django_q/models.py +++ b/django_q/models.py @@ -137,6 +137,7 @@ class Schedule(models.Model): args = models.TextField(null=True, blank=True, help_text=_("e.g. 1, 2, 'John'")) kwargs = models.TextField(null=True, blank=True, help_text=_("e.g. x=1, y=2, name='John'")) ONCE = 'O' + MINUTES = 'I' HOURLY = 'H' DAILY = 'D' WEEKLY = 'W' @@ -145,6 +146,7 @@ class Schedule(models.Model): YEARLY = 'Y' TYPE = ( (ONCE, _('Once')), + (MINUTES, _('Minutes')), (HOURLY, _('Hourly')), (DAILY, _('Daily')), (WEEKLY, _('Weekly')), @@ -153,6 +155,8 @@ class Schedule(models.Model): (YEARLY, _('Yearly')), ) schedule_type = models.CharField(max_length=1, choices=TYPE, default=TYPE[0][0], verbose_name=_('Schedule Type')) + minutes = models.PositiveSmallIntegerField(null=True, blank=True, + help_text=_('Number of minutes for the Minutes type')) repeats = models.SmallIntegerField(default=-1, verbose_name=_('Repeats'), help_text=_('n = n times, -1 = forever')) next_run = models.DateTimeField(verbose_name=_('Next Run'), default=timezone.now, null=True) task = models.CharField(max_length=100, null=True, editable=False) diff --git a/django_q/tasks.py b/django_q/tasks.py index abaea1a..ea03ffa 100644 --- a/django_q/tasks.py +++ b/django_q/tasks.py @@ -66,6 +66,7 @@ def schedule(func, *args, **kwargs): name = kwargs.pop('name', None) hook = kwargs.pop('hook', None) schedule_type = kwargs.pop('schedule_type', Schedule.ONCE) + minutes = kwargs.pop('minutes', None) repeats = kwargs.pop('repeats', -1) next_run = kwargs.pop('next_run', timezone.now()) @@ -75,6 +76,7 @@ def schedule(func, *args, **kwargs): args=args, kwargs=kwargs, schedule_type=schedule_type, + minutes=minutes, repeats=repeats, next_run=next_run ) diff --git a/django_q/tests/test_scheduler.py b/django_q/tests/test_scheduler.py index 8fc3505..9214d46 100644 --- a/django_q/tests/test_scheduler.py +++ b/django_q/tests/test_scheduler.py @@ -71,6 +71,13 @@ def test_scheduler(r): hook='django_q.tests.tasks.result' ) assert hasattr(always_schedule, 'pk') is True + # Minute schedule + minute_schedule = create_schedule('django_q.tests.tasks.word_multiply', + 2, + word='django', + schedule_type=Schedule.MINUTES, + minutes=10) + assert hasattr(minute_schedule, 'pk') is True # All other types for t in Schedule.TYPE: schedule = create_schedule('django_q.tests.tasks.word_multiply', diff --git a/docs/schedules.rst b/docs/schedules.rst index 30f6706..ceb9005 100644 --- a/docs/schedules.rst +++ b/docs/schedules.rst @@ -32,6 +32,14 @@ You can manage them through the :ref:`admin_page` or directly from your code wit q_options={'timeout': 30}, schedule_type=Schedule.HOURLY) + # Run a schedule every 5 minutes, starting at 6. + import arrow + + schedule('math.hypot', + 3, 4, + schedule_type=Schedule.MINUTES, + minutes = 5, + next_run = arrow.utcnow().replace(hour=18, minute=0)) Management Commands ------------------- @@ -55,7 +63,7 @@ If you want to schedule regular Django management commands, you can use the :mod Reference --------- -.. py:function:: schedule(func, *args, name=None, hook=None, schedule_type='O', repeats=-1, next_run=now() , q_options=None, **kwargs) +.. py:function:: schedule(func, *args, name=None, hook=None, schedule_type='O', minutes=None, repeats=-1, next_run=now() , q_options=None, **kwargs) Creates a schedule @@ -63,7 +71,8 @@ Reference :param args: arguments for the scheduled function. :param str name: An optional name for your schedule. :param str hook: optional result hook function. Dotted strings only. - :param str schedule_type: (O)nce, (H)ourly, (D)aily, (W)eekly, (M)onthly, (Q)uarterly, (Y)early or :attr:`Schedule.TYPE` + :param str schedule_type: (O)nce, M(I)nutes, (H)ourly, (D)aily, (W)eekly, (M)onthly, (Q)uarterly, (Y)early or :attr:`Schedule.TYPE` + :param int minutes: Number of minutes for the Minutes type. :param int repeats: Number of times to repeat schedule. -1=Always, 0=Never, n =n. :param datetime next_run: Next or first scheduled execution datetime. :param dict q_options: async options to use for this schedule @@ -103,9 +112,14 @@ Reference .. py:attribute:: TYPE - :attr:`ONCE`, :attr:`HOURLY`, :attr:`DAILY`, :attr:`WEEKLY`, :attr:`MONTHLY`, :attr:`QUARTERLY`, :attr:`YEARLY` + :attr:`ONCE`, :attr:`MINUTES`, :attr:`HOURLY`, :attr:`DAILY`, :attr:`WEEKLY`, :attr:`MONTHLY`, :attr:`QUARTERLY`, :attr:`YEARLY` + .. py:attribute:: minutes + + The number of minutes the :attr:`MINUTES` schedule should use. + Is ignored for other schedule types. + .. py:attribute:: repeats Number of times to repeat the schedule. -1=Always, 0=Never, n =n. @@ -133,6 +147,10 @@ Reference 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:: MINUTES + + `'I'` will run every :attr:`minutes` after its first run. + .. py:attribute:: HOURLY `'H'` the scheduled task will run every hour after its first run.