Example #1
0
class Migration(migrations.Migration):

    dependencies = [
        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
    ]

    operations = [
        migrations.CreateModel(
            name='BlockCompletion',
            fields=[
                ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)),
                ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)),
                ('id', BigAutoField(serialize=False, primary_key=True)),
                ('course_key', CourseKeyField(max_length=255)),
                ('block_key', UsageKeyField(max_length=255)),
                ('block_type', models.CharField(max_length=64)),
                ('completion', models.FloatField(validators=[validate_percent])),
                ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
            ],
        ),
        migrations.AlterUniqueTogether(
            name='blockcompletion',
            unique_together=set([('course_key', 'block_key', 'user')]),
        ),
        migrations.AlterIndexTogether(
            name='blockcompletion',
            index_together=set([('course_key', 'block_type', 'user'), ('user', 'course_key', 'modified')]),
        ),
    ]
Example #2
0
class BlockCompletion(TimeStampedModel, models.Model):
    """
    Track completion of completable blocks.

    A completion is unique for each (user, course_key, block_key).

    The block_type field is included separately from the block_key to
    facilitate distinct aggregations of the completion of particular types of
    block.

    The completion value is stored as a float in the range [0.0, 1.0], and all
    calculations are performed on this float, though current practice is to
    only track binary completion, where 1.0 indicates that the block is
    complete, and 0.0 indicates that the block is incomplete.
    """
    id = BigAutoField(primary_key=True)  # pylint: disable=invalid-name
    user = models.ForeignKey(User)
    course_key = CourseKeyField(max_length=255)
    block_key = UsageKeyField(max_length=255)
    block_type = models.CharField(max_length=64)
    completion = models.FloatField(validators=[validate_percent])

    objects = BlockCompletionManager()

    class Meta(object):
        index_together = [
            ('course_key', 'block_type', 'user'),
            ('user', 'course_key', 'modified'),
        ]

        unique_together = [('course_key', 'block_key', 'user')]

    def __unicode__(self):
        return 'BlockCompletion: {username}, {course_key}, {block_key}: {completion}'.format(
            username=self.user.username,
            course_key=self.course_key,
            block_key=self.block_key,
            completion=self.completion,
        )
Example #3
0
class BlockCompletion(TimeStampedModel, models.Model):
    """
    Track completion of completable blocks.

    A completion is unique for each (user, context_key, block_key).

    The block_type field is included separately from the block_key to
    facilitate distinct aggregations of the completion of particular types of
    block.

    The completion value is stored as a float in the range [0.0, 1.0], and all
    calculations are performed on this float, though current practice is to
    only track binary completion, where 1.0 indicates that the block is
    complete, and 0.0 indicates that the block is incomplete.
    """
    id = BigAutoField(primary_key=True)  # pylint: disable=invalid-name
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    context_key = LearningContextKeyField(max_length=255,
                                          db_column="course_key")

    # note: this usage key may not have the run filled in for
    # old mongo courses.  Use the full_block_key property
    # instead when you want to use/compare the usage_key.
    block_key = UsageKeyField(max_length=255)
    block_type = models.CharField(max_length=64)
    completion = models.FloatField(validators=[validate_percent])

    objects = BlockCompletionManager()

    @property
    def full_block_key(self):
        """
        Returns the "correct" usage key value with the run filled in.
        This is only necessary for block keys from old mongo courses, which
        didn't include the run information in the block usage key.
        """
        if self.block_key.context_key.is_course and self.block_key.run is None:
            # pylint: disable=unexpected-keyword-arg, no-value-for-parameter
            return self.block_key.replace(course_key=self.context_key)
        return self.block_key

    @classmethod
    def get_learning_context_completions(cls, user, context_key):
        """
        Returns a dictionary mapping BlockKeys to completion values for all
        BlockCompletion records for the given user and learning context key.

        Return value:
            dict[BlockKey] = float
        """
        user_completions = cls.user_learning_context_completion_queryset(
            user, context_key)
        return cls.completion_by_block_key(user_completions)

    @classmethod
    def user_learning_context_completion_queryset(cls, user, context_key):
        """
        Returns a Queryset of completions for a given user and context_key.
        """
        return cls.objects.filter(user=user, context_key=context_key)

    @classmethod
    def latest_blocks_completed_all_courses(cls, user):
        """
        Returns a dictionary mapping course_keys to a tuple containing
        the block_key and modified time of the most recently modified
        completion for the course.

        This only returns results for courses and not other learning context
        types.

        Return value:
            {course_key: (modified_date, block_key)}
        """

        # Per the Django docs, dictionary params are not supported with the SQLite backend;
        # with this backend, you must pass parameters as a list. We use SQLite for unit tests,
        # so the same parameter is included twice in the parameter list below, rather than
        # including it in a dictionary once.
        latest_completions_by_course = cls.objects.raw(
            '''
            SELECT
                cbc.id AS id,
                cbc.course_key AS course_key,
                cbc.block_key AS block_key,
                cbc.modified AS modified
            FROM
                completion_blockcompletion cbc
            JOIN (
                SELECT
                     course_key,
                     MAX(modified) AS modified
                FROM
                     completion_blockcompletion
                WHERE
                     user_id = %s
                GROUP BY
                     course_key
            ) latest
            ON
                cbc.course_key = latest.course_key AND
                cbc.modified = latest.modified
            WHERE
                user_id = %s
            ;
            ''', [user.id, user.id])
        try:
            return {
                completion.context_key:
                (completion.modified, completion.block_key)
                for completion in latest_completions_by_course
                if completion.context_key.is_course
            }
        except KeyError:
            # Iteration of the queryset above will always fail
            # with a KeyError if the queryset is empty
            return {}

    @classmethod
    def get_latest_block_completed(cls, user, context_key):
        """
        Returns a BlockCompletion Object for the last modified user/context_key mapping,
        or None if no such BlockCompletion exists.

        Return value:
            obj: block completion
        """
        try:
            latest_block_completion = cls.user_learning_context_completion_queryset(
                user, context_key).latest()  # pylint: disable=no-member
        except cls.DoesNotExist:
            return None
        return latest_block_completion

    @staticmethod
    def completion_by_block_key(completion_iterable):
        """
        Return value:
            A dict mapping the full block key of a completion record to the completion value
            for each BlockCompletion object given in completion_iterable.  Each BlockKey is
            corrected to have the run field filled in via the BlockCompletion.context_key field.
        """
        return {
            completion.full_block_key: completion.completion
            for completion in completion_iterable
        }

    class Meta:
        index_together = [
            ('context_key', 'block_type', 'user'),
            ('user', 'context_key', 'modified'),
        ]

        unique_together = [('context_key', 'block_key', 'user')]
        get_latest_by = 'modified'

    def __unicode__(self):
        return 'BlockCompletion: {username}, {context_key}, {block_key}: {completion}'.format(
            username=self.user.username,
            context_key=self.context_key,
            block_key=self.block_key,
            completion=self.completion,
        )
Example #4
0
class BlockCompletion(TimeStampedModel, models.Model):
    """
    Track completion of completable blocks.

    A completion is unique for each (user, course_key, block_key).

    The block_type field is included separately from the block_key to
    facilitate distinct aggregations of the completion of particular types of
    block.

    The completion value is stored as a float in the range [0.0, 1.0], and all
    calculations are performed on this float, though current practice is to
    only track binary completion, where 1.0 indicates that the block is
    complete, and 0.0 indicates that the block is incomplete.
    """
    id = BigAutoField(primary_key=True)  # pylint: disable=invalid-name
    user = models.ForeignKey(User)
    course_key = CourseKeyField(max_length=255)
    block_key = UsageKeyField(max_length=255)
    block_type = models.CharField(max_length=64)
    completion = models.FloatField(validators=[validate_percent])

    objects = BlockCompletionManager()

    @classmethod
    def get_course_completions(cls, user, course_key):
        """
        query all completions for course/user pair

        Return value:
            dict[BlockKey] = float
        """
        course_block_completions = cls.objects.filter(
            user=user,
            course_key=course_key,
        )
        # will not return if <= 0.0
        return {completion.block_key: completion.completion for completion in course_block_completions}

    @classmethod
    def get_latest_block_completed(cls, user, course_key):
        """
        query latest completion for course/user pair

        Return value:
            obj: block completion
        """
        try:
            latest_modified_block_completion = cls.objects.filter(
                user=user,
                course_key=course_key,
            ).latest()
        except cls.DoesNotExist:
            return
        return latest_modified_block_completion

    class Meta(object):
        index_together = [
            ('course_key', 'block_type', 'user'),
            ('user', 'course_key', 'modified'),
        ]

        unique_together = [
            ('course_key', 'block_key', 'user')
        ]
        get_latest_by = 'modified'

    def __unicode__(self):
        return 'BlockCompletion: {username}, {course_key}, {block_key}: {completion}'.format(
            username=self.user.username,
            course_key=self.course_key,
            block_key=self.block_key,
            completion=self.completion,
        )
Example #5
0
class BlockCompletion(TimeStampedModel, models.Model):
    """
    Track completion of completable blocks.

    A completion is unique for each (user, course_key, block_key).

    The block_type field is included separately from the block_key to
    facilitate distinct aggregations of the completion of particular types of
    block.

    The completion value is stored as a float in the range [0.0, 1.0], and all
    calculations are performed on this float, though current practice is to
    only track binary completion, where 1.0 indicates that the block is
    complete, and 0.0 indicates that the block is incomplete.
    """
    id = BigAutoField(primary_key=True)  # pylint: disable=invalid-name
    user = models.ForeignKey(User)
    course_key = CourseKeyField(max_length=255)

    # note: this usage key may not have the run filled in for
    # old mongo courses.  Use the full_block_key property
    # instead when you want to use/compare the usage_key.
    block_key = UsageKeyField(max_length=255)
    block_type = models.CharField(max_length=64)
    completion = models.FloatField(validators=[validate_percent])

    objects = BlockCompletionManager()

    @property
    def full_block_key(self):
        """
        Returns the "correct" usage key value with the run filled in.
        """
        if self.block_key.run is None:
            # pylint: disable=unexpected-keyword-arg, no-value-for-parameter
            return self.block_key.replace(course_key=self.course_key)
        return self.block_key

    @classmethod
    def get_course_completions(cls, user, course_key):
        """
        Returns a dictionary mapping BlockKeys to completion values for all
        BlockCompletion records for the given user and course_key.

        Return value:
            dict[BlockKey] = float
        """
        user_course_completions = cls.user_course_completion_queryset(user, course_key)
        return cls.completion_by_block_key(user_course_completions)

    @classmethod
    def user_course_completion_queryset(cls, user, course_key):
        """
        Returns a Queryset of completions for a given user and course_key.
        """
        return cls.objects.filter(user=user, course_key=course_key)

    @classmethod
    def latest_blocks_completed_all_courses(cls, user):
        """
        get all latest completions for user by course

        Return value:
            dict[courseKey]: [ modified_date, block_key ]
        """

        latest_completions_by_course = cls.objects.raw(
            '''
            SELECT id, course_key, block_key, max(modified) as latest
            FROM completion_blockcompletion
            WHERE user_id=%s
            GROUP BY course_key;
            ''',
            [user.id]
        )
        try:
            return {
                completion.course_key:
                    [completion.modified, completion.block_key] for completion in latest_completions_by_course
            }
        except KeyError:
            # Manipulation of the queryset above will always fail
            # with a KeyError if the queryset is empty
            return {}

    @classmethod
    def get_latest_block_completed(cls, user, course_key):
        """
        Returns a BlockCompletion Object for the last modified user/course_key mapping,
        or None if no such BlockCompletion exists.

        Return value:
            obj: block completion
        """
        try:
            latest_block_completion = cls.user_course_completion_queryset(user, course_key).latest()
        except cls.DoesNotExist:
            return
        return latest_block_completion

    @staticmethod
    def completion_by_block_key(completion_iterable):
        """
        Return value:
            A dict mapping the full block key of a completion record to the completion value
            for each BlockCompletion object given in completion_iterable.  Each BlockKey is
            corrected to have the run field filled in via the BlockCompletion.course_key field.
        """
        return {completion.full_block_key: completion.completion for completion in completion_iterable}

    class Meta(object):
        index_together = [
            ('course_key', 'block_type', 'user'),
            ('user', 'course_key', 'modified'),
        ]

        unique_together = [
            ('course_key', 'block_key', 'user')
        ]
        get_latest_by = 'modified'

    def __unicode__(self):
        return 'BlockCompletion: {username}, {course_key}, {block_key}: {completion}'.format(
            username=self.user.username,
            course_key=self.course_key,
            block_key=self.block_key,
            completion=self.completion,
        )