Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug fixes and improvements #34

Open
wants to merge 23 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
83cc432
add seconds option to interval_units with tests
Oct 10, 2018
5506b97
add seconds option to interval_units with tests
Oct 10, 2018
70b2928
Proposed handling of intervals in seconds.
tom-price May 26, 2019
46fa4b6
Added Django delete permission check introduced in 2.1
tom-price May 27, 2019
5aa1893
Admin site improvements, added bulk enable and disable
tom-price May 28, 2019
921fa8c
Changed schedule functions
tom-price May 28, 2019
f017817
Simplified Job factories through inheritance of base factory class.
tom-price May 28, 2019
80cc705
Overall compatibility bump by changes in the RQ package.
tom-price May 29, 2019
24cd808
Re-written tests
tom-price May 29, 2019
c386c71
Made better use of job factories' kwargs and de-duped some tests
tom-price May 29, 2019
6b78dd9
Added fakeredis server for testing and cleanup of unused code in models
tom-price May 29, 2019
9fb95ec
Updated README versions
tom-price May 29, 2019
9ee9beb
Ensures RepeatableJob result_ttls are long enough so next is rescheduled
Mar 12, 2019
6fc3443
Added Run Now action to admin.
tom-price May 29, 2019
56317d6
Merge remote-tracking branch 'upstream/master' into bug_fixes_and_imp…
tom-price May 29, 2019
cbd6d13
Merge pull request #1 from tom-price/seconds_interval_units
mattjegan Mar 6, 2020
7dd8cfc
Merge pull request #2 from isl-x/master
mattjegan Mar 9, 2020
7cbc198
Merge remote-tracking branch 'remotes/upstream/master' into seconds_i…
tom-price Sep 17, 2020
861c1ab
Fixed duplicate migration and added disclaimer to README.md
tom-price Sep 17, 2020
a3ac34b
Merge branch 'seconds_option_pr_29' into seconds_interval_units
tom-price Sep 17, 2020
b132ce6
Merge remote-tracking branch 'remotes/origin/seconds_interval_units' …
tom-price Sep 17, 2020
8b263e2
Removed FakeRedis due to issues it was causing and restricted Django 3.0
tom-price Sep 17, 2020
9894b14
Removed FakeRedis due to issues it was causing and restricted Django …
tom-price Sep 17, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,7 @@ target/

# SQLite
*.sqlite3

# Pipenv
Pipfile
Pipfile.lock
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ Currently, when you pip install Django RQ Scheduler the following packages are a

* django >= 1.9
* django-model-utils >= 2.4
* django-rq >= 0.9.3 (Django RQ requires RQ >= 0.5.5)
* rq-scheduler >= 0.6.0
* django-rq >= 2.0 (Django RQ requires RQ >= 1.0 due to changes in redis >= 3.0.0)
* rq-scheduler >= 0.9.0
* pytz >= 2015.7
* croniter >= 0.3.24

Expand Down Expand Up @@ -45,6 +45,10 @@ pip install django-rq-scheduler


```
If you also wish to run the underpinning **RQ Scheduler** at an interval different from its default of
once every 60 seconds you can do so by setting `DJANGO_RQ_SCHEDULER_INTERVAL` to the new preferred interval.
This is important if you want a job to either run multiple times a minute
or to schedule a job more precisely than within a 60 second window.

2. Configure Django RQ. See https://github.com/ui/django-rq#installation

Expand Down
138 changes: 85 additions & 53 deletions scheduler/admin.py
Original file line number Diff line number Diff line change
@@ -1,52 +1,105 @@
from __future__ import unicode_literals

from django.conf import settings
from django.contrib import admin
from django.contrib import admin, messages
from django.templatetags.tz import utc
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _

from scheduler.models import CronJob, RepeatableJob, ScheduledJob


QUEUES = [(key, key) for key in settings.RQ_QUEUES.keys()]


class QueueMixin(object):
actions = ['delete_model']
class JobAdmin(admin.ModelAdmin):
actions = ['delete_model', 'disable_selected', 'enable_selected', 'run_job_now']
list_filter = ('enabled', )
list_display = ('enabled', 'name', 'job_id', 'is_scheduled',)
list_display_links = ('name',)
readonly_fields = ('job_id', )
fieldsets = (
(None, {
'fields': ('name', 'callable', 'enabled',),
}),
(_('RQ Settings'), {
'fields': ('queue', 'job_id',),
}),
)

def get_actions(self, request):
actions = super(QueueMixin, self).get_actions(request)
del actions['delete_selected']
actions = super(JobAdmin, self).get_actions(request)
actions.pop('delete_selected', None)
return actions

def get_form(self, request, obj=None, **kwargs):
queue_field = self.model._meta.get_field('queue')
queue_field.choices = QUEUES
return super(QueueMixin, self).get_form(request, obj, **kwargs)
return super(JobAdmin, self).get_form(request, obj, **kwargs)

def delete_model(self, request, obj):
if hasattr(obj, 'all'):
for o in obj.all():
o.delete()
else:
def delete_model(self, request, queryset):
rows_deleted = 0
for obj in queryset.all().iterator():
obj.delete()
rows_deleted += 1
if rows_deleted == 1:
message_bit = "1 job was"
else:
message_bit = "%s jobs were" % rows_deleted

level = messages.WARNING if not rows_deleted else messages.INFO
self.message_user(request, "%s successfully deleted." % message_bit, level=level)
delete_model.short_description = _("Delete selected %(verbose_name_plural)s")
delete_model.allowed_permissions = ('delete',)

def disable_selected(self, request, queryset):
rows_updated = 0
for obj in queryset.filter(enabled=True).iterator():
obj.enabled = False
obj.save()
rows_updated += 1
if rows_updated == 1:
message_bit = "1 job was"
else:
message_bit = "%s jobs were" % rows_updated

level = messages.WARNING if not rows_updated else messages.INFO
self.message_user(request, "%s successfully disabled." % message_bit, level=level)
disable_selected.short_description = _("Disable selected %(verbose_name_plural)s")
disable_selected.allowed_permissions = ('change',)

def enable_selected(self, request, queryset):
rows_updated = 0
for obj in queryset.filter(enabled=False).iterator():
obj.enabled = True
obj.save()
rows_updated += 1
if rows_updated == 1:
message_bit = "1 job was"
else:
message_bit = "%s jobs were" % rows_updated
level = messages.WARNING if not rows_updated else messages.INFO
self.message_user(request, "%s successfully enabled." % message_bit, level=level)
enable_selected.short_description = _("Enable selected %(verbose_name_plural)s")
enable_selected.allowed_permissions = ('change',)

def run_job_now(self, request, queryset):
job_names = []
for obj in queryset:
kwargs = dict(timeout=obj.timeout)
if hasattr(obj, 'result_ttl') and obj.result_ttl is not None:
kwargs['job_result_ttl'] = obj.result_ttl
obj.scheduler().enqueue_at(utc(now()), obj.callable_func(), **kwargs)
job_names.append(obj.name)
self.message_user(request, "The following jobs have been run: %s" % ', '.join(job_names))
run_job_now.short_description = "Run now"
run_job_now.allowed_permissions = ('change',)


@admin.register(ScheduledJob)
class ScheduledJobAdmin(QueueMixin, admin.ModelAdmin):
list_display = (
'name', 'job_id', 'is_scheduled', 'scheduled_time', 'enabled')
list_filter = ('enabled', )
list_editable = ('enabled', )
class ScheduledJobAdmin(JobAdmin):
list_display = JobAdmin.list_display + ('scheduled_time',)

readonly_fields = ('job_id', )
fieldsets = (
(None, {
'fields': ('name', 'callable', 'enabled', ),
}),
(_('RQ Settings'), {
'fields': ('queue', 'job_id', ),
}),
fieldsets = JobAdmin.fieldsets + (
(_('Scheduling'), {
'fields': (
'scheduled_time',
Expand All @@ -58,48 +111,27 @@ class ScheduledJobAdmin(QueueMixin, admin.ModelAdmin):


@admin.register(RepeatableJob)
class RepeatableJobAdmin(QueueMixin, admin.ModelAdmin):
list_display = (
'name', 'job_id', 'is_scheduled', 'scheduled_time', 'interval_display',
'enabled')
list_filter = ('enabled', )
list_editable = ('enabled', )
class RepeatableJobAdmin(JobAdmin):
list_display = JobAdmin.list_display + ('scheduled_time', 'interval_display')

readonly_fields = ('job_id', )
fieldsets = (
(None, {
'fields': ('name', 'callable', 'enabled', ),
}),
(_('RQ Settings'), {
'fields': ('queue', 'job_id', ),
}),
fieldsets = JobAdmin.fieldsets + (
(_('Scheduling'), {
'fields': (
'scheduled_time',
('interval', 'interval_unit', ),
'repeat',
'timeout',
'result_ttl'
'result_ttl',
),
}),
)


@admin.register(CronJob)
class CronJobAdmin(QueueMixin, admin.ModelAdmin):
list_display = (
'name', 'job_id', 'is_scheduled', 'cron_string', 'enabled')
list_filter = ('enabled', )
list_editable = ('enabled', )
class CronJobAdmin(JobAdmin):
list_display = JobAdmin.list_display + ('cron_string',)

readonly_fields = ('job_id', )
fieldsets = (
(None, {
'fields': ('name', 'callable', 'enabled', ),
}),
(_('RQ Settings'), {
'fields': ('queue', 'job_id', ),
}),
fieldsets = JobAdmin.fieldsets + (
(_('Scheduling'), {
'fields': (
'cron_string',
Expand Down
18 changes: 18 additions & 0 deletions scheduler/migrations/0006_seconds_interval_units.py
Original file line number Diff line number Diff line change
@@ -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'),
),
]
Loading