feat: add celery task to reset course progress for learner

This commit is contained in:
hajorg
2024-03-11 18:40:26 +01:00
parent 844db29d64
commit 9a4e56005d
4 changed files with 124 additions and 4 deletions

View File

@@ -0,0 +1,55 @@
""" Celery Tasks for the Instructor App. """
from datetime import datetime
import logging
from celery import shared_task
from submissions import api as sub_api
from edx_django_utils.monitoring import set_code_owner_attribute
from common.djangoapps.student.models.course_enrollment import CourseEnrollment
from common.djangoapps.student.models.user import get_user_by_username_or_email
from lms.djangoapps.courseware.courses import get_course
from lms.djangoapps.courseware.models import StudentModule
from lms.djangoapps.grades.rest_api.v1.views import SubmissionHistoryView
from lms.djangoapps.instructor.enrollment import reset_student_attempts
from lms.djangoapps.support.models import CourseResetAudit
log = logging.getLogger(__name__)
def update_audit_fields(audit_instance, status, completed_at=False):
audit_instance.status = status
if completed_at:
audit_instance.completed_at = datetime.now()
audit_instance.save()
@shared_task
@set_code_owner_attribute
def reset_student_course(course_id, learner_email, reset_by_user_email):
"""
Resets a learner's course progress
"""
user = get_user_by_username_or_email(learner_email)
reset_by_user = get_user_by_username_or_email(reset_by_user_email)
enrollment = CourseEnrollment.objects.get(
course=course_id,
user=user,
is_active=True
)
course_overview = enrollment.course_overview
course_reset_audit = CourseResetAudit.objects.filter(course_enrollment=enrollment).first()
update_audit_fields(course_reset_audit, CourseResetAudit.CourseResetStatus.IN_PROGRESS)
try:
course = get_course(course_overview.id, depth=4)
history = SubmissionHistoryView.get_problem_blocks(course)
for data in history:
try:
reset_student_attempts(course.id, user, data.scope_ids.usage_id, reset_by_user, True)
except StudentModule.DoesNotExist:
pass
update_audit_fields(course_reset_audit, CourseResetAudit.CourseResetStatus.COMPLETE, True)
except sub_api.SubmissionError as e:
logging.exception(e)
update_audit_fields(course_reset_audit, CourseResetAudit.CourseResetStatus.FAILED)

View File

@@ -0,0 +1,55 @@
"""
Unit tests for reset_student_course task
"""
from datetime import datetime, timedelta
from pytz import UTC
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
from lms.djangoapps.support.tasks import reset_student_course
from lms.djangoapps.support.tests.factories import CourseResetAuditFactory, CourseResetCourseOptInFactory
from lms.djangoapps.support.models import CourseResetAudit
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from common.djangoapps.student.roles import SupportStaffRole
from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory
class ResetStudentCourse(ModuleStoreTestCase):
""" Test expire_waiting_enrollments task """
USERNAME = "support"
EMAIL = "support@example.com"
PASSWORD = "support"
def setUp(self):
"""
Set permissions, create a course and learner, enroll learner and opt into course reset
"""
super().setUp()
self.user = UserFactory(username=self.USERNAME, email=self.EMAIL, password=self.PASSWORD)
SupportStaffRole().add_users(self.user)
self.now = datetime.now().replace(tzinfo=UTC)
self.course = CourseFactory.create(
start=self.now - timedelta(days=90),
end=self.now + timedelta(days=90),
)
self.course_id = str(self.course.id)
self.course_overview = CourseOverview.get_from_id(self.course.id)
self.learner = UserFactory.create()
self.enrollment = CourseEnrollmentFactory.create(user=self.learner, course_id=self.course.id)
self.opt_in = CourseResetCourseOptInFactory.create(course_id=self.course.id)
self.audit = CourseResetAuditFactory.create(
course=self.opt_in,
course_enrollment=self.enrollment,
reset_by=self.user,
status=CourseResetAudit.CourseResetStatus.FAILED
)
def test_reset_student_course(self):
reset_student_course(self.course_id, self.learner.email, self.user.email)
course_reset_audit = CourseResetAudit.objects.filter(course_enrollment=self.enrollment).first()
self.assertTrue(course_reset_audit.completed_at)

View File

@@ -2377,7 +2377,8 @@ class TestResetCourseViewPost(SupportViewTestCase):
response = self.client.post(self._url(username='does_not_exist'), data={'course_id': 'course-v1:aa+bb+c'})
self.assertEqual(response.status_code, 404)
def test_learner_course_reset(self):
@patch('lms.djangoapps.support.views.course_reset.reset_student_course')
def test_learner_course_reset(self, mock_reset_student_course):
response = self.client.post(self._url(username=self.user.username), data={'course_id': self.course_id})
self.assertEqual(response.status_code, 201)
self.assertEqual(response.data, {
@@ -2386,12 +2387,16 @@ class TestResetCourseViewPost(SupportViewTestCase):
'can_reset': False,
'display_name': self.course.display_name
})
self.assertEqual(
mock_reset_student_course.delay.call_count, 1
)
def test_course_not_opt_in(self):
response = self.client.post(self._url(username=self.user.username), data={'course_id': 'course-v1:aa+bb+c'})
self.assertEqual(response.status_code, 404)
def test_course_reset_failed(self):
@patch('lms.djangoapps.support.views.course_reset.reset_student_course')
def test_course_reset_failed(self, mock_reset_student_course):
course = CourseFactory.create(
org='xx',
course='yy',
@@ -2416,6 +2421,9 @@ class TestResetCourseViewPost(SupportViewTestCase):
status=CourseResetAudit.CourseResetStatus.FAILED
)
response = self.client.post(self._url(username=self.user.username), data={'course_id': course.id})
self.assertEqual(
mock_reset_student_course.delay.call_count, 1
)
self.assertEqual(response.status_code, 200)
def test_course_reset_dupe(self):

View File

@@ -14,6 +14,7 @@ from lms.djangoapps.support.models import (
CourseResetCourseOptIn,
CourseResetAudit
)
from ..tasks import reset_student_course
User = get_user_model()
@@ -131,7 +132,8 @@ class CourseResetAPIView(APIView):
):
course_reset_audit.status = CourseResetAudit.CourseResetStatus.ENQUEUED
course_reset_audit.save()
# Call celery task
reset_student_course.delay(course_id, user.email, request.user.email)
resp = {
'course_id': course_id,
'status': course_reset_audit.status_message(),
@@ -159,7 +161,7 @@ class CourseResetAPIView(APIView):
'display_name': course_overview.display_name
}
# Call celery task
reset_student_course.delay(course_id, user.email, request.user.email)
return Response(resp, status=201)
else:
return Response(None, status=400)