Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add function to clear course progress for a learner #284

Merged
merged 3 commits into from
Mar 21, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ Change Log
Unreleased
~~~~~~~~~~

[4.5.0] - 2024-3-19
~~~~~~~~~~~~~~~~~~~~
* Added ``clear_learning_context_completion`` to enable clearing a learner's
completion for a course

[4.4.1] - 2023-10-27
~~~~~~~~~~~~~~~~~~~~
* Fix RemovedInDjango41Warning by removing `django_app_config`
Expand Down
2 changes: 1 addition & 1 deletion completion/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
"""


__version__ = '4.4.1'
__version__ = '4.5.0'
16 changes: 16 additions & 0 deletions completion/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,22 @@ def submit_batch_completion(self, user, blocks):
block_completions[block_completion] = is_new
return block_completions

@transaction.atomic()
def clear_learning_context_completion(self, user, context_key):
"""
Performs a batch delete of all completion objects for a specified user and context.

Parameters:
* user (django.contrib.auth.models.User): The user for whom the
completions are being deleted.
* blocks: (ContextKey) The course / context identifier for which
jansenk marked this conversation as resolved.
Show resolved Hide resolved
completions are being deleted.

Return Value: (int) The number of models deleted
"""
total, _ = BlockCompletion.user_learning_context_completion_queryset(user, context_key).delete()
return total


# pylint: disable=model-has-unicode
class BlockCompletion(TimeStampedModel, models.Model):
Expand Down
91 changes: 91 additions & 0 deletions completion/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
"""

import datetime
from random import randint
from uuid import uuid4
from pytz import UTC

from django.core.exceptions import ValidationError
Expand Down Expand Up @@ -215,3 +217,92 @@ def test_latest_blocks_completed_all_courses(self):
self.course_key_one: (datetime.datetime(2050, 1, 3, tzinfo=UTC), self.block_keys_one[2])
}
)


class CompletionClearingTestCase(CompletionSetUpMixin, TestCase):
"""
Tests for clear_learning_context_completion
"""
COMPLETION_SWITCH_ENABLED = True
BLOCKS_PER_CONTEXT = 3

def setUp(self):
super().setUp()
# Create two learning contexts of five blocks each
jansenk marked this conversation as resolved.
Show resolved Hide resolved
self.context_key, self.blocks = self._set_up_course('SomeCourse')
self.other_context_key, self.other_blocks = self._set_up_course('SomeOtherCourse')

# Create two users
self.user = UserFactory()
self.other_user = UserFactory()

# Create completions for all blocks in both contexts for each learner
self._create_test_completions(self.user)
self._create_test_completions(self.other_user)

def _create_test_completions(self, user):
# Create random completions for `user` for all blocks in both test contexts
models.BlockCompletion.objects.submit_batch_completion(
user,
[
(block, float(f"0.{randint(1,9)}"))
for block in self.blocks + self.other_blocks
]
)

def _set_up_course(self, course):
""" Create a context with five blocks """
blocks = [
UsageKey.from_string(f'block-v1:edx+{course}+run+type@problem+block@{uuid4()}')
for _ in range(self.BLOCKS_PER_CONTEXT)
]
return blocks[0].context_key, blocks

def _assert_completions(self, user, context, expect_completions):
""" Helper to assert the existance of completions for a given learner and context """
completions = models.BlockCompletion.get_learning_context_completions(user, context)
if expect_completions:
assert len(completions) == self.BLOCKS_PER_CONTEXT
else:
assert not completions

def test_clear_learning_context_completion(self):
"""
When we clear learning context completion, it should clear all completion records for
the given user and the given context without affecting any other user or context
"""
self._assert_completions(self.user, self.context_key, True)
self._assert_completions(self.user, self.other_context_key, True)
self._assert_completions(self.other_user, self.context_key, True)
self._assert_completions(self.other_user, self.other_context_key, True)

deleted = models.BlockCompletion.objects.clear_learning_context_completion(
self.user, self.context_key
)
assert deleted == self.BLOCKS_PER_CONTEXT

self._assert_completions(self.user, self.context_key, False)
self._assert_completions(self.user, self.other_context_key, True)
self._assert_completions(self.other_user, self.context_key, True)
self._assert_completions(self.other_user, self.other_context_key, True)

deleted = models.BlockCompletion.objects.clear_learning_context_completion(
self.other_user, self.other_context_key
)
assert deleted == self.BLOCKS_PER_CONTEXT

self._assert_completions(self.user, self.context_key, False)
self._assert_completions(self.user, self.other_context_key, True)
self._assert_completions(self.other_user, self.context_key, True)
self._assert_completions(self.other_user, self.other_context_key, False)

def test_user_no_completions(self):
"""
Calling the method for a user with no completions does nothing and raises no error
"""
stranger = UserFactory()
assert not models.BlockCompletion.objects.filter(user=stranger).exists()
deleted = models.BlockCompletion.objects.clear_learning_context_completion(
stranger, self.context_key
)
assert deleted == 0
Loading