-
Notifications
You must be signed in to change notification settings - Fork 3
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: send proctoring update emails #201
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
49c8976
feat: send email on attempt update
zacharis278 4d25319
feat: add django-ses requirement
zacharis278 70ad8e3
feat: use edx support contact for review
zacharis278 7eda67c
style: code review
zacharis278 a911aa6
chore: update requirements
zacharis278 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
""" | ||
Handles rendering templates and sending emails. | ||
""" | ||
import logging | ||
|
||
from django.conf import settings | ||
from django.core.mail.message import EmailMessage | ||
from django.template import loader | ||
|
||
from edx_exams.apps.core.exam_types import get_exam_type | ||
from edx_exams.apps.core.statuses import ExamAttemptStatus | ||
|
||
log = logging.getLogger(__name__) | ||
|
||
|
||
def send_attempt_status_email(attempt): | ||
""" | ||
Send email for attempt status if necessary | ||
""" | ||
exam = attempt.exam | ||
exam_type = get_exam_type(exam.exam_type) | ||
|
||
# do not send emails on practice exams or non-proctored exams | ||
if not exam_type.is_proctored or exam_type.is_practice: | ||
return | ||
|
||
if attempt.status == ExamAttemptStatus.submitted: | ||
email_template = 'email/proctoring_attempt_submitted.html' | ||
elif attempt.status == ExamAttemptStatus.verified: | ||
email_template = 'email/proctoring_attempt_verified.html' | ||
elif attempt.status == ExamAttemptStatus.rejected: | ||
email_template = 'email/proctoring_attempt_rejected.html' | ||
else: | ||
return # do not send emails for other statuses | ||
|
||
email_template = loader.get_template(email_template) | ||
course_url = f'{settings.LEARNING_MICROFRONTEND_URL}/course/{exam.course_id}' | ||
contact_url = f'{settings.LMS_ROOT_URL}/support/contact_us' | ||
|
||
email_subject = f'Proctored exam {exam.exam_name} for user {attempt.user.username}' | ||
body = email_template.render({ | ||
'exam_name': exam.exam_name, | ||
'course_url': course_url, | ||
'contact_url': contact_url, | ||
}) | ||
|
||
email = EmailMessage( | ||
subject=email_subject, | ||
body=body, | ||
from_email=settings.DEFAULT_FROM_EMAIL, | ||
to=[attempt.user.email], | ||
) | ||
email.content_subtype = 'html' | ||
|
||
try: | ||
email.send() | ||
except Exception as err: # pylint: disable=broad-except | ||
log.error( | ||
'Error while sending proctoring status email for ' | ||
f'user_id {attempt.user.id}, exam_id {exam.id}: {err}' | ||
) |
33 changes: 33 additions & 0 deletions
33
edx_exams/apps/core/templates/email/proctoring_attempt_rejected.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
{% load i18n %} | ||
|
||
<p> | ||
{% block introduction %} | ||
{% blocktrans %} | ||
Hello {{ username }}, | ||
{% endblocktrans %} | ||
{% endblock %} | ||
</p> | ||
<p> | ||
{% block status_information %} | ||
{% blocktrans %} | ||
Your proctored exam "{{ exam_name }}" in | ||
<a href="{{ course_url }}">{{ course_url }}</a> was reviewed and the | ||
course team has identified one or more violations of the proctored exam rules. Examples | ||
of issues that may result in a rules violation include browsing | ||
the internet, using a phone, | ||
or getting help from another person. As a result of the identified issue(s), | ||
you did not successfully meet the proctored exam requirements. | ||
{% endblocktrans %} | ||
{% endblock %} | ||
</p> | ||
<p> | ||
{% block contact_information %} | ||
{% blocktrans %} | ||
To appeal your proctored exam results, please reach out with any relevant information | ||
about your exam at | ||
<a href="{{contact_url}}"> | ||
{{ contact_url_text }} | ||
</a>. | ||
{% endblocktrans %} | ||
{% endblock %} | ||
</p> |
20 changes: 20 additions & 0 deletions
20
edx_exams/apps/core/templates/email/proctoring_attempt_submitted.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
{% load i18n %} | ||
|
||
<p> | ||
{% block introduction %} | ||
{% blocktrans %} | ||
Hello {{ username }}, | ||
{% endblocktrans %} | ||
{% endblock %} | ||
</p> | ||
<p> | ||
{% block status_information %} | ||
{% blocktrans %} | ||
Your proctored exam "{{ exam_name }}" in | ||
<a href="{{ course_url }}">{{ course_url }}</a> was submitted | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These used to have the course name here. For right now we don't have that in the data model. |
||
successfully and will now be reviewed to ensure all exam | ||
rules were followed. You should receive an email with your exam | ||
status within 5 business days. | ||
{% endblocktrans %} | ||
{% endblock %} | ||
</p> |
28 changes: 28 additions & 0 deletions
28
edx_exams/apps/core/templates/email/proctoring_attempt_verified.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
{% load i18n %} | ||
|
||
<p> | ||
{% block introduction %} | ||
{% blocktrans %} | ||
Hello {{ username }}, | ||
{% endblocktrans %} | ||
{% endblock %} | ||
</p> | ||
<p> | ||
{% block status_information %} | ||
{% blocktrans %} | ||
Your proctored exam "{{ exam_name }}" in | ||
<a href="{{ course_url }}">{{ course_url }}</a> was reviewed and you | ||
met all proctoring requirements. | ||
{% endblocktrans %} | ||
{% endblock %} | ||
</p> | ||
<p> | ||
{% block contact_information %} | ||
{% blocktrans %} | ||
If you have any questions about your results, you can reach out at | ||
<a href="{{contact_url}}"> | ||
{{ contact_url }} | ||
</a>. | ||
{% endblocktrans %} | ||
{% endblock %} | ||
</p> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
""" | ||
Test email notifications for attempt status change | ||
""" | ||
|
||
import ddt | ||
import mock | ||
from django.core import mail | ||
from django.test import TestCase | ||
|
||
from edx_exams.apps.core.api import update_attempt_status | ||
from edx_exams.apps.core.test_utils.factories import ExamAttemptFactory, ExamFactory, UserFactory | ||
|
||
|
||
@ddt.ddt | ||
class TestEmail(TestCase): | ||
""" | ||
Test email notifications for attempt status change | ||
""" | ||
def setUp(self): | ||
super().setUp() | ||
|
||
self.user = UserFactory.create() | ||
self.proctored_exam = ExamFactory.create( | ||
exam_type='proctored', | ||
) | ||
self.started_attempt = ExamAttemptFactory.create( | ||
exam=self.proctored_exam, | ||
user=self.user, | ||
status='started', | ||
) | ||
|
||
@staticmethod | ||
def _normalize_whitespace(string): | ||
""" | ||
Replaces newlines and multiple spaces with a single space. | ||
""" | ||
return ' '.join(string.replace('\n', '').split()) | ||
|
||
@ddt.data( | ||
('submitted', 'was submitted successfully'), | ||
('verified', 'was reviewed and you met all proctoring requirements'), | ||
('rejected', 'the course team has identified one or more violations'), | ||
) | ||
@ddt.unpack | ||
def test_send_email(self, status, expected_message): | ||
""" | ||
Test correct message is sent for statuses that trigger an email | ||
""" | ||
update_attempt_status(self.started_attempt.id, status) | ||
self.assertEqual(len(mail.outbox), 1) | ||
self.assertIn(self.started_attempt.user.email, mail.outbox[0].to) | ||
self.assertIn(expected_message, self._normalize_whitespace(mail.outbox[0].body)) | ||
|
||
@mock.patch('edx_exams.apps.core.email.log.error') | ||
def test_send_email_failure(self, mock_log_error): | ||
""" | ||
Test error is logged when an email fails to send | ||
""" | ||
with mock.patch('edx_exams.apps.core.email.EmailMessage.send', side_effect=Exception): | ||
update_attempt_status(self.started_attempt.id, 'submitted') | ||
mock_log_error.assert_called_once() | ||
self.assertIn('Error while sending proctoring status email', mock_log_error.call_args[0][0]) | ||
|
||
@ddt.data( | ||
'created', | ||
'ready_to_start', | ||
'download_software_clicked', | ||
'started', | ||
'ready_to_submit', | ||
'error', | ||
) | ||
def test_status_should_not_send_email(self, status): | ||
""" | ||
Test no email is sent for statuses that should not trigger | ||
""" | ||
update_attempt_status(self.started_attempt.id, status) | ||
self.assertEqual(len(mail.outbox), 0) | ||
|
||
def test_non_proctored_exam_should_not_send_email(self): | ||
""" | ||
Test no email is sent for non-proctored exams | ||
""" | ||
timed_attempt = ExamAttemptFactory.create( | ||
exam=ExamFactory.create( | ||
exam_type='timed', | ||
), | ||
user=self.user, | ||
status='started', | ||
) | ||
update_attempt_status(timed_attempt.id, 'submitted') | ||
self.assertEqual(len(mail.outbox), 0) | ||
|
||
def test_practice_exam_should_not_send_email(self): | ||
""" | ||
Test no email is sent for practice exams | ||
""" | ||
practice_proctored_attempt = ExamAttemptFactory.create( | ||
exam=ExamFactory.create( | ||
exam_type='onboarding', | ||
), | ||
user=self.user, | ||
status='started', | ||
) | ||
update_attempt_status(practice_proctored_attempt.id, 'submitted') | ||
self.assertEqual(len(mail.outbox), 0) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -157,6 +157,9 @@ def root(*path_fragments): | |
root('static'), | ||
) | ||
|
||
# EMAIL CONFIGURATION | ||
DEFAULT_FROM_EMAIL: '[email protected]' | ||
|
||
# TEMPLATE CONFIGURATION | ||
# See: https://docs.djangoproject.com/en/2.2/ref/settings/#templates | ||
TEMPLATES = [ | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
|
||
|
||
# A central location for most common version constraints | ||
# (across edx repos) for pip-installation. | ||
# | ||
|
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this email the correct place to escalate to? It's set up per provider, and I feel like it would make more sense for the course team to review this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is a really good point. In my head this was the escalation email but that's not true, this email is just tech support for the tool. We don't save an escalation email for LTI but it sounds like we need one. I'm going to open a ticket to discuss adding this end to end.
For now I'm going to update this so this falls back on edx support in absence of a course team email.