diff --git a/django_q/__init__.py b/django_q/__init__.py index b9c8897..2a8b9d2 100644 --- a/django_q/__init__.py +++ b/django_q/__init__.py @@ -9,6 +9,6 @@ from .models import Task, Schedule, Success, Failure from .cluster import Cluster from .monitor import Stat -VERSION = (0, 4, 6) +VERSION = (0, 5, 0) default_app_config = 'django_q.apps.DjangoQConfig' 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/_static/scheduled.png b/docs/_static/scheduled.png new file mode 100644 index 0000000..47684ce Binary files /dev/null and b/docs/_static/scheduled.png differ diff --git a/docs/_static/successful.png b/docs/_static/successful.png new file mode 100644 index 0000000..5090848 Binary files /dev/null and b/docs/_static/successful.png differ diff --git a/docs/admin.rst b/docs/admin.rst index 821cd28..8871dc7 100644 --- a/docs/admin.rst +++ b/docs/admin.rst @@ -11,9 +11,11 @@ Successful tasks ---------------- Shows all successfully executed tasks. Meaning they did not encounter any errors during execution. -From here you can look at details of each task or delete them. Use the group column to sort your results by schedule name or group id. +From here you can look at details of each task or delete them. Use the group filter to filter your results by schedule name or group id. The table is searchable by `name`, `func` and `group` +.. image:: _static/successful.png + Uses the :class:`Success` proxy model. .. tip:: @@ -36,6 +38,8 @@ Scheduled tasks Here you can check on the status of your scheduled tasks, create, edit or delete them. +.. image:: _static/scheduled.png + Repeats ~~~~~~~ If you want a schedule to only run a finite amount of times, e.g. every hour for the next 24 hours, you can do that using the :attr:`Schedule.repeats` attribute. diff --git a/docs/conf.py b/docs/conf.py index 9c71f37..51e01a8 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -70,9 +70,9 @@ author = 'Ilan Steemers' # built documents. # # The short X.Y version. -version = '0.4' +version = '0.5' # The full version, including alpha/beta/rc tags. -release = '0.4.6' +release = '0.5.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. 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. diff --git a/setup.py b/setup.py index 0fdb706..4a59765 100644 --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ class PyTest(Command): setup( name='django-q', - version='0.4.6', + version='0.5.0', author='Ilan Steemers', author_email='koed00@gmail.com', keywords='django task queue worker redis multiprocessing',