From b62ab09d85db48e366c661bf8c4552db6311e04e Mon Sep 17 00:00:00 2001 From: Zainab Amir Date: Fri, 23 Nov 2018 11:27:40 +0500 Subject: [PATCH] LEARNER-6643 Added management command to update credit eligibility deadline Added tests for command change_eligibility_deadline --- .../commands/change_eligibility_deadline.py | 101 +++++++++++++++++ .../tests/test_change_eligibility_deadline.py | 107 ++++++++++++++++++ 2 files changed, 208 insertions(+) create mode 100644 common/djangoapps/student/management/commands/change_eligibility_deadline.py create mode 100644 common/djangoapps/student/management/tests/test_change_eligibility_deadline.py diff --git a/common/djangoapps/student/management/commands/change_eligibility_deadline.py b/common/djangoapps/student/management/commands/change_eligibility_deadline.py new file mode 100644 index 0000000000..6b2f2bcdcf --- /dev/null +++ b/common/djangoapps/student/management/commands/change_eligibility_deadline.py @@ -0,0 +1,101 @@ +""" Command line script to change credit course eligibility deadline. """ + +import logging +from datetime import datetime, timedelta + +from course_modes.models import CourseMode +from django.core.management.base import BaseCommand +from opaque_keys import InvalidKeyError +from opaque_keys.edx.keys import CourseKey +from student.models import CourseEnrollment, User + +from openedx.core.djangoapps.credit.models import CreditEligibility + +logger = logging.getLogger(__name__) # pylint: disable=invalid-name + +DEFAULT_DAYS = 30 + + +class IncorrectDeadline(Exception): + """ + Exception raised explicitly to use default date when date given by user is prior to today. + """ + pass + + +class Command(BaseCommand): + + help = """ + Changes the credit course eligibility deadline for a student in a particular course. + It can be used to update the expired deadline to make student credit eligible. + + Example: + + Change credit eligibility deadline for user joe enrolled in credit course : + + $ ... change_eligibility_deadline -u joe -d 2018-12-30 -c course-v1:org-course-run + """ + + def add_arguments(self, parser): + parser.add_argument('-u', '--username', + metavar='USERNAME', + required=True, + help='username of the student') + parser.add_argument('-d', '--date', + dest='deadline', + metavar='DEADLINE', + help='Desired eligibility deadline for credit course') + parser.add_argument('-c', '--course', + metavar='COURSE_KEY', + dest='course_key', + required=True, + help='Course Key') + + def handle(self, *args, **options): + """ + Handler for the command + + It performs checks for username, course and enrollment validity and + then calls update_deadline for the given arguments + """ + username = options['username'] + course_id = options['course_key'] + + try: + user = User.objects.get(username=username) + except User.DoesNotExist: + logger.exception('Invalid or non-existent username {}'.format(username)) + raise + + try: + course_key = CourseKey.from_string(course_id) + CourseEnrollment.objects.get(user=user, course_id=course_key, mode=CourseMode.CREDIT_MODE) + except InvalidKeyError: + logger.exception('Invalid or non-existent course id {}'.format(course_id)) + raise + except CourseEnrollment.DoesNotExist: + logger.exception('No enrollment found in database for {username} in course {course_id}' + .format(username=username, course_id=course_id)) + raise + + try: + expected_date = datetime.strptime(options['deadline'], '%Y-%m-%d') + current_date = datetime.utcnow() + if expected_date < current_date: + raise IncorrectDeadline('Incorrect Deadline') + except (TypeError, KeyError, IncorrectDeadline): + logger.warning('Invalid date or date not provided. Setting deadline to one month from now') + expected_date = datetime.utcnow() + timedelta(days=DEFAULT_DAYS) + + self.update_credit_eligibility_deadline(username, course_key, expected_date) + logger.info("Successfully updated credit eligibility deadline for {}".format(username)) + + def update_credit_eligibility_deadline(self, username, course_key, new_deadline): + """ Update Credit Eligibility new_deadline for a specific user """ + try: + eligibility_record = CreditEligibility.objects.get(username=username, course__course_key=course_key) + eligibility_record.deadline = new_deadline + eligibility_record.save() + except CreditEligibility.DoesNotExist: + logger.exception('User is not credit eligible') + raise diff --git a/common/djangoapps/student/management/tests/test_change_eligibility_deadline.py b/common/djangoapps/student/management/tests/test_change_eligibility_deadline.py new file mode 100644 index 0000000000..8bc4189bcb --- /dev/null +++ b/common/djangoapps/student/management/tests/test_change_eligibility_deadline.py @@ -0,0 +1,107 @@ +""" Test the change_eligibility_deadline command line script.""" + +from datetime import datetime, timedelta + +from course_modes.tests.factories import CourseMode +from django.core.management import call_command +from opaque_keys import InvalidKeyError +from six import text_type +from student.models import CourseEnrollment, User +from student.tests.factories import UserFactory +from testfixtures import LogCapture +from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase +from xmodule.modulestore.tests.factories import CourseFactory + +from openedx.core.djangoapps.credit.models import CreditCourse, CreditEligibility + +LOGGER_NAME = 'student.management.commands.change_eligibility_deadline' +command_args = '--username {username} --course {course} --date {date}' + + +class ChangeEligibilityDeadlineTests(SharedModuleStoreTestCase): + """ Test the deadline change functionality of the change_eligibility_deadline script.""" + + def setUp(self): + """ Initial set up for tests """ + super(ChangeEligibilityDeadlineTests, self).setUp() + self.course = CourseFactory.create() + + self.enrolled_user = UserFactory.create(username='amy', email='amy@pond.com', password='password') + CourseEnrollment.enroll(self.enrolled_user, self.course.id, mode=CourseMode.CREDIT_MODE).save() + + credit_course = CreditCourse.objects.create(course_key=self.course.id) + self.credit_eligibility = CreditEligibility.objects.create( + username=self.enrolled_user.username, + course=credit_course, + ) + self.credit_eligibility.deadline = datetime.strptime('2013-12-30', '%Y-%m-%d') + self.credit_eligibility.save() + + def test_invalid_command_arguments(self): + """ Test command with invalid arguments """ + course_id_str = text_type(self.course.id) + username = self.enrolled_user.username + + # Incorrect username + with self.assertRaises(User.DoesNotExist): + call_command('change_eligibility_deadline', + *command_args.format(username='XYZ', course=course_id_str, date='2018-12-30').split(' ') + ) + # Incorrect course id + with self.assertRaises(InvalidKeyError): + call_command('change_eligibility_deadline', + *command_args.format(username=username, course='XYZ', date='2018-12-30').split(' ') + ) + # Student not enrolled + with self.assertRaises(CourseEnrollment.DoesNotExist): + unenrolled_user = UserFactory.create() + call_command('change_eligibility_deadline', + *command_args.format(username=unenrolled_user.username, course=course_id_str, + date='2018-12-30').split(' ') + ) + # Date format Invalid + with self.assertRaises(ValueError): + call_command('change_eligibility_deadline', + *command_args.format(username=username, course=course_id_str, date='30-12-2018').split(' ') + ) + # Date not provided + with self.assertRaises(KeyError): + call_command('change_eligibility_deadline', + *command_args.format(username=username, course=course_id_str,).split(' ')) + + def test_invalid_date(self): + """ + Tests the command when the date is prior to today + + In case the date given as deadline is prior to today it sets the deadline to + default value which is one month from today. It then continues to run the code + to change eligibility deadline. + """ + course_key = text_type(self.course.id) + username = self.enrolled_user.username + + # Test Date set prior to today + with LogCapture(LOGGER_NAME) as logger: + call_command( + 'change_eligibility_deadline', + *command_args.format(username=username, course=course_key, date='2000-12-30').split(' ') + ) + logger.check( + (LOGGER_NAME, 'WARNING', 'Invalid date or date not provided. Setting deadline to one month from now'), + (LOGGER_NAME, 'INFO', 'Successfully updated credit eligibility deadline for {}'.format(username)) + ) + + def test_valid_command_arguments(self): + """ Test command with valid arguments """ + course_key = text_type(self.course.id) + username = self.enrolled_user.username + new_deadline = datetime.utcnow() + timedelta(days=30) + + call_command('change_eligibility_deadline', + *command_args.format(username=username, course=course_key, date=new_deadline.date()).split(' ') + ) + + credit_eligibility = CreditEligibility.objects.get(username=username, + course__course_key=self.course.id) + credit_deadline = credit_eligibility.deadline.date() + self.assertEqual(credit_deadline, new_deadline.date())