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
a3154b92
Commit
a3154b92
authored
Mar 22, 2017
by
J. Cliff Dyer
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Create task to process a batch of grade computations.
TNL-6690
parent
d19fbca1
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
90 additions
and
29 deletions
+90
-29
lms/djangoapps/grades/new/course_grade.py
+2
-2
lms/djangoapps/grades/tasks.py
+21
-14
lms/djangoapps/grades/tests/test_tasks.py
+67
-13
No files found.
lms/djangoapps/grades/new/course_grade.py
View file @
a3154b92
...
...
@@ -368,7 +368,7 @@ class CourseGradeFactory(object):
GradeResult
=
namedtuple
(
'GradeResult'
,
[
'student'
,
'course_grade'
,
'err_msg'
])
def
iter
(
self
,
course
,
students
):
def
iter
(
self
,
course
,
students
,
read_only
=
True
):
"""
Given a course and an iterable of students (User), yield a GradeResult
for every student enrolled in the course. GradeResult is a named tuple of:
...
...
@@ -388,7 +388,7 @@ class CourseGradeFactory(object):
for
student
in
students
:
with
dog_stats_api
.
timer
(
'lms.grades.CourseGradeFactory.iter'
,
tags
=
[
u'action:{}'
.
format
(
course
.
id
)]):
try
:
course_grade
=
CourseGradeFactory
()
.
create
(
student
,
course
,
collected_block_structure
)
course_grade
=
CourseGradeFactory
()
.
create
(
student
,
course
,
collected_block_structure
,
read_only
=
read_only
)
yield
self
.
GradeResult
(
student
,
course_grade
,
""
)
except
Exception
as
exc
:
# pylint: disable=broad-except
...
...
lms/djangoapps/grades/tasks.py
View file @
a3154b92
...
...
@@ -19,8 +19,10 @@ from celery_utils.logged_task import LoggedTask
from
celery_utils.persist_on_failure
import
PersistOnFailureTask
from
courseware.model_data
import
get_score
from
lms.djangoapps.course_blocks.api
import
get_course_blocks
from
opaque_keys.edx.keys
import
UsageKey
from
lms.djangoapps.courseware
import
courses
from
opaque_keys.edx.keys
import
CourseKey
,
UsageKey
from
opaque_keys.edx.locator
import
CourseLocator
from
student.models
import
CourseEnrollment
from
submissions
import
api
as
sub_api
from
track.event_transaction_utils
import
(
set_event_transaction_type
,
...
...
@@ -31,6 +33,7 @@ from xmodule.modulestore.django import modulestore
from
.constants
import
ScoreDatabaseTableEnum
from
.new.subsection_grade
import
SubsectionGradeFactory
from
.new.course_grade
import
CourseGradeFactory
from
.signals.signals
import
SUBSECTION_SCORE_CHANGED
from
.transformer
import
GradesTransformer
...
...
@@ -58,12 +61,24 @@ class _BaseTask(PersistOnFailureTask, LoggedTask): # pylint: disable=abstract-m
abstract
=
True
@task
def
compute_grades_for_course
(
course_key
,
offset
,
batch_size
):
# pylint: disable=unused-argument
@task
(
base
=
_BaseTask
)
def
compute_grades_for_course
(
course_key
,
offset
,
batch_size
):
"""
TODO: TNL-6690: Fill this task in and remove pylint disables
Compute grades for a set of students in the specified course.
The set of students will be determined by the order of enrollment date, and
limited to at most <batch_size> students, starting from the specified
offset.
"""
pass
course
=
courses
.
get_course_by_id
(
CourseKey
.
from_string
(
course_key
))
enrollments
=
CourseEnrollment
.
objects
.
filter
(
course_id
=
course
.
id
)
.
order_by
(
'created'
)
student_iter
=
(
enrollment
.
user
for
enrollment
in
enrollments
[
offset
:
offset
+
batch_size
])
list
(
CourseGradeFactory
()
.
iter
(
course
,
students
=
student_iter
,
read_only
=
False
,
))
@task
(
bind
=
True
,
base
=
_BaseTask
,
default_retry_delay
=
30
,
routing_key
=
settings
.
RECALCULATE_GRADES_ROUTING_KEY
)
...
...
@@ -136,7 +151,7 @@ def _recalculate_subsection_grade(self, **kwargs):
self
.
request
.
id
,
kwargs
,
))
raise
_retry_recalculate_subsection_grade
(
self
,
exc
=
exc
,
**
kwargs
)
raise
self
.
retry
(
kwargs
=
kwargs
,
exc
=
exc
)
def
_has_db_updated_with_new_score
(
self
,
scored_block_usage_key
,
**
kwargs
):
...
...
@@ -218,11 +233,3 @@ def _update_subsection_grades(
user
=
student
,
subsection_grade
=
subsection_grade
,
)
def
_retry_recalculate_subsection_grade
(
self
,
exc
=
None
,
**
kwargs
):
"""
Calls retry for the recalculate_subsection_grade task with the
given inputs.
"""
self
.
retry
(
kwargs
=
kwargs
,
exc
=
exc
)
lms/djangoapps/grades/tests/test_tasks.py
View file @
a3154b92
...
...
@@ -11,10 +11,11 @@ from django.db.utils import IntegrityError
import
itertools
from
mock
import
patch
,
MagicMock
import
pytz
import
six
from
util.date_utils
import
to_timestamp
from
openedx.core.djangoapps.content.block_structure.exceptions
import
BlockStructureNotFound
from
student.models
import
anonymous_id_for_user
from
student.models
import
CourseEnrollment
,
anonymous_id_for_user
from
student.tests.factories
import
UserFactory
from
track.event_transaction_utils
import
(
create_new_event_transaction_id
,
...
...
@@ -29,22 +30,17 @@ from lms.djangoapps.grades.config.models import PersistentGradesEnabledFlag
from
lms.djangoapps.grades.constants
import
ScoreDatabaseTableEnum
from
lms.djangoapps.grades.models
import
PersistentCourseGrade
,
PersistentSubsectionGrade
from
lms.djangoapps.grades.signals.signals
import
PROBLEM_WEIGHTED_SCORE_CHANGED
from
lms.djangoapps.grades.tasks
import
recalculate_subsection_grade_v3
,
RECALCULATE_GRADE_DELAY
from
lms.djangoapps.grades.tasks
import
(
compute_grades_for_course
,
recalculate_subsection_grade_v3
,
RECALCULATE_GRADE_DELAY
)
@patch.dict
(
settings
.
FEATURES
,
{
'PERSISTENT_GRADES_ENABLED_FOR_ALL_TESTS'
:
False
})
@ddt.ddt
class
RecalculateSubsectionGradeTest
(
ModuleStoreTestCase
):
class
HasCourseWithProblemsMixin
(
object
):
"""
Ensures that the recalculate subsection grade task functions as expected when run.
Mixin to provide tests with a sample course with graded subsections
"""
ENABLED_SIGNALS
=
[
'course_published'
,
'pre_publish'
]
def
setUp
(
self
):
super
(
RecalculateSubsectionGradeTest
,
self
)
.
setUp
()
self
.
user
=
UserFactory
()
PersistentGradesEnabledFlag
.
objects
.
create
(
enabled_for_all_courses
=
True
,
enabled
=
True
)
def
set_up_course
(
self
,
enable_persistent_grades
=
True
,
create_multiple_subsections
=
False
):
"""
Configures the course for this test.
...
...
@@ -100,6 +96,20 @@ class RecalculateSubsectionGradeTest(ModuleStoreTestCase):
_
=
anonymous_id_for_user
(
self
.
user
,
self
.
course
.
id
)
# pylint: enable=attribute-defined-outside-init,no-member
@patch.dict
(
settings
.
FEATURES
,
{
'PERSISTENT_GRADES_ENABLED_FOR_ALL_TESTS'
:
False
})
@ddt.ddt
class
RecalculateSubsectionGradeTest
(
HasCourseWithProblemsMixin
,
ModuleStoreTestCase
):
"""
Ensures that the recalculate subsection grade task functions as expected when run.
"""
ENABLED_SIGNALS
=
[
'course_published'
,
'pre_publish'
]
def
setUp
(
self
):
super
(
RecalculateSubsectionGradeTest
,
self
)
.
setUp
()
self
.
user
=
UserFactory
()
PersistentGradesEnabledFlag
.
objects
.
create
(
enabled_for_all_courses
=
True
,
enabled
=
True
)
@contextmanager
def
mock_get_score
(
self
,
score
=
MagicMock
(
grade
=
1.0
,
max_grade
=
2.0
)):
"""
...
...
@@ -363,3 +373,47 @@ class RecalculateSubsectionGradeTest(ModuleStoreTestCase):
Verifies the task was not retried.
"""
self
.
assertFalse
(
mock_retry
.
called
)
@ddt.ddt
class
ComputeGradesForCourseTest
(
HasCourseWithProblemsMixin
,
ModuleStoreTestCase
):
"""
Test compute_grades_for_course task.
"""
ENABLED_SIGNALS
=
[
'course_published'
,
'pre_publish'
]
def
setUp
(
self
):
super
(
ComputeGradesForCourseTest
,
self
)
.
setUp
()
self
.
users
=
[
UserFactory
.
create
()
for
_
in
xrange
(
12
)]
self
.
set_up_course
()
for
user
in
self
.
users
:
CourseEnrollment
.
enroll
(
user
,
self
.
course
.
id
)
@ddt.data
(
*
xrange
(
0
,
12
,
3
))
def
test_behavior
(
self
,
batch_size
):
result
=
compute_grades_for_course
.
delay
(
course_key
=
six
.
text_type
(
self
.
course
.
id
),
batch_size
=
batch_size
,
offset
=
4
)
self
.
assertTrue
(
result
.
successful
)
self
.
assertEqual
(
PersistentCourseGrade
.
objects
.
filter
(
course_id
=
self
.
course
.
id
)
.
count
(),
min
(
batch_size
,
8
)
# No more than 8 due to offset
)
self
.
assertEqual
(
PersistentSubsectionGrade
.
objects
.
filter
(
course_id
=
self
.
course
.
id
)
.
count
(),
min
(
batch_size
,
8
)
# No more than 8 due to offset
)
@ddt.data
(
*
xrange
(
1
,
12
,
3
))
def
test_database_calls
(
self
,
batch_size
):
per_user_queries
=
16
*
min
(
batch_size
,
6
)
# No more than 6 due to offset
with
self
.
assertNumQueries
(
3
+
16
*
min
(
batch_size
,
6
)):
with
check_mongo_calls
(
1
):
compute_grades_for_course
.
delay
(
course_key
=
six
.
text_type
(
self
.
course
.
id
),
batch_size
=
batch_size
,
offset
=
6
,
)
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