[Python-modules-commits] [python-django-celery-beat] 03/16: New upstream version 1.1.0
Michael Fladischer
fladi at moszumanska.debian.org
Fri Nov 24 11:09:07 UTC 2017
This is an automated email from the git hooks/post-receive script.
fladi pushed a commit to branch debian/master
in repository python-django-celery-beat.
commit 2ec8b7e702fa7a48ad01ab3a06ffed2db0460f89
Author: Michael Fladischer <FladischerMichael at fladi.at>
Date: Mon Nov 13 12:39:37 2017 +0100
New upstream version 1.1.0
---
.bumpversion.cfg | 2 +-
.travis.yml | 1 +
AUTHORS | 3 +
Changelog | 21 +++
README.rst | 39 ++--
django_celery_beat/__init__.py | 4 +-
django_celery_beat/admin.py | 67 ++++++-
django_celery_beat/apps.py | 4 +-
.../migrations/0002_auto_20161118_0346.py | 52 ++++++
.../migrations/0003_auto_20161209_0049.py | 26 +++
.../migrations/0004_auto_20170221_0000.py | 22 +++
django_celery_beat/models.py | 109 +++++++++--
django_celery_beat/schedulers.py | 19 +-
django_celery_beat/utils.py | 5 +-
docs/includes/installation.txt | 6 -
docs/includes/introduction.txt | 16 +-
requirements/default.txt | 1 +
requirements/test-django111.txt | 1 +
setup.py | 6 +-
t/proj/celery.py | 2 +-
t/proj/settings.py | 9 +-
t/unit/test_schedulers.py | 200 ++++++++++++++++++++-
tox.ini | 7 +-
23 files changed, 549 insertions(+), 73 deletions(-)
diff --git a/.bumpversion.cfg b/.bumpversion.cfg
index f97eab0..4bf3197 100644
--- a/.bumpversion.cfg
+++ b/.bumpversion.cfg
@@ -1,5 +1,5 @@
[bumpversion]
-current_version = 1.0.1
+current_version = 1.1.0
commit = True
tag = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?P<releaselevel>[a-z]+)?
diff --git a/.travis.yml b/.travis.yml
index 7128256..30aba81 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -27,6 +27,7 @@ env:
- TOXENV=pydocstyle
- TOXENV=cov
install: travis_retry pip install -U tox
+services: rabbitmq
script: tox -v -- -v
after_success:
- .tox/$TRAVIS_PYTHON_VERSION/bin/coverage xml
diff --git a/AUTHORS b/AUTHORS
index e8a5c4d..ef97528 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -46,6 +46,8 @@ Jeffrey Hu <zhiwehu at gmail.com>
Jens Alm <jens.alm at mac.com>
Jerzy Kozera <jerzy.kozera at gmail.com>
Jesper Noehr <jesper at noehr.org>
+Jimmy Bradshaw <james.g.bradshaw at gmail.com>
+Joey Wilhelm <tarkatronic at gmail.com>
John Andrews <johna at stjit011.(none)>
John Watson <johnw at mahalo.com>
Jonas Haag <jonas at lophus.org>
@@ -57,6 +59,7 @@ Justin Quick <justquick at gmail.com>
Keith Perkins <keith at tasteoftheworld.us>
Kirill Panshin <kipanshi at gmail.com>
Mark Hellewell <mark.hellewell at gmail.com>
+Mark Heppner <mheppner at users.noreply.github.com>
Mark Lavin <markdlavin at gmail.com>
Mark Stover <stovenator at gmail.com>
Maxim Bodyansky <bodyansky at gmail.com>
diff --git a/Changelog b/Changelog
index 5ec084a..235c483 100644
--- a/Changelog
+++ b/Changelog
@@ -4,6 +4,27 @@
Change history
================
+.. _version-1.1.0:
+
+1.1.0
+=====
+:release-date: 2017-10-31 2:30 p.m. UTC+3:00
+:release-by: Omer Katz
+
+- Adds default_app_config (Issue celery/celery#3567)
+- Adds "run now" admin action for tasks.
+- Adds admin actions to toggle tasks.
+- Add solar schedules (Issue #8)
+- Notify beat of changes when Interval/Crontab models change. (Issue celery/celery#3683)
+- Fix PeriodicTask.enable sync issues
+- Notify beat of changes when Solar model changes.
+- Resolve CSS class conflict with django-adminlte2 package.
+- We now support Django 1.11
+- Deletes are now performed cascadingly.
+- Return schedule for solar periodic tasks so that Celery Beat does not crash when one is scheduled.
+- Adding nowfun to solar and crontab schedulers so that the Django timezone is used.
+
+
.. _version-1.0.1:
1.0.1
diff --git a/README.rst b/README.rst
index 49e07c5..74cce77 100644
--- a/README.rst
+++ b/README.rst
@@ -4,7 +4,7 @@
|build-status| |coverage| |license| |wheel| |pyversion| |pyimp|
-:Version: 1.0.1
+:Version: 1.1.0
:Web: http://django-celery-beat.readthedocs.io/
:Download: http://pypi.python.org/pypi/django-celery-beat
:Source: http://github.com/celery/django-celery-beat
@@ -19,10 +19,10 @@ database.
The periodic tasks can be managed from the Django Admin interface, where you
can create, edit and delete periodic tasks and how often they should run.
-Installing
-==========
+Using the Extension
+===================
-The installation instructions for this extension is available
+Usage and installation instructions for this extension are available
from the `Celery documentation`_:
http://docs.celeryproject.org/en/latest/userguide/periodic-tasks.html#using-custom-scheduler-classes
@@ -31,23 +31,24 @@ http://docs.celeryproject.org/en/latest/userguide/periodic-tasks.html#using-cust
.. _`Celery documentation`:
http://docs.celeryproject.org/en/latest/userguide/periodic-tasks.html#using-custom-scheduler-classes
-Important Warning
-=================
+Important Warning about Time Zones
+==================================
-.. admonition:: Timezones
+.. warning::
-If you change the Django ``TIME_ZONE`` setting your periodic task schedule
-will still be based on the old timezone.
+ If you change the Django ``TIME_ZONE`` setting your periodic task schedule
+ will still be based on the old timezone.
-To fix that you would have to reset the "last run time" for each periodic
-task::
+ To fix that you would have to reset the "last run time" for each periodic
+ task::
- >>> from django_celery_beat import PeriodicTask, PeriodicTasks
- >>> PeriodicTask.objects.all().update(last_run_at=None)
- >>> PeriodicTasks.changed()
+ >>> from django_celery_beat.models import PeriodicTask, PeriodicTasks
+ >>> PeriodicTask.objects.all().update(last_run_at=None)
+ >>> for task in PeriodicTask.objects.all():
+ >>> PeriodicTasks.changed(task)
-Note that this will reset the state as if the periodic tasks have never run
-before.
+ Note that this will reset the state as if the periodic tasks have never run
+ before.
Models
======
@@ -189,8 +190,6 @@ You can use the ``enabled`` flag to temporarily disable a periodic task::
>>> periodic_task.enabled = False
>>> periodic_task.save()
-.. _installation:
-
Installation
============
@@ -201,8 +200,6 @@ To install using `pip`,::
$ pip install -U django-celery-beat
-.. _installing-from-source:
-
Downloading and installing from source
--------------------------------------
@@ -219,8 +216,6 @@ You can install it by doing the following,::
The last command must be executed as a privileged user if
you are not currently using a virtualenv.
-.. _installing-from-git:
-
Using the development version
-----------------------------
diff --git a/django_celery_beat/__init__.py b/django_celery_beat/__init__.py
index 15d021f..b72ffb3 100644
--- a/django_celery_beat/__init__.py
+++ b/django_celery_beat/__init__.py
@@ -10,7 +10,7 @@ import re
from collections import namedtuple
-__version__ = '1.0.1'
+__version__ = '1.1.0'
__author__ = 'Ask Solem'
__contact__ = 'ask at celeryproject.org'
__homepage__ = 'https://github.com/celery/django-celery-beat'
@@ -32,3 +32,5 @@ del(_temp)
del(re)
__all__ = []
+
+default_app_config = 'django_celery_beat.apps.BeatConfig'
diff --git a/django_celery_beat/admin.py b/django_celery_beat/admin.py
index 08b4d85..df67fcb 100644
--- a/django_celery_beat/admin.py
+++ b/django_celery_beat/admin.py
@@ -5,13 +5,18 @@ from django import forms
from django.conf import settings
from django.contrib import admin
from django.forms.widgets import Select
+from django.template.defaultfilters import pluralize
from django.utils.translation import ugettext_lazy as _
from celery import current_app
from celery.utils import cached_property
from kombu.utils.json import loads
-from .models import PeriodicTask, IntervalSchedule, CrontabSchedule
+from .models import (
+ PeriodicTask, PeriodicTasks,
+ IntervalSchedule, CrontabSchedule,
+ SolarSchedule
+)
from .utils import is_database_scheduler
try:
@@ -106,27 +111,29 @@ class PeriodicTaskForm(forms.ModelForm):
class PeriodicTaskAdmin(admin.ModelAdmin):
- """Admin-interface for peridic tasks."""
+ """Admin-interface for periodic tasks."""
form = PeriodicTaskForm
model = PeriodicTask
+ celery_app = current_app
list_display = ('__str__', 'enabled')
+ actions = ('enable_tasks', 'disable_tasks', 'run_tasks')
fieldsets = (
(None, {
'fields': ('name', 'regtask', 'task', 'enabled'),
'classes': ('extrapretty', 'wide'),
}),
('Schedule', {
- 'fields': ('interval', 'crontab'),
+ 'fields': ('interval', 'crontab', 'solar'),
'classes': ('extrapretty', 'wide', ),
}),
('Arguments', {
'fields': ('args', 'kwargs'),
- 'classes': ('extrapretty', 'wide', 'collapse'),
+ 'classes': ('extrapretty', 'wide', 'collapse', 'in'),
}),
('Execution Options', {
'fields': ('expires', 'queue', 'exchange', 'routing_key'),
- 'classes': ('extrapretty', 'wide', 'collapse'),
+ 'classes': ('extrapretty', 'wide', 'collapse', 'in'),
}),
)
@@ -139,7 +146,55 @@ class PeriodicTaskAdmin(admin.ModelAdmin):
def get_queryset(self, request):
qs = super(PeriodicTaskAdmin, self).get_queryset(request)
- return qs.select_related('interval', 'crontab')
+ return qs.select_related('interval', 'crontab', 'solar')
+
+ def enable_tasks(self, request, queryset):
+ rows_updated = queryset.update(enabled=True)
+ PeriodicTasks.update_changed()
+ self.message_user(
+ request,
+ _('{0} task{1} {2} successfully enabled').format(
+ rows_updated,
+ pluralize(rows_updated),
+ pluralize(rows_updated, _('was,were')),
+ ),
+ )
+ enable_tasks.short_description = _('Enable selected tasks')
+
+ def disable_tasks(self, request, queryset):
+ rows_updated = queryset.update(enabled=False)
+ PeriodicTasks.update_changed()
+ self.message_user(
+ request,
+ _('{0} task{1} {2} successfully disabled').format(
+ rows_updated,
+ pluralize(rows_updated),
+ pluralize(rows_updated, _('was,were')),
+ ),
+ )
+ disable_tasks.short_description = _('Disable selected tasks')
+
+ def run_tasks(self, request, queryset):
+ self.celery_app.loader.import_default_modules()
+ tasks = [(self.celery_app.tasks.get(task.task),
+ loads(task.args),
+ loads(task.kwargs))
+ for task in queryset]
+ task_ids = [task.delay(*args, **kwargs)
+ for task, args, kwargs in tasks]
+ tasks_run = len(task_ids)
+ self.message_user(
+ request,
+ _('{0} task{1} {2} successfully run').format(
+ tasks_run,
+ pluralize(tasks_run),
+ pluralize(tasks_run, _('was,were')),
+ ),
+ )
+ run_tasks.short_description = _('Run selected tasks')
+
+
admin.site.register(IntervalSchedule)
admin.site.register(CrontabSchedule)
+admin.site.register(SolarSchedule)
admin.site.register(PeriodicTask, PeriodicTaskAdmin)
diff --git a/django_celery_beat/apps.py b/django_celery_beat/apps.py
index b4253c3..4856a69 100644
--- a/django_celery_beat/apps.py
+++ b/django_celery_beat/apps.py
@@ -11,5 +11,5 @@ class BeatConfig(AppConfig):
"""Default configuration for django_celery_beat app."""
name = 'django_celery_beat'
- label = 'beat'
- verbose_name = _('Beat')
+ label = 'django_celery_beat'
+ verbose_name = _('Periodic Tasks')
diff --git a/django_celery_beat/migrations/0002_auto_20161118_0346.py b/django_celery_beat/migrations/0002_auto_20161118_0346.py
new file mode 100644
index 0000000..6560473
--- /dev/null
+++ b/django_celery_beat/migrations/0002_auto_20161118_0346.py
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.3 on 2016-11-18 03:46
+from __future__ import absolute_import, unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('django_celery_beat', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='SolarSchedule',
+ fields=[
+ ('id', models.AutoField(
+ auto_created=True, primary_key=True,
+ serialize=False, verbose_name='ID')),
+ ('event', models.CharField(
+ choices=[('dusk_nautical', 'dusk_nautical'),
+ ('dawn_astronomical', 'dawn_astronomical'),
+ ('dawn_nautical', 'dawn_nautical'),
+ ('dawn_civil', 'dawn_civil'),
+ ('sunset', 'sunset'),
+ ('solar_noon', 'solar_noon'),
+ ('dusk_astronomical', 'dusk_astronomical'),
+ ('sunrise', 'sunrise'),
+ ('dusk_civil', 'dusk_civil')],
+ max_length=24, verbose_name='event')),
+ ('latitude', models.DecimalField(
+ decimal_places=6, max_digits=9, verbose_name='latitude')),
+ ('longitude', models.DecimalField(
+ decimal_places=6, max_digits=9, verbose_name='latitude')),
+ ],
+ options={
+ 'ordering': ['event', 'latitude', 'longitude'],
+ 'verbose_name': 'solar',
+ 'verbose_name_plural': 'solars',
+ },
+ ),
+ migrations.AddField(
+ model_name='periodictask',
+ name='solar',
+ field=models.ForeignKey(
+ blank=True, help_text='Use a solar schedule',
+ null=True, on_delete=django.db.models.deletion.CASCADE,
+ to='django_celery_beat.SolarSchedule', verbose_name='solar'),
+ ),
+ ]
diff --git a/django_celery_beat/migrations/0003_auto_20161209_0049.py b/django_celery_beat/migrations/0003_auto_20161209_0049.py
new file mode 100644
index 0000000..d403b35
--- /dev/null
+++ b/django_celery_beat/migrations/0003_auto_20161209_0049.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.11 on 2016-12-09 00:49
+from __future__ import absolute_import, unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('django_celery_beat', '0002_auto_20161118_0346'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='solarschedule',
+ options={
+ 'ordering': ('event', 'latitude', 'longitude'),
+ 'verbose_name': 'solar event',
+ 'verbose_name_plural': 'solar events'},
+ ),
+ migrations.AlterUniqueTogether(
+ name='solarschedule',
+ unique_together=set([('event', 'latitude', 'longitude')]),
+ ),
+ ]
diff --git a/django_celery_beat/migrations/0004_auto_20170221_0000.py b/django_celery_beat/migrations/0004_auto_20170221_0000.py
new file mode 100644
index 0000000..2c6f2a8
--- /dev/null
+++ b/django_celery_beat/migrations/0004_auto_20170221_0000.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('django_celery_beat', '0003_auto_20161209_0049'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='solarschedule',
+ name='longitude',
+ field=models.DecimalField(
+ verbose_name='longitude',
+ max_digits=9,
+ decimal_places=6),
+ ),
+ ]
diff --git a/django_celery_beat/models.py b/django_celery_beat/models.py
index 211ec24..ad7d77d 100644
--- a/django_celery_beat/models.py
+++ b/django_celery_beat/models.py
@@ -12,7 +12,7 @@ from celery import schedules
from celery.five import python_2_unicode_compatible
from . import managers
-from .utils import now
+from .utils import now, make_aware
DAYS = 'days'
HOURS = 'hours'
@@ -28,6 +28,8 @@ PERIOD_CHOICES = (
(MICROSECONDS, _('Microseconds')),
)
+SOLAR_SCHEDULES = [(x, _(x)) for x in schedules.solar._all_events]
+
def cronexp(field):
"""Representation of cron expression."""
@@ -35,6 +37,56 @@ def cronexp(field):
@python_2_unicode_compatible
+class SolarSchedule(models.Model):
+ """Schedule following astronomical patterns."""
+
+ event = models.CharField(
+ _('event'), max_length=24, choices=SOLAR_SCHEDULES
+ )
+ latitude = models.DecimalField(
+ _('latitude'), max_digits=9, decimal_places=6
+ )
+ longitude = models.DecimalField(
+ _('longitude'), max_digits=9, decimal_places=6
+ )
+
+ class Meta:
+ """Table information."""
+
+ verbose_name = _('solar event')
+ verbose_name_plural = _('solar events')
+ ordering = ('event', 'latitude', 'longitude')
+ unique_together = ('event', 'latitude', 'longitude')
+
+ @property
+ def schedule(self):
+ return schedules.solar(self.event,
+ self.latitude,
+ self.longitude,
+ nowfun=lambda: make_aware(now()))
+
+ @classmethod
+ def from_schedule(cls, schedule):
+ spec = {'event': schedule.event,
+ 'latitude': schedule.lat,
+ 'longitude': schedule.lon}
+ try:
+ return cls.objects.get(**spec)
+ except cls.DoesNotExist:
+ return cls(**spec)
+ except MultipleObjectsReturned:
+ cls.objects.filter(**spec).delete()
+ return cls(**spec)
+
+ def __str__(self):
+ return '{0} ({1}, {2})'.format(
+ self.get_event_display(),
+ self.latitude,
+ self.longitude
+ )
+
+
+ at python_2_unicode_compatible
class IntervalSchedule(models.Model):
"""Schedule executing every n seconds."""
@@ -122,7 +174,8 @@ class CrontabSchedule(models.Model):
hour=self.hour,
day_of_week=self.day_of_week,
day_of_month=self.day_of_month,
- month_of_year=self.month_of_year)
+ month_of_year=self.month_of_year,
+ nowfun=lambda: make_aware(now()))
@classmethod
def from_schedule(cls, schedule):
@@ -151,8 +204,11 @@ class PeriodicTasks(models.Model):
@classmethod
def changed(cls, instance, **kwargs):
if not instance.no_changes:
- cls.objects.update_or_create(ident=1,
- defaults={'last_update': now()})
+ cls.update_changed()
+
+ @classmethod
+ def update_changed(cls, **kwargs):
+ cls.objects.update_or_create(ident=1, defaults={'last_update': now()})
@classmethod
def last_change(cls):
@@ -172,12 +228,16 @@ class PeriodicTask(models.Model):
)
task = models.CharField(_('task name'), max_length=200)
interval = models.ForeignKey(
- IntervalSchedule,
+ IntervalSchedule, on_delete=models.CASCADE,
null=True, blank=True, verbose_name=_('interval'),
)
crontab = models.ForeignKey(
- CrontabSchedule, null=True, blank=True, verbose_name=_('crontab'),
- help_text=_('Use one of interval/crontab'),
+ CrontabSchedule, on_delete=models.CASCADE, null=True, blank=True,
+ verbose_name=_('crontab'), help_text=_('Use one of interval/crontab'),
+ )
+ solar = models.ForeignKey(
+ SolarSchedule, on_delete=models.CASCADE, null=True, blank=True,
+ verbose_name=_('solar'), help_text=_('Use a solar schedule')
)
args = models.TextField(
_('Arguments'), blank=True, default='[]',
@@ -224,12 +284,18 @@ class PeriodicTask(models.Model):
def validate_unique(self, *args, **kwargs):
super(PeriodicTask, self).validate_unique(*args, **kwargs)
- if not self.interval and not self.crontab:
- raise ValidationError(
- {'interval': ['One of interval or crontab must be set.']})
- if self.interval and self.crontab:
- raise ValidationError(
- {'crontab': ['Only one of interval or crontab must be set']})
+ if not self.interval and not self.crontab and not self.solar:
+ raise ValidationError({
+ 'interval': [
+ 'One of interval, crontab, or solar must be set.'
+ ]
+ })
+ if self.interval and self.crontab and self.solar:
+ raise ValidationError({
+ 'crontab': [
+ 'Only one of interval, crontab, or solar must be set'
+ ]
+ })
def save(self, *args, **kwargs):
self.exchange = self.exchange or None
@@ -245,6 +311,8 @@ class PeriodicTask(models.Model):
fmt = '{0.name}: {0.interval}'
if self.crontab:
fmt = '{0.name}: {0.crontab}'
+ if self.solar:
+ fmt = '{0.name}: {0.solar}'
return fmt.format(self)
@property
@@ -253,6 +321,21 @@ class PeriodicTask(models.Model):
return self.interval.schedule
if self.crontab:
return self.crontab.schedule
+ if self.solar:
+ return self.solar.schedule
+
signals.pre_delete.connect(PeriodicTasks.changed, sender=PeriodicTask)
signals.pre_save.connect(PeriodicTasks.changed, sender=PeriodicTask)
+signals.pre_delete.connect(
+ PeriodicTasks.update_changed, sender=IntervalSchedule)
+signals.post_save.connect(
+ PeriodicTasks.update_changed, sender=IntervalSchedule)
+signals.post_delete.connect(
+ PeriodicTasks.update_changed, sender=CrontabSchedule)
+signals.post_save.connect(
+ PeriodicTasks.update_changed, sender=CrontabSchedule)
+signals.post_delete.connect(
+ PeriodicTasks.update_changed, sender=SolarSchedule)
+signals.post_save.connect(
+ PeriodicTasks.update_changed, sender=SolarSchedule)
diff --git a/django_celery_beat/schedulers.py b/django_celery_beat/schedulers.py
index a285f3e..0a8b0a2 100644
--- a/django_celery_beat/schedulers.py
+++ b/django_celery_beat/schedulers.py
@@ -20,6 +20,7 @@ from django.core.exceptions import ObjectDoesNotExist
from .models import (
PeriodicTask, PeriodicTasks,
CrontabSchedule, IntervalSchedule,
+ SolarSchedule,
)
from .utils import make_aware
@@ -34,7 +35,7 @@ except ImportError: # pragma: no cover
DEFAULT_MAX_INTERVAL = 5 # seconds
ADD_ENTRY_ERROR = """\
-Couldn't add entry %r to database schedule: %r. Contents: %r
+Cannot add entry %r to database schedule: %r. Contents: %r
"""
logger = get_logger(__name__)
@@ -47,6 +48,7 @@ class ModelEntry(ScheduleEntry):
model_schedules = (
(schedules.crontab, CrontabSchedule, 'crontab'),
(schedules.schedule, IntervalSchedule, 'interval'),
+ (schedules.solar, SolarSchedule, 'solar'),
)
save_fields = ['last_run_at', 'total_run_count', 'no_changes']
@@ -220,7 +222,7 @@ class DatabaseScheduler(Scheduler):
return False
def reserve(self, entry):
- new_entry = Scheduler.reserve(self, entry)
+ new_entry = next(entry)
# Need to store entry by name, because the entry may change
# in the mean time.
self._dirty.add(new_entry.name)
@@ -245,11 +247,16 @@ class DatabaseScheduler(Scheduler):
def update_from_dict(self, mapping):
s = {}
- for name, entry in items(mapping):
+ for name, entry_fields in items(mapping):
try:
- s[name] = self.Entry.from_entry(name, app=self.app, **entry)
+ entry = self.Entry.from_entry(name,
+ app=self.app,
+ **entry_fields)
+ if entry.model.enabled:
+ s[name] = entry
+
except Exception as exc:
- logger.error(ADD_ENTRY_ERROR, name, exc, entry)
+ logger.error(ADD_ENTRY_ERROR, name, exc, entry_fields)
self.schedule.update(s)
def install_default_entries(self, data):
@@ -278,6 +285,8 @@ class DatabaseScheduler(Scheduler):
if update:
self.sync()
self._schedule = self.all_as_schedule()
+ # the schedule changed, invalidate the heap in Scheduler.tick
+ self._heap = None
if logger.isEnabledFor(logging.DEBUG):
debug('Current schedule:\n%s', '\n'.join(
repr(entry) for entry in values(self._schedule)),
diff --git a/django_celery_beat/utils.py b/django_celery_beat/utils.py
index ba20a8b..c38226f 100644
--- a/django_celery_beat/utils.py
+++ b/django_celery_beat/utils.py
@@ -38,4 +38,7 @@ def is_database_scheduler(scheduler):
return False
from kombu.utils import symbol_by_name
from .schedulers import DatabaseScheduler
- return issubclass(symbol_by_name(scheduler), DatabaseScheduler)
+ return (
+ scheduler == 'django' or
+ issubclass(symbol_by_name(scheduler), DatabaseScheduler)
+ )
diff --git a/docs/includes/installation.txt b/docs/includes/installation.txt
index ddf011d..ac16b11 100644
--- a/docs/includes/installation.txt
+++ b/docs/includes/installation.txt
@@ -1,5 +1,3 @@
-.. _installation:
-
Installation
============
@@ -10,8 +8,6 @@ To install using `pip`,::
$ pip install -U django-celery-beat
-.. _installing-from-source:
-
Downloading and installing from source
--------------------------------------
@@ -28,8 +24,6 @@ You can install it by doing the following,::
The last command must be executed as a privileged user if
you are not currently using a virtualenv.
-.. _installing-from-git:
-
Using the development version
-----------------------------
diff --git a/docs/includes/introduction.txt b/docs/includes/introduction.txt
index 216cd00..dae02ff 100644
--- a/docs/includes/introduction.txt
+++ b/docs/includes/introduction.txt
@@ -1,4 +1,4 @@
-:Version: 1.0.1
+:Version: 1.1.0
:Web: http://django-celery-beat.readthedocs.io/
:Download: http://pypi.python.org/pypi/django-celery-beat
:Source: http://github.com/celery/django-celery-beat
@@ -13,10 +13,10 @@ database.
The periodic tasks can be managed from the Django Admin interface, where you
can create, edit and delete periodic tasks and how often they should run.
-Installing
-==========
+Using the Extension
+===================
-The installation instructions for this extension is available
+Usage and installation instructions for this extension are available
from the `Celery documentation`_:
http://docs.celeryproject.org/en/latest/userguide/periodic-tasks.html#using-custom-scheduler-classes
@@ -25,10 +25,10 @@ http://docs.celeryproject.org/en/latest/userguide/periodic-tasks.html#using-cust
.. _`Celery documentation`:
http://docs.celeryproject.org/en/latest/userguide/periodic-tasks.html#using-custom-scheduler-classes
-Important Warning
-=================
+Important Warning about Time Zones
+==================================
-.. admonition:: Timezones
+.. warning::
If you change the Django ``TIME_ZONE`` setting your periodic task schedule
will still be based on the old timezone.
@@ -36,7 +36,7 @@ Important Warning
To fix that you would have to reset the "last run time" for each periodic
task::
- >>> from django_celery_beat import PeriodicTask, PeriodicTasks
+ >>> from django_celery_beat.models import PeriodicTask, PeriodicTasks
>>> PeriodicTask.objects.all().update(last_run_at=None)
>>> PeriodicTasks.changed()
diff --git a/requirements/default.txt b/requirements/default.txt
index 01be9da..8d9f878 100644
--- a/requirements/default.txt
+++ b/requirements/default.txt
@@ -1 +1,2 @@
celery>=4.0,<5.0
+ephem>=3.7.6.0
diff --git a/requirements/test-django111.txt b/requirements/test-django111.txt
new file mode 100644
index 0000000..4891c35
--- /dev/null
+++ b/requirements/test-django111.txt
@@ -0,0 +1 @@
+django>=1.11,<2.0
diff --git a/setup.py b/setup.py
index e8db5dd..265387d 100644
--- a/setup.py
+++ b/setup.py
@@ -16,7 +16,8 @@ except (AttributeError, ImportError):
def _pyimp():
return 'Python'
-NAME = 'django_celery_beat'
+NAME = 'django-celery-beat'
+PACKAGE = 'django_celery_beat'
E_UNSUPPORTED_PYTHON = '%s 1.0 requires %%s %%s or later!' % (NAME,)
@@ -50,6 +51,7 @@ classes = """
Framework :: Django :: 1.8
Framework :: Django :: 1.9
Framework :: Django :: 1.10
+ Framework :: Django :: 1.11
Operating System :: OS Independent
Topic :: Communications
Topic :: System :: Distributed Computing
@@ -74,7 +76,7 @@ def add_doc(m):
pats = {re_meta: add_default,
re_doc: add_doc}
here = os.path.abspath(os.path.dirname(__file__))
-with open(os.path.join(here, NAME, '__init__.py')) as meta_fh:
+with open(os.path.join(here, PACKAGE, '__init__.py')) as meta_fh:
meta = {}
for line in meta_fh:
if line.strip() == '# -eof meta-':
diff --git a/t/proj/celery.py b/t/proj/celery.py
index 8324e6f..6ef18d4 100644
--- a/t/proj/celery.py
+++ b/t/proj/celery.py
@@ -4,7 +4,7 @@ import os
from celery import Celery
-os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'proj.settings')
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 't.proj.settings')
app = Celery('proj')
diff --git a/t/proj/settings.py b/t/proj/settings.py
index 84f86cf..ed76492 100644
--- a/t/proj/settings.py
+++ b/t/proj/settings.py
@@ -53,9 +53,14 @@ INSTALLED_APPS = [
]
MIDDLEWARE_CLASSES = [
+ 'django.middleware.security.SecurityMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.common.CommonMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
]
-ROOT_URLCONF = 'proj.urls'
+ROOT_URLCONF = 't.proj.urls'
TEMPLATES = [
{
@@ -73,7 +78,7 @@ TEMPLATES = [
},
]
-WSGI_APPLICATION = 'proj.wsgi.application'
+WSGI_APPLICATION = 't.proj.wsgi.application'
# Database
diff --git a/t/unit/test_schedulers.py b/t/unit/test_schedulers.py
index 3df420f..3b1538b 100644
--- a/t/unit/test_schedulers.py
+++ b/t/unit/test_schedulers.py
@@ -5,13 +5,20 @@ import pytest
from datetime import datetime, timedelta
from itertools import count
+from django.contrib.admin.sites import AdminSite
+from django.contrib.messages.storage.fallback import FallbackStorage
+from django.test import RequestFactory
+
from celery.five import monotonic, text_t
-from celery.schedules import schedule, crontab
+from celery.schedules import schedule, crontab, solar
from django_celery_beat import schedulers
+from django_celery_beat.admin import PeriodicTaskAdmin
from django_celery_beat.models import (
PeriodicTask, PeriodicTasks, IntervalSchedule, CrontabSchedule,
+ SolarSchedule
)
+from django_celery_beat.utils import make_aware
_ids = count(0)
@@ -64,6 +71,22 @@ class SchedulerCase:
crontab.save()
return self.create_model(crontab=crontab, **kwargs)
+ def create_model_solar(self, schedule, **kwargs):
+ solar = SolarSchedule.from_schedule(schedule)
+ solar.save()
+ return self.create_model(solar=solar, **kwargs)
+
+ def create_conf_entry(self):
+ name = 'thefoo{0}'.format(next(_ids))
+ return name, dict(
+ task='djcelery.unittest.add{0}'.format(next(_ids)),
+ schedule=timedelta(0, 600),
+ args=(),
+ relative=False,
+ kwargs={},
+ options={'queue': 'extra_queue'}
+ )
+
def create_model(self, Model=PeriodicTask, **kwargs):
entry = dict(
name='thefoo{0}'.format(next(_ids)),
@@ -95,6 +118,8 @@ class test_ModelEntry(SchedulerCase):
assert e.options['routing_key'] == 'cpu'
right_now = self.app.now()
+ # Entry.last_run_at returns naive tz, so make this naive for comparison
+ right_now = right_now.replace(tzinfo=None)
m2 = self.create_model_interval(
schedule(timedelta(seconds=10)),
last_run_at=right_now,
@@ -109,6 +134,47 @@ class test_ModelEntry(SchedulerCase):
@pytest.mark.django_db()
+class test_DatabaseSchedulerFromAppConf(SchedulerCase):
+ Scheduler = TrackingScheduler
+
+ @pytest.mark.django_db()
+ @pytest.fixture(autouse=True)
+ def setup_scheduler(self, app):
+ self.app = app
+
+ self.entry_name, entry = self.create_conf_entry()
+ self.app.conf.beat_schedule = {self.entry_name: entry}
+ self.m1 = PeriodicTask(name=self.entry_name)
+
+ def test_constructor(self):
+ s = self.Scheduler(app=self.app)
+
+ assert isinstance(s._dirty, set)
+ assert s._last_sync is None
+ assert s.sync_every
+
+ def test_periodic_task_model_enabled_schedule(self):
+ s = self.Scheduler(app=self.app)
+ sched = s.schedule
+ assert len(sched) == 2
+ assert 'celery.backend_cleanup' in sched
+ assert self.entry_name in sched
+ for n, e in sched.items():
+ assert isinstance(e, s.Entry)
+
+ def test_periodic_task_model_disabled_schedule(self):
+ self.m1.enabled = False
+ self.m1.save()
+
+ s = self.Scheduler(app=self.app)
+ sched = s.schedule
+ assert sched
+ assert len(sched) == 1
+ assert 'celery.backend_cleanup' in sched
+ assert self.entry_name not in sched
+
+
+ at pytest.mark.django_db()
class test_DatabaseScheduler(SchedulerCase):
Scheduler = TrackingScheduler
@@ -122,15 +188,28 @@ class test_DatabaseScheduler(SchedulerCase):
schedule(timedelta(seconds=10)))
self.m1.save()
self.m1.refresh_from_db()
+
self.m2 = self.create_model_interval(
schedule(timedelta(minutes=20)))
self.m2.save()
self.m2.refresh_from_db()
+
self.m3 = self.create_model_crontab(
... 202 lines suppressed ...
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-django-celery-beat.git
More information about the Python-modules-commits
mailing list