From 72aa58406b26f117a40fb259b1aee0f42e962e95 Mon Sep 17 00:00:00 2001 From: "Albert (AJ) St. Aubin" Date: Thu, 29 Oct 2020 10:51:34 -0400 Subject: [PATCH] [MICROBA-676] Changes to the notify_credentials task to filter on username --- lms/djangoapps/certificates/api.py | 5 ++- lms/djangoapps/grades/models_api.py | 4 +- .../management/commands/notify_credentials.py | 27 ++++++++++--- .../commands/tests/test_notify_credentials.py | 39 ++++++++++++++++++- 4 files changed, 65 insertions(+), 10 deletions(-) diff --git a/lms/djangoapps/certificates/api.py b/lms/djangoapps/certificates/api.py index 60e0f63387..8d2da6949d 100644 --- a/lms/djangoapps/certificates/api.py +++ b/lms/djangoapps/certificates/api.py @@ -155,7 +155,7 @@ def get_certificates_for_user_by_course_keys(user, course_keys): } -def get_recently_modified_certificates(course_keys=None, start_date=None, end_date=None): +def get_recently_modified_certificates(course_keys=None, start_date=None, end_date=None, username=None): """ Returns a QuerySet of GeneratedCertificate objects filtered by the input parameters and ordered by modified_date. @@ -171,6 +171,9 @@ def get_recently_modified_certificates(course_keys=None, start_date=None, end_da if end_date: cert_filter_args['modified_date__lte'] = end_date + if username: + cert_filter_args['user__username'] = username + return GeneratedCertificate.objects.filter(**cert_filter_args).order_by('modified_date') diff --git a/lms/djangoapps/grades/models_api.py b/lms/djangoapps/grades/models_api.py index 7c3e37b34f..fff9e5af0f 100644 --- a/lms/djangoapps/grades/models_api.py +++ b/lms/djangoapps/grades/models_api.py @@ -35,7 +35,7 @@ def clear_prefetched_course_and_subsection_grades(course_key): _PersistentCourseGrade.clear_prefetched_data(course_key) -def get_recently_modified_grades(course_keys, start_date, end_date): +def get_recently_modified_grades(course_keys, start_date, end_date, user=None): """ Returns a QuerySet of PersistentCourseGrade objects filtered by the input parameters and ordered by modified date. @@ -47,6 +47,8 @@ def get_recently_modified_grades(course_keys, start_date, end_date): grade_filter_args['modified__gte'] = start_date if end_date: grade_filter_args['modified__lte'] = end_date + if user: + grade_filter_args['user_id'] = user.id return _PersistentCourseGrade.objects.filter(**grade_filter_args).order_by('modified') diff --git a/openedx/core/djangoapps/credentials/management/commands/notify_credentials.py b/openedx/core/djangoapps/credentials/management/commands/notify_credentials.py index 17cefccbee..6eec685060 100644 --- a/openedx/core/djangoapps/credentials/management/commands/notify_credentials.py +++ b/openedx/core/djangoapps/credentials/management/commands/notify_credentials.py @@ -19,7 +19,7 @@ import time from datetime import datetime, timedelta import dateutil.parser -from django.contrib.auth.models import User +from django.contrib.auth import get_user_model from django.core.management.base import BaseCommand, CommandError from opaque_keys import InvalidKeyError from opaque_keys.edx.keys import CourseKey @@ -34,6 +34,7 @@ from openedx.core.djangoapps.credentials.signals import handle_cert_change, send from openedx.core.djangoapps.programs.signals import handle_course_cert_changed, handle_course_cert_awarded from openedx.core.djangoapps.site_configuration.models import SiteConfiguration +User = get_user_model() log = logging.getLogger(__name__) @@ -160,6 +161,11 @@ class Command(BaseCommand): action='store_true', help='Send program award notifications with course notification tasks', ) + parser.add_argument( + '--username', + default=None, + help='Run the command for a single user', + ) def get_args_from_database(self): """ Returns an options dictionary from the current NotifyCredentialsConfig model. """ @@ -183,7 +189,7 @@ class Command(BaseCommand): log.info( u"notify_credentials starting, dry-run=%s, site=%s, delay=%d seconds, page_size=%d, " - u"from=%s, to=%s, notify_programs=%s, execution=%s", + u"from=%s, to=%s, notify_programs=%s, username=%s, execution=%s", options['dry_run'], options['site'], options['delay'], @@ -191,6 +197,7 @@ class Command(BaseCommand): options['start_date'] if options['start_date'] else 'NA', options['end_date'] if options['end_date'] else 'NA', options['notify_programs'], + options['username'], 'auto' if options['auto'] else 'manual', ) @@ -200,11 +207,19 @@ class Command(BaseCommand): log.error(u'No site configuration found for site %s', options['site']) course_keys = self.get_course_keys(options['courses']) - if not (course_keys or options['start_date'] or options['end_date']): - raise CommandError('You must specify a filter (e.g. --courses= or --start-date)') + if not (course_keys or options['start_date'] or options['end_date'] or options['username']): + raise CommandError('You must specify a filter (e.g. --courses= or --start-date or --username)') - 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']) + certs = get_recently_modified_certificates( + course_keys, options['start_date'], options['end_date'], options['username'] + ) + + user = None + if options['username']: + user = User.objects.get(username=options['username']) + grades = get_recently_modified_grades( + course_keys, options['start_date'], options['end_date'], user + ) log.info('notify_credentials Sending notifications for {certs} certificates and {grades} grades'.format( certs=certs.count(), diff --git a/openedx/core/djangoapps/credentials/management/commands/tests/test_notify_credentials.py b/openedx/core/djangoapps/credentials/management/commands/tests/test_notify_credentials.py index b2b7b37a69..4cf4549d90 100644 --- a/openedx/core/djangoapps/credentials/management/commands/tests/test_notify_credentials.py +++ b/openedx/core/djangoapps/credentials/management/commands/tests/test_notify_credentials.py @@ -33,6 +33,7 @@ class TestNotifyCredentials(TestCase): def setUp(self): super(TestNotifyCredentials, self).setUp() self.user = UserFactory.create() + self.user2 = UserFactory.create() with freeze_time(datetime(2017, 1, 1)): self.cert1 = GeneratedCertificateFactory(user=self.user, course_id='course-v1:edX+Test+1') @@ -42,7 +43,7 @@ class TestNotifyCredentials(TestCase): 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 + user=self.user2, course_id='course-v1:edX+Test+4', status=CertificateStatuses.downloadable ) print(('self.cert1.modified_date', self.cert1.modified_date)) @@ -57,7 +58,7 @@ class TestNotifyCredentials(TestCase): self.grade3 = PersistentCourseGrade.objects.create(user_id=self.user.id, course_id='course-v1:testX+Test+3', 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', + self.grade4 = PersistentCourseGrade.objects.create(user_id=self.user2.id, course_id='course-v1:edX+Test+4', percent_grade=1) print(('self.grade1.modified', self.grade1.modified)) @@ -122,6 +123,40 @@ class TestNotifyCredentials(TestCase): 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') + def test_username_arg(self, mock_send): + call_command( + Command(), '--start-date', '2017-02-01', '--end-date', '2017-02-02', '--username', self.user2.username + ) + self.assertTrue(mock_send.called) + self.assertListEqual(list(mock_send.call_args[0][0]), [self.cert4]) + self.assertListEqual(list(mock_send.call_args[0][1]), [self.grade4]) + mock_send.reset_mock() + + call_command( + Command(), '--username', self.user2.username + ) + self.assertTrue(mock_send.called) + self.assertListEqual(list(mock_send.call_args[0][0]), [self.cert4]) + self.assertListEqual(list(mock_send.call_args[0][1]), [self.grade4]) + mock_send.reset_mock() + + call_command( + Command(), '--start-date', '2017-02-01', '--end-date', '2017-02-02', '--username', self.user.username + ) + 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_send.reset_mock() + + call_command( + Command(), '--username', self.user2.username + ) + self.assertTrue(mock_send.called) + self.assertListEqual(list(mock_send.call_args[0][0]), [self.cert4]) + self.assertListEqual(list(mock_send.call_args[0][1]), [self.grade4]) + mock_send.reset_mock() + @mock.patch(COMMAND_MODULE + '.Command.send_notifications') def test_no_args(self, mock_send): with self.assertRaisesRegex(CommandError, 'You must specify a filter.*'):