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')]), ), ]
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, )
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, )
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, )
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, )