@@ -39,6 +39,9 @@ def failure_callback(job, connection, result, *args, **kwargs):
39
39
mail_admins (f'Task { task .id } /{ task .name } has failed' ,
40
40
'See django-admin for logs' , )
41
41
task .job_id = None
42
+ if isinstance (task , (CronTask , RepeatableTask )):
43
+ task .failed_runs += 1
44
+ task .last_failed_run = timezone .now ()
42
45
task .save (schedule_job = True )
43
46
44
47
@@ -51,6 +54,9 @@ def success_callback(job, connection, result, *args, **kwargs):
51
54
if task is None :
52
55
return
53
56
task .job_id = None
57
+ if isinstance (task , (CronTask , RepeatableTask )):
58
+ task .successful_runs += 1
59
+ task .last_successful_run = timezone .now ()
54
60
task .save (schedule_job = True )
55
61
56
62
@@ -76,9 +82,6 @@ class BaseTask(models.Model):
76
82
job_id = models .CharField (
77
83
_ ('job id' ), max_length = 128 , editable = False , blank = True , null = True ,
78
84
help_text = _ ('Current job_id on queue' ))
79
- repeat = models .PositiveIntegerField (
80
- _ ('repeat' ), blank = True , null = True ,
81
- help_text = _ ('Number of times to run the job. Leaving this blank means it will run forever.' ), )
82
85
at_front = models .BooleanField (
83
86
_ ('At front' ), default = False , blank = True , null = True ,
84
87
help_text = _ ('When queuing the job, add it in the front of the queue' ), )
@@ -104,14 +107,14 @@ def is_scheduled(self) -> bool:
104
107
"""Check whether a next job for this task is queued/scheduled to be executed"""
105
108
if self .job_id is None : # no job_id => is not scheduled
106
109
return False
107
- # check whether job_id is in scheduled/enqueued /active jobs
110
+ # check whether job_id is in scheduled/queued /active jobs
108
111
scheduled_jobs = self .rqueue .scheduled_job_registry .get_job_ids ()
109
112
enqueued_jobs = self .rqueue .get_job_ids ()
110
113
active_jobs = self .rqueue .started_job_registry .get_job_ids ()
111
114
res = ((self .job_id in scheduled_jobs )
112
115
or (self .job_id in enqueued_jobs )
113
116
or (self .job_id in active_jobs ))
114
- # If the job_id is not scheduled/enqueued /started,
117
+ # If the job_id is not scheduled/queued /started,
115
118
# update the job_id to None. (The job_id belongs to a previous run which is completed)
116
119
if not res :
117
120
self .job_id = None
@@ -152,7 +155,6 @@ def _enqueue_args(self) -> Dict:
152
155
"""
153
156
res = dict (
154
157
meta = dict (
155
- repeat = self .repeat ,
156
158
task_type = self .TASK_TYPE ,
157
159
scheduled_task_id = self .id ,
158
160
),
@@ -249,14 +251,18 @@ def to_dict(self) -> Dict:
249
251
for arg in self .callable_kwargs .all ()],
250
252
enabled = self .enabled ,
251
253
queue = self .queue ,
252
- repeat = self . repeat ,
254
+ repeat = getattr ( self , ' repeat' , None ) ,
253
255
at_front = self .at_front ,
254
256
timeout = self .timeout ,
255
257
result_ttl = self .result_ttl ,
256
258
cron_string = getattr (self , 'cron_string' , None ),
257
259
scheduled_time = self ._schedule_time ().isoformat (),
258
260
interval = getattr (self , 'interval' , None ),
259
261
interval_unit = getattr (self , 'interval_unit' , None ),
262
+ successful_runs = getattr (self , 'successful_runs' , None ),
263
+ failed_runs = getattr (self , 'failed_runs' , None ),
264
+ last_successful_run = getattr (self , 'last_successful_run' , None ),
265
+ last_failed_run = getattr (self , 'last_failed_run' , None ),
260
266
)
261
267
return res
262
268
@@ -315,8 +321,25 @@ class Meta:
315
321
abstract = True
316
322
317
323
324
+ class RepeatableMixin (models .Model ):
325
+ failed_runs = models .PositiveIntegerField (
326
+ _ ('failed runs' ), default = 0 ,
327
+ help_text = _ ('Number of times the task has failed' ), )
328
+ successful_runs = models .PositiveIntegerField (
329
+ _ ('successful runs' ), default = 0 ,
330
+ help_text = _ ('Number of times the task has succeeded' ), )
331
+ last_successful_run = models .DateTimeField (
332
+ _ ('last successful run' ), blank = True , null = True ,
333
+ help_text = _ ('Last time the task has succeeded' ), )
334
+ last_failed_run = models .DateTimeField (
335
+ _ ('last failed run' ), blank = True , null = True ,
336
+ help_text = _ ('Last time the task has failed' ), )
337
+
338
+ class Meta :
339
+ abstract = True
340
+
341
+
318
342
class ScheduledTask (ScheduledTimeMixin , BaseTask ):
319
- repeat = None
320
343
TASK_TYPE = 'ScheduledTask'
321
344
322
345
def ready_for_schedule (self ) -> bool :
@@ -330,7 +353,7 @@ class Meta:
330
353
ordering = ('name' ,)
331
354
332
355
333
- class RepeatableTask (ScheduledTimeMixin , BaseTask ):
356
+ class RepeatableTask (RepeatableMixin , ScheduledTimeMixin , BaseTask ):
334
357
class TimeUnits (models .TextChoices ):
335
358
SECONDS = 'seconds' , _ ('seconds' )
336
359
MINUTES = 'minutes' , _ ('minutes' )
@@ -342,6 +365,9 @@ class TimeUnits(models.TextChoices):
342
365
interval_unit = models .CharField (
343
366
_ ('interval unit' ), max_length = 12 , choices = TimeUnits .choices , default = TimeUnits .HOURS
344
367
)
368
+ repeat = models .PositiveIntegerField (
369
+ _ ('repeat' ), blank = True , null = True ,
370
+ help_text = _ ('Number of times to run the job. Leaving this blank means it will run forever.' ), )
345
371
TASK_TYPE = 'RepeatableTask'
346
372
347
373
def clean (self ):
@@ -384,6 +410,7 @@ def interval_seconds(self):
384
410
def _enqueue_args (self ):
385
411
res = super (RepeatableTask , self )._enqueue_args ()
386
412
res ['meta' ]['interval' ] = self .interval_seconds ()
413
+ res ['meta' ]['repeat' ] = self .repeat
387
414
return res
388
415
389
416
def _schedule_time (self ):
@@ -409,7 +436,7 @@ class Meta:
409
436
ordering = ('name' ,)
410
437
411
438
412
- class CronTask (BaseTask ):
439
+ class CronTask (RepeatableMixin , BaseTask ):
413
440
TASK_TYPE = 'CronTask'
414
441
415
442
cron_string = models .CharField (
0 commit comments