Files
edx-platform/lms/djangoapps/support/tasks.py
Rodrigo Martin e768d6d9e5 feat: send email after course reset completion (#34460)
* feat: send email after course reset completion

* fix: lint test

* fix: clean code

* fix: correct expected email parts

* fix: logs

* fix: email assertion
2024-04-05 12:27:21 -03:00

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)