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
edx
edx-proctoring
Commits
ff6329fe
Commit
ff6329fe
authored
Jul 28, 2017
by
Tyler Hallada
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Undo grade override on status rejected -> verified
parent
e9b43963
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
143 additions
and
13 deletions
+143
-13
edx_proctoring/api.py
+28
-2
edx_proctoring/tests/test_api.py
+69
-5
edx_proctoring/tests/test_services.py
+46
-6
No files found.
edx_proctoring/api.py
View file @
ff6329fe
...
...
@@ -751,6 +751,7 @@ def update_attempt_status(exam_id, user_id, to_status,
else
:
return
from_status
=
exam_attempt_obj
.
status
exam
=
get_exam_by_id
(
exam_id
)
#
...
...
@@ -765,7 +766,7 @@ def update_attempt_status(exam_id, user_id, to_status,
'A status transition from {from_status} to {to_status} was attempted '
'on exam_id {exam_id} for user_id {user_id}. This is not '
'allowed!'
.
format
(
from_status
=
exam_attempt_obj
.
status
,
from_status
=
from_
status
,
to_status
=
to_status
,
exam_id
=
exam_id
,
user_id
=
user_id
...
...
@@ -780,7 +781,7 @@ def update_attempt_status(exam_id, user_id, to_status,
'A status transition from {from_status} to {to_status} was attempted '
'on exam_id {exam_id} for user_id {user_id}. This is not '
'allowed!'
.
format
(
from_status
=
exam_attempt_obj
.
status
,
from_status
=
from_
status
,
to_status
=
to_status
,
exam_id
=
exam_id
,
user_id
=
user_id
...
...
@@ -916,6 +917,31 @@ def update_attempt_status(exam_id, user_id, to_status,
earned_graded
=
REJECTED_GRADE_OVERRIDE_EARNED
)
if
(
to_status
==
ProctoredExamStudentAttemptStatus
.
verified
and
ProctoredExamStudentAttemptStatus
.
needs_grade_override
(
from_status
)):
grades_service
=
get_runtime_service
(
'grades'
)
log_msg
=
(
'Deleting override of exam subsection grade for '
'user_id {user_id} on {course_id} for '
'content_id {content_id}. Override '
'earned_all: {earned_all}, '
'earned_graded: {earned_graded}.'
.
format
(
user_id
=
exam_attempt_obj
.
user_id
,
course_id
=
exam
[
'course_id'
],
content_id
=
exam_attempt_obj
.
proctored_exam
.
content_id
,
earned_all
=
REJECTED_GRADE_OVERRIDE_EARNED
,
earned_graded
=
REJECTED_GRADE_OVERRIDE_EARNED
)
)
log
.
info
(
log_msg
)
grades_service
.
undo_override_subsection_grade
(
user_id
=
exam_attempt_obj
.
user_id
,
course_key_or_id
=
exam
[
'course_id'
],
usage_key_or_id
=
exam_attempt_obj
.
proctored_exam
.
content_id
,
)
# call service to get course name.
credit_service
=
get_runtime_service
(
'credit'
)
credit_state
=
credit_service
.
get_credit_state
(
...
...
edx_proctoring/tests/test_api.py
View file @
ff6329fe
...
...
@@ -920,23 +920,87 @@ class ProctoredExamApiTests(ProctoredExamTestCase):
the learner's subsection grade for the exam
"""
set_runtime_service
(
'grades'
,
MockGradesService
())
grades_service
=
get_runtime_service
(
'grades'
)
exam_attempt
=
self
.
_create_started_exam_attempt
()
# Pretend learner answered 5 graded questions in the exam correctly
grades_service
.
init_grade
(
user_id
=
self
.
user
.
id
,
course_key_or_id
=
exam_attempt
.
proctored_exam
.
course_id
,
usage_key_or_id
=
exam_attempt
.
proctored_exam
.
content_id
,
earned_all
=
5.0
,
earned_graded
=
5.0
)
update_attempt_status
(
exam_attempt
.
proctored_exam_id
,
self
.
user
.
id
,
ProctoredExamStudentAttemptStatus
.
rejected
)
grades_service
=
get_runtime_service
(
'grades'
)
grades
=
grades_service
.
get_subsection_grade
(
user_id
=
self
.
user
.
id
,
course_key_or_id
=
exam_attempt
.
proctored_exam
.
course_id
,
usage_key_or_id
=
exam_attempt
.
proctored_exam
.
content_id
)
# Rejected exam attempt should override learner's grade to 0
override
=
grades_service
.
get_subsection_grade_override
(
user_id
=
self
.
user
.
id
,
course_key_or_id
=
exam_attempt
.
proctored_exam
.
course_id
,
usage_key_or_id
=
exam_attempt
.
proctored_exam
.
content_id
)
self
.
assertDictEqual
({
'earned_all'
:
override
.
earned_all_override
,
'earned_graded'
:
override
.
earned_graded_override
},
{
'earned_all'
:
0.0
,
'earned_graded'
:
0.0
})
# The MockGradeService updates the PersistentSubsectionGrade synchronously, but in the real GradesService, this
# would be updated by an asynchronous recalculation celery task.
grade
=
grades_service
.
get_subsection_grade
(
user_id
=
self
.
user
.
id
,
course_key_or_id
=
exam_attempt
.
proctored_exam
.
course_id
,
usage_key_or_id
=
exam_attempt
.
proctored_exam
.
content_id
)
self
.
assertEqual
(
grades
,
{
self
.
assertDictEqual
({
'earned_all'
:
grade
.
earned_all
,
'earned_graded'
:
grade
.
earned_graded
},
{
'earned_all'
:
0.0
,
'earned_graded'
:
0.0
})
# Verify that transitioning an attempt from the rejected state to the verified state
# will remove the override for the learner's subsection grade on the exam that was created
# when the attempt entered the rejected state.
update_attempt_status
(
exam_attempt
.
proctored_exam_id
,
self
.
user
.
id
,
ProctoredExamStudentAttemptStatus
.
verified
)
override
=
grades_service
.
get_subsection_grade_override
(
user_id
=
self
.
user
.
id
,
course_key_or_id
=
exam_attempt
.
proctored_exam
.
course_id
,
usage_key_or_id
=
exam_attempt
.
proctored_exam
.
content_id
)
self
.
assertIsNone
(
override
)
grade
=
grades_service
.
get_subsection_grade
(
user_id
=
self
.
user
.
id
,
course_key_or_id
=
exam_attempt
.
proctored_exam
.
course_id
,
usage_key_or_id
=
exam_attempt
.
proctored_exam
.
content_id
)
# Grade has returned to original score
self
.
assertDictEqual
({
'earned_all'
:
grade
.
earned_all
,
'earned_graded'
:
grade
.
earned_graded
},
{
'earned_all'
:
5.0
,
'earned_graded'
:
5.0
})
@ddt.data
(
(
ProctoredExamStudentAttemptStatus
.
declined
,
ProctoredExamStudentAttemptStatus
.
eligible
),
(
ProctoredExamStudentAttemptStatus
.
timed_out
,
ProctoredExamStudentAttemptStatus
.
created
),
...
...
edx_proctoring/tests/test_services.py
View file @
ff6329fe
...
...
@@ -175,22 +175,62 @@ class TestProctoringService(unittest.TestCase):
self
.
assertIs
(
service1
,
service2
)
class
MockGrade
(
object
):
"""Fake PersistentSubsectionGrade instance."""
def
__init__
(
self
,
earned_all
=
0.0
,
earned_graded
=
0.0
):
self
.
earned_all
=
earned_all
self
.
earned_graded
=
earned_graded
class
MockGradeOverride
(
object
):
"""Fake PersistentSubsectionGradeOverride instance."""
def
__init__
(
self
,
earned_all
=
0.0
,
earned_graded
=
0.0
):
self
.
earned_all_override
=
earned_all
self
.
earned_graded_override
=
earned_graded
class
MockGradesService
(
object
):
"""
Simple mock of the Grades Service
"""
def
__init__
(
self
):
"""Initialize empty data store
for grades (a dict
)"""
"""Initialize empty data store
s for grades and overrides (just dicts
)"""
self
.
grades
=
{}
self
.
overrides
=
{}
def
init_grade
(
self
,
user_id
,
course_key_or_id
,
usage_key_or_id
,
earned_all
,
earned_graded
):
"""Initialize a grade in MockGradesService for testing. Actual GradesService does not have this method."""
self
.
grades
[
str
(
user_id
)
+
str
(
course_key_or_id
)
+
str
(
usage_key_or_id
)]
=
MockGrade
(
earned_all
=
earned_all
,
earned_graded
=
earned_graded
)
def
get_subsection_grade
(
self
,
user_id
,
course_key_or_id
,
usage_key_or_id
):
"""Returns entered grade override for key (user_id + course_key + subsection) or None"""
"""Returns entered grade for key (user_id + course_key + subsection) or None"""
key
=
str
(
user_id
)
+
str
(
course_key_or_id
)
+
str
(
usage_key_or_id
)
if
key
in
self
.
overrides
:
# pretend override was applied
return
MockGrade
(
earned_all
=
self
.
overrides
[
key
]
.
earned_all_override
,
earned_graded
=
self
.
overrides
[
key
]
.
earned_graded_override
)
return
self
.
grades
.
get
(
str
(
user_id
)
+
str
(
course_key_or_id
)
+
str
(
usage_key_or_id
))
def
get_subsection_grade_override
(
self
,
user_id
,
course_key_or_id
,
usage_key_or_id
):
"""Returns entered grade override for key (user_id + course_key + subsection) or None"""
return
self
.
overrides
.
get
(
str
(
user_id
)
+
str
(
course_key_or_id
)
+
str
(
usage_key_or_id
))
def
override_subsection_grade
(
self
,
user_id
,
course_key_or_id
,
usage_key_or_id
,
earned_all
=
None
,
earned_graded
=
None
):
"""Sets grade override earned points for key (user_id + course_key + subsection)"""
self
.
grades
[
str
(
user_id
)
+
str
(
course_key_or_id
)
+
str
(
usage_key_or_id
)]
=
{
'earned_all'
:
earned_all
,
'earned_graded'
:
earned_graded
}
key
=
str
(
user_id
)
+
str
(
course_key_or_id
)
+
str
(
usage_key_or_id
)
self
.
overrides
[
key
]
=
MockGradeOverride
(
earned_all
=
earned_all
,
earned_graded
=
earned_graded
)
def
undo_override_subsection_grade
(
self
,
user_id
,
course_key_or_id
,
usage_key_or_id
):
"""Deletes grade override for key (user_id + course_key + subsection)"""
key
=
str
(
user_id
)
+
str
(
course_key_or_id
)
+
str
(
usage_key_or_id
)
if
key
in
self
.
overrides
:
del
self
.
overrides
[
key
]
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