Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
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-platform
Commits
8cc82f76
Commit
8cc82f76
authored
Aug 16, 2015
by
Braden MacDonald
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Fix Offline Grade Calculation
parent
aa5fed71
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
138 additions
and
11 deletions
+138
-11
lms/djangoapps/instructor/offline_gradecalc.py
+31
-11
lms/djangoapps/instructor/tests/test_offline_gradecalc.py
+107
-0
No files found.
lms/djangoapps/instructor/offline_gradecalc.py
View file @
8cc82f76
...
...
@@ -13,19 +13,20 @@ from json import JSONEncoder
from
courseware
import
grades
,
models
from
courseware.courses
import
get_course_by_id
from
django.contrib.auth.models
import
User
from
opaque_keys
import
OpaqueKey
from
opaque_keys.edx.keys
import
UsageKey
from
xmodule.graders
import
Score
from
instructor.utils
import
DummyRequest
class
MyEncoder
(
JSONEncoder
):
def
_iterencode
(
self
,
obj
,
markers
=
None
):
if
isinstance
(
obj
,
tuple
)
and
hasattr
(
obj
,
'_asdict'
):
gen
=
self
.
_iterencode_dict
(
obj
.
_asdict
(),
markers
)
else
:
gen
=
JSONEncoder
.
_iterencode
(
self
,
obj
,
markers
)
for
chunk
in
gen
:
yield
chunk
""" JSON Encoder that can encode OpaqueKeys """
def
default
(
self
,
obj
):
# pylint: disable=method-hidden
""" Encode an object that the default encoder hasn't been able to. """
if
isinstance
(
obj
,
OpaqueKey
):
return
unicode
(
obj
)
return
JSONEncoder
.
default
(
self
,
obj
)
def
offline_grade_calculation
(
course_key
):
...
...
@@ -50,9 +51,15 @@ def offline_grade_calculation(course_key):
request
.
session
=
{}
gradeset
=
grades
.
grade
(
student
,
request
,
course
,
keep_raw_scores
=
True
)
gs
=
enc
.
encode
(
gradeset
)
# Convert Score namedtuples to dicts:
totaled_scores
=
gradeset
[
'totaled_scores'
]
for
section
in
totaled_scores
:
totaled_scores
[
section
]
=
[
score
.
_asdict
()
for
score
in
totaled_scores
[
section
]]
gradeset
[
'raw_scores'
]
=
[
score
.
_asdict
()
for
score
in
gradeset
[
'raw_scores'
]]
# Encode as JSON and save:
gradeset_str
=
enc
.
encode
(
gradeset
)
ocg
,
_created
=
models
.
OfflineComputedGrade
.
objects
.
get_or_create
(
user
=
student
,
course_id
=
course_key
)
ocg
.
gradeset
=
g
s
ocg
.
gradeset
=
g
radeset_str
ocg
.
save
()
print
"
%
s done"
%
student
# print statement used because this is run by a management command
...
...
@@ -93,4 +100,17 @@ def student_grades(student, request, course, keep_raw_scores=False, use_offline=
msg
=
'Error: no offline gradeset available for {}, {}'
.
format
(
student
,
course
.
id
)
)
return
json
.
loads
(
ocg
.
gradeset
)
gradeset
=
json
.
loads
(
ocg
.
gradeset
)
# Convert score dicts back to Score tuples:
def
score_from_dict
(
encoded
):
""" Given a formerly JSON-encoded Score tuple, return the Score tuple """
if
encoded
[
'module_id'
]:
encoded
[
'module_id'
]
=
UsageKey
.
from_string
(
encoded
[
'module_id'
])
return
Score
(
**
encoded
)
totaled_scores
=
gradeset
[
'totaled_scores'
]
for
section
in
totaled_scores
:
totaled_scores
[
section
]
=
[
score_from_dict
(
score
)
for
score
in
totaled_scores
[
section
]]
gradeset
[
'raw_scores'
]
=
[
score_from_dict
(
score
)
for
score
in
gradeset
[
'raw_scores'
]]
return
gradeset
lms/djangoapps/instructor/tests/test_offline_gradecalc.py
0 → 100644
View file @
8cc82f76
"""
Tests for offline_gradecalc.py
"""
import
json
from
mock
import
patch
from
courseware.models
import
OfflineComputedGrade
from
student.models
import
CourseEnrollment
from
student.tests.factories
import
UserFactory
from
xmodule.graders
import
Score
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
..offline_gradecalc
import
offline_grade_calculation
,
student_grades
def
mock_grade
(
_student
,
_request
,
course
,
**
_kwargs
):
""" Return some fake grade data to mock grades.grade() """
return
{
'grade'
:
u'Pass'
,
'totaled_scores'
:
{
u'Homework'
:
[
Score
(
earned
=
10.0
,
possible
=
10.0
,
graded
=
True
,
section
=
u'Subsection 1'
,
module_id
=
None
),
]
},
'percent'
:
0.85
,
'raw_scores'
:
[
Score
(
earned
=
5.0
,
possible
=
5.0
,
graded
=
True
,
section
=
u'Numerical Input'
,
module_id
=
course
.
id
.
make_usage_key
(
'problem'
,
'problem1'
),
),
Score
(
earned
=
5.0
,
possible
=
5.0
,
graded
=
True
,
section
=
u'Multiple Choice'
,
module_id
=
course
.
id
.
make_usage_key
(
'problem'
,
'problem2'
),
),
],
'section_breakdown'
:
[
{
'category'
:
u'Homework'
,
'percent'
:
1.0
,
'detail'
:
u'Homework 1 - Test - 100
%
(10/10)'
,
'label'
:
u'HW 01'
},
{
'category'
:
u'Final Exam'
,
'prominent'
:
True
,
'percent'
:
0
,
'detail'
:
u'Final = 0
%
'
,
'label'
:
u'Final'
}
],
'grade_breakdown'
:
[
{
'category'
:
u'Homework'
,
'percent'
:
0.85
,
'detail'
:
u'Homework = 85.00
%
of a possible 85.00
%
'
},
{
'category'
:
u'Final Exam'
,
'percent'
:
0.0
,
'detail'
:
u'Final Exam = 0.00
%
of a possible 15.00
%
'
}
]
}
class
TestOfflineGradeCalc
(
ModuleStoreTestCase
):
""" Test Offline Grade Calculation with some mocked grades """
def
setUp
(
self
):
super
(
TestOfflineGradeCalc
,
self
)
.
setUp
()
with
modulestore
()
.
default_store
(
ModuleStoreEnum
.
Type
.
split
):
# Test with split b/c old mongo keys are messy
self
.
course
=
CourseFactory
.
create
()
self
.
user
=
UserFactory
.
create
()
CourseEnrollment
.
enroll
(
self
.
user
,
self
.
course
.
id
)
patcher
=
patch
(
'courseware.grades.grade'
,
new
=
mock_grade
)
patcher
.
start
()
self
.
addCleanup
(
patcher
.
stop
)
def
test_output
(
self
):
offline_grades
=
OfflineComputedGrade
.
objects
self
.
assertEqual
(
offline_grades
.
filter
(
user
=
self
.
user
,
course_id
=
self
.
course
.
id
)
.
count
(),
0
)
offline_grade_calculation
(
self
.
course
.
id
)
result
=
offline_grades
.
get
(
user
=
self
.
user
,
course_id
=
self
.
course
.
id
)
decoded
=
json
.
loads
(
result
.
gradeset
)
self
.
assertEqual
(
decoded
[
'grade'
],
"Pass"
)
self
.
assertEqual
(
decoded
[
'percent'
],
0.85
)
self
.
assertEqual
(
decoded
[
'totaled_scores'
],
{
"Homework"
:
[
{
"earned"
:
10.0
,
"possible"
:
10.0
,
"graded"
:
True
,
"section"
:
"Subsection 1"
,
"module_id"
:
None
}
]
})
self
.
assertEqual
(
decoded
[
'raw_scores'
],
[
{
"earned"
:
5.0
,
"possible"
:
5.0
,
"graded"
:
True
,
"section"
:
"Numerical Input"
,
"module_id"
:
unicode
(
self
.
course
.
id
.
make_usage_key
(
'problem'
,
'problem1'
)),
},
{
"earned"
:
5.0
,
"possible"
:
5.0
,
"graded"
:
True
,
"section"
:
"Multiple Choice"
,
"module_id"
:
unicode
(
self
.
course
.
id
.
make_usage_key
(
'problem'
,
'problem2'
)),
}
])
self
.
assertEqual
(
decoded
[
'section_breakdown'
],
[
{
"category"
:
"Homework"
,
"percent"
:
1.0
,
"detail"
:
"Homework 1 - Test - 100
%
(10/10)"
,
"label"
:
"HW 01"
},
{
"category"
:
"Final Exam"
,
"label"
:
"Final"
,
"percent"
:
0
,
"detail"
:
"Final = 0
%
"
,
"prominent"
:
True
}
])
self
.
assertEqual
(
decoded
[
'grade_breakdown'
],
[
{
"category"
:
"Homework"
,
"percent"
:
0.85
,
"detail"
:
"Homework = 85.00
%
of a possible 85.00
%
"
},
{
"category"
:
"Final Exam"
,
"percent"
:
0.0
,
"detail"
:
"Final Exam = 0.00
%
of a possible 15.00
%
"
}
])
def
test_student_grades
(
self
):
""" Test that the data returned by student_grades() and grades.grade() match """
offline_grade_calculation
(
self
.
course
.
id
)
with
patch
(
'courseware.grades.grade'
,
side_effect
=
AssertionError
(
'Should not re-grade'
)):
result
=
student_grades
(
self
.
user
,
None
,
self
.
course
,
use_offline
=
True
)
self
.
assertEqual
(
result
,
mock_grade
(
self
.
user
,
None
,
self
.
course
))
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