class CompletedTask(models.Model): # the "name" of the task/function to be run task_name = models.CharField(max_length=255, db_index=True) # the json encoded parameters to pass to the task task_params = models.TextField() # a sha1 hash of the name and params, to lookup already scheduled tasks task_hash = models.CharField(max_length=40, db_index=True) verbose_name = models.CharField(max_length=255, null=True, blank=True) # what priority the task has priority = models.IntegerField(default=0, db_index=True) # when the task should be run run_at = models.DateTimeField(db_index=True) repeat = models.BigIntegerField(choices=Task.REPEAT_CHOICES, default=Task.NEVER) repeat_until = models.DateTimeField(null=True, blank=True) # the "name" of the queue this is to be run on queue = models.CharField(max_length=255, db_index=True, null=True, blank=True) # how many times the task has been tried attempts = models.IntegerField(default=0, db_index=True) # when the task last failed failed_at = models.DateTimeField(db_index=True, null=True, blank=True) # details of the error that occurred last_error = models.TextField(blank=True) # details of who's trying to run the task at the moment locked_by = models.CharField(max_length=64, db_index=True, null=True, blank=True) locked_at = models.DateTimeField(db_index=True, null=True, blank=True) creator_content_type = models.ForeignKey( ContentType, null=True, blank=True, related_name='completed_background_task', on_delete=models.CASCADE ) creator_object_id = models.PositiveIntegerField(null=True, blank=True) creator = GenericForeignKey('creator_content_type', 'creator_object_id') objects = CompletedTaskQuerySet.as_manager() def __str__(self): return u'{} - {}'.format( self.verbose_name or self.task_name, self.run_at, )
class Review(TimeStampedModel, models.Model): object_id = models.CharField(max_length=50, db_index=True) content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) content_object = GenericForeignKey() rating = models.ForeignKey(Rating, related_name='reviews', on_delete=models.CASCADE) user = models.ForeignKey(surveys_settings.REVIEWER_MODEL, on_delete=models.CASCADE) comment = models.TextField(blank=True) would_recommend = models.NullBooleanField(default=None) rating_value = None objects = ReviewManager() class Meta: unique_together = [ ("object_id", "content_type", "user"), ] def __str__(self): return six.text_type("{0} ({1})".format(self.name, self.rating)) @property def name(self): """ Returns the stored user name. """ if self.user is not None: return self.user.get_full_name() @property def is_negative(self): return self.rating.value and (self.rating.value < (self.rating.type.max_value / 2)) def save(self, *args, **kwargs): super(Review, self).save(*args, **kwargs) rated_obj = self.content_object signal_kwargs = { 'sender': rated_obj.__class__, 'instance': rated_obj, 'value': self.rating.value, 'is_negative': self.is_negative, } logger.debug("Firing signal with: %s", signal_kwargs) post_rating.send(**signal_kwargs)
class Task(models.Model): # the "name" of the task/function to be run task_name = models.CharField(max_length=190, db_index=True) # the json encoded parameters to pass to the task task_params = models.TextField() # a sha1 hash of the name and params, to lookup already scheduled tasks task_hash = models.CharField(max_length=40, db_index=True) verbose_name = models.CharField(max_length=255, null=True, blank=True) # what priority the task has priority = models.IntegerField(default=0, db_index=True) # when the task should be run run_at = models.DateTimeField(db_index=True) # Repeat choices are encoded as number of seconds # The repeat implementation is based on this encoding HOURLY = 3600 DAILY = 24 * HOURLY WEEKLY = 7 * DAILY EVERY_2_WEEKS = 2 * WEEKLY EVERY_4_WEEKS = 4 * WEEKLY NEVER = 0 REPEAT_CHOICES = ( (HOURLY, 'hourly'), (DAILY, 'daily'), (WEEKLY, 'weekly'), (EVERY_2_WEEKS, 'every 2 weeks'), (EVERY_4_WEEKS, 'every 4 weeks'), (NEVER, 'never'), ) repeat = models.BigIntegerField(choices=REPEAT_CHOICES, default=NEVER) repeat_until = models.DateTimeField(null=True, blank=True) # the "name" of the queue this is to be run on queue = models.CharField(max_length=190, db_index=True, null=True, blank=True) # how many times the task has been tried attempts = models.IntegerField(default=0, db_index=True) # when the task last failed failed_at = models.DateTimeField(db_index=True, null=True, blank=True) # details of the error that occurred last_error = models.TextField(blank=True) # details of who's trying to run the task at the moment locked_by = models.CharField(max_length=64, db_index=True, null=True, blank=True) locked_at = models.DateTimeField(db_index=True, null=True, blank=True) creator_content_type = models.ForeignKey(ContentType, null=True, blank=True, related_name='background_task', on_delete=models.CASCADE) creator_object_id = models.PositiveIntegerField(null=True, blank=True) creator = GenericForeignKey('creator_content_type', 'creator_object_id') objects = TaskManager() def locked_by_pid_running(self): """ Check if the locked_by process is still running. """ if self.locked_by: try: # won't kill the process. kill is a bad named system call os.kill(int(self.locked_by), 0) return True except: return False else: return None locked_by_pid_running.boolean = True def has_error(self): """ Check if the last_error field is empty. """ return bool(self.last_error) has_error.boolean = True def params(self): args, kwargs = json.loads(self.task_params) # need to coerce kwargs keys to str kwargs = dict((str(k), v) for k, v in kwargs.items()) return args, kwargs def lock(self, locked_by): now = timezone.now() unlocked = Task.objects.unlocked(now).filter(pk=self.pk) updated = unlocked.update(locked_by=locked_by, locked_at=now) if updated: return Task.objects.get(pk=self.pk) return None def _extract_error(self, type, err, tb): file = StringIO() traceback.print_exception(type, err, tb, None, file) return file.getvalue() def increment_attempts(self): self.attempts += 1 self.save() def has_reached_max_attempts(self): max_attempts = app_settings.BACKGROUND_TASK_MAX_ATTEMPTS return self.attempts >= max_attempts def is_repeating_task(self): return self.repeat > self.NEVER def reschedule(self, type, err, traceback): ''' Set a new time to run the task in future, or create a CompletedTask and delete the Task if it has reached the maximum of allowed attempts ''' self.last_error = self._extract_error(type, err, traceback) self.increment_attempts() if self.has_reached_max_attempts() or isinstance( err, InvalidTaskError): self.failed_at = timezone.now() logger.warning('Marking task %s as failed', self) completed = self.create_completed_task() task_failed.send(sender=self.__class__, task_id=self.id, completed_task=completed) self.delete() else: backoff = timedelta(seconds=(self.attempts**4) + 5) self.run_at = timezone.now() + backoff logger.warning('Rescheduling task %s for %s later at %s', self, backoff, self.run_at) task_rescheduled.send(sender=self.__class__, task=self) self.locked_by = None self.locked_at = None self.save() def create_completed_task(self): ''' Returns a new CompletedTask instance with the same values ''' from background_task.models_completed import CompletedTask completed_task = CompletedTask( task_name=self.task_name, task_params=self.task_params, task_hash=self.task_hash, priority=self.priority, run_at=timezone.now(), queue=self.queue, attempts=self.attempts, failed_at=self.failed_at, last_error=self.last_error, locked_by=self.locked_by, locked_at=self.locked_at, verbose_name=self.verbose_name, creator=self.creator, repeat=self.repeat, repeat_until=self.repeat_until, ) completed_task.save() return completed_task def create_repetition(self): """ :return: A new Task with an offset of self.repeat, or None if the self.repeat_until is reached """ if not self.is_repeating_task(): return None if self.repeat_until and self.repeat_until <= timezone.now(): # Repeat chain completed return None args, kwargs = self.params() new_run_at = self.run_at + timedelta(seconds=self.repeat) while new_run_at < timezone.now(): new_run_at += timedelta(seconds=self.repeat) new_task = TaskManager().new_task( task_name=self.task_name, args=args, kwargs=kwargs, run_at=new_run_at, priority=self.priority, queue=self.queue, verbose_name=self.verbose_name, creator=self.creator, repeat=self.repeat, repeat_until=self.repeat_until, ) new_task.save() return new_task def save(self, *arg, **kw): # force NULL rather than empty string self.locked_by = self.locked_by or None return super(Task, self).save(*arg, **kw) def __str__(self): return u'{}'.format(self.verbose_name or self.task_name) class Meta: # db_table = 'background_task' app_label = 'background_task'
class CompletedTask(models.Model): # the "name" of the task/function to be run task_name = models.CharField(max_length=190, db_index=True) # additional fields to query tasks from outside. task_uid = models.CharField(max_length=50, db_index=True, default=None, null=True) task_group = models.CharField(max_length=50, db_index=True, default=None, null=True) # the json encoded parameters to pass to the task task_params = models.TextField() # a sha1 hash of the name and params, to lookup already scheduled tasks task_hash = models.CharField(max_length=40, db_index=True) verbose_name = models.CharField(max_length=255, null=True, blank=True) # what priority the task has priority = models.IntegerField(default=0, db_index=True) # when the task should be run run_at = models.DateTimeField(db_index=True) repeat = models.BigIntegerField(choices=Task.REPEAT_CHOICES, default=Task.NEVER) repeat_until = models.DateTimeField(null=True, blank=True) # the "name" of the queue this is to be run on queue = models.CharField(max_length=190, db_index=True, null=True, blank=True) # how many times the task has been tried attempts = models.IntegerField(default=0, db_index=True) # when the task last failed failed_at = models.DateTimeField(db_index=True, null=True, blank=True) # details of the error that occurred last_error = models.TextField(blank=True) # details of who's trying to run the task at the moment locked_by = models.CharField(max_length=64, db_index=True, null=True, blank=True) locked_at = models.DateTimeField(db_index=True, null=True, blank=True) creator_content_type = models.ForeignKey( ContentType, null=True, blank=True, related_name='completed_background_task', on_delete=models.CASCADE) creator_object_id = models.PositiveIntegerField(null=True, blank=True) creator = GenericForeignKey('creator_content_type', 'creator_object_id') objects = CompletedTaskQuerySet.as_manager() def locked_by_pid_running(self): """ Check if the locked_by process is still running. """ if self.locked_by: try: # won't kill the process. kill is a bad named system call os.kill(int(self.locked_by), 0) return True except: return False else: return None locked_by_pid_running.boolean = True def has_error(self): """ Check if the last_error field is empty. """ return bool(self.last_error) has_error.boolean = True def __str__(self): return u'{} - {}'.format( self.verbose_name or self.task_name, self.run_at, )