* feat: send email after course reset completion * fix: lint test * fix: clean code * fix: correct expected email parts * fix: logs * fix: email assertion
138 lines
5.1 KiB
Python
138 lines
5.1 KiB
Python
""" Celery Tasks for the Instructor App. """
|
|
|
|
from datetime import datetime
|
|
import logging
|
|
from celery import shared_task
|
|
from completion.models import BlockCompletion
|
|
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.instructor.enrollment import reset_student_attempts
|
|
from lms.djangoapps.support.models import CourseResetAudit
|
|
from lms.djangoapps.grades.api import clear_user_course_grades
|
|
from openedx.core.djangoapps.ace_common.template_context import get_base_template_context
|
|
|
|
from edx_ace import ace
|
|
from django.contrib.sites.models import Site
|
|
from edx_ace.recipient import Recipient
|
|
from openedx.core.lib.celery.task_utils import emulate_http_request
|
|
from openedx.core.djangoapps.lang_pref import LANGUAGE_KEY
|
|
from openedx.core.djangoapps.user_api.preferences.api import get_user_preference
|
|
from lms.djangoapps.support.message_types import WholeCourseReset
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
def update_audit_status(audit_instance, status):
|
|
audit_instance.status = status
|
|
if status == CourseResetAudit.CourseResetStatus.COMPLETE:
|
|
audit_instance.completed_at = datetime.now()
|
|
audit_instance.save()
|
|
|
|
|
|
def get_blocks(course):
|
|
""" Get a list of problem xblock for the course."""
|
|
blocks = []
|
|
for section in course.get_children():
|
|
for subsection in section.get_children():
|
|
for vertical in subsection.get_children():
|
|
for block in vertical.get_children():
|
|
blocks.append(block)
|
|
return blocks
|
|
|
|
|
|
@shared_task
|
|
@set_code_owner_attribute
|
|
def send_reset_course_completion_email(course, user):
|
|
"""
|
|
Sends email to a learner when whole course reset is complete.
|
|
"""
|
|
site = Site.objects.get_current()
|
|
|
|
message_context = get_base_template_context(site)
|
|
message_context.update({
|
|
'course_title': course.display_name,
|
|
})
|
|
|
|
try:
|
|
log.info(
|
|
f"Sending whole course reset email to {user.profile.name} (Email: {user.email}) "
|
|
f"from course {course.display_name} (CourseId: {course.id})"
|
|
)
|
|
with emulate_http_request(site=site, user=user):
|
|
msg = WholeCourseReset(context=message_context).personalize(
|
|
recipient=Recipient(user.id, user.email),
|
|
language=get_user_preference(user, LANGUAGE_KEY),
|
|
user_context={'full_name': user.profile.name}
|
|
)
|
|
ace.send(msg)
|
|
except Exception as exc: # pylint: disable=broad-except
|
|
log.exception(
|
|
f"Whole course reset email to {user.profile.name} (Email: {user.email}) "
|
|
f"from course {course.display_name} (CourseId: {course.id}) failed."
|
|
f"Error: {exc.response['Error']['Code']}"
|
|
)
|
|
return False
|
|
else:
|
|
log.info(
|
|
f"Whole course reset email sent successfully to {user.profile.name} (Email: {user.email}) "
|
|
f"from course {course.display_name} (CourseId: {course.id})"
|
|
)
|
|
return True
|
|
|
|
|
|
@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.get(
|
|
course_enrollment=enrollment,
|
|
status=CourseResetAudit.CourseResetStatus.ENQUEUED
|
|
)
|
|
update_audit_status(course_reset_audit, CourseResetAudit.CourseResetStatus.IN_PROGRESS)
|
|
|
|
try:
|
|
course = get_course(course_overview.id, depth=4)
|
|
blocks = get_blocks(course)
|
|
|
|
# Clear student state and score
|
|
for data in blocks:
|
|
try:
|
|
reset_student_attempts(
|
|
course.id,
|
|
user,
|
|
data.scope_ids.usage_id,
|
|
reset_by_user,
|
|
delete_module=True,
|
|
emit_signals_and_events=False
|
|
)
|
|
except StudentModule.DoesNotExist:
|
|
pass
|
|
|
|
# Clear block completion data
|
|
BlockCompletion.objects.clear_learning_context_completion(user, course.id)
|
|
# Clear a student grades for a course
|
|
clear_user_course_grades(user.id, course.id)
|
|
|
|
update_audit_status(course_reset_audit, CourseResetAudit.CourseResetStatus.COMPLETE)
|
|
|
|
# Send email upon completion
|
|
send_reset_course_completion_email(course, user)
|
|
|
|
except Exception as e: # pylint: disable=broad-except
|
|
logging.exception(f'Error occurred for Course Audit with ID {course_reset_audit.id}: {e}.')
|
|
update_audit_status(course_reset_audit, CourseResetAudit.CourseResetStatus.FAILED)
|