Unverified Commit 12cb635d by Sylvia Pearce Committed by GitHub

Merge branch 'master' into sylvia/DOC-3842

parents 76bffefa 8039cfa7
...@@ -11,6 +11,8 @@ before_install: ...@@ -11,6 +11,8 @@ before_install:
install: install:
- pip install setuptools==32.3.1 # need newer version than Travis default - pip install setuptools==32.3.1 # need newer version than Travis default
- make install - make install
before_script:
- npm install -g gulp-cli
script: script:
- make test-all - make test-all
- make test-js - make test-js
......
...@@ -4,6 +4,6 @@ The exam proctoring subsystem for the Open edX platform. ...@@ -4,6 +4,6 @@ The exam proctoring subsystem for the Open edX platform.
from __future__ import absolute_import from __future__ import absolute_import
__version__ = '1.2.0' __version__ = '1.3.1'
default_app_config = 'edx_proctoring.apps.EdxProctoringConfig' # pylint: disable=invalid-name default_app_config = 'edx_proctoring.apps.EdxProctoringConfig' # pylint: disable=invalid-name
...@@ -313,6 +313,10 @@ def get_exam_by_content_id(course_id, content_id): ...@@ -313,6 +313,10 @@ def get_exam_by_content_id(course_id, content_id):
""" """
proctored_exam = ProctoredExam.get_exam_by_content_id(course_id, content_id) proctored_exam = ProctoredExam.get_exam_by_content_id(course_id, content_id)
if proctored_exam is None: if proctored_exam is None:
log.exception(
'Cannot find the proctored exams in this course %s with content_id: %s',
course_id, content_id
)
raise ProctoredExamNotFoundException raise ProctoredExamNotFoundException
serialized_exam_object = ProctoredExamSerializer(proctored_exam) serialized_exam_object = ProctoredExamSerializer(proctored_exam)
...@@ -349,12 +353,12 @@ def add_allowance_for_user(exam_id, user_info, key, value): ...@@ -349,12 +353,12 @@ def add_allowance_for_user(exam_id, user_info, key, value):
emit_event(exam, 'allowance.{action}'.format(action=action), override_data=data) emit_event(exam, 'allowance.{action}'.format(action=action), override_data=data)
def get_allowances_for_course(course_id, timed_exams_only=False): def get_allowances_for_course(course_id):
""" """
Get all the allowances for the course. Get all the allowances for the course.
""" """
student_allowances = ProctoredExamStudentAllowance.get_allowances_for_course( student_allowances = ProctoredExamStudentAllowance.get_allowances_for_course(
course_id, timed_exams_only=timed_exams_only course_id
) )
return [ProctoredExamStudentAllowanceSerializer(allowance).data for allowance in student_allowances] return [ProctoredExamStudentAllowanceSerializer(allowance).data for allowance in student_allowances]
...@@ -1981,15 +1985,15 @@ def get_exam_violation_report(course_id, include_practice_exams=False): ...@@ -1981,15 +1985,15 @@ def get_exam_violation_report(course_id, include_practice_exams=False):
for review in reviews: for review in reviews:
attempt_code = review.attempt_code attempt_code = review.attempt_code
if attempt_code in attempts_by_code:
attempts_by_code[attempt_code]['review_status'] = review.review_status
attempts_by_code[attempt_code]['review_status'] = review.review_status for comment in review.proctoredexamsoftwaresecurecomment_set.all():
comments_key = '{status} Comments'.format(status=comment.status)
for comment in review.proctoredexamsoftwaresecurecomment_set.all():
comments_key = '{status} Comments'.format(status=comment.status)
if comments_key not in attempts_by_code[attempt_code]: if comments_key not in attempts_by_code[attempt_code]:
attempts_by_code[attempt_code][comments_key] = [] attempts_by_code[attempt_code][comments_key] = []
attempts_by_code[attempt_code][comments_key].append(comment.comment) attempts_by_code[attempt_code][comments_key].append(comment.comment)
return sorted(attempts_by_code.values(), key=lambda a: a['exam_name']) return sorted(attempts_by_code.values(), key=lambda a: a['exam_name'])
...@@ -10,7 +10,7 @@ msgstr "" ...@@ -10,7 +10,7 @@ msgstr ""
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-15 17:16-0500\n" "POT-Creation-Date: 2017-02-15 17:16-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: ADRIAN ABREU GONZALEZ <aabreuglez@gmail.com>, 2016\n" "Last-Translator: URJConline <online.mooc@urjc.es>, 2017\n"
"Language-Team: Spanish (Spain) (https://www.transifex.com/open-edx/teams/6205/es_ES/)\n" "Language-Team: Spanish (Spain) (https://www.transifex.com/open-edx/teams/6205/es_ES/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
......
...@@ -10,7 +10,7 @@ msgstr "" ...@@ -10,7 +10,7 @@ msgstr ""
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-15 17:16-0500\n" "POT-Creation-Date: 2017-02-15 17:16-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Mariangeles Fernandez <mariangelesfm@gmail.com>, 2016\n" "Last-Translator: Miguel Angel Cordova <miguel.angel.cordova.uned@gmail.com>, 2017\n"
"Language-Team: Spanish (Spain) (https://www.transifex.com/open-edx/teams/6205/es_ES/)\n" "Language-Team: Spanish (Spain) (https://www.transifex.com/open-edx/teams/6205/es_ES/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
......
...@@ -10,7 +10,7 @@ msgstr "" ...@@ -10,7 +10,7 @@ msgstr ""
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-15 17:16-0500\n" "POT-Creation-Date: 2017-02-15 17:16-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Matheus Gomes Correia <matheus.gomes03@hotmail.com>, 2017\n" "Last-Translator: Paulo Romano <pauloromanocarvalho@gmail.com>, 2017\n"
"Language-Team: Portuguese (Brazil) (https://www.transifex.com/open-edx/teams/6205/pt_BR/)\n" "Language-Team: Portuguese (Brazil) (https://www.transifex.com/open-edx/teams/6205/pt_BR/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
......
...@@ -10,7 +10,7 @@ msgstr "" ...@@ -10,7 +10,7 @@ msgstr ""
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-15 17:16-0500\n" "POT-Creation-Date: 2017-02-15 17:16-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Matheus Gomes Correia <matheus.gomes03@hotmail.com>, 2017\n" "Last-Translator: Paulo Romano <pauloromanocarvalho@gmail.com>, 2017\n"
"Language-Team: Portuguese (Brazil) (https://www.transifex.com/open-edx/teams/6205/pt_BR/)\n" "Language-Team: Portuguese (Brazil) (https://www.transifex.com/open-edx/teams/6205/pt_BR/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
......
...@@ -10,7 +10,7 @@ msgstr "" ...@@ -10,7 +10,7 @@ msgstr ""
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-15 17:16-0500\n" "POT-Creation-Date: 2017-02-15 17:16-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Manuel Pacurar <manuel@pcdoctor.ro>, 2017\n" "Last-Translator: Mihaela Caraulasu <mihaela@accelerole.com>, 2017\n"
"Language-Team: Romanian (https://www.transifex.com/open-edx/teams/6205/ro/)\n" "Language-Team: Romanian (https://www.transifex.com/open-edx/teams/6205/ro/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
......
...@@ -10,7 +10,7 @@ msgstr "" ...@@ -10,7 +10,7 @@ msgstr ""
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-15 17:16-0500\n" "POT-Creation-Date: 2017-02-15 17:16-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Manuel Pacurar <manuel@pcdoctor.ro>, 2017\n" "Last-Translator: Mihaela Caraulasu <mihaela@accelerole.com>, 2017\n"
"Language-Team: Romanian (https://www.transifex.com/open-edx/teams/6205/ro/)\n" "Language-Team: Romanian (https://www.transifex.com/open-edx/teams/6205/ro/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
......
...@@ -10,7 +10,7 @@ msgstr "" ...@@ -10,7 +10,7 @@ msgstr ""
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-15 17:16-0500\n" "POT-Creation-Date: 2017-02-15 17:16-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Mehlika Pişkin <mehlikapiskin@gmail.com>, 2016\n" "Last-Translator: Ali Işıngör <ali@artistanbul.io>, 2017\n"
"Language-Team: Turkish (https://www.transifex.com/open-edx/teams/6205/tr/)\n" "Language-Team: Turkish (https://www.transifex.com/open-edx/teams/6205/tr/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
......
...@@ -10,7 +10,7 @@ msgstr "" ...@@ -10,7 +10,7 @@ msgstr ""
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-15 17:16-0500\n" "POT-Creation-Date: 2017-02-15 17:16-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Oğuzhan KAYAR <oguzhank77@gmail.com>, 2016\n" "Last-Translator: Ali Işıngör <ali@artistanbul.io>, 2017\n"
"Language-Team: Turkish (https://www.transifex.com/open-edx/teams/6205/tr/)\n" "Language-Team: Turkish (https://www.transifex.com/open-edx/teams/6205/tr/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
......
...@@ -10,7 +10,7 @@ msgstr "" ...@@ -10,7 +10,7 @@ msgstr ""
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-15 17:16-0500\n" "POT-Creation-Date: 2017-02-15 17:16-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: junnyml <inactive+junnyml@transifex.com>, 2016\n" "Last-Translator: olexiim <olexiim@gmail.com>, 2016\n"
"Language-Team: Ukrainian (https://www.transifex.com/open-edx/teams/6205/uk/)\n" "Language-Team: Ukrainian (https://www.transifex.com/open-edx/teams/6205/uk/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
...@@ -56,7 +56,7 @@ msgstr "" ...@@ -56,7 +56,7 @@ msgstr ""
#: admin.py:383 #: admin.py:383
msgid "Started" msgid "Started"
msgstr "" msgstr "Розпочався"
#: admin.py:384 #: admin.py:384
msgid "Ready To Submit" msgid "Ready To Submit"
...@@ -80,7 +80,7 @@ msgstr "" ...@@ -80,7 +80,7 @@ msgstr ""
#: admin.py:389 #: admin.py:389
msgid "Verified" msgid "Verified"
msgstr "" msgstr "Перевірений"
#: admin.py:390 #: admin.py:390
msgid "Rejected" msgid "Rejected"
...@@ -504,7 +504,7 @@ msgstr "" ...@@ -504,7 +504,7 @@ msgstr ""
#: templates/proctored_exam/instructions.html:50 #: templates/proctored_exam/instructions.html:50
msgid "Start Proctored Exam" msgid "Start Proctored Exam"
msgstr "" msgstr "Розпочати контрольовані іспити"
#: templates/proctored_exam/instructions.html:60 #: templates/proctored_exam/instructions.html:60
msgid "Close" msgid "Close"
......
...@@ -10,7 +10,7 @@ msgstr "" ...@@ -10,7 +10,7 @@ msgstr ""
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-15 17:16-0500\n" "POT-Creation-Date: 2017-02-15 17:16-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: IvanPrimachenko <jastudent1@gmail.com>, 2016\n" "Last-Translator: olexiim <olexiim@gmail.com>, 2016\n"
"Language-Team: Ukrainian (https://www.transifex.com/open-edx/teams/6205/uk/)\n" "Language-Team: Ukrainian (https://www.transifex.com/open-edx/teams/6205/uk/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
...@@ -56,7 +56,7 @@ msgstr "" ...@@ -56,7 +56,7 @@ msgstr ""
#: admin.py:383 #: admin.py:383
msgid "Started" msgid "Started"
msgstr "" msgstr "Розпочався"
#: admin.py:384 #: admin.py:384
msgid "Ready To Submit" msgid "Ready To Submit"
...@@ -80,7 +80,7 @@ msgstr "" ...@@ -80,7 +80,7 @@ msgstr ""
#: admin.py:389 #: admin.py:389
msgid "Verified" msgid "Verified"
msgstr "" msgstr "Перевірений"
#: admin.py:390 #: admin.py:390
msgid "Rejected" msgid "Rejected"
...@@ -504,7 +504,7 @@ msgstr "" ...@@ -504,7 +504,7 @@ msgstr ""
#: templates/proctored_exam/instructions.html:50 #: templates/proctored_exam/instructions.html:50
msgid "Start Proctored Exam" msgid "Start Proctored Exam"
msgstr "" msgstr "Розпочати контрольовані іспити"
#: templates/proctored_exam/instructions.html:60 #: templates/proctored_exam/instructions.html:60
msgid "Close" msgid "Close"
......
...@@ -10,7 +10,7 @@ msgstr "" ...@@ -10,7 +10,7 @@ msgstr ""
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-15 17:16-0500\n" "POT-Creation-Date: 2017-02-15 17:16-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Anh Phan <vietnamesel10n@gmail.com>, 2016\n" "Last-Translator: Hoà Lê Thanh <hoa.lethanh@gmail.com>, 2017\n"
"Language-Team: Vietnamese (https://www.transifex.com/open-edx/teams/6205/vi/)\n" "Language-Team: Vietnamese (https://www.transifex.com/open-edx/teams/6205/vi/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
......
...@@ -10,7 +10,7 @@ msgstr "" ...@@ -10,7 +10,7 @@ msgstr ""
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-15 17:16-0500\n" "POT-Creation-Date: 2017-02-15 17:16-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Anh Phan <vietnamesel10n@gmail.com>, 2016\n" "Last-Translator: Hoà Lê Thanh <hoa.lethanh@gmail.com>, 2017\n"
"Language-Team: Vietnamese (https://www.transifex.com/open-edx/teams/6205/vi/)\n" "Language-Team: Vietnamese (https://www.transifex.com/open-edx/teams/6205/vi/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
......
...@@ -10,7 +10,7 @@ msgstr "" ...@@ -10,7 +10,7 @@ msgstr ""
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-15 17:16-0500\n" "POT-Creation-Date: 2017-02-15 17:16-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: jsgang <jsgang9@gmail.com>, 2017\n" "Last-Translator: 张太红 <zth@xjau.edu.cn>, 2017\n"
"Language-Team: Chinese (China) (https://www.transifex.com/open-edx/teams/6205/zh_CN/)\n" "Language-Team: Chinese (China) (https://www.transifex.com/open-edx/teams/6205/zh_CN/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
......
...@@ -10,7 +10,7 @@ msgstr "" ...@@ -10,7 +10,7 @@ msgstr ""
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-15 17:16-0500\n" "POT-Creation-Date: 2017-02-15 17:16-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: jsgang <jsgang9@gmail.com>, 2017\n" "Last-Translator: 张太红 <zth@xjau.edu.cn>, 2017\n"
"Language-Team: Chinese (China) (https://www.transifex.com/open-edx/teams/6205/zh_CN/)\n" "Language-Team: Chinese (China) (https://www.transifex.com/open-edx/teams/6205/zh_CN/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
......
...@@ -394,27 +394,20 @@ class ProctoredExamStudentAttemptManager(models.Manager): ...@@ -394,27 +394,20 @@ class ProctoredExamStudentAttemptManager(models.Manager):
exam_attempt_obj = None exam_attempt_obj = None
return exam_attempt_obj return exam_attempt_obj
def get_all_exam_attempts(self, course_id, timed_exams_only=False): def get_all_exam_attempts(self, course_id):
""" """
Returns the Student Exam Attempts for the given course_id. Returns the Student Exam Attempts for the given course_id.
""" """
filtered_query = Q(proctored_exam__course_id=course_id) filtered_query = Q(proctored_exam__course_id=course_id)
if timed_exams_only:
filtered_query = filtered_query & Q(proctored_exam__is_proctored=False)
return self.filter(filtered_query).order_by('-created') return self.filter(filtered_query).order_by('-created')
def get_filtered_exam_attempts(self, course_id, search_by, timed_exams_only=False): def get_filtered_exam_attempts(self, course_id, search_by):
""" """
Returns the Student Exam Attempts for the given course_id filtered by search_by. Returns the Student Exam Attempts for the given course_id filtered by search_by.
""" """
filtered_query = Q(proctored_exam__course_id=course_id) & ( filtered_query = Q(proctored_exam__course_id=course_id) & (
Q(user__username__contains=search_by) | Q(user__email__contains=search_by) Q(user__username__contains=search_by) | Q(user__email__contains=search_by)
) )
if timed_exams_only:
filtered_query = filtered_query & Q(proctored_exam__is_proctored=False)
return self.filter(filtered_query).order_by('-created') # pylint: disable=no-member return self.filter(filtered_query).order_by('-created') # pylint: disable=no-member
def get_proctored_exam_attempts(self, course_id, username): def get_proctored_exam_attempts(self, course_id, username):
...@@ -720,14 +713,11 @@ class ProctoredExamStudentAllowance(TimeStampedModel): ...@@ -720,14 +713,11 @@ class ProctoredExamStudentAllowance(TimeStampedModel):
verbose_name = 'proctored allowance' verbose_name = 'proctored allowance'
@classmethod @classmethod
def get_allowances_for_course(cls, course_id, timed_exams_only=False): def get_allowances_for_course(cls, course_id):
""" """
Returns all the allowances for a course. Returns all the allowances for a course.
""" """
filtered_query = Q(proctored_exam__course_id=course_id) filtered_query = Q(proctored_exam__course_id=course_id)
if timed_exams_only:
filtered_query = filtered_query & Q(proctored_exam__is_proctored=False)
return cls.objects.filter(filtered_query) return cls.objects.filter(filtered_query)
@classmethod @classmethod
......
...@@ -49,6 +49,9 @@ var edx = edx || {}; ...@@ -49,6 +49,9 @@ var edx = edx || {};
/* will call into the rendering */ /* will call into the rendering */
this.model.fetch(); this.model.fetch();
}, },
events: {
'click #toggle_timer': 'toggleTimerVisibility'
},
detectScroll: function(event) { detectScroll: function(event) {
if ($(event.currentTarget).scrollTop() > this.timerBarTopPosition) { if ($(event.currentTarget).scrollTop() > this.timerBarTopPosition) {
$(".proctored_exam_status").addClass('is-fixed'); $(".proctored_exam_status").addClass('is-fixed');
...@@ -171,6 +174,22 @@ var edx = edx || {}; ...@@ -171,6 +174,22 @@ var edx = edx || {};
// refresh the page when the timer expired // refresh the page when the timer expired
self.reloadPage(); self.reloadPage();
} }
},
toggleTimerVisibility: function (event) {
var button = $(event.currentTarget);
var icon = button.find('i');
var timer = this.$el.find('span#time_remaining_id b');
if (timer.hasClass('timer-hidden')) {
timer.removeClass('timer-hidden');
button.attr('aria-pressed', 'false');
icon.removeClass('fa-eye').addClass('fa-eye-slash');
} else {
timer.addClass('timer-hidden');
button.attr('aria-pressed', 'true');
icon.removeClass('fa-eye-slash').addClass('fa-eye');
}
event.stopPropagation();
event.preventDefault();
} }
}); });
this.edx.coursware.proctored_exam.ProctoredExamView = edx.coursware.proctored_exam.ProctoredExamView; this.edx.coursware.proctored_exam.ProctoredExamView = edx.coursware.proctored_exam.ProctoredExamView;
......
...@@ -9,7 +9,11 @@ describe('ProctoredExamView', function () { ...@@ -9,7 +9,11 @@ describe('ProctoredExamView', function () {
'You are taking "' + 'You are taking "' +
'<a href="<%= exam_url_path %>"> <%= exam_display_name %> </a>' + '<a href="<%= exam_url_path %>"> <%= exam_display_name %> </a>' +
'" as a proctored exam. The timer on the right shows the time remaining in the exam' + '" as a proctored exam. The timer on the right shows the time remaining in the exam' +
'<span id="time_remaining_id" class="pull-right"> <b> </b> </span> </div>' + '<span class="exam-timer-clock"> <span id="time_remaining_id">' +
'<b> </b> <button role="button" id="toggle_timer" aria-label="Hide Timer" aria-pressed="false">' +
'<i class="fa fa-eye-slash" aria-hidden="true"></i></button>' +
'</span> </span>' +
'</div>' +
'</script>'+ '</script>'+
'</div>' '</div>'
); );
...@@ -55,6 +59,15 @@ describe('ProctoredExamView', function () { ...@@ -55,6 +59,15 @@ describe('ProctoredExamView', function () {
this.proctored_exam_view.render(); this.proctored_exam_view.render();
expect(this.proctored_exam_view.$el.find('div.exam-timer')).toHaveClass('low-time critical'); expect(this.proctored_exam_view.$el.find('div.exam-timer')).toHaveClass('low-time critical');
}); });
it('toggles timer visibility correctly', function() {
var button = this.proctored_exam_view.$el.find('#toggle_timer');
var timer = this.proctored_exam_view.$el.find('span#time_remaining_id b');
expect(timer).not.toHaveClass('timer-hidden');
button.click();
expect(timer).toHaveClass('timer-hidden');
button.click();
expect(timer).not.toHaveClass('timer-hidden');
});
it("reload the page when the exam time finishes", function(){ it("reload the page when the exam time finishes", function(){
this.proctored_exam_view.secondsLeft = -10; this.proctored_exam_view.secondsLeft = -10;
var reloadPage = spyOn(this.proctored_exam_view, 'reloadPage'); var reloadPage = spyOn(this.proctored_exam_view, 'reloadPage');
......
...@@ -409,7 +409,7 @@ class ProctoredExamApiTests(ProctoredExamTestCase): ...@@ -409,7 +409,7 @@ class ProctoredExamApiTests(ProctoredExamTestCase):
Test to get all the allowances for a course. Test to get all the allowances for a course.
""" """
allowance = self._add_allowance_for_user() allowance = self._add_allowance_for_user()
course_allowances = get_allowances_for_course(self.course_id, False) course_allowances = get_allowances_for_course(self.course_id)
self.assertEqual(len(course_allowances), 1) self.assertEqual(len(course_allowances), 1)
self.assertEqual(course_allowances[0]['proctored_exam']['course_id'], allowance.proctored_exam.course_id) self.assertEqual(course_allowances[0]['proctored_exam']['course_id'], allowance.proctored_exam.course_id)
...@@ -1817,3 +1817,37 @@ class ProctoredExamApiTests(ProctoredExamTestCase): ...@@ -1817,3 +1817,37 @@ class ProctoredExamApiTests(ProctoredExamTestCase):
self.assertEqual(report[1]['review_status'], 'Clean') self.assertEqual(report[1]['review_status'], 'Clean')
self.assertIsNone(report[0]['review_status']) self.assertIsNone(report[0]['review_status'])
def test_get_exam_violation_report_with_deleted_exam_attempt(self):
"""
Tests that get_exam_violation_report does not fail in scenerio
where an exam attempt does not exist for related review.
"""
test_exam_id = create_exam(
course_id=self.course_id,
content_id='test_content_1',
exam_name='test_exam',
time_limit_mins=self.default_time_limit
)
test_attempt_id = create_exam_attempt(
exam_id=test_exam_id,
user_id=self.user_id
)
exam1_attempt = ProctoredExamStudentAttempt.objects.get_exam_attempt_by_id(test_attempt_id)
ProctoredExamSoftwareSecureReview.objects.create(
exam=ProctoredExam.get_exam_by_id(test_exam_id),
attempt_code=exam1_attempt.attempt_code,
review_status="Suspicious"
)
# exam attempt is deleted but corresponding review instance exists.
exam1_attempt.delete()
report = get_exam_violation_report(self.course_id)
# call to get_exam_violation_report did not fail. Assert that report is empty as
# the only exam atempt was deleted.
self.assertEqual(len(report), 0)
...@@ -1190,11 +1190,10 @@ class TestStudentProctoredExamAttempt(LoggedInTestCase): ...@@ -1190,11 +1190,10 @@ class TestStudentProctoredExamAttempt(LoggedInTestCase):
response_data = json.loads(response.content) response_data = json.loads(response.content)
self.assertEqual(len(response_data['proctored_exam_attempts']), 1) self.assertEqual(len(response_data['proctored_exam_attempts']), 1)
def test_exam_attempts_not_staff(self): def test_exam_attempts_not_global_staff(self):
""" """
Test to get the exam attempts in a course as a not Test to get both timed and proctored exam attempts
staff user but still we get the timed exams attempts in a course as a course staff
but not the proctored exam attempts
""" """
# Create an timed_exam. # Create an timed_exam.
timed_exam = ProctoredExam.objects.create( timed_exam = ProctoredExam.objects.create(
...@@ -1241,11 +1240,15 @@ class TestStudentProctoredExamAttempt(LoggedInTestCase): ...@@ -1241,11 +1240,15 @@ class TestStudentProctoredExamAttempt(LoggedInTestCase):
response = self.client.get(url) response = self.client.get(url)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
response_data = json.loads(response.content) response_data = json.loads(response.content)
# we should only get the timed exam attempt in this case # assert that both timed and proctored exam attempts are in response data
# so the len should be 1 # so the len should be 2
self.assertEqual(len(response_data['proctored_exam_attempts']), 1) self.assertEqual(len(response_data['proctored_exam_attempts']), 2)
self.assertEqual( self.assertEqual(
response_data['proctored_exam_attempts'][0]['proctored_exam']['is_proctored'], response_data['proctored_exam_attempts'][0]['proctored_exam']['is_proctored'],
proctored_exam.is_proctored
)
self.assertEqual(
response_data['proctored_exam_attempts'][1]['proctored_exam']['is_proctored'],
timed_exam.is_proctored timed_exam.is_proctored
) )
...@@ -2317,9 +2320,9 @@ class TestExamAllowanceView(LoggedInTestCase): ...@@ -2317,9 +2320,9 @@ class TestExamAllowanceView(LoggedInTestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
response_data = json.loads(response.content) response_data = json.loads(response.content)
# we should only get the timed exam allowance # assert that both timed and proctored exams allowance are in response data
# We are not logged in as a global user # so the len should be 2
self.assertEqual(len(response_data), 1) self.assertEqual(len(response_data), 2)
self.assertEqual(response_data[0]['proctored_exam']['course_id'], timed_exam.course_id) self.assertEqual(response_data[0]['proctored_exam']['course_id'], timed_exam.course_id)
self.assertEqual(response_data[0]['proctored_exam']['content_id'], timed_exam.content_id) self.assertEqual(response_data[0]['proctored_exam']['content_id'], timed_exam.content_id)
self.assertEqual(response_data[0]['key'], allowance_data['key']) self.assertEqual(response_data[0]['key'], allowance_data['key'])
......
...@@ -611,18 +611,16 @@ class StudentProctoredExamAttemptsByCourse(AuthenticatedAPIView): ...@@ -611,18 +611,16 @@ class StudentProctoredExamAttemptsByCourse(AuthenticatedAPIView):
def get(self, request, course_id, search_by=None): # pylint: disable=unused-argument def get(self, request, course_id, search_by=None): # pylint: disable=unused-argument
""" """
HTTP GET Handler. Returns the status of the exam attempt. HTTP GET Handler. Returns the status of the exam attempt.
Course and Global staff can view both timed and proctored exam attempts.
""" """
# course staff only views attempts of timed exams. edx staff can view both timed and proctored attempts.
time_exams_only = not request.user.is_staff
if search_by is not None: if search_by is not None:
exam_attempts = ProctoredExamStudentAttempt.objects.get_filtered_exam_attempts( exam_attempts = ProctoredExamStudentAttempt.objects.get_filtered_exam_attempts(
course_id, search_by, time_exams_only course_id, search_by
) )
attempt_url = reverse('edx_proctoring.proctored_exam.attempts.search', args=[course_id, search_by]) attempt_url = reverse('edx_proctoring.proctored_exam.attempts.search', args=[course_id, search_by])
else: else:
exam_attempts = ProctoredExamStudentAttempt.objects.get_all_exam_attempts( exam_attempts = ProctoredExamStudentAttempt.objects.get_all_exam_attempts(
course_id, time_exams_only course_id
) )
attempt_url = reverse('edx_proctoring.proctored_exam.attempts.course', args=[course_id]) attempt_url = reverse('edx_proctoring.proctored_exam.attempts.course', args=[course_id])
...@@ -701,13 +699,10 @@ class ExamAllowanceView(AuthenticatedAPIView): ...@@ -701,13 +699,10 @@ class ExamAllowanceView(AuthenticatedAPIView):
def get(self, request, course_id): # pylint: disable=unused-argument def get(self, request, course_id): # pylint: disable=unused-argument
""" """
HTTP GET handler. Get all allowances for a course. HTTP GET handler. Get all allowances for a course.
Course and Global staff can view both timed and proctored exam allowances.
""" """
# course staff only views attempts of timed exams. edx staff can view both timed and proctored attempts.
time_exams_only = not request.user.is_staff
result_set = get_allowances_for_course( result_set = get_allowances_for_course(
course_id=course_id, course_id=course_id
timed_exams_only=time_exams_only
) )
return Response(result_set) return Response(result_set)
......
...@@ -19,15 +19,15 @@ module.exports = function(config) { ...@@ -19,15 +19,15 @@ module.exports = function(config) {
plugins:[ plugins:[
'karma-jasmine', 'karma-jasmine',
'karma-jasmine-jquery', 'karma-jasmine-jquery',
'karma-firefox-launcher',
'karma-jasmine-jquery', 'karma-jasmine-jquery',
'karma-chrome-launcher', 'karma-chrome-launcher',
'karma-phantomjs-launcher',
'karma-coverage', 'karma-coverage',
'karma-sinon' 'karma-sinon'
], ],
// start the browser // start the browser
browsers: ['Firefox'], browsers: ['PhantomJS'],
//frameworks to use //frameworks to use
frameworks: ['jasmine-jquery', 'jasmine', 'sinon'], frameworks: ['jasmine-jquery', 'jasmine', 'sinon'],
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
# http://open-edx-proposals.readthedocs.io/en/latest/oeps/oep-0002.html#specification # http://open-edx-proposals.readthedocs.io/en/latest/oeps/oep-0002.html#specification
nick: proc nick: proc
owner: edx/teaching-and-learning owner: edx/educator-dahlia
tags: tags:
- lms - lms
oeps: oeps:
......
...@@ -7,12 +7,15 @@ ...@@ -7,12 +7,15 @@
"devDependencies": { "devDependencies": {
"gulp": "^3.9.0", "gulp": "^3.9.0",
"gulp-karma": "0.0.1", "gulp-karma": "0.0.1",
"jasmine-core": "^2.8.0",
"karma": "^0.13.0", "karma": "^0.13.0",
"karma-chrome-launcher": "^0.2.0", "karma-chrome-launcher": "^0.2.0",
"karma-coverage": "latest", "karma-coverage": "^1.1.1",
"karma-firefox-launcher": "latest", "karma-phantomjs-launcher": "^1.0.4",
"karma-jasmine": "^0.3.6", "karma-jasmine": "^0.3.6",
"karma-jasmine-jquery": "0.1.1", "karma-jasmine-jquery": "0.1.1",
"karma-sinon": "latest" "karma-sinon": "^1.0.5",
"phantomjs-prebuilt": "^2.1.14",
"sinon": "^3.2.1"
} }
} }
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
# Django/Framework Packages # Django/Framework Packages
django>=1.8,<1.9a django>=1.8,<1.9a
django-model-utils>=2.3.1 django-model-utils>=2.3.1
djangorestframework>=3.1 djangorestframework>=3.1,<3.7
django-ipware>=1.1.0 django-ipware>=1.1.0
pytz>=2012h pytz>=2012h
pycrypto>=2.6 pycrypto>=2.6
......
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