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
OpenEdx
edx-platform
Commits
214cb516
Commit
214cb516
authored
Oct 04, 2017
by
Nimisha Asthagiri
Committed by
Dillon Dumesnil
Oct 31, 2017
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Have SubsectionGrade compute problem_scores lazily
parent
51c44e44
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
100 additions
and
109 deletions
+100
-109
lms/djangoapps/grades/subsection_grade.py
+87
-96
lms/djangoapps/grades/subsection_grade_factory.py
+6
-6
lms/djangoapps/grades/tests/test_course_grade_factory.py
+2
-2
lms/djangoapps/grades/tests/test_subsection_grade.py
+3
-3
lms/djangoapps/instructor_task/tasks_helper/grades.py
+2
-2
No files found.
lms/djangoapps/grades/subsection_grade.py
View file @
214cb516
"""
"""
SubsectionGrade Class
SubsectionGrade Class
"""
"""
from
abc
import
ABCMeta
from
collections
import
OrderedDict
from
collections
import
OrderedDict
from
logging
import
getLogger
from
logging
import
getLogger
...
@@ -17,8 +18,10 @@ log = getLogger(__name__)
...
@@ -17,8 +18,10 @@ log = getLogger(__name__)
class
SubsectionGradeBase
(
object
):
class
SubsectionGradeBase
(
object
):
"""
"""
C
lass for Subsection Grades.
Abstract base c
lass for Subsection Grades.
"""
"""
__metaclass__
=
ABCMeta
def
__init__
(
self
,
subsection
):
def
__init__
(
self
,
subsection
):
self
.
location
=
subsection
.
location
self
.
location
=
subsection
.
location
self
.
display_name
=
block_metadata_utils
.
display_name_with_default_escaped
(
subsection
)
self
.
display_name
=
block_metadata_utils
.
display_name_with_default_escaped
(
subsection
)
...
@@ -108,47 +111,53 @@ class ZeroSubsectionGrade(SubsectionGradeBase):
...
@@ -108,47 +111,53 @@ class ZeroSubsectionGrade(SubsectionGradeBase):
return
locations
return
locations
class
SubsectionGrade
(
SubsectionGradeBase
):
class
NonZero
SubsectionGrade
(
SubsectionGradeBase
):
"""
"""
Class for Subsection Grades.
Abstract base class for Subsection Grades with
possibly NonZero values.
"""
"""
def
__init__
(
self
,
subsection
,
problem_scores
,
all_total
,
graded_total
,
override
=
None
):
__metaclass__
=
ABCMeta
super
(
SubsectionGrade
,
self
)
.
__init__
(
subsection
)
self
.
problem_scores
=
problem_scores
def
__init__
(
self
,
subsection
,
all_total
,
graded_total
,
override
=
None
):
super
(
NonZeroSubsectionGrade
,
self
)
.
__init__
(
subsection
)
self
.
all_total
=
all_total
self
.
all_total
=
all_total
self
.
graded_total
=
graded_total
self
.
graded_total
=
graded_total
self
.
override
=
override
self
.
override
=
override
@classmethod
@property
def
create
(
cls
,
subsection
,
course_structure
,
submissions_scores
,
csm_scores
):
def
attempted_graded
(
self
):
"""
return
self
.
graded_total
.
first_attempted
is
not
None
Compute and create the subsection grade.
"""
problem_scores
=
OrderedDict
()
for
block_key
in
course_structure
.
post_order_traversal
(
filter_func
=
possibly_scored
,
start_node
=
subsection
.
location
,
):
problem_score
=
cls
.
_compute_block_score
(
block_key
,
course_structure
,
submissions_scores
,
csm_scores
)
if
problem_score
:
problem_scores
[
block_key
]
=
problem_score
all_total
,
graded_total
=
graders
.
aggregate_scores
(
problem_scores
.
values
())
return
cls
(
subsection
,
problem_scores
,
all_total
,
graded_total
)
@staticmethod
def
_compute_block_score
(
block_key
,
course_structure
,
submissions_scores
,
csm_scores
,
persisted_block
=
None
,
):
try
:
block
=
course_structure
[
block_key
]
except
KeyError
:
# It's possible that the user's access to that
# block has changed since the subsection grade
# was last persisted.
pass
else
:
if
getattr
(
block
,
'has_score'
,
False
):
return
get_score
(
submissions_scores
,
csm_scores
,
persisted_block
,
block
,
)
@classmethod
def
read
(
cls
,
subsection
,
model
,
course_structure
,
submissions_scores
,
csm_scores
):
"""
Read the subsection grade from the persisted model.
"""
problem_scores
=
OrderedDict
()
for
block
in
model
.
visible_blocks
.
blocks
:
problem_score
=
cls
.
_compute_block_score
(
block
.
locator
,
course_structure
,
submissions_scores
,
csm_scores
,
block
,
)
if
problem_score
:
problem_scores
[
block
.
locator
]
=
problem_score
class
ReadSubsectionGrade
(
NonZeroSubsectionGrade
):
"""
Class for Subsection grades that are read from the database.
"""
def
__init__
(
self
,
subsection
,
model
,
course_structure
,
submissions_scores
,
csm_scores
):
all_total
=
AggregatedScore
(
all_total
=
AggregatedScore
(
tw_earned
=
model
.
earned_all
,
tw_earned
=
model
.
earned_all
,
tw_possible
=
model
.
possible_all
,
tw_possible
=
model
.
possible_all
,
...
@@ -162,7 +171,51 @@ class SubsectionGrade(SubsectionGradeBase):
...
@@ -162,7 +171,51 @@ class SubsectionGrade(SubsectionGradeBase):
first_attempted
=
model
.
first_attempted
,
first_attempted
=
model
.
first_attempted
,
)
)
override
=
model
.
override
if
hasattr
(
model
,
'override'
)
else
None
override
=
model
.
override
if
hasattr
(
model
,
'override'
)
else
None
return
cls
(
subsection
,
problem_scores
,
all_total
,
graded_total
,
override
)
# save these for later since we compute problem_scores lazily
self
.
model
=
model
self
.
course_structure
=
course_structure
self
.
submissions_scores
=
submissions_scores
self
.
csm_scores
=
csm_scores
super
(
ReadSubsectionGrade
,
self
)
.
__init__
(
subsection
,
all_total
,
graded_total
,
override
)
@lazy
def
problem_scores
(
self
):
problem_scores
=
OrderedDict
()
for
block
in
self
.
model
.
visible_blocks
.
blocks
:
problem_score
=
self
.
_compute_block_score
(
block
.
locator
,
self
.
course_structure
,
self
.
submissions_scores
,
self
.
csm_scores
,
block
,
)
if
problem_score
:
problem_scores
[
block
.
locator
]
=
problem_score
return
problem_scores
class
CreateSubsectionGrade
(
NonZeroSubsectionGrade
):
"""
Class for Subsection grades that are newly created or updated.
"""
def
__init__
(
self
,
subsection
,
course_structure
,
submissions_scores
,
csm_scores
):
self
.
problem_scores
=
OrderedDict
()
for
block_key
in
course_structure
.
post_order_traversal
(
filter_func
=
possibly_scored
,
start_node
=
subsection
.
location
,
):
problem_score
=
self
.
_compute_block_score
(
block_key
,
course_structure
,
submissions_scores
,
csm_scores
)
if
problem_score
:
self
.
problem_scores
[
block_key
]
=
problem_score
all_total
,
graded_total
=
graders
.
aggregate_scores
(
self
.
problem_scores
.
values
())
super
(
CreateSubsectionGrade
,
self
)
.
__init__
(
subsection
,
all_total
,
graded_total
)
def
update_or_create_model
(
self
,
student
,
score_deleted
=
False
):
"""
Saves or updates the subsection grade in a persisted model.
"""
if
self
.
_should_persist_per_attempted
(
score_deleted
):
return
PersistentSubsectionGrade
.
update_or_create_grade
(
**
self
.
_persisted_model_params
(
student
))
@classmethod
@classmethod
def
bulk_create_models
(
cls
,
student
,
subsection_grades
,
course_key
):
def
bulk_create_models
(
cls
,
student
,
subsection_grades
,
course_key
):
...
@@ -177,18 +230,6 @@ class SubsectionGrade(SubsectionGradeBase):
...
@@ -177,18 +230,6 @@ class SubsectionGrade(SubsectionGradeBase):
]
]
return
PersistentSubsectionGrade
.
bulk_create_grades
(
params
,
student
.
id
,
course_key
)
return
PersistentSubsectionGrade
.
bulk_create_grades
(
params
,
student
.
id
,
course_key
)
def
update_or_create_model
(
self
,
student
,
score_deleted
=
False
):
"""
Saves or updates the subsection grade in a persisted model.
"""
if
self
.
_should_persist_per_attempted
(
score_deleted
):
self
.
_log_event
(
log
.
debug
,
u"update_or_create_model"
,
student
)
return
PersistentSubsectionGrade
.
update_or_create_grade
(
**
self
.
_persisted_model_params
(
student
))
@property
def
attempted_graded
(
self
):
return
self
.
graded_total
.
first_attempted
is
not
None
def
_should_persist_per_attempted
(
self
,
score_deleted
=
False
):
def
_should_persist_per_attempted
(
self
,
score_deleted
=
False
):
"""
"""
Returns whether the SubsectionGrade's model should be
Returns whether the SubsectionGrade's model should be
...
@@ -202,34 +243,6 @@ class SubsectionGrade(SubsectionGradeBase):
...
@@ -202,34 +243,6 @@ class SubsectionGrade(SubsectionGradeBase):
score_deleted
score_deleted
)
)
@staticmethod
def
_compute_block_score
(
block_key
,
course_structure
,
submissions_scores
,
csm_scores
,
persisted_block
=
None
,
):
"""
Compute score for the given block. If persisted_values
is provided, it is used for possible and weight.
"""
try
:
block
=
course_structure
[
block_key
]
except
KeyError
:
# It's possible that the user's access to that
# block has changed since the subsection grade
# was last persisted.
pass
else
:
if
getattr
(
block
,
'has_score'
,
False
):
return
get_score
(
submissions_scores
,
csm_scores
,
persisted_block
,
block
,
)
def
_persisted_model_params
(
self
,
student
):
def
_persisted_model_params
(
self
,
student
):
"""
"""
Returns the parameters for creating/updating the
Returns the parameters for creating/updating the
...
@@ -258,25 +271,3 @@ class SubsectionGrade(SubsectionGradeBase):
...
@@ -258,25 +271,3 @@ class SubsectionGrade(SubsectionGradeBase):
for
location
,
score
in
for
location
,
score
in
self
.
problem_scores
.
iteritems
()
self
.
problem_scores
.
iteritems
()
]
]
def
_log_event
(
self
,
log_func
,
log_statement
,
student
):
"""
Logs the given statement, for this instance.
"""
log_func
(
u"Grades: SG.{}, subsection: {}, course: {}, "
u"version: {}, edit: {}, user: {},"
u"total: {}/{}, graded: {}/{}, show_correctness: {}"
.
format
(
log_statement
,
self
.
location
,
self
.
location
.
course_key
,
self
.
course_version
,
self
.
subtree_edited_timestamp
,
student
.
id
,
self
.
all_total
.
earned
,
self
.
all_total
.
possible
,
self
.
graded_total
.
earned
,
self
.
graded_total
.
possible
,
self
.
show_correctness
,
)
)
lms/djangoapps/grades/subsection_grade_factory.py
View file @
214cb516
...
@@ -12,7 +12,7 @@ from student.models import anonymous_id_for_user
...
@@ -12,7 +12,7 @@ from student.models import anonymous_id_for_user
from
submissions
import
api
as
submissions_api
from
submissions
import
api
as
submissions_api
from
.course_data
import
CourseData
from
.course_data
import
CourseData
from
.subsection_grade
import
SubsectionGrade
,
ZeroSubsectionGrade
from
.subsection_grade
import
CreateSubsectionGrade
,
Read
SubsectionGrade
,
ZeroSubsectionGrade
log
=
getLogger
(
__name__
)
log
=
getLogger
(
__name__
)
...
@@ -43,7 +43,7 @@ class SubsectionGradeFactory(object):
...
@@ -43,7 +43,7 @@ class SubsectionGradeFactory(object):
if
assume_zero_if_absent
(
self
.
course_data
.
course_key
):
if
assume_zero_if_absent
(
self
.
course_data
.
course_key
):
subsection_grade
=
ZeroSubsectionGrade
(
subsection
,
self
.
course_data
)
subsection_grade
=
ZeroSubsectionGrade
(
subsection
,
self
.
course_data
)
else
:
else
:
subsection_grade
=
SubsectionGrade
.
creat
e
(
subsection_grade
=
CreateSubsectionGrad
e
(
subsection
,
self
.
course_data
.
structure
,
self
.
_submissions_scores
,
self
.
_csm_scores
,
subsection
,
self
.
course_data
.
structure
,
self
.
_submissions_scores
,
self
.
_csm_scores
,
)
)
if
should_persist_grades
(
self
.
course_data
.
course_key
):
if
should_persist_grades
(
self
.
course_data
.
course_key
):
...
@@ -58,7 +58,7 @@ class SubsectionGradeFactory(object):
...
@@ -58,7 +58,7 @@ class SubsectionGradeFactory(object):
"""
"""
Bulk creates all the unsaved subsection_grades to this point.
Bulk creates all the unsaved subsection_grades to this point.
"""
"""
SubsectionGrade
.
bulk_create_models
(
Create
SubsectionGrade
.
bulk_create_models
(
self
.
student
,
self
.
_unsaved_subsection_grades
.
values
(),
self
.
course_data
.
course_key
self
.
student
,
self
.
_unsaved_subsection_grades
.
values
(),
self
.
course_data
.
course_key
)
)
self
.
_unsaved_subsection_grades
.
clear
()
self
.
_unsaved_subsection_grades
.
clear
()
...
@@ -69,7 +69,7 @@ class SubsectionGradeFactory(object):
...
@@ -69,7 +69,7 @@ class SubsectionGradeFactory(object):
"""
"""
self
.
_log_event
(
log
.
debug
,
u"update, subsection: {}"
.
format
(
subsection
.
location
),
subsection
)
self
.
_log_event
(
log
.
debug
,
u"update, subsection: {}"
.
format
(
subsection
.
location
),
subsection
)
calculated_grade
=
SubsectionGrade
.
creat
e
(
calculated_grade
=
CreateSubsectionGrad
e
(
subsection
,
self
.
course_data
.
structure
,
self
.
_submissions_scores
,
self
.
_csm_scores
,
subsection
,
self
.
course_data
.
structure
,
self
.
_submissions_scores
,
self
.
_csm_scores
,
)
)
...
@@ -80,7 +80,7 @@ class SubsectionGradeFactory(object):
...
@@ -80,7 +80,7 @@ class SubsectionGradeFactory(object):
except
PersistentSubsectionGrade
.
DoesNotExist
:
except
PersistentSubsectionGrade
.
DoesNotExist
:
pass
pass
else
:
else
:
orig_subsection_grade
=
SubsectionGrade
.
read
(
orig_subsection_grade
=
ReadSubsectionGrade
(
subsection
,
grade_model
,
self
.
course_data
.
structure
,
self
.
_submissions_scores
,
self
.
_csm_scores
,
subsection
,
grade_model
,
self
.
course_data
.
structure
,
self
.
_submissions_scores
,
self
.
_csm_scores
,
)
)
if
not
is_score_higher_or_equal
(
if
not
is_score_higher_or_equal
(
...
@@ -125,7 +125,7 @@ class SubsectionGradeFactory(object):
...
@@ -125,7 +125,7 @@ class SubsectionGradeFactory(object):
saved_subsection_grades
=
self
.
_get_bulk_cached_subsection_grades
()
saved_subsection_grades
=
self
.
_get_bulk_cached_subsection_grades
()
grade
=
saved_subsection_grades
.
get
(
subsection
.
location
)
grade
=
saved_subsection_grades
.
get
(
subsection
.
location
)
if
grade
:
if
grade
:
return
SubsectionGrade
.
read
(
return
ReadSubsectionGrade
(
subsection
,
grade
,
self
.
course_data
.
structure
,
self
.
_submissions_scores
,
self
.
_csm_scores
,
subsection
,
grade
,
self
.
course_data
.
structure
,
self
.
_submissions_scores
,
self
.
_csm_scores
,
)
)
...
...
lms/djangoapps/grades/tests/test_course_grade_factory.py
View file @
214cb516
...
@@ -14,7 +14,7 @@ from xmodule.modulestore.tests.factories import CourseFactory
...
@@ -14,7 +14,7 @@ from xmodule.modulestore.tests.factories import CourseFactory
from
..config.waffle
import
ASSUME_ZERO_GRADE_IF_ABSENT
,
waffle
from
..config.waffle
import
ASSUME_ZERO_GRADE_IF_ABSENT
,
waffle
from
..course_grade
import
CourseGrade
,
ZeroCourseGrade
from
..course_grade
import
CourseGrade
,
ZeroCourseGrade
from
..course_grade_factory
import
CourseGradeFactory
from
..course_grade_factory
import
CourseGradeFactory
from
..subsection_grade
import
SubsectionGrade
,
ZeroSubsectionGrade
from
..subsection_grade
import
Read
SubsectionGrade
,
ZeroSubsectionGrade
from
.base
import
GradeTestBase
from
.base
import
GradeTestBase
from
.utils
import
mock_get_score
from
.utils
import
mock_get_score
...
@@ -131,7 +131,7 @@ class TestCourseGradeFactory(GradeTestBase):
...
@@ -131,7 +131,7 @@ class TestCourseGradeFactory(GradeTestBase):
course_grade
=
CourseGradeFactory
()
.
update
(
self
.
request
.
user
,
self
.
course
)
course_grade
=
CourseGradeFactory
()
.
update
(
self
.
request
.
user
,
self
.
course
)
subsection1_grade
=
course_grade
.
subsection_grades
[
self
.
sequence
.
location
]
subsection1_grade
=
course_grade
.
subsection_grades
[
self
.
sequence
.
location
]
subsection2_grade
=
course_grade
.
subsection_grades
[
self
.
sequence2
.
location
]
subsection2_grade
=
course_grade
.
subsection_grades
[
self
.
sequence2
.
location
]
self
.
assertIsInstance
(
subsection1_grade
,
SubsectionGrade
)
self
.
assertIsInstance
(
subsection1_grade
,
Read
SubsectionGrade
)
self
.
assertIsInstance
(
subsection2_grade
,
ZeroSubsectionGrade
)
self
.
assertIsInstance
(
subsection2_grade
,
ZeroSubsectionGrade
)
@ddt.data
(
True
,
False
)
@ddt.data
(
True
,
False
)
...
...
lms/djangoapps/grades/tests/test_subsection_grade.py
View file @
214cb516
from
..models
import
PersistentSubsectionGrade
from
..models
import
PersistentSubsectionGrade
from
..subsection_grade
import
SubsectionGrade
from
..subsection_grade
import
CreateSubsectionGrade
,
Read
SubsectionGrade
from
.utils
import
mock_get_score
from
.utils
import
mock_get_score
from
.base
import
GradeTestBase
from
.base
import
GradeTestBase
...
@@ -8,7 +8,7 @@ class SubsectionGradeTest(GradeTestBase):
...
@@ -8,7 +8,7 @@ class SubsectionGradeTest(GradeTestBase):
def
test_create_and_read
(
self
):
def
test_create_and_read
(
self
):
with
mock_get_score
(
1
,
2
):
with
mock_get_score
(
1
,
2
):
# Create a grade that *isn't* saved to the database
# Create a grade that *isn't* saved to the database
created_grade
=
SubsectionGrade
.
creat
e
(
created_grade
=
CreateSubsectionGrad
e
(
self
.
sequence
,
self
.
sequence
,
self
.
course_structure
,
self
.
course_structure
,
self
.
subsection_grade_factory
.
_submissions_scores
,
self
.
subsection_grade_factory
.
_submissions_scores
,
...
@@ -25,7 +25,7 @@ class SubsectionGradeTest(GradeTestBase):
...
@@ -25,7 +25,7 @@ class SubsectionGradeTest(GradeTestBase):
user_id
=
self
.
request
.
user
.
id
,
user_id
=
self
.
request
.
user
.
id
,
usage_key
=
self
.
sequence
.
location
,
usage_key
=
self
.
sequence
.
location
,
)
)
read_grade
=
SubsectionGrade
.
read
(
read_grade
=
ReadSubsectionGrade
(
self
.
sequence
,
self
.
sequence
,
saved_model
,
saved_model
,
self
.
course_structure
,
self
.
course_structure
,
...
...
lms/djangoapps/instructor_task/tasks_helper/grades.py
View file @
214cb516
...
@@ -222,7 +222,7 @@ class CourseGradeReport(object):
...
@@ -222,7 +222,7 @@ class CourseGradeReport(object):
Returns a list of all applicable column headers for this grade report.
Returns a list of all applicable column headers for this grade report.
"""
"""
return
(
return
(
[
"Student ID"
,
"Email"
,
"Username"
,
"Grade"
]
+
[
"Student ID"
,
"Email"
,
"Username"
]
+
self
.
_grades_header
(
context
)
+
self
.
_grades_header
(
context
)
+
([
'Cohort Name'
]
if
context
.
cohorts_enabled
else
[])
+
([
'Cohort Name'
]
if
context
.
cohorts_enabled
else
[])
+
[
u'Experiment Group ({})'
.
format
(
partition
.
name
)
for
partition
in
context
.
course_experiments
]
+
[
u'Experiment Group ({})'
.
format
(
partition
.
name
)
for
partition
in
context
.
course_experiments
]
+
...
@@ -278,7 +278,7 @@ class CourseGradeReport(object):
...
@@ -278,7 +278,7 @@ class CourseGradeReport(object):
Returns the applicable grades-related headers for this report.
Returns the applicable grades-related headers for this report.
"""
"""
graded_assignments
=
context
.
graded_assignments
graded_assignments
=
context
.
graded_assignments
grades_header
=
[]
grades_header
=
[
"Grade"
]
for
assignment_info
in
graded_assignments
.
itervalues
():
for
assignment_info
in
graded_assignments
.
itervalues
():
if
assignment_info
[
'separate_subsection_avg_headers'
]:
if
assignment_info
[
'separate_subsection_avg_headers'
]:
grades_header
.
extend
(
assignment_info
[
'subsection_headers'
]
.
itervalues
())
grades_header
.
extend
(
assignment_info
[
'subsection_headers'
]
.
itervalues
())
...
...
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