mirror of
https://github.com/django-q2/django-q2.git
synced 2026-10-10 01:00:55 +08:00
Adds task groups
* task now takes an optional keyword `group` * added result_group() and fetch_group() * added group column to Success admin * group column in searchable * added `name` field to Schedules * Scheduled tasks take schedule name as group
This commit is contained in:
@@ -4,7 +4,7 @@ import sys
|
|||||||
myPath = os.path.dirname(os.path.abspath(__file__))
|
myPath = os.path.dirname(os.path.abspath(__file__))
|
||||||
sys.path.insert(0, myPath)
|
sys.path.insert(0, myPath)
|
||||||
|
|
||||||
from .tasks import async, schedule, result, fetch
|
from .tasks import async, schedule, result, result_group, fetch, fetch_group
|
||||||
from .models import Task, Schedule, Success, Failure
|
from .models import Task, Schedule, Success, Failure
|
||||||
from .cluster import Cluster
|
from .cluster import Cluster
|
||||||
|
|
||||||
|
|||||||
+5
-3
@@ -11,7 +11,8 @@ class TaskAdmin(admin.ModelAdmin):
|
|||||||
'func',
|
'func',
|
||||||
'started',
|
'started',
|
||||||
'stopped',
|
'stopped',
|
||||||
'time_taken'
|
'time_taken',
|
||||||
|
'group'
|
||||||
)
|
)
|
||||||
|
|
||||||
def has_add_permission(self, request, obj=None):
|
def has_add_permission(self, request, obj=None):
|
||||||
@@ -23,7 +24,7 @@ class TaskAdmin(admin.ModelAdmin):
|
|||||||
qs = super(TaskAdmin, self).get_queryset(request)
|
qs = super(TaskAdmin, self).get_queryset(request)
|
||||||
return qs.filter(success=True)
|
return qs.filter(success=True)
|
||||||
|
|
||||||
search_fields = ('name', 'func')
|
search_fields = ('name', 'func', 'group')
|
||||||
readonly_fields = []
|
readonly_fields = []
|
||||||
|
|
||||||
def get_readonly_fields(self, request, obj=None):
|
def get_readonly_fields(self, request, obj=None):
|
||||||
@@ -65,6 +66,7 @@ class FailAdmin(admin.ModelAdmin):
|
|||||||
class ScheduleAdmin(admin.ModelAdmin):
|
class ScheduleAdmin(admin.ModelAdmin):
|
||||||
list_display = (
|
list_display = (
|
||||||
'id',
|
'id',
|
||||||
|
'name',
|
||||||
'func',
|
'func',
|
||||||
'schedule_type',
|
'schedule_type',
|
||||||
'repeats',
|
'repeats',
|
||||||
@@ -75,7 +77,7 @@ class ScheduleAdmin(admin.ModelAdmin):
|
|||||||
|
|
||||||
list_filter = ('next_run', 'schedule_type')
|
list_filter = ('next_run', 'schedule_type')
|
||||||
search_fields = ('func',)
|
search_fields = ('func',)
|
||||||
list_display_links = ('id', 'func')
|
list_display_links = ('id', 'name')
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(Schedule, ScheduleAdmin)
|
admin.site.register(Schedule, ScheduleAdmin)
|
||||||
|
|||||||
+7
-4
@@ -404,12 +404,13 @@ def save_task(task):
|
|||||||
Task.objects.create(id=task['id'],
|
Task.objects.create(id=task['id'],
|
||||||
name=task['name'],
|
name=task['name'],
|
||||||
func=task['func'],
|
func=task['func'],
|
||||||
hook=task['hook'],
|
hook=task.get('hook'),
|
||||||
args=task['args'],
|
args=task['args'],
|
||||||
kwargs=task['kwargs'],
|
kwargs=task['kwargs'],
|
||||||
started=task['started'],
|
started=task['started'],
|
||||||
stopped=task['stopped'],
|
stopped=task['stopped'],
|
||||||
result=task['result'],
|
result=task['result'],
|
||||||
|
group=task.get('group'),
|
||||||
success=task['success'])
|
success=task['success'])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
@@ -455,13 +456,15 @@ def scheduler(list_key=Conf.Q_LIST):
|
|||||||
s.repeats += -1
|
s.repeats += -1
|
||||||
# send it to the cluster
|
# send it to the cluster
|
||||||
kwargs['list_key'] = list_key
|
kwargs['list_key'] = list_key
|
||||||
|
kwargs['group'] = s.name or s.id
|
||||||
s.task = tasks.async(s.func, *args, **kwargs)
|
s.task = tasks.async(s.func, *args, **kwargs)
|
||||||
# log it
|
# log it
|
||||||
if not s.task:
|
if not s.task:
|
||||||
logger.error(_('{} failed to create a task from schedule {} [{}]').format(current_process().name, s.id),
|
logger.error(
|
||||||
s.func)
|
_('{} failed to create a task from schedule [{}]').format(current_process().name, s.name or s.id))
|
||||||
else:
|
else:
|
||||||
logger.info(_('{} created a task from schedule {} [{}]').format(current_process().name, s.id, s.func))
|
logger.info(
|
||||||
|
_('{} created a task from schedule [{}]').format(current_process().name, s.name or s.id))
|
||||||
# default behavior is to delete a ONCE schedule
|
# default behavior is to delete a ONCE schedule
|
||||||
if s.schedule_type == s.ONCE:
|
if s.schedule_type == s.ONCE:
|
||||||
if s.repeats < 0:
|
if s.repeats < 0:
|
||||||
|
|||||||
@@ -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', '0004_auto_20150710_1043'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='schedule',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(max_length=256, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='task',
|
||||||
|
name='group',
|
||||||
|
field=models.CharField(max_length=100, null=True, editable=False),
|
||||||
|
),
|
||||||
|
]
|
||||||
+12
-1
@@ -1,6 +1,6 @@
|
|||||||
import importlib
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
import importlib
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.db import models
|
from django.db import models
|
||||||
@@ -18,6 +18,7 @@ class Task(models.Model):
|
|||||||
args = PickledObjectField(null=True)
|
args = PickledObjectField(null=True)
|
||||||
kwargs = PickledObjectField(null=True)
|
kwargs = PickledObjectField(null=True)
|
||||||
result = PickledObjectField(null=True)
|
result = PickledObjectField(null=True)
|
||||||
|
group = models.CharField(max_length=100, editable=False, null=True)
|
||||||
started = models.DateTimeField(editable=False)
|
started = models.DateTimeField(editable=False)
|
||||||
stopped = models.DateTimeField(editable=False)
|
stopped = models.DateTimeField(editable=False)
|
||||||
success = models.BooleanField(default=True, editable=False)
|
success = models.BooleanField(default=True, editable=False)
|
||||||
@@ -29,6 +30,11 @@ class Task(models.Model):
|
|||||||
elif Task.objects.filter(name=task_id).exists():
|
elif Task.objects.filter(name=task_id).exists():
|
||||||
return Task.objects.get(name=task_id).result
|
return Task.objects.get(name=task_id).result
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_group_result(group_id):
|
||||||
|
# values_list() doesn't work here cause it returns encoded fields
|
||||||
|
return [t.result for t in Task.get_task_group(group_id)]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_task(task_id):
|
def get_task(task_id):
|
||||||
if len(task_id) == 32 and Task.objects.filter(id=task_id).exists():
|
if len(task_id) == 32 and Task.objects.filter(id=task_id).exists():
|
||||||
@@ -36,6 +42,10 @@ class Task(models.Model):
|
|||||||
elif Task.objects.filter(name=task_id).exists():
|
elif Task.objects.filter(name=task_id).exists():
|
||||||
return Task.objects.get(name=task_id)
|
return Task.objects.get(name=task_id)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_task_group(group_id):
|
||||||
|
return Task.objects.filter(group=group_id)
|
||||||
|
|
||||||
def time_taken(self):
|
def time_taken(self):
|
||||||
return (self.stopped - self.started).total_seconds()
|
return (self.stopped - self.started).total_seconds()
|
||||||
|
|
||||||
@@ -101,6 +111,7 @@ class Failure(Task):
|
|||||||
|
|
||||||
|
|
||||||
class Schedule(models.Model):
|
class Schedule(models.Model):
|
||||||
|
name = models.CharField(max_length=256, null=True)
|
||||||
func = models.CharField(max_length=256, help_text='e.g. module.tasks.function')
|
func = models.CharField(max_length=256, help_text='e.g. module.tasks.function')
|
||||||
hook = models.CharField(max_length=256, null=True, blank=True, help_text='e.g. module.tasks.result_function')
|
hook = models.CharField(max_length=256, null=True, blank=True, help_text='e.g. module.tasks.result_function')
|
||||||
args = models.TextField(null=True, blank=True, help_text=_("e.g. 1, 2, 'John'"))
|
args = models.TextField(null=True, blank=True, help_text=_("e.g. 1, 2, 'John'"))
|
||||||
|
|||||||
+31
-5
@@ -25,20 +25,27 @@ def async(func, *args, **kwargs):
|
|||||||
# optional list_key
|
# optional list_key
|
||||||
list_key = kwargs.pop('list_key', Conf.Q_LIST)
|
list_key = kwargs.pop('list_key', Conf.Q_LIST)
|
||||||
# optional redis connection
|
# optional redis connection
|
||||||
r = kwargs.pop('redis', redis_client)
|
redis = kwargs.pop('redis', redis_client)
|
||||||
# optional sync mode
|
# optional sync mode
|
||||||
s = kwargs.pop('sync', False)
|
sync = kwargs.pop('sync', False)
|
||||||
|
# optional group
|
||||||
|
group = kwargs.pop('group', None)
|
||||||
# get an id
|
# get an id
|
||||||
tag = uuid()
|
tag = uuid()
|
||||||
# build the task package
|
# build the task package
|
||||||
task = {'id': tag[1], 'name': tag[0], 'func': func, 'hook': hook, 'args': args, 'kwargs': kwargs,
|
task = {'id': tag[1], 'name': tag[0], 'func': func, 'args': args, 'kwargs': kwargs,
|
||||||
'started': timezone.now()}
|
'started': timezone.now()}
|
||||||
|
# add optionals
|
||||||
|
if hook:
|
||||||
|
task['hook'] = hook
|
||||||
|
if group:
|
||||||
|
task['group'] = group
|
||||||
# sign it
|
# sign it
|
||||||
pack = signing.SignedPackage.dumps(task)
|
pack = signing.SignedPackage.dumps(task)
|
||||||
if s:
|
if sync:
|
||||||
return _sync(task['id'], pack)
|
return _sync(task['id'], pack)
|
||||||
# push it
|
# push it
|
||||||
r.rpush(list_key, pack)
|
redis.rpush(list_key, pack)
|
||||||
logger.debug('Pushed {}'.format(tag))
|
logger.debug('Pushed {}'.format(tag))
|
||||||
return task['id']
|
return task['id']
|
||||||
|
|
||||||
@@ -83,6 +90,15 @@ def result(task_id):
|
|||||||
return Task.get_result(task_id)
|
return Task.get_result(task_id)
|
||||||
|
|
||||||
|
|
||||||
|
def result_group(group_id):
|
||||||
|
"""
|
||||||
|
returns a list of results for a task group
|
||||||
|
:param str group_id: the group id
|
||||||
|
:return: list or results
|
||||||
|
"""
|
||||||
|
return Task.get_group_result(group_id)
|
||||||
|
|
||||||
|
|
||||||
def fetch(task_id):
|
def fetch(task_id):
|
||||||
"""
|
"""
|
||||||
Returns the processed task
|
Returns the processed task
|
||||||
@@ -94,6 +110,16 @@ def fetch(task_id):
|
|||||||
return Task.get_task(task_id)
|
return Task.get_task(task_id)
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_group(group_id):
|
||||||
|
"""
|
||||||
|
Returns a list of Tasks for a task group
|
||||||
|
:param str group_id: the group id
|
||||||
|
:return: list of Tasks
|
||||||
|
"""
|
||||||
|
|
||||||
|
return Task.get_task_group(group_id)
|
||||||
|
|
||||||
|
|
||||||
def _sync(task_id, pack):
|
def _sync(task_id, pack):
|
||||||
"""
|
"""
|
||||||
Simulates a package travelling through the cluster.
|
Simulates a package travelling through the cluster.
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ sys.path.insert(0, myPath + '/../')
|
|||||||
|
|
||||||
from django_q.cluster import Cluster, Sentinel, pusher, worker, monitor
|
from django_q.cluster import Cluster, Sentinel, pusher, worker, monitor
|
||||||
from django_q.humanhash import DEFAULT_WORDLIST
|
from django_q.humanhash import DEFAULT_WORDLIST
|
||||||
from django_q.tasks import fetch, async, result
|
from django_q.tasks import fetch, fetch_group, async, result, result_group
|
||||||
from django_q.models import Task
|
from django_q.models import Task
|
||||||
from django_q.conf import Conf, redis_client
|
from django_q.conf import Conf, redis_client
|
||||||
from .tasks import multiply
|
from .tasks import multiply
|
||||||
@@ -119,10 +119,10 @@ def test_async(r, admin_user):
|
|||||||
f = async(multiply, 753, 2, hook=assert_result, list_key=list_key, redis=r)
|
f = async(multiply, 753, 2, hook=assert_result, list_key=list_key, redis=r)
|
||||||
# model as argument
|
# model as argument
|
||||||
g = async('django_q.tests.tasks.get_task_name', Task(name='John'), list_key=list_key, redis=r)
|
g = async('django_q.tests.tasks.get_task_name', Task(name='John'), list_key=list_key, redis=r)
|
||||||
# args and kwargs and broken hook
|
# args,kwargs, group and broken hook
|
||||||
h = async('django_q.tests.tasks.word_multiply', 2, word='django', hook='fail.me', list_key=list_key, redis=r)
|
h = async('django_q.tests.tasks.word_multiply', 2, word='django', hook='fail.me', list_key=list_key, redis=r)
|
||||||
# args unpickle test
|
# args unpickle test
|
||||||
j = async('django_q.tests.tasks.get_user_id', admin_user, list_key=list_key, redis=r)
|
j = async('django_q.tests.tasks.get_user_id', admin_user, list_key=list_key, group='test_j', redis=r)
|
||||||
# check if everything has a task id
|
# check if everything has a task id
|
||||||
assert isinstance(a, str)
|
assert isinstance(a, str)
|
||||||
assert isinstance(b, str)
|
assert isinstance(b, str)
|
||||||
@@ -197,6 +197,8 @@ def test_async(r, admin_user):
|
|||||||
assert result_j is not None
|
assert result_j is not None
|
||||||
assert result_j.success is True
|
assert result_j.success is True
|
||||||
assert result_j.result == result_j.args[0].id
|
assert result_j.result == result_j.args[0].id
|
||||||
|
assert result_group('test_j') == [result_j.result]
|
||||||
|
assert fetch_group('test_j')[0].id == [result_j][0].id
|
||||||
r.delete(list_key)
|
r.delete(list_key)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user