Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-proctoring
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
OpenEdx
edx-proctoring
Commits
f3e1da2d
Commit
f3e1da2d
authored
Oct 14, 2015
by
chrisndodge
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #200 from edx/cdodge/declined-status
store declines in the CreditRequirementStatus table
parents
4f897783
d5de1d82
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
123 additions
and
19 deletions
+123
-19
edx_proctoring/api.py
+32
-8
edx_proctoring/tests/test_api.py
+89
-9
edx_proctoring/tests/test_views.py
+2
-2
No files found.
edx_proctoring/api.py
View file @
f3e1da2d
...
@@ -600,11 +600,13 @@ def update_attempt_status(exam_id, user_id, to_status, raise_if_not_found=True,
...
@@ -600,11 +600,13 @@ def update_attempt_status(exam_id, user_id, to_status, raise_if_not_found=True,
exam
=
get_exam_by_id
(
exam_id
)
exam
=
get_exam_by_id
(
exam_id
)
if
to_status
==
ProctoredExamStudentAttemptStatus
.
verified
:
if
to_status
==
ProctoredExamStudentAttemptStatus
.
verified
:
verification
=
'satisfied'
credit_requirement_status
=
'satisfied'
elif
to_status
==
ProctoredExamStudentAttemptStatus
.
submitted
:
elif
to_status
==
ProctoredExamStudentAttemptStatus
.
submitted
:
verification
=
'submitted'
credit_requirement_status
=
'submitted'
elif
to_status
==
ProctoredExamStudentAttemptStatus
.
declined
:
credit_requirement_status
=
'declined'
else
:
else
:
verification
=
'failed'
credit_requirement_status
=
'failed'
log_msg
=
(
log_msg
=
(
'Calling set_credit_requirement_status for '
'Calling set_credit_requirement_status for '
...
@@ -613,7 +615,7 @@ def update_attempt_status(exam_id, user_id, to_status, raise_if_not_found=True,
...
@@ -613,7 +615,7 @@ def update_attempt_status(exam_id, user_id, to_status, raise_if_not_found=True,
user_id
=
exam_attempt_obj
.
user_id
,
user_id
=
exam_attempt_obj
.
user_id
,
course_id
=
exam
[
'course_id'
],
course_id
=
exam
[
'course_id'
],
content_id
=
exam_attempt_obj
.
proctored_exam
.
content_id
,
content_id
=
exam_attempt_obj
.
proctored_exam
.
content_id
,
status
=
verification
status
=
credit_requirement_status
)
)
)
)
log
.
info
(
log_msg
)
log
.
info
(
log_msg
)
...
@@ -623,7 +625,7 @@ def update_attempt_status(exam_id, user_id, to_status, raise_if_not_found=True,
...
@@ -623,7 +625,7 @@ def update_attempt_status(exam_id, user_id, to_status, raise_if_not_found=True,
course_key_or_id
=
exam
[
'course_id'
],
course_key_or_id
=
exam
[
'course_id'
],
req_namespace
=
'proctored_exam'
,
req_namespace
=
'proctored_exam'
,
req_name
=
exam_attempt_obj
.
proctored_exam
.
content_id
,
req_name
=
exam_attempt_obj
.
proctored_exam
.
content_id
,
status
=
verification
status
=
credit_requirement_status
)
)
if
cascade_effects
and
ProctoredExamStudentAttemptStatus
.
is_a_cascadable_failure
(
to_status
):
if
cascade_effects
and
ProctoredExamStudentAttemptStatus
.
is_a_cascadable_failure
(
to_status
):
...
@@ -963,6 +965,7 @@ def _are_prerequirements_satisfied(prerequisites_statuses, evaluate_for_requirem
...
@@ -963,6 +965,7 @@ def _are_prerequirements_satisfied(prerequisites_statuses, evaluate_for_requirem
satisfied_prerequisites
=
[]
satisfied_prerequisites
=
[]
failed_prerequisites
=
[]
failed_prerequisites
=
[]
pending_prerequisites
=
[]
pending_prerequisites
=
[]
declined_prerequisites
=
[]
# insure an ordered and filtered list
# insure an ordered and filtered list
# we remove 'grade' requirements since those cannot be
# we remove 'grade' requirements since those cannot be
...
@@ -996,6 +999,8 @@ def _are_prerequirements_satisfied(prerequisites_statuses, evaluate_for_requirem
...
@@ -996,6 +999,8 @@ def _are_prerequirements_satisfied(prerequisites_statuses, evaluate_for_requirem
satisfied_prerequisites
.
append
(
requirement
)
satisfied_prerequisites
.
append
(
requirement
)
elif
status
==
'failed'
:
elif
status
==
'failed'
:
failed_prerequisites
.
append
(
requirement
)
failed_prerequisites
.
append
(
requirement
)
elif
status
==
'declined'
:
declined_prerequisites
.
append
(
requirement
)
else
:
else
:
pending_prerequisites
.
append
(
requirement
)
pending_prerequisites
.
append
(
requirement
)
...
@@ -1005,12 +1010,13 @@ def _are_prerequirements_satisfied(prerequisites_statuses, evaluate_for_requirem
...
@@ -1005,12 +1010,13 @@ def _are_prerequirements_satisfied(prerequisites_statuses, evaluate_for_requirem
# all prequisites are satisfied if there are no failed or pending requirement
# all prequisites are satisfied if there are no failed or pending requirement
# statuses
# statuses
'are_prerequisites_satisifed'
:
(
'are_prerequisites_satisifed'
:
(
not
failed_prerequisites
and
not
pending_prerequisites
not
failed_prerequisites
and
not
pending_prerequisites
and
not
declined_prerequisites
),
),
# note that we reverse the list here, because we assempled it by walking backwards
# note that we reverse the list here, because we assempled it by walking backwards
'satisfied_prerequisites'
:
list
(
reversed
(
satisfied_prerequisites
)),
'satisfied_prerequisites'
:
list
(
reversed
(
satisfied_prerequisites
)),
'failed_prerequisites'
:
list
(
reversed
(
failed_prerequisites
)),
'failed_prerequisites'
:
list
(
reversed
(
failed_prerequisites
)),
'pending_prerequisites'
:
list
(
reversed
(
pending_prerequisites
)),
'pending_prerequisites'
:
list
(
reversed
(
pending_prerequisites
)),
'declined_prerequisites'
:
list
(
reversed
(
declined_prerequisites
))
}
}
...
@@ -1364,7 +1370,9 @@ def _get_proctored_exam_view(exam, context, exam_id, user_id, course_id):
...
@@ -1364,7 +1370,9 @@ def _get_proctored_exam_view(exam, context, exam_id, user_id, course_id):
# so, show them:
# so, show them:
# 1) If there are failed prerequisites then block user and say why
# 1) If there are failed prerequisites then block user and say why
# 2) If there are pending prerequisites then block user and allow them to remediate them
# 2) If there are pending prerequisites then block user and allow them to remediate them
# 3) Otherwise - all prerequisites are satisfied - then give user
# 3) If there are declined prerequisites, then we auto-decline proctoring since user
# explicitly declined their interest in credit
# 4) Otherwise - all prerequisites are satisfied - then give user
# option to take exam as proctored
# option to take exam as proctored
# get information about prerequisites
# get information about prerequisites
...
@@ -1386,7 +1394,23 @@ def _get_proctored_exam_view(exam, context, exam_id, user_id, course_id):
...
@@ -1386,7 +1394,23 @@ def _get_proctored_exam_view(exam, context, exam_id, user_id, course_id):
})
})
if
not
prerequisite_status
[
'are_prerequisites_satisifed'
]:
if
not
prerequisite_status
[
'are_prerequisites_satisifed'
]:
# do we have failed prerequisites? That takes priority
# do we have any declined prerequisites, if so, then we
# will auto-decline this proctored exam
if
prerequisite_status
[
'declined_prerequisites'
]:
# user hasn't a record of attempt, create one now
# so we can mark it as declined
create_exam_attempt
(
exam_id
,
user_id
)
update_attempt_status
(
exam_id
,
user_id
,
ProctoredExamStudentAttemptStatus
.
declined
,
raise_if_not_found
=
False
)
return
None
# do we have failed prerequisites? That takes priority in terms of
# messaging
if
prerequisite_status
[
'failed_prerequisites'
]:
if
prerequisite_status
[
'failed_prerequisites'
]:
# Let's resolve the URLs to jump to this prequisite
# Let's resolve the URLs to jump to this prequisite
prerequisite_status
[
'failed_prerequisites'
]
=
_resolve_prerequisite_links
(
prerequisite_status
[
'failed_prerequisites'
]
=
_resolve_prerequisite_links
(
...
...
edx_proctoring/tests/test_api.py
View file @
f3e1da2d
...
@@ -155,6 +155,39 @@ class ProctoredExamApiTests(LoggedInTestCase):
...
@@ -155,6 +155,39 @@ class ProctoredExamApiTests(LoggedInTestCase):
},
},
]
]
self
.
declined_prerequisites
=
[
{
'namespace'
:
'proctoring'
,
'name'
:
'proc1'
,
'order'
:
2
,
'status'
:
'satisfied'
,
},
{
'namespace'
:
'reverification'
,
'name'
:
'rever1'
,
'order'
:
1
,
'status'
:
'satisfied'
,
},
{
'namespace'
:
'grade'
,
'name'
:
'grade1'
,
'order'
:
0
,
'status'
:
'pending'
,
},
{
'namespace'
:
'reverification'
,
'name'
:
'rever2'
,
'order'
:
3
,
'status'
:
'declined'
,
},
{
'namespace'
:
'proctoring'
,
'name'
:
'proc2'
,
'order'
:
4
,
'status'
:
'pending'
,
},
]
def
_create_proctored_exam
(
self
):
def
_create_proctored_exam
(
self
):
"""
"""
Calls the api's create_exam to create an exam object.
Calls the api's create_exam to create an exam object.
...
@@ -575,6 +608,7 @@ class ProctoredExamApiTests(LoggedInTestCase):
...
@@ -575,6 +608,7 @@ class ProctoredExamApiTests(LoggedInTestCase):
@ddt.data
(
@ddt.data
(
(
ProctoredExamStudentAttemptStatus
.
verified
,
'satisfied'
),
(
ProctoredExamStudentAttemptStatus
.
verified
,
'satisfied'
),
(
ProctoredExamStudentAttemptStatus
.
submitted
,
'submitted'
),
(
ProctoredExamStudentAttemptStatus
.
submitted
,
'submitted'
),
(
ProctoredExamStudentAttemptStatus
.
declined
,
'declined'
),
(
ProctoredExamStudentAttemptStatus
.
error
,
'failed'
)
(
ProctoredExamStudentAttemptStatus
.
error
,
'failed'
)
)
)
@ddt.unpack
@ddt.unpack
...
@@ -797,11 +831,15 @@ class ProctoredExamApiTests(LoggedInTestCase):
...
@@ -797,11 +831,15 @@ class ProctoredExamApiTests(LoggedInTestCase):
(
'reverification'
,
'pending'
,
'The following prerequisites are in a <strong>pending</strong> state'
,
True
),
(
'reverification'
,
'pending'
,
'The following prerequisites are in a <strong>pending</strong> state'
,
True
),
(
'reverification'
,
'failed'
,
'You did not satisfy the following prerequisites'
,
True
),
(
'reverification'
,
'failed'
,
'You did not satisfy the following prerequisites'
,
True
),
(
'reverification'
,
'satisfied'
,
'To be eligible to earn credit for this course'
,
False
),
(
'reverification'
,
'satisfied'
,
'To be eligible to earn credit for this course'
,
False
),
(
'reverification'
,
'declined'
,
None
,
False
),
(
'proctored_exam'
,
None
,
'The following prerequisites are in a <strong>pending</strong> state'
,
True
),
(
'proctored_exam'
,
None
,
'The following prerequisites are in a <strong>pending</strong> state'
,
True
),
(
'proctored_exam'
,
'pending'
,
'The following prerequisites are in a <strong>pending</strong> state'
,
True
),
(
'proctored_exam'
,
'pending'
,
'The following prerequisites are in a <strong>pending</strong> state'
,
True
),
(
'proctored_exam'
,
'failed'
,
'You did not satisfy the following prerequisites'
,
True
),
(
'proctored_exam'
,
'failed'
,
'You did not satisfy the following prerequisites'
,
True
),
(
'proctored_exam'
,
'satisfied'
,
'To be eligible to earn credit for this course'
,
False
),
(
'proctored_exam'
,
'satisfied'
,
'To be eligible to earn credit for this course'
,
False
),
(
'grade'
,
'failed'
,
'To be eligible to earn credit for this course'
,
False
)
(
'proctored_exam'
,
'declined'
,
None
,
False
),
(
'grade'
,
'failed'
,
'To be eligible to earn credit for this course'
,
False
),
# this is nonesense, but let's double check it
(
'grade'
,
'declined'
,
'To be eligible to earn credit for this course'
,
False
),
)
)
@ddt.unpack
@ddt.unpack
def
test_prereq_scenarios
(
self
,
namespace
,
req_status
,
expected_content
,
should_see_prereq
):
def
test_prereq_scenarios
(
self
,
namespace
,
req_status
,
expected_content
,
should_see_prereq
):
...
@@ -839,7 +877,16 @@ class ProctoredExamApiTests(LoggedInTestCase):
...
@@ -839,7 +877,16 @@ class ProctoredExamApiTests(LoggedInTestCase):
}
}
)
)
self
.
assertIn
(
expected_content
,
rendered_response
)
if
expected_content
:
self
.
assertIn
(
expected_content
,
rendered_response
)
else
:
self
.
assertIsNone
(
rendered_response
)
if
req_status
==
'declined'
and
not
expected_content
:
# also we should have auto-declined if a pre-requisite was declined
attempt
=
get_exam_attempt
(
exam
[
'id'
],
self
.
user_id
)
self
.
assertIsNotNone
(
attempt
)
self
.
assertEqual
(
attempt
[
'status'
],
ProctoredExamStudentAttemptStatus
.
declined
)
if
should_see_prereq
:
if
should_see_prereq
:
self
.
assertIn
(
'Foo Requirement'
,
rendered_response
)
self
.
assertIn
(
'Foo Requirement'
,
rendered_response
)
...
@@ -2134,19 +2181,20 @@ class ProctoredExamApiTests(LoggedInTestCase):
...
@@ -2134,19 +2181,20 @@ class ProctoredExamApiTests(LoggedInTestCase):
self
.
assertEqual
(
ordered_list
[
3
][
'name'
],
'proc2'
)
self
.
assertEqual
(
ordered_list
[
3
][
'name'
],
'proc2'
)
@ddt.data
(
@ddt.data
(
(
'rever1'
,
True
,
0
,
0
,
0
),
(
'rever1'
,
True
,
0
,
0
,
0
,
0
),
(
'proc1'
,
True
,
1
,
0
,
0
),
(
'proc1'
,
True
,
1
,
0
,
0
,
0
),
(
'rever2'
,
True
,
2
,
0
,
0
),
(
'rever2'
,
True
,
2
,
0
,
0
,
0
),
(
'proc2'
,
False
,
2
,
1
,
0
),
(
'proc2'
,
False
,
2
,
1
,
0
,
0
),
(
'unknown'
,
False
,
2
,
1
,
1
),
(
'unknown'
,
False
,
2
,
1
,
1
,
0
),
(
None
,
False
,
2
,
1
,
1
),
(
None
,
False
,
2
,
1
,
1
,
0
),
)
)
@ddt.unpack
@ddt.unpack
def
test_are_prerequisite_satisifed
(
self
,
content_id
,
def
test_are_prerequisite_satisifed
(
self
,
content_id
,
expected_are_prerequisites_satisifed
,
expected_are_prerequisites_satisifed
,
expected_len_satisfied_prerequisites
,
expected_len_satisfied_prerequisites
,
expected_len_failed_prerequisites
,
expected_len_failed_prerequisites
,
expected_len_pending_prerequisites
):
expected_len_pending_prerequisites
,
expected_len_declined_prerequisites
):
"""
"""
verify proper operation of the logic when computing is prerequisites are satisfied
verify proper operation of the logic when computing is prerequisites are satisfied
"""
"""
...
@@ -2161,3 +2209,35 @@ class ProctoredExamApiTests(LoggedInTestCase):
...
@@ -2161,3 +2209,35 @@ class ProctoredExamApiTests(LoggedInTestCase):
self
.
assertEqual
(
len
(
results
[
'satisfied_prerequisites'
]),
expected_len_satisfied_prerequisites
)
self
.
assertEqual
(
len
(
results
[
'satisfied_prerequisites'
]),
expected_len_satisfied_prerequisites
)
self
.
assertEqual
(
len
(
results
[
'failed_prerequisites'
]),
expected_len_failed_prerequisites
)
self
.
assertEqual
(
len
(
results
[
'failed_prerequisites'
]),
expected_len_failed_prerequisites
)
self
.
assertEqual
(
len
(
results
[
'pending_prerequisites'
]),
expected_len_pending_prerequisites
)
self
.
assertEqual
(
len
(
results
[
'pending_prerequisites'
]),
expected_len_pending_prerequisites
)
self
.
assertEqual
(
len
(
results
[
'declined_prerequisites'
]),
expected_len_declined_prerequisites
)
@ddt.data
(
(
'rever1'
,
True
,
0
,
0
,
0
,
0
),
(
'proc1'
,
True
,
1
,
0
,
0
,
0
),
(
'rever2'
,
True
,
2
,
0
,
0
,
0
),
(
'proc2'
,
False
,
2
,
0
,
0
,
1
),
(
'unknown'
,
False
,
2
,
0
,
1
,
1
),
(
None
,
False
,
2
,
0
,
1
,
1
),
)
@ddt.unpack
def
test_declined_prerequisites
(
self
,
content_id
,
expected_are_prerequisites_satisifed
,
expected_len_satisfied_prerequisites
,
expected_len_failed_prerequisites
,
expected_len_pending_prerequisites
,
expected_len_declined_prerequisites
):
"""
verify proper operation of the logic when computing is prerequisites are satisfied
"""
results
=
_are_prerequirements_satisfied
(
self
.
declined_prerequisites
,
content_id
,
filter_out_namespaces
=
[
'grade'
]
)
self
.
assertEqual
(
results
[
'are_prerequisites_satisifed'
],
expected_are_prerequisites_satisifed
)
self
.
assertEqual
(
len
(
results
[
'satisfied_prerequisites'
]),
expected_len_satisfied_prerequisites
)
self
.
assertEqual
(
len
(
results
[
'failed_prerequisites'
]),
expected_len_failed_prerequisites
)
self
.
assertEqual
(
len
(
results
[
'pending_prerequisites'
]),
expected_len_pending_prerequisites
)
self
.
assertEqual
(
len
(
results
[
'declined_prerequisites'
]),
expected_len_declined_prerequisites
)
edx_proctoring/tests/test_views.py
View file @
f3e1da2d
...
@@ -1573,7 +1573,7 @@ class TestStudentProctoredExamAttempt(LoggedInTestCase):
...
@@ -1573,7 +1573,7 @@ class TestStudentProctoredExamAttempt(LoggedInTestCase):
)
)
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
response
.
status_code
,
200
)
# make sure we
fail
ed the requirement status
# make sure we
declin
ed the requirement status
credit_service
=
get_runtime_service
(
'credit'
)
credit_service
=
get_runtime_service
(
'credit'
)
credit_status
=
credit_service
.
get_credit_state
(
self
.
user
.
id
,
proctored_exam
.
course_id
)
credit_status
=
credit_service
.
get_credit_state
(
self
.
user
.
id
,
proctored_exam
.
course_id
)
...
@@ -1581,7 +1581,7 @@ class TestStudentProctoredExamAttempt(LoggedInTestCase):
...
@@ -1581,7 +1581,7 @@ class TestStudentProctoredExamAttempt(LoggedInTestCase):
self
.
assertEqual
(
len
(
credit_status
[
'credit_requirement_status'
]),
1
)
self
.
assertEqual
(
len
(
credit_status
[
'credit_requirement_status'
]),
1
)
self
.
assertEqual
(
self
.
assertEqual
(
credit_status
[
'credit_requirement_status'
][
0
][
'status'
],
credit_status
[
'credit_requirement_status'
][
0
][
'status'
],
'
fail
ed'
'
declin
ed'
)
)
def
test_exam_callback
(
self
):
def
test_exam_callback
(
self
):
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment