Send program cert visible_date
When sending a program cert to Credentials, also send along a calculated visible_date along with it. LEARNER-6262
This commit is contained in:
committed by
Michael Terry
parent
05debb8d54
commit
755ebc8c7f
@@ -29,6 +29,7 @@ MAX_RETRIES = 11
|
||||
|
||||
PROGRAM_CERTIFICATE = 'program'
|
||||
COURSE_CERTIFICATE = 'course-run'
|
||||
VISIBLE_DATE_FORMAT = '%Y-%m-%dT%H:%M:%SZ'
|
||||
|
||||
|
||||
def get_completed_programs(site, student):
|
||||
@@ -40,11 +41,11 @@ def get_completed_programs(site, student):
|
||||
student (User): Representing the student whose completed programs to check for.
|
||||
|
||||
Returns:
|
||||
list of program UUIDs
|
||||
dict of {program_UUIDs: visible_dates}
|
||||
|
||||
"""
|
||||
meter = ProgramProgressMeter(site, student)
|
||||
return meter.completed_programs
|
||||
return meter.completed_programs_with_available_dates
|
||||
|
||||
|
||||
def get_certified_programs(student):
|
||||
@@ -66,7 +67,7 @@ def get_certified_programs(student):
|
||||
return certified_programs
|
||||
|
||||
|
||||
def award_program_certificate(client, username, program_uuid):
|
||||
def award_program_certificate(client, username, program_uuid, visible_date):
|
||||
"""
|
||||
Issue a new certificate of completion to the given student for the given program.
|
||||
|
||||
@@ -77,6 +78,8 @@ def award_program_certificate(client, username, program_uuid):
|
||||
The username of the student
|
||||
program_uuid:
|
||||
uuid of the completed program
|
||||
visible_date:
|
||||
when the program credential should be visible to user
|
||||
|
||||
Returns:
|
||||
None
|
||||
@@ -88,7 +91,12 @@ def award_program_certificate(client, username, program_uuid):
|
||||
'type': PROGRAM_CERTIFICATE,
|
||||
'program_uuid': program_uuid
|
||||
},
|
||||
'attributes': []
|
||||
'attributes': [
|
||||
{
|
||||
'name': 'visible_date',
|
||||
'value': visible_date.strftime(VISIBLE_DATE_FORMAT)
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
@@ -136,10 +144,10 @@ def award_program_certificates(self, username):
|
||||
LOGGER.exception('Task award_program_certificates was called with invalid username %s', username)
|
||||
# Don't retry for this case - just conclude the task.
|
||||
return
|
||||
program_uuids = []
|
||||
completed_programs = {}
|
||||
for site in Site.objects.all():
|
||||
program_uuids.extend(get_completed_programs(site, student))
|
||||
if not program_uuids:
|
||||
completed_programs.update(get_completed_programs(site, student))
|
||||
if not completed_programs:
|
||||
# No reason to continue beyond this point unless/until this
|
||||
# task gets updated to support revocation of program certs.
|
||||
LOGGER.info('Task award_program_certificates was called for user %s with no completed programs', username)
|
||||
@@ -158,7 +166,7 @@ def award_program_certificates(self, username):
|
||||
# This logic is important, because we will retry the whole task if awarding any particular program cert fails.
|
||||
#
|
||||
# N.B. the list is sorted to facilitate deterministic ordering, e.g. for tests.
|
||||
new_program_uuids = sorted(list(set(program_uuids) - set(existing_program_uuids)))
|
||||
new_program_uuids = sorted(list(set(completed_programs.keys()) - set(existing_program_uuids)))
|
||||
if new_program_uuids:
|
||||
try:
|
||||
credentials_client = get_credentials_api_client(
|
||||
@@ -171,8 +179,9 @@ def award_program_certificates(self, username):
|
||||
|
||||
failed_program_certificate_award_attempts = []
|
||||
for program_uuid in new_program_uuids:
|
||||
visible_date = completed_programs[program_uuid]
|
||||
try:
|
||||
award_program_certificate(credentials_client, username, program_uuid)
|
||||
award_program_certificate(credentials_client, username, program_uuid, visible_date)
|
||||
LOGGER.info('Awarded certificate for program %s to user %s', program_uuid, username)
|
||||
except exceptions.HttpNotFoundError:
|
||||
LOGGER.exception(
|
||||
@@ -237,7 +246,7 @@ def post_course_certificate(client, username, certificate, visible_date):
|
||||
'attributes': [
|
||||
{
|
||||
'name': 'visible_date',
|
||||
'value': visible_date.strftime('%Y-%m-%dT%H:%M:%SZ')
|
||||
'value': visible_date.strftime(VISIBLE_DATE_FORMAT)
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
@@ -89,7 +89,7 @@ class AwardProgramCertificateTestCase(TestCase):
|
||||
'http://test-server/credentials/',
|
||||
)
|
||||
|
||||
tasks.award_program_certificate(test_client, test_username, 123)
|
||||
tasks.award_program_certificate(test_client, test_username, 123, datetime(2010, 5, 30))
|
||||
|
||||
expected_body = {
|
||||
'username': test_username,
|
||||
@@ -97,7 +97,12 @@ class AwardProgramCertificateTestCase(TestCase):
|
||||
'program_uuid': 123,
|
||||
'type': tasks.PROGRAM_CERTIFICATE,
|
||||
},
|
||||
'attributes': []
|
||||
'attributes': [
|
||||
{
|
||||
'name': 'visible_date',
|
||||
'value': '2010-05-30T00:00:00Z',
|
||||
}
|
||||
]
|
||||
}
|
||||
self.assertEqual(json.loads(httpretty.last_request().body), expected_body)
|
||||
|
||||
@@ -154,7 +159,7 @@ class AwardProgramCertificatesTestCase(CatalogIntegrationMixin, CredentialsApiCo
|
||||
Checks that the Credentials API is used to award certificates for
|
||||
the proper programs.
|
||||
"""
|
||||
mock_get_completed_programs.return_value = [1, 2, 3]
|
||||
mock_get_completed_programs.return_value = {1: 1, 2: 2, 3: 3}
|
||||
mock_get_certified_programs.return_value = already_awarded_program_uuids
|
||||
|
||||
tasks.award_program_certificates.delay(self.student.username).get()
|
||||
@@ -162,6 +167,9 @@ class AwardProgramCertificatesTestCase(CatalogIntegrationMixin, CredentialsApiCo
|
||||
actual_program_uuids = [call[0][2] for call in mock_award_program_certificate.call_args_list]
|
||||
self.assertEqual(actual_program_uuids, expected_awarded_program_uuids)
|
||||
|
||||
actual_visible_dates = [call[0][3] for call in mock_award_program_certificate.call_args_list]
|
||||
self.assertEqual(actual_visible_dates, expected_awarded_program_uuids) # program uuids are same as mock dates
|
||||
|
||||
@ddt.data(
|
||||
('credentials', 'enable_learner_issuance'),
|
||||
)
|
||||
@@ -205,7 +213,7 @@ class AwardProgramCertificatesTestCase(CatalogIntegrationMixin, CredentialsApiCo
|
||||
Checks that the task will be aborted without further action if there
|
||||
are no programs for which to award a certificate.
|
||||
"""
|
||||
mock_get_completed_programs.return_value = []
|
||||
mock_get_completed_programs.return_value = {}
|
||||
tasks.award_program_certificates.delay(self.student.username).get()
|
||||
self.assertTrue(mock_get_completed_programs.called)
|
||||
self.assertFalse(mock_get_certified_programs.called)
|
||||
@@ -244,7 +252,7 @@ class AwardProgramCertificatesTestCase(CatalogIntegrationMixin, CredentialsApiCo
|
||||
successfully awarded certs are logged as INFO and warning is logged
|
||||
for failed requests if there are retries available.
|
||||
"""
|
||||
mock_get_completed_programs.return_value = [1, 2]
|
||||
mock_get_completed_programs.return_value = {1: 1, 2: 2}
|
||||
mock_get_certified_programs.side_effect = [[], [2]]
|
||||
mock_award_program_certificate.side_effect = self._make_side_effect([Exception('boom'), None])
|
||||
|
||||
@@ -288,7 +296,7 @@ class AwardProgramCertificatesTestCase(CatalogIntegrationMixin, CredentialsApiCo
|
||||
transient API errors) will cause the task to be failed and queued for
|
||||
retry.
|
||||
"""
|
||||
mock_get_completed_programs.return_value = [1, 2]
|
||||
mock_get_completed_programs.return_value = {1: 1, 2: 2}
|
||||
mock_get_certified_programs.return_value = [1]
|
||||
mock_get_certified_programs.side_effect = self._make_side_effect([Exception('boom'), None])
|
||||
tasks.award_program_certificates.delay(self.student.username).get()
|
||||
@@ -306,7 +314,7 @@ class AwardProgramCertificatesTestCase(CatalogIntegrationMixin, CredentialsApiCo
|
||||
"""
|
||||
exception = exceptions.HttpClientError()
|
||||
exception.response = mock.Mock(status_code=429)
|
||||
mock_get_completed_programs.return_value = [1, 2]
|
||||
mock_get_completed_programs.return_value = {1: 1, 2: 2}
|
||||
mock_award_program_certificate.side_effect = self._make_side_effect(
|
||||
[exception, None]
|
||||
)
|
||||
@@ -326,7 +334,7 @@ class AwardProgramCertificatesTestCase(CatalogIntegrationMixin, CredentialsApiCo
|
||||
"""
|
||||
exception = exceptions.HttpNotFoundError()
|
||||
exception.response = mock.Mock(status_code=404)
|
||||
mock_get_completed_programs.return_value = [1, 2]
|
||||
mock_get_completed_programs.return_value = {1: 1, 2: 2}
|
||||
mock_award_program_certificate.side_effect = self._make_side_effect(
|
||||
[exception, None]
|
||||
)
|
||||
@@ -346,7 +354,7 @@ class AwardProgramCertificatesTestCase(CatalogIntegrationMixin, CredentialsApiCo
|
||||
"""
|
||||
exception = exceptions.HttpClientError()
|
||||
exception.response = mock.Mock(status_code=418)
|
||||
mock_get_completed_programs.return_value = [1, 2]
|
||||
mock_get_completed_programs.return_value = {1: 1, 2: 2}
|
||||
mock_award_program_certificate.side_effect = self._make_side_effect(
|
||||
[exception, None]
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user