Skip to content

Commit

Permalink
feat: send email after course reset completion (#34460)
Browse files Browse the repository at this point in the history
* feat: send email after course reset completion

* fix: lint test

* fix: clean code

* fix: correct expected email parts

* fix: logs

* fix: email assertion
  • Loading branch information
Rodra authored Apr 5, 2024
1 parent a08a10c commit e768d6d
Show file tree
Hide file tree
Showing 8 changed files with 145 additions and 0 deletions.
18 changes: 18 additions & 0 deletions lms/djangoapps/support/message_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"""
ACE message types for support-related emails.
"""

from openedx.core.djangoapps.ace_common.message import BaseMessageType


class WholeCourseReset(BaseMessageType):
"""
A message to the user when whole course reset was successful.
"""

APP_LABEL = 'support'
Name = 'wholecoursereset'

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.options['transactional'] = True
53 changes: 53 additions & 0 deletions lms/djangoapps/support/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@
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__)

Expand All @@ -35,6 +44,46 @@ def get_blocks(course):
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):
Expand Down Expand Up @@ -79,6 +128,10 @@ def reset_student_course(course_id, learner_email, reset_by_user_email):
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)
21 changes: 21 additions & 0 deletions lms/djangoapps/support/tests/test_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

from unittest.mock import patch, Mock, call

from django.conf import settings
from django.core import mail
from xmodule.modulestore.tests.factories import BlockFactory

from lms.djangoapps.courseware.tests.test_submitting_problems import TestSubmittingProblems
Expand All @@ -15,6 +17,7 @@
from common.djangoapps.student.roles import SupportStaffRole
from common.djangoapps.student.tests.factories import UserFactory
from xmodule.video_block import VideoBlock
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers


class ResetStudentCourse(TestSubmittingProblems):
Expand Down Expand Up @@ -109,6 +112,20 @@ def basic_setup(self):

self.refresh_course()

def assert_email_sent_successfully(self, expected):
"""
Verify that the course reset email has been sent to the user.
"""
from_email = configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL)
sent_message = mail.outbox[-1]
body = sent_message.body

assert expected['subject'] in sent_message.subject
assert expected['body'] in body
assert sent_message.from_email == from_email
assert len(sent_message.to) == 1
assert self.student_user.email in sent_message.to

def test_reset_student_course(self):
""" Test that it resets student attempts """
with patch(
Expand Down Expand Up @@ -157,6 +174,10 @@ def test_reset_student_course(self):
course_reset_audit = CourseResetAudit.objects.get(course_enrollment=self.enrollment)
self.assertIsNotNone(course_reset_audit.completed_at)
self.assertEqual(course_reset_audit.status, CourseResetAudit.CourseResetStatus.COMPLETE)
self.assert_email_sent_successfully({
'subject': f'The course { self.course.display_name } has been reset !',
'body': f'Your progress in course { self.course.display_name } has been reset on your behalf.'
})

def test_reset_student_course_student_module_not_found(self):

Expand Down
39 changes: 39 additions & 0 deletions lms/templates/support/edx_ace/wholecoursereset/email/body.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{% extends 'ace_common/edx_ace/common/base_body.html' %}

{% load i18n %}
{% load static %}
{% block content %}
<table width="100%" align="left" border="0" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td>
<h1>
{% trans "The course {{ course_title }} has been reset !" as tmsg %}{{ tmsg | force_escape }}
</h1>
<p style="color: rgba(0,0,0,.75);">
{% filter force_escape %}
{% blocktrans %}Hello {{ full_name }},{% endblocktrans %}
{% endfilter %}
</p>
<p style="color: rgba(0,0,0,.75);">
{% filter force_escape %}
{% blocktrans %}Your progress in course {{course_title}} has been reset on your behalf.{% endblocktrans %}
{% endfilter %}

{% filter force_escape %}
{% blocktrans %}You will be able to re-attempt this course and earn a verified certificate upon successful completion.{% endblocktrans %}
{% endfilter %}
<br/>
</p>

<p>
{% trans "Best," as tmsg %}{{ tmsg | force_escape }}
<br/>
{% filter force_escape %}
{% blocktrans %}The {{ platform_name }} Team {% endblocktrans %}
{% endfilter %}
</p>

</td>
</tr>
</table>
{% endblock %}
8 changes: 8 additions & 0 deletions lms/templates/support/edx_ace/wholecoursereset/email/body.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{% load i18n %}{% autoescape off %}
{% blocktrans %}Hello {{full_name}}, {% endblocktrans %}
{% blocktrans %}Your progress in course {{course_title}} has been reset on your behalf.{% endblocktrans %}
{% blocktrans %}You will be able to re-attempt this course and earn a verified certificate upon successful completion.{% endblocktrans %}

{% trans "Best," %}
{% blocktrans %}The {{ platform_name }} Team {% endblocktrans %}
{% endautoescape %}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{ platform_name }}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{% extends 'ace_common/edx_ace/common/base_head.html' %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{% load i18n %}
{% autoescape off %}
{% blocktrans trimmed %}The course {{ course_title }} has been reset !{% endblocktrans %}
{% endautoescape %}

0 comments on commit e768d6d

Please sign in to comment.