Commit cd0897ed by Muhammad Shoaib Committed by Afzal Wali

PHX-24

Proctored Exm Status Screens

Added back the timed exam completed template

Some changes regarding get_student_view flow depending on exam status.

Renamed the templates to match the status of the attempt.
improved coverage.

renamed some functions

lint issues.
parent b6bcbaa6
# pylint: disable=too-many-branches # pylint: disable=too-many-branches
# pylint: disable=too-many-statements
""" """
In-Proc API (aka Library) for the edx_proctoring subsystem. This is not to be confused with a HTTP REST In-Proc API (aka Library) for the edx_proctoring subsystem. This is not to be confused with a HTTP REST
...@@ -495,7 +496,6 @@ def get_student_view(user_id, course_id, content_id, ...@@ -495,7 +496,6 @@ def get_student_view(user_id, course_id, content_id,
if user_role != 'student': if user_role != 'student':
return None return None
has_finished_exam = False
student_view_template = None student_view_template = None
exam_id = None exam_id = None
...@@ -552,10 +552,20 @@ def get_student_view(user_id, course_id, content_id, ...@@ -552,10 +552,20 @@ def get_student_view(user_id, course_id, content_id,
}) })
else: else:
student_view_template = 'proctoring/seq_timed_exam_entrance.html' student_view_template = 'proctoring/seq_timed_exam_entrance.html'
elif has_finished_exam:
student_view_template = 'proctoring/seq_timed_exam_completed.html'
elif has_time_expired: elif has_time_expired:
student_view_template = 'proctoring/seq_timed_exam_expired.html' student_view_template = 'proctoring/seq_timed_exam_expired.html'
elif attempt['status'] == ProctoredExamStudentAttemptStatus.submitted:
student_view_template = 'proctoring/seq_proctored_exam_submitted.html'
elif attempt['status'] == ProctoredExamStudentAttemptStatus.verified:
student_view_template = 'proctoring/seq_proctored_exam_verified.html'
elif attempt['status'] == ProctoredExamStudentAttemptStatus.rejected:
student_view_template = 'proctoring/seq_proctored_exam_rejected.html'
elif attempt['status'] == ProctoredExamStudentAttemptStatus.completed:
if is_proctored:
student_view_template = 'proctoring/seq_proctored_exam_completed.html'
else:
student_view_template = 'proctoring/seq_timed_exam_completed.html'
if student_view_template: if student_view_template:
template = loader.get_template(student_view_template) template = loader.get_template(student_view_template)
django_context = Context(context) django_context = Context(context)
......
{% load i18n %}
<div class="sequence proctored-exam completed" data-exam-id="{{exam_id}}">
<h3>
{% blocktrans %}
This is the end of your proctored exam
{% endblocktrans %}
</h3>
<p>
{% blocktrans %}
Make sure your responses and work are ready to be submitted. Once they are, you may end the exam below.
Your worked will then be graded and your proctored session will be reviewed separately.
{% endblocktrans %}
</p>
<button type="button" name="submit-proctored-exam" >
{% blocktrans %}
I'm ready! Submit my answers and end my proctored exam
{% endblocktrans %}
</button>
</div>
<div class="footer-sequence border-b-0 padding-b-0">
<span> {% trans "What happens next ?" %} </span>
<p>
{% blocktrans %}
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor.
Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim.
{% endblocktrans %}
</p>
</div>
...@@ -38,7 +38,7 @@ ...@@ -38,7 +38,7 @@
rules for online proctoring. rules for online proctoring.
{% endblocktrans %} {% endblocktrans %}
</p> </p>
<p class="proctored-exam-option"> <p class="proctored-exam-instruction">
{% blocktrans %} {% blocktrans %}
Don't want to take this exam with online proctoring? <a href="#">Take this exam as an open exam instead.</a> Don't want to take this exam with online proctoring? <a href="#">Take this exam as an open exam instead.</a>
{% endblocktrans %} {% endblocktrans %}
......
{% load i18n %}
<div class="failure sequence proctored-exam" data-exam-id="{{exam_id}}">
<h3>
{% blocktrans %}
Your proctoring session was reviewed and did not pass requirements
{% endblocktrans %}
</h3>
<h4>
{% blocktrans %}
Your Proctoring Session review: <b class="failure"> Failed </b>
{% endblocktrans %}
</h4>
<p>
{% blocktrans %}
Your session was reviewed by one of our proctors and some activities noted violate the
<a href="#">requirements and guidelines</a> needed for a successful review,
{% endblocktrans %}
</p>
<hr>
<p>
{% blocktrans %}
Please see <a href="{{progress_page_url}}">your progress in this course </a>
for your general course credit worthiness.
{% endblocktrans %}
</p>
</div>
<div class="footer-sequence border-b-0 padding-b-0">
<span> {% trans "Can I contest this review?" %} </span>
<p class="proctored-exam-option">
{% blocktrans %}
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor.
Aenean massa.
{% endblocktrans %}
</p>
<a href="#" class="contest-review">Contest this review</a>
<hr class="clearfix">
<span> {% trans "Is there anything I can do to make up/replace this session?" %} </span>
<p class="proctored-exam-option">
{% blocktrans %}
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor.
Aenean massa.
{% endblocktrans %}
</p>
<hr class="clearfix">
<span> {% trans "See Also" %} </span>
<p>
{% blocktrans %}
<a class="footer-link" href="#">
Frequently asked questions about proctoring and earning college credit.
</a>
{% endblocktrans %}
</p>
</div>
{% load i18n %}
<div class="sequence proctored-exam completed" data-exam-id="{{exam_id}}">
<h3>
{% blocktrans %}
You have submitted this proctored exam for review
{% endblocktrans %}
</h3>
<h4>
{% blocktrans %}
Your Proctoring Session review: <b> Pending </b>
{% endblocktrans %}
</h4>
<p>
{% blocktrans %}
In general sessions are reviewed in 24-48 hours of submission. If you have questions about
the status of this review after that timeframe, <a href="#">contact edX support</a>
{% endblocktrans %}
</p>
<hr>
<p>
{% blocktrans %}
Please see <a href="{{progress_page_url}}">your progress in this course </a>
for your general course credit worthiness.
{% endblocktrans %}
</p>
</div>
<div class="footer-sequence border-b-0 padding-b-0">
<span> {% trans "See Also" %} </span>
<p>
{% blocktrans %}
<a class="footer-link" href="#">
Frequently asked questions about proctoring and earning college credit.
</a>
{% endblocktrans %}
</p>
</div>
{% load i18n %}
<div class="success sequence proctored-exam passed" data-exam-id="{{exam_id}}">
<h3>
{% blocktrans %}
Your proctoring session was reviewed and passed all requirements
{% endblocktrans %}
</h3>
<h4>
{% blocktrans %}
Your Proctoring Session review: <b class="success"> Passed </b>
{% endblocktrans %}
</h4>
<p>
{% blocktrans %}
This satisfies the requirement that you complete this exam in a proctored format and contributes
to your credit earning work for this course.
{% endblocktrans %}
</p>
<hr>
<p>
{% blocktrans %}
Please see <a href="{{progress_page_url}}">your progress in this course </a>
for your general course credit worthiness.
{% endblocktrans %}
</p>
</div>
<div class="footer-sequence border-b-0 padding-b-0">
<span> {% trans "See Also" %} </span>
<p>
{% blocktrans %}
<a class="footer-link" href="#">
Frequently asked questions about proctoring and earning college credit.
</a>
{% endblocktrans %}
</p>
</div>
{% load i18n %} {% load i18n %}
<div class="sequence timed-exam completed"> <div class="sequence timed-exam completed" data-exam-id="{{exam_id}}">
<div class="gated-sequence"> <h3>
{% trans "You have finished your exam!" %} {% blocktrans %}
</div> This is the end of your timed exam
{% endblocktrans %}
</h3>
<p>
{% blocktrans %}
Make sure your responses and work are ready to be submitted. Once they are, you may end the exam below
and your worked will then be graded.
{% endblocktrans %}
</p>
<button type="button" name="submit-timed-exam" >
{% blocktrans %}
I'm ready! Submit my answers and end my timed exam
{% endblocktrans %}
</button>
</div>
<div class="footer-sequence border-b-0 padding-b-0">
<span> {% trans "What happens next ?" %} </span>
<p>
{% blocktrans %}
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor.
Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim.
{% endblocktrans %}
</p>
</div> </div>
{% include 'proctoring/seq_timed_exam_footer.html' %}
...@@ -62,6 +62,7 @@ class ProctoredExamApiTests(LoggedInTestCase): ...@@ -62,6 +62,7 @@ class ProctoredExamApiTests(LoggedInTestCase):
self.default_time_limit = 21 self.default_time_limit = 21
self.course_id = 'test_course' self.course_id = 'test_course'
self.content_id = 'test_content_id' self.content_id = 'test_content_id'
self.content_id_timed = 'test_content_id_timed'
self.disabled_content_id = 'test_disabled_content_id' self.disabled_content_id = 'test_disabled_content_id'
self.exam_name = 'Test Exam' self.exam_name = 'Test Exam'
self.user_id = self.user.id self.user_id = self.user.id
...@@ -69,6 +70,7 @@ class ProctoredExamApiTests(LoggedInTestCase): ...@@ -69,6 +70,7 @@ class ProctoredExamApiTests(LoggedInTestCase):
self.value = 'Test Value' self.value = 'Test Value'
self.external_id = 'test_external_id' self.external_id = 'test_external_id'
self.proctored_exam_id = self._create_proctored_exam() self.proctored_exam_id = self._create_proctored_exam()
self.timed_exam = self._create_timed_exam()
self.disabled_exam_id = self._create_disabled_exam() self.disabled_exam_id = self._create_disabled_exam()
# Messages for get_student_view # Messages for get_student_view
...@@ -77,6 +79,11 @@ class ProctoredExamApiTests(LoggedInTestCase): ...@@ -77,6 +79,11 @@ class ProctoredExamApiTests(LoggedInTestCase):
self.exam_time_expired_msg = 'You did not complete the exam in the allotted time' self.exam_time_expired_msg = 'You did not complete the exam in the allotted time'
self.exam_time_error_msg = 'Your exam has been marked as failed due to an error.' self.exam_time_error_msg = 'Your exam has been marked as failed due to an error.'
self.chose_proctored_exam_msg = 'You have chosen to take %s as a proctored exam' self.chose_proctored_exam_msg = 'You have chosen to take %s as a proctored exam'
self.proctored_exam_completed_msg = 'This is the end of your proctored exam'
self.proctored_exam_submitted_msg = 'You have submitted this proctored exam for review'
self.proctored_exam_verified_msg = 'Your proctoring session was reviewed and passed all requirements'
self.proctored_exam_rejected_msg = 'Your proctoring session was reviewed and did not pass requirements'
self.timed_exam_completed_msg = 'This is the end of your timed exam'
def _create_proctored_exam(self): def _create_proctored_exam(self):
""" """
...@@ -89,6 +96,18 @@ class ProctoredExamApiTests(LoggedInTestCase): ...@@ -89,6 +96,18 @@ class ProctoredExamApiTests(LoggedInTestCase):
time_limit_mins=self.default_time_limit time_limit_mins=self.default_time_limit
) )
def _create_timed_exam(self):
"""
Calls the api's create_exam to create an exam object.
"""
return create_exam(
course_id=self.course_id,
content_id=self.content_id_timed,
exam_name=self.exam_name,
time_limit_mins=self.default_time_limit,
is_proctored=False
)
def _create_disabled_exam(self): def _create_disabled_exam(self):
""" """
Calls the api's create_exam to create an exam object. Calls the api's create_exam to create an exam object.
...@@ -101,23 +120,23 @@ class ProctoredExamApiTests(LoggedInTestCase): ...@@ -101,23 +120,23 @@ class ProctoredExamApiTests(LoggedInTestCase):
is_active=False is_active=False
) )
def _create_unstarted_exam_attempt(self): def _create_unstarted_exam_attempt(self, is_proctored=True):
""" """
Creates the ProctoredExamStudentAttempt object. Creates the ProctoredExamStudentAttempt object.
""" """
return ProctoredExamStudentAttempt.objects.create( return ProctoredExamStudentAttempt.objects.create(
proctored_exam_id=self.proctored_exam_id, proctored_exam_id=self.proctored_exam_id if is_proctored else self.timed_exam,
user_id=self.user_id, user_id=self.user_id,
external_id=self.external_id, external_id=self.external_id,
allowed_time_limit_mins=10 allowed_time_limit_mins=10
) )
def _create_started_exam_attempt(self, started_at=None): def _create_started_exam_attempt(self, started_at=None, is_proctored=True):
""" """
Creates the ProctoredExamStudentAttempt object. Creates the ProctoredExamStudentAttempt object.
""" """
return ProctoredExamStudentAttempt.objects.create( return ProctoredExamStudentAttempt.objects.create(
proctored_exam_id=self.proctored_exam_id, proctored_exam_id=self.proctored_exam_id if is_proctored else self.timed_exam,
user_id=self.user_id, user_id=self.user_id,
external_id=self.external_id, external_id=self.external_id,
started_at=started_at if started_at else datetime.now(pytz.UTC), started_at=started_at if started_at else datetime.now(pytz.UTC),
...@@ -196,7 +215,7 @@ class ProctoredExamApiTests(LoggedInTestCase): ...@@ -196,7 +215,7 @@ class ProctoredExamApiTests(LoggedInTestCase):
self.assertEqual(proctored_exam['exam_name'], self.exam_name) self.assertEqual(proctored_exam['exam_name'], self.exam_name)
exams = get_all_exams_for_course(self.course_id) exams = get_all_exams_for_course(self.course_id)
self.assertEqual(len(exams), 2) self.assertEqual(len(exams), 3)
def test_get_invalid_proctored_exam(self): def test_get_invalid_proctored_exam(self):
""" """
...@@ -530,30 +549,31 @@ class ProctoredExamApiTests(LoggedInTestCase): ...@@ -530,30 +549,31 @@ class ProctoredExamApiTests(LoggedInTestCase):
) )
) )
def test_getstudentview_timedexam(self): def test_get_studentview_unstarted_exam(self): # pylint: disable=invalid-name
""" """
Test for get_student_view Timed exam which is not proctored. Test for get_student_view proctored exam which has not started yet.
""" """
self._create_unstarted_exam_attempt()
rendered_response = get_student_view( rendered_response = get_student_view(
user_id=self.user_id, user_id=self.user_id,
course_id="abc", course_id=self.course_id,
content_id=self.content_id, content_id=self.content_id,
context={ context={
'is_proctored': False, 'is_proctored': True,
'display_name': self.exam_name, 'display_name': self.exam_name,
'default_time_limit_mins': 90 'default_time_limit_mins': 90
} }
) )
self.assertNotIn('data-exam-id="%d"' % self.proctored_exam_id, rendered_response) self.assertIn(self.chose_proctored_exam_msg % self.exam_name, rendered_response)
self.assertIn(self.timed_exam_msg % self.exam_name, rendered_response)
self.assertNotIn(self.start_an_exam_msg % self.exam_name, rendered_response)
def test_getstudentview_unstartedxm(self): def test_get_studentview_started_exam(self): # pylint: disable=invalid-name
""" """
Test for get_student_view proctored exam which has not started yet. Test for get_student_view proctored exam which has started.
""" """
self._create_unstarted_exam_attempt() self._create_started_exam_attempt()
rendered_response = get_student_view( rendered_response = get_student_view(
user_id=self.user_id, user_id=self.user_id,
...@@ -565,16 +585,37 @@ class ProctoredExamApiTests(LoggedInTestCase): ...@@ -565,16 +585,37 @@ class ProctoredExamApiTests(LoggedInTestCase):
'default_time_limit_mins': 90 'default_time_limit_mins': 90
} }
) )
self.assertIn(self.chose_proctored_exam_msg % self.exam_name, rendered_response) self.assertIsNone(rendered_response)
def test_getstudentview_startedxam(self): def test_get_studentview_submitted_status(self): # pylint: disable=invalid-name
""" """
Test for get_student_view proctored exam which has started. Test for get_student_view proctored exam which has been submitted.
""" """
exam_attempt = self._create_started_exam_attempt()
exam_attempt.status = "submitted"
exam_attempt.save()
self._create_started_exam_attempt() rendered_response = get_student_view(
user_id=self.user_id,
course_id=self.course_id,
content_id=self.content_id,
context={
'is_proctored': True,
'display_name': self.exam_name,
'default_time_limit_mins': 90
}
)
self.assertIn(self.proctored_exam_submitted_msg, rendered_response)
get_student_view( def test_get_studentview_rejected_status(self): # pylint: disable=invalid-name
"""
Test for get_student_view proctored exam which has been rejected.
"""
exam_attempt = self._create_started_exam_attempt()
exam_attempt.status = "rejected"
exam_attempt.save()
rendered_response = get_student_view(
user_id=self.user_id, user_id=self.user_id,
course_id=self.course_id, course_id=self.course_id,
content_id=self.content_id, content_id=self.content_id,
...@@ -584,15 +625,51 @@ class ProctoredExamApiTests(LoggedInTestCase): ...@@ -584,15 +625,51 @@ class ProctoredExamApiTests(LoggedInTestCase):
'default_time_limit_mins': 90 'default_time_limit_mins': 90
} }
) )
# TO DO Not getting anything back in this call to get_student_view. self.assertIn(self.proctored_exam_rejected_msg, rendered_response)
# Will have to implement the testcase later when functionality is implemented.
# TO DO No condition set for finished exam in the student view. Will have to test later. def test_get_studentview_verified_status(self): # pylint: disable=invalid-name
# def test_getstudentview_finishedxam(self): """
Test for get_student_view proctored exam which has been verified.
"""
exam_attempt = self._create_started_exam_attempt()
exam_attempt.status = "verified"
exam_attempt.save()
def test_getstudentview_expiredxam(self): rendered_response = get_student_view(
user_id=self.user_id,
course_id=self.course_id,
content_id=self.content_id,
context={
'is_proctored': True,
'display_name': self.exam_name,
'default_time_limit_mins': 90
}
)
self.assertIn(self.proctored_exam_verified_msg, rendered_response)
def test_get_studentview_completed_status(self): # pylint: disable=invalid-name
""" """
Test for get_student_view proctored exam which has started. Test for get_student_view proctored exam which has been completed.
"""
exam_attempt = self._create_started_exam_attempt()
exam_attempt.status = "completed"
exam_attempt.save()
rendered_response = get_student_view(
user_id=self.user_id,
course_id=self.course_id,
content_id=self.content_id,
context={
'is_proctored': True,
'display_name': self.exam_name,
'default_time_limit_mins': 90
}
)
self.assertIn(self.proctored_exam_completed_msg, rendered_response)
def test_get_studentview_expired(self):
"""
Test for get_student_view proctored exam which has expired.
""" """
self._create_started_exam_attempt(started_at=datetime.now(pytz.UTC).replace(year=2010)) self._create_started_exam_attempt(started_at=datetime.now(pytz.UTC).replace(year=2010))
...@@ -635,3 +712,41 @@ class ProctoredExamApiTests(LoggedInTestCase): ...@@ -635,3 +712,41 @@ class ProctoredExamApiTests(LoggedInTestCase):
} }
) )
self.assertIn(self.exam_time_error_msg, rendered_response) self.assertIn(self.exam_time_error_msg, rendered_response)
def test_get_studentview_unstarted_timed_exam(self): # pylint: disable=invalid-name
"""
Test for get_student_view Timed exam which is not proctored and has not started yet.
"""
rendered_response = get_student_view(
user_id=self.user_id,
course_id="abc",
content_id=self.content_id,
context={
'is_proctored': False,
'display_name': self.exam_name,
'default_time_limit_mins': 90
}
)
self.assertNotIn('data-exam-id="%d"' % self.proctored_exam_id, rendered_response)
self.assertIn(self.timed_exam_msg % self.exam_name, rendered_response)
self.assertNotIn(self.start_an_exam_msg % self.exam_name, rendered_response)
def test_get_studentview_completed_timed_exam(self): # pylint: disable=invalid-name
"""
Test for get_student_view timed exam which has completed.
"""
exam_attempt = self._create_started_exam_attempt(is_proctored=False)
exam_attempt.status = "completed"
exam_attempt.save()
rendered_response = get_student_view(
user_id=self.user_id,
course_id=self.course_id,
content_id=self.content_id_timed,
context={
'is_proctored': False,
'display_name': self.exam_name,
'default_time_limit_mins': 90
}
)
self.assertIn(self.timed_exam_completed_msg, rendered_response)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment