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
4f839e65
Commit
4f839e65
authored
Aug 18, 2015
by
Chris Dodge
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
also mark other proctored exams as declined if we fail one exam
parent
c609b163
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
154 additions
and
6 deletions
+154
-6
edx_proctoring/api.py
+49
-4
edx_proctoring/models.py
+18
-2
edx_proctoring/tests/test_api.py
+87
-0
No files found.
edx_proctoring/api.py
View file @
4f839e65
...
...
@@ -498,7 +498,7 @@ def mark_exam_attempt_as_ready(exam_id, user_id):
return
update_attempt_status
(
exam_id
,
user_id
,
ProctoredExamStudentAttemptStatus
.
ready_to_start
)
def
update_attempt_status
(
exam_id
,
user_id
,
to_status
,
raise_if_not_found
=
True
):
def
update_attempt_status
(
exam_id
,
user_id
,
to_status
,
raise_if_not_found
=
True
,
cascade_effects
=
True
):
"""
Internal helper to handle state transitions of attempt status
"""
...
...
@@ -561,9 +561,6 @@ def update_attempt_status(exam_id, user_id, to_status, raise_if_not_found=True):
exam_attempt_obj
.
status
=
to_status
exam_attempt_obj
.
save
()
# trigger workflow, as needed
credit_service
=
get_runtime_service
(
'credit'
)
# see if the status transition this changes credit requirement status
update_credit
=
to_status
in
[
ProctoredExamStudentAttemptStatus
.
verified
,
ProctoredExamStudentAttemptStatus
.
rejected
,
...
...
@@ -572,6 +569,9 @@ def update_attempt_status(exam_id, user_id, to_status, raise_if_not_found=True):
]
if
update_credit
:
# trigger credit workflow, as needed
credit_service
=
get_runtime_service
(
'credit'
)
exam
=
get_exam_by_id
(
exam_id
)
if
to_status
==
ProctoredExamStudentAttemptStatus
.
verified
:
verification
=
'satisfied'
...
...
@@ -600,6 +600,51 @@ def update_attempt_status(exam_id, user_id, to_status, raise_if_not_found=True):
status
=
verification
)
if
cascade_effects
:
# some state transitions (namely to a rejected or declined status)
# will mark other exams as declined because once we fail or decline
# one exam all other (un-completed) proctored exams will we likewise
# updated to reflect a declined status
cascade_failure
=
to_status
in
[
ProctoredExamStudentAttemptStatus
.
rejected
,
ProctoredExamStudentAttemptStatus
.
declined
]
if
cascade_failure
:
# get all other unattempted exams and mark also as declined
_exams
=
ProctoredExam
.
get_all_exams_for_course
(
exam_attempt_obj
.
proctored_exam
.
course_id
,
active_only
=
True
)
# we just want other exams which are proctored and are not practice
exams
=
[
exam
for
exam
in
_exams
if
(
exam
.
content_id
!=
exam_attempt_obj
.
proctored_exam
.
content_id
and
exam
.
is_proctored
and
not
exam
.
is_practice_exam
)
]
for
exam
in
exams
:
if
exam
.
content_id
!=
exam_attempt_obj
.
proctored_exam
.
content_id
:
# make sure there was no attempt
attempt
=
get_exam_attempt
(
exam
.
id
,
user_id
)
if
attempt
and
ProctoredExamStudentAttemptStatus
.
is_completed_status
(
attempt
[
'status'
]):
# don't touch any completed statuses
continue
if
not
attempt
:
create_exam_attempt
(
exam
.
id
,
user_id
,
taking_as_proctored
=
False
)
# update any new or existing status to declined
update_attempt_status
(
exam
.
id
,
user_id
,
ProctoredExamStudentAttemptStatus
.
declined
,
cascade_effects
=
False
)
if
to_status
==
ProctoredExamStudentAttemptStatus
.
submitted
:
# also mark the exam attempt completed_at timestamp
# after we submit the attempt
...
...
edx_proctoring/models.py
View file @
4f839e65
...
...
@@ -71,11 +71,14 @@ class ProctoredExam(TimeStampedModel):
return
proctored_exam
@classmethod
def
get_all_exams_for_course
(
cls
,
course_id
):
def
get_all_exams_for_course
(
cls
,
course_id
,
active_only
=
False
):
"""
Returns all exams for a give course
"""
return
cls
.
objects
.
filter
(
course_id
=
course_id
)
result
=
cls
.
objects
.
filter
(
course_id
=
course_id
)
if
active_only
:
result
=
result
.
filter
(
is_active
=
True
)
return
result
class
ProctoredExamStudentAttemptStatus
(
object
):
...
...
@@ -131,6 +134,19 @@ class ProctoredExamStudentAttemptStatus(object):
# the exam is believed to be in error
error
=
'error'
@classmethod
def
is_completed_status
(
cls
,
status
):
"""
Returns a boolean if the passed in status is in a "completed" state, meaning
that it cannot go backwards in state
"""
return
status
in
[
ProctoredExamStudentAttemptStatus
.
declined
,
ProctoredExamStudentAttemptStatus
.
timed_out
,
ProctoredExamStudentAttemptStatus
.
submitted
,
ProctoredExamStudentAttemptStatus
.
verified
,
ProctoredExamStudentAttemptStatus
.
rejected
,
ProctoredExamStudentAttemptStatus
.
not_reviewed
,
ProctoredExamStudentAttemptStatus
.
error
]
class
ProctoredExamStudentAttemptManager
(
models
.
Manager
):
"""
...
...
edx_proctoring/tests/test_api.py
View file @
4f839e65
...
...
@@ -1083,6 +1083,93 @@ class ProctoredExamApiTests(LoggedInTestCase):
)
@ddt.data
(
(
ProctoredExamStudentAttemptStatus
.
declined
,
False
,
None
,
ProctoredExamStudentAttemptStatus
.
declined
),
(
ProctoredExamStudentAttemptStatus
.
rejected
,
False
,
None
,
ProctoredExamStudentAttemptStatus
.
declined
),
(
ProctoredExamStudentAttemptStatus
.
rejected
,
True
,
ProctoredExamStudentAttemptStatus
.
verified
,
ProctoredExamStudentAttemptStatus
.
verified
),
(
ProctoredExamStudentAttemptStatus
.
declined
,
True
,
ProctoredExamStudentAttemptStatus
.
submitted
,
ProctoredExamStudentAttemptStatus
.
submitted
),
)
@ddt.unpack
def
test_cascading
(
self
,
to_status
,
create_attempt
,
second_attempt_status
,
expected_second_status
):
"""
Make sure that when we decline/reject one attempt all other exams in the course
are auto marked as declined
"""
# create other exams in course
second_exam_id
=
create_exam
(
course_id
=
self
.
course_id
,
content_id
=
"2nd exam"
,
exam_name
=
"2nd exam"
,
time_limit_mins
=
self
.
default_time_limit
,
is_practice_exam
=
False
,
is_proctored
=
True
)
practice_exam_id
=
create_exam
(
course_id
=
self
.
course_id
,
content_id
=
"practice"
,
exam_name
=
"practice"
,
time_limit_mins
=
self
.
default_time_limit
,
is_practice_exam
=
True
,
is_proctored
=
True
)
timed_exam_id
=
create_exam
(
course_id
=
self
.
course_id
,
content_id
=
"timed"
,
exam_name
=
"timed"
,
time_limit_mins
=
self
.
default_time_limit
,
is_practice_exam
=
False
,
is_proctored
=
False
)
if
create_attempt
:
create_exam_attempt
(
second_exam_id
,
self
.
user_id
,
taking_as_proctored
=
False
)
if
second_attempt_status
:
update_attempt_status
(
second_exam_id
,
self
.
user_id
,
second_attempt_status
)
exam_attempt
=
self
.
_create_started_exam_attempt
()
update_attempt_status
(
exam_attempt
.
proctored_exam_id
,
self
.
user
.
id
,
to_status
)
# make sure we reamain in the right status
read_back
=
get_exam_attempt
(
exam_attempt
.
proctored_exam_id
,
self
.
user
.
id
)
self
.
assertEqual
(
read_back
[
'status'
],
to_status
)
# make sure an attempt was made for second_exam
second_exam_attempt
=
get_exam_attempt
(
second_exam_id
,
self
.
user_id
)
self
.
assertIsNotNone
(
second_exam_attempt
)
self
.
assertEqual
(
second_exam_attempt
[
'status'
],
expected_second_status
)
# no auto-generated attempts for practice and timed exams
self
.
assertIsNone
(
get_exam_attempt
(
practice_exam_id
,
self
.
user_id
))
self
.
assertIsNone
(
get_exam_attempt
(
timed_exam_id
,
self
.
user_id
))
@ddt.data
(
(
ProctoredExamStudentAttemptStatus
.
declined
,
ProctoredExamStudentAttemptStatus
.
eligible
),
(
ProctoredExamStudentAttemptStatus
.
timed_out
,
ProctoredExamStudentAttemptStatus
.
created
),
(
ProctoredExamStudentAttemptStatus
.
submitted
,
ProctoredExamStudentAttemptStatus
.
ready_to_start
),
...
...
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