add command
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
"""
|
||||
Command to recalculate grades for all subsections with problem submissions
|
||||
in the specified time range.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
from datetime import datetime
|
||||
import logging
|
||||
from pytz import utc
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from lms.djangoapps.grades.constants import ScoreDatabaseTableEnum
|
||||
from lms.djangoapps.grades.signals.handlers import PROBLEM_SUBMITTED_EVENT_TYPE
|
||||
from lms.djangoapps.grades.tasks import recalculate_subsection_grade_v3
|
||||
from courseware.models import StudentModule
|
||||
from student.models import user_by_anonymous_id
|
||||
from submissions.models import Submission
|
||||
from track.event_transaction_utils import create_new_event_transaction_id, set_event_transaction_type
|
||||
from util.date_utils import to_timestamp
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
DATE_FORMAT = "%Y-%m-%d %H:%M"
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
"""
|
||||
Example usage:
|
||||
$ ./manage.py lms recalculate_subsection_grades
|
||||
--modified_start '2016-08-23 16:43' --modified_end '2016-08-25 16:43' --settings=devstack
|
||||
"""
|
||||
args = 'fill this in'
|
||||
help = 'Recalculates subsection grades for all subsections modified within the given time range.'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
"""
|
||||
Entry point for subclassed commands to add custom arguments.
|
||||
"""
|
||||
parser.add_argument(
|
||||
'--modified_start',
|
||||
dest='modified_start',
|
||||
help='Starting range for modified date (inclusive): e.g. "2016-08-23 16:43"; expected in UTC.',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--modified_end',
|
||||
dest='modified_end',
|
||||
help='Ending range for modified date (inclusive): e.g. "2016-12-23 16:43"; expected in UTC.',
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
if 'modified_start' not in options:
|
||||
raise CommandError('modified_start must be provided.')
|
||||
|
||||
if 'modified_end' not in options:
|
||||
raise CommandError('modified_end must be provided.')
|
||||
|
||||
modified_start = utc.localize(datetime.strptime(options['modified_start'], DATE_FORMAT))
|
||||
modified_end = utc.localize(datetime.strptime(options['modified_end'], DATE_FORMAT))
|
||||
event_transaction_id = create_new_event_transaction_id()
|
||||
set_event_transaction_type(PROBLEM_SUBMITTED_EVENT_TYPE)
|
||||
kwargs = {'modified__range': (modified_start, modified_end), 'module_type': 'problem'}
|
||||
for record in StudentModule.objects.filter(**kwargs):
|
||||
task_args = {
|
||||
"user_id": record.student_id,
|
||||
"course_id": unicode(record.course_id),
|
||||
"usage_id": unicode(record.module_state_key),
|
||||
"only_if_higher": False,
|
||||
"expected_modified_time": to_timestamp(record.modified),
|
||||
"score_deleted": False,
|
||||
"event_transaction_id": unicode(event_transaction_id),
|
||||
"event_transaction_type": PROBLEM_SUBMITTED_EVENT_TYPE,
|
||||
"score_db_table": ScoreDatabaseTableEnum.courseware_student_module,
|
||||
}
|
||||
recalculate_subsection_grade_v3.apply_async(kwargs=task_args)
|
||||
|
||||
kwargs = {'created_at__range': (modified_start, modified_end)}
|
||||
for record in Submission.objects.filter(**kwargs):
|
||||
task_args = {
|
||||
"user_id": user_by_anonymous_id(record.student_item.student_id).id,
|
||||
"anonymous_user_id": record.student_item.student_id,
|
||||
"course_id": unicode(record.student_item.course_id),
|
||||
"usage_id": unicode(record.student_item.item_id),
|
||||
"only_if_higher": False,
|
||||
"expected_modified_time": to_timestamp(record.created_at),
|
||||
"score_deleted": False,
|
||||
"event_transaction_id": unicode(event_transaction_id),
|
||||
"event_transaction_type": PROBLEM_SUBMITTED_EVENT_TYPE,
|
||||
"score_db_table": ScoreDatabaseTableEnum.submissions,
|
||||
}
|
||||
recalculate_subsection_grade_v3.apply_async(kwargs=task_args)
|
||||
@@ -0,0 +1,78 @@
|
||||
"""
|
||||
Tests for reset_grades management command.
|
||||
"""
|
||||
|
||||
import ddt
|
||||
from datetime import datetime
|
||||
from django.conf import settings
|
||||
from mock import patch, MagicMock
|
||||
from pytz import utc
|
||||
|
||||
from lms.djangoapps.grades.management.commands import recalculate_subsection_grades
|
||||
from lms.djangoapps.grades.constants import ScoreDatabaseTableEnum
|
||||
from lms.djangoapps.grades.tests.test_tasks import HasCourseWithProblemsMixin
|
||||
from track.event_transaction_utils import get_event_transaction_id
|
||||
from util.date_utils import to_timestamp
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
|
||||
DATE_FORMAT = "%Y-%m-%d %H:%M"
|
||||
|
||||
|
||||
@patch.dict(settings.FEATURES, {'PERSISTENT_GRADES_ENABLED_FOR_ALL_TESTS': False})
|
||||
@ddt.ddt
|
||||
class TestRecalculateSubsectionGrades(HasCourseWithProblemsMixin, ModuleStoreTestCase):
|
||||
"""
|
||||
Tests recalculate subsection grades management command.
|
||||
"""
|
||||
def setUp(self):
|
||||
super(TestRecalculateSubsectionGrades, self).setUp()
|
||||
self.command = recalculate_subsection_grades.Command()
|
||||
|
||||
@patch('lms.djangoapps.grades.management.commands.recalculate_subsection_grades.Submission')
|
||||
@patch('lms.djangoapps.grades.management.commands.recalculate_subsection_grades.user_by_anonymous_id')
|
||||
@patch('lms.djangoapps.grades.management.commands.recalculate_subsection_grades.recalculate_subsection_grade_v3')
|
||||
def test_submissions(self, task_mock, id_mock, subs_mock):
|
||||
submission = MagicMock()
|
||||
submission.student_item = MagicMock(
|
||||
student_id="anonymousID",
|
||||
course_id='x/y/z',
|
||||
item_id='abc',
|
||||
)
|
||||
submission.created_at = utc.localize(datetime.strptime('2016-08-23 16:43', DATE_FORMAT))
|
||||
subs_mock.objects.filter.return_value = [submission]
|
||||
id_mock.return_value = MagicMock()
|
||||
id_mock.return_value.id = "ID"
|
||||
self._run_command_and_check_output(task_mock, ScoreDatabaseTableEnum.submissions, include_anonymous_id=True)
|
||||
|
||||
@patch('lms.djangoapps.grades.management.commands.recalculate_subsection_grades.StudentModule')
|
||||
@patch('lms.djangoapps.grades.management.commands.recalculate_subsection_grades.user_by_anonymous_id')
|
||||
@patch('lms.djangoapps.grades.management.commands.recalculate_subsection_grades.recalculate_subsection_grade_v3')
|
||||
def test_csm(self, task_mock, id_mock, csm_mock):
|
||||
csm_record = MagicMock()
|
||||
csm_record.student_id = "ID"
|
||||
csm_record.course_id = "x/y/z"
|
||||
csm_record.module_state_key = "abc"
|
||||
csm_record.modified = utc.localize(datetime.strptime('2016-08-23 16:43', DATE_FORMAT))
|
||||
csm_mock.objects.filter.return_value = [csm_record]
|
||||
id_mock.return_value = MagicMock()
|
||||
id_mock.return_value.id = "ID"
|
||||
self._run_command_and_check_output(task_mock, ScoreDatabaseTableEnum.courseware_student_module)
|
||||
|
||||
def _run_command_and_check_output(self, task_mock, score_db_table, include_anonymous_id=False):
|
||||
self.command.handle(modified_start='2016-08-25 16:42', modified_end='2018-08-25 16:44')
|
||||
kwargs = {
|
||||
"user_id": "ID",
|
||||
"course_id": u'x/y/z',
|
||||
"usage_id": u'abc',
|
||||
"only_if_higher": False,
|
||||
"expected_modified_time": to_timestamp(utc.localize(datetime.strptime('2016-08-23 16:43', DATE_FORMAT))),
|
||||
"score_deleted": False,
|
||||
"event_transaction_id": unicode(get_event_transaction_id()),
|
||||
"event_transaction_type": u'edx.grades.problem.submitted',
|
||||
"score_db_table": score_db_table,
|
||||
}
|
||||
|
||||
if include_anonymous_id:
|
||||
kwargs['anonymous_user_id'] = 'anonymousID'
|
||||
|
||||
task_mock.apply_async.assert_called_with(kwargs=kwargs)
|
||||
Reference in New Issue
Block a user