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
7899528b
Commit
7899528b
authored
Nov 16, 2016
by
Nimisha Asthagiri
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Compute Attempted versus Grade of Zero
TNL-5953
parent
863aa78b
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
106 additions
and
35 deletions
+106
-35
common/lib/xmodule/xmodule/graders.py
+11
-5
common/lib/xmodule/xmodule/tests/test_graders.py
+21
-12
lms/djangoapps/grades/new/subsection_grade.py
+10
-0
lms/djangoapps/grades/scores.py
+17
-6
lms/djangoapps/grades/tests/test_grades.py
+1
-0
lms/djangoapps/grades/tests/test_new.py
+3
-1
lms/djangoapps/grades/tests/test_scores.py
+30
-8
lms/djangoapps/grades/tests/utils.py
+13
-3
No files found.
common/lib/xmodule/xmodule/graders.py
View file @
7899528b
...
...
@@ -21,13 +21,15 @@ class ScoreBase(object):
display_name (string) - the display name of the module
module_id (UsageKey) - the location of the module
graded (boolean) - whether or not this module is graded
attempted (boolean) - whether the module was attempted
"""
__metaclass__
=
abc
.
ABCMeta
def
__init__
(
self
,
graded
,
display_name
,
module_id
):
def
__init__
(
self
,
graded
,
display_name
,
module_id
,
attempted
):
self
.
graded
=
graded
self
.
display_name
=
display_name
self
.
module_id
=
module_id
self
.
attempted
=
attempted
def
__eq__
(
self
,
other
):
if
type
(
other
)
is
type
(
self
):
...
...
@@ -91,15 +93,19 @@ def aggregate_scores(scores, display_name="summary", location=None):
"""
total_correct_graded
=
float_sum
(
score
.
earned
for
score
in
scores
if
score
.
graded
)
total_possible_graded
=
float_sum
(
score
.
possible
for
score
in
scores
if
score
.
graded
)
any_attempted_graded
=
any
(
score
.
attempted
for
score
in
scores
if
score
.
graded
)
total_correct
=
float_sum
(
score
.
earned
for
score
in
scores
)
total_possible
=
float_sum
(
score
.
possible
for
score
in
scores
)
any_attempted
=
any
(
score
.
attempted
for
score
in
scores
)
#regardless of whether it is graded
all_total
=
AggregatedScore
(
total_correct
,
total_possible
,
False
,
display_name
,
location
)
#
regardless of whether it is graded
all_total
=
AggregatedScore
(
total_correct
,
total_possible
,
False
,
display_name
,
location
,
any_attempted
)
#selecting only graded things
graded_total
=
AggregatedScore
(
total_correct_graded
,
total_possible_graded
,
True
,
display_name
,
location
)
# selecting only graded things
graded_total
=
AggregatedScore
(
total_correct_graded
,
total_possible_graded
,
True
,
display_name
,
location
,
any_attempted_graded
,
)
return
all_total
,
graded_total
...
...
common/lib/xmodule/xmodule/tests/test_graders.py
View file @
7899528b
...
...
@@ -12,9 +12,12 @@ class GradesheetTest(unittest.TestCase):
def
test_weighted_grading
(
self
):
scores
=
[]
agg_fields
=
dict
(
display_name
=
"aggregated_score"
,
module_id
=
None
)
prob_fields
=
dict
(
display_name
=
"problem_score"
,
module_id
=
None
,
raw_earned
=
0
,
raw_possible
=
0
,
weight
=
0
)
agg_fields
=
dict
(
display_name
=
"aggregated_score"
,
module_id
=
None
,
attempted
=
False
)
prob_fields
=
dict
(
display_name
=
"problem_score"
,
module_id
=
None
,
raw_earned
=
0
,
raw_possible
=
0
,
weight
=
0
,
attempted
=
False
,
)
# No scores
all_total
,
graded_total
=
aggregate_scores
(
scores
,
display_name
=
agg_fields
[
'display_name'
])
self
.
assertEqual
(
all_total
,
...
...
@@ -25,6 +28,7 @@ class GradesheetTest(unittest.TestCase):
AggregatedScore
(
tw_earned
=
0
,
tw_possible
=
0
,
graded
=
True
,
**
agg_fields
),
)
# (0/5 non-graded)
scores
.
append
(
ProblemScore
(
weighted_earned
=
0
,
weighted_possible
=
5
,
graded
=
False
,
**
prob_fields
))
all_total
,
graded_total
=
aggregate_scores
(
scores
,
display_name
=
agg_fields
[
'display_name'
])
self
.
assertEqual
(
...
...
@@ -36,6 +40,9 @@ class GradesheetTest(unittest.TestCase):
AggregatedScore
(
tw_earned
=
0
,
tw_possible
=
0
,
graded
=
True
,
**
agg_fields
),
)
# (0/5 non-graded) + (3/5 graded) = 3/10 total, 3/5 graded
prob_fields
[
'attempted'
]
=
True
agg_fields
[
'attempted'
]
=
True
scores
.
append
(
ProblemScore
(
weighted_earned
=
3
,
weighted_possible
=
5
,
graded
=
True
,
**
prob_fields
))
all_total
,
graded_total
=
aggregate_scores
(
scores
,
display_name
=
agg_fields
[
'display_name'
])
self
.
assertAlmostEqual
(
...
...
@@ -47,6 +54,7 @@ class GradesheetTest(unittest.TestCase):
AggregatedScore
(
tw_earned
=
3
,
tw_possible
=
5
,
graded
=
True
,
**
agg_fields
),
)
# (0/5 non-graded) + (3/5 graded) + (2/5 graded) = 5/15 total, 5/10 graded
scores
.
append
(
ProblemScore
(
weighted_earned
=
2
,
weighted_possible
=
5
,
graded
=
True
,
**
prob_fields
))
all_total
,
graded_total
=
aggregate_scores
(
scores
,
display_name
=
agg_fields
[
'display_name'
])
self
.
assertAlmostEqual
(
...
...
@@ -73,25 +81,26 @@ class GraderTest(unittest.TestCase):
'Midterm'
:
[],
}
common_fields
=
dict
(
graded
=
True
,
module_id
=
None
,
attempted
=
True
)
test_gradesheet
=
{
'Homework'
:
[
AggregatedScore
(
tw_earned
=
2
,
tw_possible
=
20.0
,
graded
=
True
,
display_name
=
'hw1'
,
module_id
=
None
),
AggregatedScore
(
tw_earned
=
16
,
tw_possible
=
16.0
,
graded
=
True
,
display_name
=
'hw2'
,
module_id
=
None
)
AggregatedScore
(
tw_earned
=
2
,
tw_possible
=
20.0
,
display_name
=
'hw1'
,
**
common_fields
),
AggregatedScore
(
tw_earned
=
16
,
tw_possible
=
16.0
,
display_name
=
'hw2'
,
**
common_fields
),
],
# The dropped scores should be from the assignments that don't exist yet
'Lab'
:
[
AggregatedScore
(
tw_earned
=
1
,
tw_possible
=
2.0
,
graded
=
True
,
display_name
=
'lab1'
,
module_id
=
None
),
# Dropped
AggregatedScore
(
tw_earned
=
1
,
tw_possible
=
1.0
,
graded
=
True
,
display_name
=
'lab2'
,
module_id
=
None
),
AggregatedScore
(
tw_earned
=
1
,
tw_possible
=
1.0
,
graded
=
True
,
display_name
=
'lab3'
,
module_id
=
None
),
AggregatedScore
(
tw_earned
=
5
,
tw_possible
=
25.0
,
graded
=
True
,
display_name
=
'lab4'
,
module_id
=
None
),
# Dropped
AggregatedScore
(
tw_earned
=
3
,
tw_possible
=
4.0
,
graded
=
True
,
display_name
=
'lab5'
,
module_id
=
None
),
# Dropped
AggregatedScore
(
tw_earned
=
6
,
tw_possible
=
7.0
,
graded
=
True
,
display_name
=
'lab6'
,
module_id
=
None
),
AggregatedScore
(
tw_earned
=
5
,
tw_possible
=
6.0
,
graded
=
True
,
display_name
=
'lab7'
,
module_id
=
None
),
AggregatedScore
(
tw_earned
=
1
,
tw_possible
=
2.0
,
display_name
=
'lab1'
,
**
common_fields
),
# Dropped
AggregatedScore
(
tw_earned
=
1
,
tw_possible
=
1.0
,
display_name
=
'lab2'
,
**
common_fields
),
AggregatedScore
(
tw_earned
=
1
,
tw_possible
=
1.0
,
display_name
=
'lab3'
,
**
common_fields
),
AggregatedScore
(
tw_earned
=
5
,
tw_possible
=
25.0
,
display_name
=
'lab4'
,
**
common_fields
),
# Dropped
AggregatedScore
(
tw_earned
=
3
,
tw_possible
=
4.0
,
display_name
=
'lab5'
,
**
common_fields
),
# Dropped
AggregatedScore
(
tw_earned
=
6
,
tw_possible
=
7.0
,
display_name
=
'lab6'
,
**
common_fields
),
AggregatedScore
(
tw_earned
=
5
,
tw_possible
=
6.0
,
display_name
=
'lab7'
,
**
common_fields
),
],
'Midterm'
:
[
AggregatedScore
(
tw_earned
=
50.5
,
tw_possible
=
100
,
graded
=
True
,
display_name
=
"Midterm Exam"
,
module_id
=
None
),
AggregatedScore
(
tw_earned
=
50.5
,
tw_possible
=
100
,
display_name
=
"Midterm Exam"
,
**
common_fields
),
],
}
...
...
lms/djangoapps/grades/new/subsection_grade.py
View file @
7899528b
...
...
@@ -63,6 +63,14 @@ class SubsectionGrade(object):
"""
return
self
.
locations_to_scores
.
values
()
@property
def
attempted
(
self
):
"""
Returns whether any problem in this subsection
was attempted by the student.
"""
return
self
.
all_total
.
attempted
def
init_from_structure
(
self
,
student
,
course_structure
,
submissions_scores
,
csm_scores
):
"""
Compute the grade of this subsection for the given student and course.
...
...
@@ -90,6 +98,7 @@ class SubsectionGrade(object):
graded
=
True
,
display_name
=
self
.
display_name
,
module_id
=
self
.
location
,
attempted
=
True
,
# TODO TNL-5930
)
self
.
all_total
=
AggregatedScore
(
tw_earned
=
model
.
earned_all
,
...
...
@@ -97,6 +106,7 @@ class SubsectionGrade(object):
graded
=
False
,
display_name
=
self
.
display_name
,
module_id
=
self
.
location
,
attempted
=
True
,
# TODO TNL-5930
)
self
.
_log_event
(
log
.
debug
,
u"init_from_model"
,
student
)
return
self
...
...
lms/djangoapps/grades/scores.py
View file @
7899528b
...
...
@@ -102,7 +102,7 @@ def get_score(submissions_scores, csm_scores, persisted_block, block):
# Priority order for retrieving the scores:
# submissions API -> CSM -> grades persisted block -> latest block content
raw_earned
,
raw_possible
,
weighted_earned
,
weighted_possible
=
(
raw_earned
,
raw_possible
,
weighted_earned
,
weighted_possible
,
attempted
=
(
_get_score_from_submissions
(
submissions_scores
,
block
)
or
_get_score_from_csm
(
csm_scores
,
block
,
weight
)
or
_get_score_from_persisted_or_latest_block
(
persisted_block
,
block
,
weight
)
...
...
@@ -124,6 +124,7 @@ def get_score(submissions_scores, csm_scores, persisted_block, block):
graded
,
display_name
=
display_name_with_default_escaped
(
block
),
module_id
=
block
.
location
,
attempted
=
attempted
,
)
...
...
@@ -151,9 +152,10 @@ def _get_score_from_submissions(submissions_scores, block):
if
submissions_scores
:
submission_value
=
submissions_scores
.
get
(
unicode
(
block
.
location
))
if
submission_value
:
attempted
=
True
weighted_earned
,
weighted_possible
=
submission_value
assert
weighted_earned
>=
0.0
and
weighted_possible
>
0.0
# per contract from submissions API
return
(
None
,
None
)
+
(
weighted_earned
,
weighted_possible
)
return
(
None
,
None
)
+
(
weighted_earned
,
weighted_possible
)
+
(
attempted
,)
def
_get_score_from_csm
(
csm_scores
,
block
,
weight
):
...
...
@@ -175,9 +177,14 @@ def _get_score_from_csm(csm_scores, block, weight):
score
=
csm_scores
.
get
(
block
.
location
)
has_valid_score
=
score
and
score
.
total
is
not
None
if
has_valid_score
:
raw_earned
=
score
.
correct
if
score
.
correct
is
not
None
else
0.0
if
score
.
correct
is
not
None
:
attempted
=
True
raw_earned
=
score
.
correct
else
:
attempted
=
False
raw_earned
=
0.0
raw_possible
=
score
.
total
return
(
raw_earned
,
raw_possible
)
+
weighted_score
(
raw_earned
,
raw_possible
,
weight
)
return
(
raw_earned
,
raw_possible
)
+
weighted_score
(
raw_earned
,
raw_possible
,
weight
)
+
(
attempted
,)
def
_get_score_from_persisted_or_latest_block
(
persisted_block
,
block
,
weight
):
...
...
@@ -188,16 +195,20 @@ def _get_score_from_persisted_or_latest_block(persisted_block, block, weight):
the latest block content.
"""
raw_earned
=
0.0
attempted
=
False
if
persisted_block
:
raw_possible
=
persisted_block
.
raw_possible
else
:
raw_possible
=
block
.
transformer_data
[
GradesTransformer
]
.
max_score
# TODO TNL-5982 remove defensive code for scorables without max_score
if
raw_possible
is
None
:
return
(
raw_earned
,
raw_possible
)
+
(
None
,
None
)
weighted_scores
=
(
None
,
None
)
else
:
return
(
raw_earned
,
raw_possible
)
+
weighted_score
(
raw_earned
,
raw_possible
,
weight
)
weighted_scores
=
weighted_score
(
raw_earned
,
raw_possible
,
weight
)
return
(
raw_earned
,
raw_possible
)
+
weighted_scores
+
(
attempted
,)
def
_get_weight_from_block
(
persisted_block
,
block
):
...
...
lms/djangoapps/grades/tests/test_grades.py
View file @
7899528b
...
...
@@ -248,6 +248,7 @@ class TestWeightedProblems(SharedModuleStoreTestCase):
graded
=
expected_graded
,
display_name
=
None
,
# problem-specific, filled in by _verify_grades
module_id
=
None
,
# problem-specific, filled in by _verify_grades
attempted
=
True
,
)
self
.
_verify_grades
(
raw_earned
,
raw_possible
,
weight
,
expected_score
)
...
...
lms/djangoapps/grades/tests/test_new.py
View file @
7899528b
...
...
@@ -228,6 +228,7 @@ class TestSubsectionGradeFactory(ProblemSubmissionTestMixin, GradeTestBase):
self
.
assertFalse
(
mock_create_grade
.
called
)
self
.
assertEqual
(
grade_a
.
url_name
,
grade_b
.
url_name
)
grade_b
.
all_total
.
attempted
=
False
# TODO TNL-5930
self
.
assertEqual
(
grade_a
.
all_total
,
grade_b
.
all_total
)
def
test_update
(
self
):
...
...
@@ -342,6 +343,7 @@ class SubsectionGradeTest(GradeTestBase):
)
self
.
assertEqual
(
input_grade
.
url_name
,
loaded_grade
.
url_name
)
loaded_grade
.
all_total
.
attempted
=
False
# TODO TNL-5930
self
.
assertEqual
(
input_grade
.
all_total
,
loaded_grade
.
all_total
)
...
...
@@ -409,7 +411,7 @@ class TestMultipleProblemTypesSubsectionScores(SharedModuleStoreTestCase):
# Configure one block to return no possible score, the rest to return 3.0 earned / 7.0 possible
block_count
=
self
.
SCORED_BLOCK_COUNT
-
1
mock_score
.
side_effect
=
itertools
.
chain
(
[(
earned_per_block
,
None
,
earned_per_block
,
None
)],
[(
earned_per_block
,
None
,
earned_per_block
,
None
,
True
)],
itertools
.
repeat
(
mock_score
.
return_value
)
)
score
=
subsection_factory
.
update
(
self
.
seq1
)
...
...
lms/djangoapps/grades/tests/test_scores.py
View file @
7899528b
...
...
@@ -52,7 +52,7 @@ class TestGetScore(TestCase):
PersistedBlockValue
=
namedtuple
(
'PersistedBlockValue'
,
'exists, raw_possible, weight, graded'
)
ContentBlockValue
=
namedtuple
(
'ContentBlockValue'
,
'raw_possible, weight, explicit_graded'
)
ExpectedResult
=
namedtuple
(
'ExpectedResult'
,
'raw_earned, raw_possible, weighted_earned, weighted_possible, weight, graded'
'ExpectedResult'
,
'raw_earned, raw_possible, weighted_earned, weighted_possible, weight, graded
, attempted
'
)
def
_create_submissions_scores
(
self
,
submission_value
):
...
...
@@ -113,7 +113,9 @@ class TestGetScore(TestCase):
PersistedBlockValue
(
exists
=
True
,
raw_possible
=
5
,
weight
=
40
,
graded
=
True
),
ContentBlockValue
(
raw_possible
=
1
,
weight
=
20
,
explicit_graded
=
False
),
ExpectedResult
(
raw_earned
=
None
,
raw_possible
=
None
,
weighted_earned
=
50
,
weighted_possible
=
100
,
weight
=
40
,
graded
=
True
raw_earned
=
None
,
raw_possible
=
None
,
weighted_earned
=
50
,
weighted_possible
=
100
,
weight
=
40
,
graded
=
True
,
attempted
=
True
,
),
),
# same as above, except submissions doesn't exist; CSM values used
...
...
@@ -123,7 +125,21 @@ class TestGetScore(TestCase):
PersistedBlockValue
(
exists
=
True
,
raw_possible
=
5
,
weight
=
40
,
graded
=
True
),
ContentBlockValue
(
raw_possible
=
1
,
weight
=
20
,
explicit_graded
=
False
),
ExpectedResult
(
raw_earned
=
10
,
raw_possible
=
40
,
weighted_earned
=
10
,
weighted_possible
=
40
,
weight
=
40
,
graded
=
True
raw_earned
=
10
,
raw_possible
=
40
,
weighted_earned
=
10
,
weighted_possible
=
40
,
weight
=
40
,
graded
=
True
,
attempted
=
True
,
),
),
# CSM values exist, but with NULL earned score treated as not-attempted
(
SubmissionValue
(
exists
=
False
,
weighted_earned
=
50
,
weighted_possible
=
100
),
CSMValue
(
exists
=
True
,
raw_earned
=
None
,
raw_possible
=
40
),
PersistedBlockValue
(
exists
=
True
,
raw_possible
=
5
,
weight
=
40
,
graded
=
True
),
ContentBlockValue
(
raw_possible
=
1
,
weight
=
20
,
explicit_graded
=
False
),
ExpectedResult
(
raw_earned
=
0
,
raw_possible
=
40
,
weighted_earned
=
0
,
weighted_possible
=
40
,
weight
=
40
,
graded
=
True
,
attempted
=
False
,
),
),
# neither submissions nor CSM exist; Persisted values used
...
...
@@ -133,7 +149,9 @@ class TestGetScore(TestCase):
PersistedBlockValue
(
exists
=
True
,
raw_possible
=
5
,
weight
=
40
,
graded
=
True
),
ContentBlockValue
(
raw_possible
=
1
,
weight
=
20
,
explicit_graded
=
False
),
ExpectedResult
(
raw_earned
=
0
,
raw_possible
=
5
,
weighted_earned
=
0
,
weighted_possible
=
40
,
weight
=
40
,
graded
=
True
raw_earned
=
0
,
raw_possible
=
5
,
weighted_earned
=
0
,
weighted_possible
=
40
,
weight
=
40
,
graded
=
True
,
attempted
=
False
,
),
),
# none of submissions, CSM, or persisted exist; Latest content values used
...
...
@@ -143,7 +161,9 @@ class TestGetScore(TestCase):
PersistedBlockValue
(
exists
=
False
,
raw_possible
=
5
,
weight
=
40
,
graded
=
True
),
ContentBlockValue
(
raw_possible
=
1
,
weight
=
20
,
explicit_graded
=
False
),
ExpectedResult
(
raw_earned
=
0
,
raw_possible
=
1
,
weighted_earned
=
0
,
weighted_possible
=
20
,
weight
=
20
,
graded
=
False
raw_earned
=
0
,
raw_possible
=
1
,
weighted_earned
=
0
,
weighted_possible
=
20
,
weight
=
20
,
graded
=
False
,
attempted
=
False
,
),
),
)
...
...
@@ -259,9 +279,10 @@ class TestInternalGetScoreFromBlock(TestCase):
Verifies the result of _get_score_from_persisted_or_latest_block is as expected.
"""
# pylint: disable=unbalanced-tuple-unpacking
raw_earned
,
raw_possible
,
weighted_earned
,
weighted_possible
=
scores
.
_get_score_from_persisted_or_latest_block
(
persisted_block
,
block
,
weight
,
)
(
raw_earned
,
raw_possible
,
weighted_earned
,
weighted_possible
,
attempted
)
=
scores
.
_get_score_from_persisted_or_latest_block
(
persisted_block
,
block
,
weight
)
self
.
assertEquals
(
raw_earned
,
0.0
)
self
.
assertEquals
(
raw_possible
,
expected_r_possible
)
self
.
assertEquals
(
weighted_earned
,
0.0
)
...
...
@@ -269,6 +290,7 @@ class TestInternalGetScoreFromBlock(TestCase):
self
.
assertEquals
(
weighted_possible
,
expected_r_possible
)
else
:
self
.
assertEquals
(
weighted_possible
,
weight
)
self
.
assertFalse
(
attempted
)
@ddt.data
(
*
itertools
.
product
((
0
,
1
,
5
),
(
None
,
0
,
1
,
5
))
...
...
lms/djangoapps/grades/tests/utils.py
View file @
7899528b
...
...
@@ -24,17 +24,27 @@ def mock_get_score(earned=0, possible=1):
Mocks the get_score function to return a valid grade.
"""
with
patch
(
'lms.djangoapps.grades.new.subsection_grade.get_score'
)
as
mock_score
:
mock_score
.
return_value
=
ProblemScore
(
earned
,
possible
,
earned
,
possible
,
1
,
True
,
None
,
None
)
mock_score
.
return_value
=
ProblemScore
(
raw_earned
=
earned
,
raw_possible
=
possible
,
weighted_earned
=
earned
,
weighted_possible
=
possible
,
weight
=
1
,
graded
=
True
,
display_name
=
None
,
module_id
=
None
,
attempted
=
True
,
)
yield
mock_score
@contextmanager
def
mock_get_submissions_score
(
earned
=
0
,
possible
=
1
):
def
mock_get_submissions_score
(
earned
=
0
,
possible
=
1
,
attempted
=
True
):
"""
Mocks the _get_submissions_score function to return the specified values
"""
with
patch
(
'lms.djangoapps.grades.scores._get_score_from_submissions'
)
as
mock_score
:
mock_score
.
return_value
=
(
earned
,
possible
,
earned
,
possible
)
mock_score
.
return_value
=
(
earned
,
possible
,
earned
,
possible
,
attempted
)
yield
mock_score
...
...
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