diff --git a/lms/djangoapps/grades/management/commands/recalculate_learner_grades.py b/lms/djangoapps/grades/management/commands/recalculate_learner_grades.py new file mode 100644 index 0000000000000000000000000000000000000000..eac3b9d1d036e650c71788f81cd7ebdc9139017c --- /dev/null +++ b/lms/djangoapps/grades/management/commands/recalculate_learner_grades.py @@ -0,0 +1,37 @@ +""" +Command to recalculate a user's grades for a course, for every user in +a csv of (user, course) pairs. +""" + +from __future__ import absolute_import, division, print_function, unicode_literals + +import csv + +from django.core.management.base import BaseCommand + +from lms.djangoapps.grades.tasks import recalculate_course_and_subsection_grades_for_user + + +class Command(BaseCommand): + """ + Example usage: + $ ./manage.py lms recalculate_learner_course_grades learner_courses_to_recalculate.csv + """ + help = 'Recalculates a user\'s grades for a course, for every user in a csv of (user, course) pairs' + + def add_arguments(self, parser): + parser.add_argument('csv') + + def handle(self, *args, **options): + filename = options['csv'] + + with open(filename) as csv_file: + self.regrade_learner_in_course_from_csv(csv_file) + + def regrade_learner_in_course_from_csv(self, csv_file): + csv_reader = csv.DictReader(csv_file) + + for row in csv_reader: + recalculate_course_and_subsection_grades_for_user.apply_async( + kwargs={'user_id': row['user_id'], 'course_key': row['course_id']} + ) diff --git a/lms/djangoapps/grades/management/commands/tests/test_recalculate_learner_grades.py b/lms/djangoapps/grades/management/commands/tests/test_recalculate_learner_grades.py new file mode 100644 index 0000000000000000000000000000000000000000..bfbc4cf2a99fc6dab2e70d93f2d1198421b44fca --- /dev/null +++ b/lms/djangoapps/grades/management/commands/tests/test_recalculate_learner_grades.py @@ -0,0 +1,65 @@ +""" +Tests for recalculate_learner_grades management command. +""" + +from tempfile import NamedTemporaryFile + +import mock + +from lms.djangoapps.grades.management.commands import recalculate_learner_grades +from lms.djangoapps.grades.tests.test_tasks import HasCourseWithProblemsMixin +from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase +from xmodule.modulestore.tests.factories import CourseFactory +from student.tests.factories import UserFactory, CourseEnrollmentFactory + +DATE_FORMAT = "%Y-%m-%d %H:%M" + + +class TestRecalculateLearnerGrades(HasCourseWithProblemsMixin, ModuleStoreTestCase): + """ + Tests recalculate learner grades management command. + """ + + def setUp(self): + super(TestRecalculateLearnerGrades, self).setUp() + self.command = recalculate_learner_grades.Command() + + self.course1 = CourseFactory.create() + self.course2 = CourseFactory.create() + self.course3 = CourseFactory.create() + self.user1 = UserFactory.create() + self.user2 = UserFactory.create() + CourseEnrollmentFactory(course_id=self.course1.id, user=self.user1) + CourseEnrollmentFactory(course_id=self.course1.id, user=self.user2) + CourseEnrollmentFactory(course_id=self.course2.id, user=self.user1) + CourseEnrollmentFactory(course_id=self.course2.id, user=self.user2) + + self.user_course_pairs = [ + (str(self.user1.id), str(self.course1.id)), + (str(self.user1.id), str(self.course2.id)), + (str(self.user2.id), str(self.course1.id)), + (str(self.user2.id), str(self.course2.id)) + ] + + @mock.patch( + 'lms.djangoapps.grades.management.commands.recalculate_learner_grades.' + 'recalculate_course_and_subsection_grades_for_user' + ) + def test_recalculate_grades(self, task_mock): + with NamedTemporaryFile() as csv: + csv.write("course_id,user_id\n") + csv.writelines(course + "," + user + "\n" for user, course in self.user_course_pairs) + csv.seek(0) + + self.command.handle(csv=csv.name) + + expected_calls = [] + for user, course in self.user_course_pairs: + expected_calls.append(mock.call( + kwargs={ + "user_id": user, + "course_key": course + } + )) + + task_mock.apply_async.assert_has_calls(expected_calls, any_order=True)