Skip to content
Snippets Groups Projects
Commit 69a63380 authored by Albert (AJ) St. Aubin's avatar Albert (AJ) St. Aubin
Browse files

Adding in a call to send Program award events to Credentials in

management command
parent ff478fd1
No related branches found
No related tags found
No related merge requests found
...@@ -13,6 +13,7 @@ signals.) ...@@ -13,6 +13,7 @@ signals.)
import logging import logging
import math import math
import shlex
import sys import sys
import time import time
...@@ -28,8 +29,9 @@ from six.moves import range ...@@ -28,8 +29,9 @@ from six.moves import range
from lms.djangoapps.certificates.api import get_recently_modified_certificates from lms.djangoapps.certificates.api import get_recently_modified_certificates
from lms.djangoapps.grades.api import get_recently_modified_grades from lms.djangoapps.grades.api import get_recently_modified_grades
from openedx.core.djangoapps.credentials.models import NotifyCredentialsConfig from openedx.core.djangoapps.credentials.models import NotifyCredentialsConfig
from lms.djangoapps.certificates.models import CertificateStatuses
from openedx.core.djangoapps.credentials.signals import handle_cert_change, send_grade_if_interesting from openedx.core.djangoapps.credentials.signals import handle_cert_change, send_grade_if_interesting
from openedx.core.djangoapps.programs.signals import handle_course_cert_changed from openedx.core.djangoapps.programs.signals import handle_course_cert_changed, handle_course_cert_awarded
from openedx.core.djangoapps.site_configuration.models import SiteConfiguration from openedx.core.djangoapps.site_configuration.models import SiteConfiguration
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -153,6 +155,11 @@ class Command(BaseCommand): ...@@ -153,6 +155,11 @@ class Command(BaseCommand):
action='store_true', action='store_true',
help='Run grade/cert change signal in verbose mode', help='Run grade/cert change signal in verbose mode',
) )
parser.add_argument(
'--notify_programs',
action='store_true',
help='Send program award notifications with course notification tasks',
)
def get_args_from_database(self): def get_args_from_database(self):
""" Returns an options dictionary from the current NotifyCredentialsConfig model. """ """ Returns an options dictionary from the current NotifyCredentialsConfig model. """
...@@ -160,9 +167,9 @@ class Command(BaseCommand): ...@@ -160,9 +167,9 @@ class Command(BaseCommand):
if not config.enabled: if not config.enabled:
raise CommandError('NotifyCredentialsConfig is disabled, but --args-from-database was requested.') raise CommandError('NotifyCredentialsConfig is disabled, but --args-from-database was requested.')
# We don't need fancy shell-style whitespace/quote handling - none of our arguments are complicated # This split will allow for quotes to wrap datetimes, like "2020-10-20 04:00:00" and other
argv = config.arguments.split() # arguments as if it were the command line
argv = shlex.split(config.arguments)
parser = self.create_parser('manage.py', 'notify_credentials') parser = self.create_parser('manage.py', 'notify_credentials')
return parser.parse_args(argv).__dict__ # we want a dictionary, not a non-iterable Namespace object return parser.parse_args(argv).__dict__ # we want a dictionary, not a non-iterable Namespace object
...@@ -176,13 +183,14 @@ class Command(BaseCommand): ...@@ -176,13 +183,14 @@ class Command(BaseCommand):
log.info( log.info(
u"notify_credentials starting, dry-run=%s, site=%s, delay=%d seconds, page_size=%d, " u"notify_credentials starting, dry-run=%s, site=%s, delay=%d seconds, page_size=%d, "
u"from=%s, to=%s, execution=%s", u"from=%s, to=%s, notify_programs=%s, execution=%s",
options['dry_run'], options['dry_run'],
options['site'], options['site'],
options['delay'], options['delay'],
options['page_size'], options['page_size'],
options['start_date'] if options['start_date'] else 'NA', options['start_date'] if options['start_date'] else 'NA',
options['end_date'] if options['end_date'] else 'NA', options['end_date'] if options['end_date'] else 'NA',
options['notify_programs'],
'auto' if options['auto'] else 'manual', 'auto' if options['auto'] else 'manual',
) )
...@@ -198,18 +206,28 @@ class Command(BaseCommand): ...@@ -198,18 +206,28 @@ class Command(BaseCommand):
certs = get_recently_modified_certificates(course_keys, options['start_date'], options['end_date']) certs = get_recently_modified_certificates(course_keys, options['start_date'], options['end_date'])
grades = get_recently_modified_grades(course_keys, options['start_date'], options['end_date']) grades = get_recently_modified_grades(course_keys, options['start_date'], options['end_date'])
log.info('notify_credentials Sending notifications for {certs} certificates and {grades} grades'.format(
certs=certs.count(),
grades=grades.count()
))
if options['dry_run']: if options['dry_run']:
self.print_dry_run(certs, grades) self.print_dry_run(certs, grades)
else: else:
self.send_notifications(certs, grades, self.send_notifications(
site_config=site_config, certs,
delay=options['delay'], grades,
page_size=options['page_size'], site_config=site_config,
verbose=options['verbose']) delay=options['delay'],
page_size=options['page_size'],
verbose=options['verbose'],
notify_programs=options['notify_programs']
)
log.info('notify_credentials finished') log.info('notify_credentials finished')
def send_notifications(self, certs, grades, site_config=None, delay=0, page_size=0, verbose=False): def send_notifications(
self, certs, grades, site_config=None, delay=0, page_size=0, verbose=False, notify_programs=False
):
""" Run actual handler commands for the provided certs and grades. """ """ Run actual handler commands for the provided certs and grades. """
course_cert_info = {} course_cert_info = {}
...@@ -240,6 +258,8 @@ class Command(BaseCommand): ...@@ -240,6 +258,8 @@ class Command(BaseCommand):
course_cert_info[(cert.user.id, str(cert.course_id))] = data course_cert_info[(cert.user.id, str(cert.course_id))] = data
handle_course_cert_changed(**signal_args) handle_course_cert_changed(**signal_args)
if notify_programs and CertificateStatuses.is_passing_status(cert.status):
handle_course_cert_awarded(**signal_args)
# Then do grades # Then do grades
for i, grade in paged_query(grades, delay, page_size): for i, grade in paged_query(grades, delay, page_size):
......
...@@ -13,7 +13,7 @@ from django.test import TestCase, override_settings ...@@ -13,7 +13,7 @@ from django.test import TestCase, override_settings
from freezegun import freeze_time from freezegun import freeze_time
from lms.djangoapps.certificates.tests.factories import GeneratedCertificateFactory from lms.djangoapps.certificates.tests.factories import GeneratedCertificateFactory
from lms.djangoapps.certificates.models import GeneratedCertificate from lms.djangoapps.certificates.models import GeneratedCertificate, CertificateStatuses
from lms.djangoapps.grades.models import PersistentCourseGrade from lms.djangoapps.grades.models import PersistentCourseGrade
from openedx.core.djangoapps.credentials.models import NotifyCredentialsConfig from openedx.core.djangoapps.credentials.models import NotifyCredentialsConfig
from openedx.core.djangoapps.site_configuration.tests.factories import SiteConfigurationFactory from openedx.core.djangoapps.site_configuration.tests.factories import SiteConfigurationFactory
...@@ -36,10 +36,14 @@ class TestNotifyCredentials(TestCase): ...@@ -36,10 +36,14 @@ class TestNotifyCredentials(TestCase):
with freeze_time(datetime(2017, 1, 1)): with freeze_time(datetime(2017, 1, 1)):
self.cert1 = GeneratedCertificateFactory(user=self.user, course_id='course-v1:edX+Test+1') self.cert1 = GeneratedCertificateFactory(user=self.user, course_id='course-v1:edX+Test+1')
with freeze_time(datetime(2017, 2, 1)): with freeze_time(datetime(2017, 2, 1, 0)):
self.cert2 = GeneratedCertificateFactory(user=self.user, course_id='course-v1:edX+Test+2') self.cert2 = GeneratedCertificateFactory(user=self.user, course_id='course-v1:edX+Test+2')
with freeze_time(datetime(2017, 3, 1)): with freeze_time(datetime(2017, 3, 1)):
self.cert3 = GeneratedCertificateFactory(user=self.user, course_id='course-v1:testX+Test+3') self.cert3 = GeneratedCertificateFactory(user=self.user, course_id='course-v1:testX+Test+3')
with freeze_time(datetime(2017, 2, 1, 5)):
self.cert4 = GeneratedCertificateFactory(
user=self.user, course_id='course-v1:edX+Test+4', status=CertificateStatuses.downloadable
)
print(('self.cert1.modified_date', self.cert1.modified_date)) print(('self.cert1.modified_date', self.cert1.modified_date))
# No factory for these # No factory for these
...@@ -52,6 +56,9 @@ class TestNotifyCredentials(TestCase): ...@@ -52,6 +56,9 @@ class TestNotifyCredentials(TestCase):
with freeze_time(datetime(2017, 3, 1)): with freeze_time(datetime(2017, 3, 1)):
self.grade3 = PersistentCourseGrade.objects.create(user_id=self.user.id, course_id='course-v1:testX+Test+3', self.grade3 = PersistentCourseGrade.objects.create(user_id=self.user.id, course_id='course-v1:testX+Test+3',
percent_grade=1) percent_grade=1)
with freeze_time(datetime(2017, 2, 1, 5)):
self.grade4 = PersistentCourseGrade.objects.create(user_id=self.user.id, course_id='course-v1:edX+Test+4',
percent_grade=1)
print(('self.grade1.modified', self.grade1.modified)) print(('self.grade1.modified', self.grade1.modified))
@mock.patch(COMMAND_MODULE + '.Command.send_notifications') @mock.patch(COMMAND_MODULE + '.Command.send_notifications')
...@@ -94,20 +101,26 @@ class TestNotifyCredentials(TestCase): ...@@ -94,20 +101,26 @@ class TestNotifyCredentials(TestCase):
def test_date_args(self, mock_send): def test_date_args(self, mock_send):
call_command(Command(), '--start-date', '2017-01-31') call_command(Command(), '--start-date', '2017-01-31')
self.assertTrue(mock_send.called) self.assertTrue(mock_send.called)
self.assertListEqual(list(mock_send.call_args[0][0]), [self.cert2, self.cert3]) self.assertListEqual(list(mock_send.call_args[0][0]), [self.cert2, self.cert4, self.cert3])
self.assertListEqual(list(mock_send.call_args[0][1]), [self.grade2, self.grade3]) self.assertListEqual(list(mock_send.call_args[0][1]), [self.grade2, self.grade4, self.grade3])
mock_send.reset_mock() mock_send.reset_mock()
call_command(Command(), '--start-date', '2017-02-01', '--end-date', '2017-02-02') call_command(Command(), '--start-date', '2017-02-01', '--end-date', '2017-02-02')
self.assertTrue(mock_send.called) self.assertTrue(mock_send.called)
self.assertListEqual(list(mock_send.call_args[0][0]), [self.cert2]) self.assertListEqual(list(mock_send.call_args[0][0]), [self.cert2, self.cert4])
self.assertListEqual(list(mock_send.call_args[0][1]), [self.grade2]) self.assertListEqual(list(mock_send.call_args[0][1]), [self.grade2, self.grade4])
mock_send.reset_mock() mock_send.reset_mock()
call_command(Command(), '--end-date', '2017-02-02') call_command(Command(), '--end-date', '2017-02-02')
self.assertTrue(mock_send.called) self.assertTrue(mock_send.called)
self.assertListEqual(list(mock_send.call_args[0][0]), [self.cert1, self.cert2]) self.assertListEqual(list(mock_send.call_args[0][0]), [self.cert1, self.cert2, self.cert4])
self.assertListEqual(list(mock_send.call_args[0][1]), [self.grade1, self.grade2]) self.assertListEqual(list(mock_send.call_args[0][1]), [self.grade1, self.grade2, self.grade4])
mock_send.reset_mock()
call_command(Command(), '--start-date', "2017-02-01 00:00:00", '--end-date', '2017-02-01 04:00:00')
self.assertTrue(mock_send.called)
self.assertListEqual(list(mock_send.call_args[0][0]), [self.cert2])
self.assertListEqual(list(mock_send.call_args[0][1]), [self.grade2])
@mock.patch(COMMAND_MODULE + '.Command.send_notifications') @mock.patch(COMMAND_MODULE + '.Command.send_notifications')
def test_no_args(self, mock_send): def test_no_args(self, mock_send):
...@@ -120,12 +133,22 @@ class TestNotifyCredentials(TestCase): ...@@ -120,12 +133,22 @@ class TestNotifyCredentials(TestCase):
call_command(Command(), '--dry-run', '--start-date', '2017-02-01') call_command(Command(), '--dry-run', '--start-date', '2017-02-01')
self.assertFalse(mock_send.called) self.assertFalse(mock_send.called)
@mock.patch(COMMAND_MODULE + '.handle_course_cert_awarded')
@mock.patch(COMMAND_MODULE + '.send_grade_if_interesting') @mock.patch(COMMAND_MODULE + '.send_grade_if_interesting')
@mock.patch(COMMAND_MODULE + '.handle_course_cert_changed') @mock.patch(COMMAND_MODULE + '.handle_course_cert_changed')
def test_hand_off(self, mock_grade_interesting, mock_program_changed): def test_hand_off(self, mock_grade_interesting, mock_program_changed, mock_program_awarded):
call_command(Command(), '--start-date', '2017-02-01') call_command(Command(), '--start-date', '2017-02-01')
self.assertEqual(mock_grade_interesting.call_count, 2) self.assertEqual(mock_grade_interesting.call_count, 3)
self.assertEqual(mock_program_changed.call_count, 2) self.assertEqual(mock_program_changed.call_count, 3)
self.assertEqual(mock_program_awarded.call_count, 0)
mock_grade_interesting.reset_mock()
mock_program_changed.reset_mock()
mock_program_awarded.reset_mock()
call_command(Command(), '--start-date', '2017-02-01', '--notify_programs')
self.assertEqual(mock_grade_interesting.call_count, 3)
self.assertEqual(mock_program_changed.call_count, 3)
self.assertEqual(mock_program_awarded.call_count, 1)
@mock.patch(COMMAND_MODULE + '.time') @mock.patch(COMMAND_MODULE + '.time')
def test_delay(self, mock_time): def test_delay(self, mock_time):
...@@ -145,7 +168,7 @@ class TestNotifyCredentials(TestCase): ...@@ -145,7 +168,7 @@ class TestNotifyCredentials(TestCase):
reset_queries() reset_queries()
call_command(Command(), '--start-date', '2017-01-01', '--page-size=1') call_command(Command(), '--start-date', '2017-01-01', '--page-size=1')
self.assertEqual(len(connection.queries), baseline + 4) # two extra page queries each for certs & grades self.assertEqual(len(connection.queries), baseline + 6) # two extra page queries each for certs & grades
reset_queries() reset_queries()
call_command(Command(), '--start-date', '2017-01-01', '--page-size=2') call_command(Command(), '--start-date', '2017-01-01', '--page-size=2')
...@@ -168,13 +191,13 @@ class TestNotifyCredentials(TestCase): ...@@ -168,13 +191,13 @@ class TestNotifyCredentials(TestCase):
# Add a config # Add a config
config = NotifyCredentialsConfig.current() config = NotifyCredentialsConfig.current()
config.arguments = '--start-date 2017-03-01' config.arguments = '--start-date "2017-03-01 00:00:00"'
config.enabled = True config.enabled = True
config.save() config.save()
# Not told to use config, should ignore it # Not told to use config, should ignore it
call_command(Command(), '--start-date', '2017-01-01') call_command(Command(), '--start-date', '2017-01-01')
self.assertEqual(len(mock_send.call_args[0][0]), 3) self.assertEqual(len(mock_send.call_args[0][0]), 4) # Number of certs expected
# Told to use it, and enabled. Should use config in preference of command line # Told to use it, and enabled. Should use config in preference of command line
call_command(Command(), '--start-date', '2017-01-01', '--args-from-database') call_command(Command(), '--start-date', '2017-01-01', '--args-from-database')
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment