@@ -39,6 +39,9 @@ def failure_callback(job, connection, result, *args, **kwargs):
3939 mail_admins (f'Task { task .id } /{ task .name } has failed' ,
4040 'See django-admin for logs' , )
4141 task .job_id = None
42+ if isinstance (task , (CronTask , RepeatableTask )):
43+ task .failed_runs += 1
44+ task .last_failed_run = timezone .now ()
4245 task .save (schedule_job = True )
4346
4447
@@ -51,6 +54,9 @@ def success_callback(job, connection, result, *args, **kwargs):
5154 if task is None :
5255 return
5356 task .job_id = None
57+ if isinstance (task , (CronTask , RepeatableTask )):
58+ task .successful_runs += 1
59+ task .last_successful_run = timezone .now ()
5460 task .save (schedule_job = True )
5561
5662
@@ -76,9 +82,6 @@ class BaseTask(models.Model):
7682 job_id = models .CharField (
7783 _ ('job id' ), max_length = 128 , editable = False , blank = True , null = True ,
7884 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.' ), )
8285 at_front = models .BooleanField (
8386 _ ('At front' ), default = False , blank = True , null = True ,
8487 help_text = _ ('When queuing the job, add it in the front of the queue' ), )
@@ -104,14 +107,14 @@ def is_scheduled(self) -> bool:
104107 """Check whether a next job for this task is queued/scheduled to be executed"""
105108 if self .job_id is None : # no job_id => is not scheduled
106109 return False
107- # check whether job_id is in scheduled/enqueued /active jobs
110+ # check whether job_id is in scheduled/queued /active jobs
108111 scheduled_jobs = self .rqueue .scheduled_job_registry .get_job_ids ()
109112 enqueued_jobs = self .rqueue .get_job_ids ()
110113 active_jobs = self .rqueue .started_job_registry .get_job_ids ()
111114 res = ((self .job_id in scheduled_jobs )
112115 or (self .job_id in enqueued_jobs )
113116 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,
115118 # update the job_id to None. (The job_id belongs to a previous run which is completed)
116119 if not res :
117120 self .job_id = None
@@ -152,7 +155,6 @@ def _enqueue_args(self) -> Dict:
152155 """
153156 res = dict (
154157 meta = dict (
155- repeat = self .repeat ,
156158 task_type = self .TASK_TYPE ,
157159 scheduled_task_id = self .id ,
158160 ),
@@ -249,14 +251,18 @@ def to_dict(self) -> Dict:
249251 for arg in self .callable_kwargs .all ()],
250252 enabled = self .enabled ,
251253 queue = self .queue ,
252- repeat = self . repeat ,
254+ repeat = getattr ( self , ' repeat' , None ) ,
253255 at_front = self .at_front ,
254256 timeout = self .timeout ,
255257 result_ttl = self .result_ttl ,
256258 cron_string = getattr (self , 'cron_string' , None ),
257259 scheduled_time = self ._schedule_time ().isoformat (),
258260 interval = getattr (self , 'interval' , None ),
259261 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 ),
260266 )
261267 return res
262268
@@ -315,8 +321,25 @@ class Meta:
315321 abstract = True
316322
317323
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+
318342class ScheduledTask (ScheduledTimeMixin , BaseTask ):
319- repeat = None
320343 TASK_TYPE = 'ScheduledTask'
321344
322345 def ready_for_schedule (self ) -> bool :
@@ -330,7 +353,7 @@ class Meta:
330353 ordering = ('name' ,)
331354
332355
333- class RepeatableTask (ScheduledTimeMixin , BaseTask ):
356+ class RepeatableTask (RepeatableMixin , ScheduledTimeMixin , BaseTask ):
334357 class TimeUnits (models .TextChoices ):
335358 SECONDS = 'seconds' , _ ('seconds' )
336359 MINUTES = 'minutes' , _ ('minutes' )
@@ -342,6 +365,9 @@ class TimeUnits(models.TextChoices):
342365 interval_unit = models .CharField (
343366 _ ('interval unit' ), max_length = 12 , choices = TimeUnits .choices , default = TimeUnits .HOURS
344367 )
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.' ), )
345371 TASK_TYPE = 'RepeatableTask'
346372
347373 def clean (self ):
@@ -384,6 +410,7 @@ def interval_seconds(self):
384410 def _enqueue_args (self ):
385411 res = super (RepeatableTask , self )._enqueue_args ()
386412 res ['meta' ]['interval' ] = self .interval_seconds ()
413+ res ['meta' ]['repeat' ] = self .repeat
387414 return res
388415
389416 def _schedule_time (self ):
@@ -409,7 +436,7 @@ class Meta:
409436 ordering = ('name' ,)
410437
411438
412- class CronTask (BaseTask ):
439+ class CronTask (RepeatableMixin , BaseTask ):
413440 TASK_TYPE = 'CronTask'
414441
415442 cron_string = models .CharField (
0 commit comments