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,
        )
示例#2
0
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)
示例#3
0
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,
        )