Commit 810a5a79 by Adam

Merge pull request #12072 from edx/merge-release-into-master

Merge release into master
parents abba913b 76f0fb0f
......@@ -398,7 +398,7 @@ class CourseMode(models.Model):
@classmethod
def has_verified_mode(cls, course_mode_dict):
"""Check whether the modes for a course allow a student to pursue a verfied certificate.
"""Check whether the modes for a course allow a student to pursue a verified certificate.
Args:
course_mode_dict (dictionary mapping course mode slugs to Modes)
......
......@@ -260,5 +260,10 @@ def generate_certificate_for_user(request):
return HttpResponseBadRequest(msg)
# Attempt to generate certificate
generate_certificates_for_students(request, params["course_key"], students=[params["user"]])
generate_certificates_for_students(
request,
params["course_key"],
student_set="specific_student",
specific_student_id=params["user"].id
)
return HttpResponse(200)
......@@ -217,6 +217,27 @@ class VerifiedUpgradeDeadlineDate(DateSummary):
return ecommerce_service.checkout_page_url(course_mode.sku)
return reverse('verify_student_upgrade_and_verify', args=(self.course.id,))
@property
def is_enabled(self):
"""
Whether or not this summary block should be shown.
By default, the summary is only shown if it has date and the date is in the
future and the user's enrollment is in upsell modes
"""
is_enabled = super(VerifiedUpgradeDeadlineDate, self).is_enabled
if not is_enabled:
return False
enrollment_mode, is_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course.id)
# Return `true` if user is not enrolled in course
if enrollment_mode is None and is_active is None:
return True
# Show the summary if user enrollment is in which allow user to upsell
return is_active and enrollment_mode in CourseMode.UPSELL_TO_VERIFIED_MODES
@lazy
def date(self):
try:
......
......@@ -42,7 +42,9 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase):
days_till_start=1,
days_till_end=14,
days_till_upgrade_deadline=4,
enroll_user=True,
enrollment_mode=CourseMode.VERIFIED,
course_min_price=100,
days_till_verification_deadline=14,
verification_status=None,
sku=None
......@@ -64,11 +66,13 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase):
course_id=self.course.id,
mode_slug=enrollment_mode,
expiration_datetime=now + timedelta(days=days_till_upgrade_deadline),
min_price=course_min_price,
sku=sku
)
if enroll_user:
enrollment_mode = enrollment_mode or CourseMode.DEFAULT_MODE_SLUG
CourseEnrollmentFactory.create(course_id=self.course.id, user=self.user, mode=enrollment_mode)
else:
CourseEnrollmentFactory.create(course_id=self.course.id, user=self.user)
if days_till_verification_deadline is not None:
VerificationDeadline.objects.create(
......@@ -95,21 +99,36 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase):
self.assertEqual(set(type(b) for b in blocks), set(expected_blocks))
@ddt.data(
# Before course starts
({}, (CourseEndDate, CourseStartDate, TodaysDate, VerificationDeadlineDate, VerifiedUpgradeDeadlineDate)),
# After course end
# Verified enrollment with no photo-verification before course start
({}, (CourseEndDate, CourseStartDate, TodaysDate, VerificationDeadlineDate)),
# Verified enrollment with `approved` photo-verification after course end
({'days_till_start': -10,
'days_till_end': -5,
'days_till_upgrade_deadline': -6,
'days_till_verification_deadline': -5,
'verification_status': 'approved'},
(TodaysDate, CourseEndDate)),
# No course end date
# Verified enrollment with `expired` photo-verification during course run
({'days_till_start': -10,
'verification_status': 'expired'},
(TodaysDate, CourseEndDate, VerificationDeadlineDate)),
# Verified enrollment with `approved` photo-verification during course run
({'days_till_start': -10,
'verification_status': 'approved'},
(TodaysDate, CourseEndDate)),
# Audit enrollment and non-upsell course.
({'days_till_start': -10,
'days_till_upgrade_deadline': None,
'days_till_verification_deadline': None,
'course_min_price': 0,
'enrollment_mode': CourseMode.AUDIT},
(TodaysDate, CourseEndDate)),
# Verified enrollment with *NO* course end date
({'days_till_end': None},
(CourseStartDate, TodaysDate, VerificationDeadlineDate, VerifiedUpgradeDeadlineDate)),
# During course run
(CourseStartDate, TodaysDate, VerificationDeadlineDate)),
# Verified enrollment with no photo-verification during course run
({'days_till_start': -1},
(TodaysDate, CourseEndDate, VerificationDeadlineDate, VerifiedUpgradeDeadlineDate)),
(TodaysDate, CourseEndDate, VerificationDeadlineDate)),
# Verification approved
({'days_till_start': -10,
'days_till_upgrade_deadline': -1,
......@@ -117,13 +136,26 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase):
'verification_status': 'approved'},
(TodaysDate, CourseEndDate)),
# After upgrade deadline
({'days_till_start': -10, 'days_till_upgrade_deadline': -1},
({'days_till_start': -10,
'days_till_upgrade_deadline': -1},
(TodaysDate, CourseEndDate, VerificationDeadlineDate)),
# After verification deadline
({'days_till_start': -10,
'days_till_upgrade_deadline': -2,
'days_till_verification_deadline': -1},
(TodaysDate, CourseEndDate, VerificationDeadlineDate))
(TodaysDate, CourseEndDate, VerificationDeadlineDate)),
# Un-enrolled user before course start
({'enroll_user': False},
(CourseStartDate, TodaysDate, CourseEndDate, VerifiedUpgradeDeadlineDate)),
# Un-enrolled user during course run
({'days_till_start': -1,
'enroll_user': False},
(TodaysDate, CourseEndDate, VerifiedUpgradeDeadlineDate)),
# Un-enrolled user after course end.
({'enroll_user': False,
'days_till_start': -10,
'days_till_end': -5},
(TodaysDate, CourseEndDate, VerifiedUpgradeDeadlineDate)),
)
@ddt.unpack
def test_enabled_block_types(self, course_options, expected_blocks):
......
......@@ -720,7 +720,6 @@ class GenerateCertificatesInstructorApiTest(SharedModuleStoreTestCase):
response = self.client.post(
url,
data=json.dumps([self.certificate_exception]),
content_type='application/json'
)
# Assert Success
......@@ -736,24 +735,49 @@ class GenerateCertificatesInstructorApiTest(SharedModuleStoreTestCase):
u"Certificate generation started for white listed students."
)
def test_generate_certificate_exceptions_invalid_user_list_error(self):
def test_generate_certificate_exceptions_whitelist_not_generated(self):
"""
Test generate certificates exceptions api endpoint returns error
when called with certificate exceptions with empty 'user_id' field
Test generate certificates exceptions api endpoint returns success
when calling with new certificate exception.
"""
url = reverse(
'generate_certificate_exceptions',
kwargs={'course_id': unicode(self.course.id), 'generate_for': 'new'}
)
# assign empty user_id
self.certificate_exception.update({'user_id': ''})
response = self.client.post(
url,
content_type='application/json'
)
# Assert Success
self.assertEqual(response.status_code, 200)
res_json = json.loads(response.content)
# Assert Request is successful
self.assertTrue(res_json['success'])
# Assert Message
self.assertEqual(
res_json['message'],
u"Certificate generation started for white listed students."
)
def test_generate_certificate_exceptions_generate_for_incorrect_value(self):
"""
Test generate certificates exceptions api endpoint returns error
when calling with generate_for without 'new' or 'all' value.
"""
url = reverse(
'generate_certificate_exceptions',
kwargs={'course_id': unicode(self.course.id), 'generate_for': ''}
)
response = self.client.post(
url,
data=json.dumps([self.certificate_exception]),
content_type='application/json'
)
# Assert Failure
self.assertEqual(response.status_code, 400)
......@@ -764,7 +788,7 @@ class GenerateCertificatesInstructorApiTest(SharedModuleStoreTestCase):
# Assert Message
self.assertEqual(
res_json['message'],
u"Invalid data, user_id must be present for all certificate exceptions."
u'Invalid data, generate_for must be "new" or "all".'
)
......
......@@ -3032,41 +3032,24 @@ def generate_certificate_exceptions(request, course_id, generate_for=None):
"""
course_key = CourseKey.from_string(course_id)
try:
certificate_white_list = json.loads(request.body)
except ValueError:
return JsonResponse({
'success': False,
'message': _('Invalid Json data, Please refresh the page and then try again.')
}, status=400)
users = [exception.get('user_id', False) for exception in certificate_white_list]
if generate_for == 'all':
# Generate Certificates for all white listed students
students = User.objects.filter(
certificatewhitelist__course_id=course_key,
certificatewhitelist__whitelist=True
)
elif not all(users):
# Invalid data, user_id must be present for all certificate exceptions
students = 'all_whitelisted'
elif generate_for == 'new':
students = 'whitelisted_not_generated'
else:
# Invalid data, generate_for must be present for all certificate exceptions
return JsonResponse(
{
'success': False,
'message': _('Invalid data, user_id must be present for all certificate exceptions.'),
'message': _('Invalid data, generate_for must be "new" or "all".'),
},
status=400
)
else:
students = User.objects.filter(
id__in=users,
certificatewhitelist__course_id=course_key,
certificatewhitelist__whitelist=True
)
if students:
# generate certificates for students if 'students' list is not empty
instructor_task.api.generate_certificates_for_students(request, course_key, students=students)
instructor_task.api.generate_certificates_for_students(request, course_key, student_set=students)
response_payload = {
'success': True,
......@@ -3275,8 +3258,10 @@ def re_validate_certificate(request, course_key, generated_certificate):
certificate_invalidation.deactivate()
# We need to generate certificate only for a single student here
students = [certificate_invalidation.generated_certificate.user]
instructor_task.api.generate_certificates_for_students(request, course_key, students=students)
student = certificate_invalidation.generated_certificate.user
instructor_task.api.generate_certificates_for_students(
request, course_key, student_set="specific_student", specific_student_id=student.id
)
def validate_request_data_and_get_certificate(certificate_invalidation, course_key):
......
......@@ -45,6 +45,13 @@ from bulk_email.models import CourseEmail
from util import milestones_helpers
class SpecificStudentIdMissingError(Exception):
"""
Exception indicating that a student id was not provided when generating a certificate for a specific student.
"""
pass
def get_running_instructor_tasks(course_id):
"""
Returns a query of InstructorTask objects of running tasks for a given course.
......@@ -437,17 +444,34 @@ def submit_export_ora2_data(request, course_key):
return submit_task(request, task_type, task_class, course_key, task_input, task_key)
def generate_certificates_for_students(request, course_key, students=None): # pylint: disable=invalid-name
def generate_certificates_for_students(request, course_key, student_set=None, specific_student_id=None): # pylint: disable=invalid-name
"""
Submits a task to generate certificates for given students enrolled in the course or
all students if argument 'students' is None
Submits a task to generate certificates for given students enrolled in the course.
Arguments:
course_key : Course Key
student_set : Semantic for student collection for certificate generation.
Options are:
'all_whitelisted': All Whitelisted students.
'whitelisted_not_generated': Whitelisted students which does not got certificates yet.
'specific_student': Single student for certificate generation.
specific_student_id : Student ID when student_set is 'specific_student'
Raises AlreadyRunningError if certificates are currently being generated.
"""
if students:
task_type = 'generate_certificates_certain_student'
students = [student.id for student in students]
task_input = {'students': students}
Raises SpecificStudentIdMissingError if student_set is 'specific_student' and specific_student_id is 'None'
"""
if student_set:
task_type = 'generate_certificates_student_set'
task_input = {'student_set': student_set}
if student_set == 'specific_student':
task_type = 'generate_certificates_certain_student'
if specific_student_id is None:
raise SpecificStudentIdMissingError(
"Attempted to generate certificate for a single student, "
"but no specific student id provided"
)
task_input.update({'specific_student_id': specific_student_id})
else:
task_type = 'generate_certificates_all_student'
task_input = {}
......@@ -466,22 +490,16 @@ def generate_certificates_for_students(request, course_key, students=None): # p
return instructor_task
def regenerate_certificates(request, course_key, statuses_to_regenerate, students=None):
def regenerate_certificates(request, course_key, statuses_to_regenerate):
"""
Submits a task to regenerate certificates for given students enrolled in the course or
all students if argument 'students' is None.
Submits a task to regenerate certificates for given students enrolled in the course.
Regenerate Certificate only if the status of the existing generated certificate is in 'statuses_to_regenerate'
list passed in the arguments.
Raises AlreadyRunningError if certificates are currently being generated.
"""
if students:
task_type = 'regenerate_certificates_certain_student'
students = [student.id for student in students]
task_input = {'students': students}
else:
task_type = 'regenerate_certificates_all_student'
task_input = {}
task_type = 'regenerate_certificates_all_student'
task_input = {}
task_input.update({"statuses_to_regenerate": statuses_to_regenerate})
task_class = generate_certificates
......
......@@ -1409,30 +1409,56 @@ def generate_students_certificates(
json column, otherwise generate certificates for all enrolled students.
"""
start_time = time()
enrolled_students = CourseEnrollment.objects.users_enrolled_in(course_id)
students_to_generate_certs_for = CourseEnrollment.objects.users_enrolled_in(course_id)
student_set = task_input.get('student_set')
if student_set == 'all_whitelisted':
# Generate Certificates for all white listed students.
students_to_generate_certs_for = students_to_generate_certs_for.filter(
certificatewhitelist__course_id=course_id,
certificatewhitelist__whitelist=True
)
students = task_input.get('students', None)
elif student_set == 'whitelisted_not_generated':
# All Whitelisted students
students_to_generate_certs_for = students_to_generate_certs_for.filter(
certificatewhitelist__course_id=course_id,
certificatewhitelist__whitelist=True
)
if students is not None:
enrolled_students = enrolled_students.filter(id__in=students)
# Whitelisted students which got certificates already.
certificate_generated_students = GeneratedCertificate.objects.filter( # pylint: disable=no-member
course_id=course_id,
)
certificate_generated_students_ids = set(certificate_generated_students.values_list('user_id', flat=True))
task_progress = TaskProgress(action_name, enrolled_students.count(), start_time)
students_to_generate_certs_for = students_to_generate_certs_for.exclude(
id__in=certificate_generated_students_ids
)
elif student_set == "specific_student":
specific_student_id = task_input.get('specific_student_id')
students_to_generate_certs_for = students_to_generate_certs_for.filter(id=specific_student_id)
task_progress = TaskProgress(action_name, students_to_generate_certs_for.count(), start_time)
current_step = {'step': 'Calculating students already have certificates'}
task_progress.update_task_state(extra_meta=current_step)
statuses_to_regenerate = task_input.get('statuses_to_regenerate', [])
if students is not None and not statuses_to_regenerate:
if student_set is not None and not statuses_to_regenerate:
# We want to skip 'filtering students' only when students are given and statuses to regenerate are not
students_require_certs = enrolled_students
students_require_certs = students_to_generate_certs_for
else:
students_require_certs = students_require_certificate(course_id, enrolled_students, statuses_to_regenerate)
students_require_certs = students_require_certificate(
course_id, students_to_generate_certs_for, statuses_to_regenerate
)
if statuses_to_regenerate:
# Mark existing generated certificates as 'unavailable' before regenerating
# We need to call this method after "students_require_certificate" otherwise "students_require_certificate"
# would return no results.
invalidate_generated_certificates(course_id, enrolled_students, statuses_to_regenerate)
invalidate_generated_certificates(course_id, students_to_generate_certs_for, statuses_to_regenerate)
task_progress.skipped = task_progress.total - len(students_require_certs)
......
......@@ -24,6 +24,7 @@ from instructor_task.api import (
generate_certificates_for_students,
regenerate_certificates,
submit_export_ora2_data,
SpecificStudentIdMissingError,
)
from instructor_task.api_helper import AlreadyRunningError
......@@ -295,6 +296,18 @@ class InstructorTaskCourseSubmitTest(TestReportMixin, InstructorTaskCourseTestCa
)
self._test_resubmission(api_call)
def test_certificate_generation_no_specific_student_id(self):
"""
Raises ValueError when student_set is 'specific_student' and 'specific_student_id' is None.
"""
with self.assertRaises(SpecificStudentIdMissingError):
generate_certificates_for_students(
self.create_task_request(self.instructor),
self.course.id,
student_set='specific_student',
specific_student_id=None
)
def test_certificate_generation_history(self):
"""
Tests that a new record is added whenever certificate generation/regeneration task is submitted.
......
......@@ -1628,8 +1628,7 @@ class TestCertificateGeneration(InstructorTaskModuleTestCase):
Verify that certificates generated for all eligible students enrolled in a course.
"""
# create 10 students
students = [self.create_student(username='student_{}'.format(i), email='student_{}@example.com'.format(i))
for i in xrange(1, 11)]
students = self._create_students(10)
# mark 2 students to have certificates generated already
for student in students[:2]:
......@@ -1644,40 +1643,157 @@ class TestCertificateGeneration(InstructorTaskModuleTestCase):
for student in students[2:7]:
CertificateWhitelistFactory.create(user=student, course_id=self.course.id, whitelist=True)
current_task = Mock()
current_task.update_state = Mock()
instructor_task = Mock()
instructor_task.task_input = json.dumps({'students': None})
task_input = {'student_set': None}
expected_results = {
'action_name': 'certificates generated',
'total': 10,
'attempted': 8,
'succeeded': 5,
'failed': 3,
'skipped': 2
}
with self.assertNumQueries(214):
with patch('instructor_task.tasks_helper._get_current_task') as mock_current_task:
mock_current_task.return_value = current_task
with patch('capa.xqueue_interface.XQueueInterface.send_to_queue') as mock_queue:
mock_queue.return_value = (0, "Successfully queued")
with patch('instructor_task.models.InstructorTask.objects.get') as instructor_task_object:
instructor_task_object.return_value = instructor_task
result = generate_students_certificates(
None, None, self.course.id, {}, 'certificates generated'
)
self.assertDictContainsSubset(
{
'action_name': 'certificates generated',
'total': 10,
'attempted': 8,
'succeeded': 5,
'failed': 3,
'skipped': 2
},
result
self.assertCertificatesGenerated(task_input, expected_results)
def test_certificate_generation_all_whitelisted(self):
"""
Verify that certificates generated for all white-listed students when using semantic task_input as
`all_whitelisted`.
"""
# create 5 students
students = self._create_students(5)
# white-list 5 students
for student in students:
CertificateWhitelistFactory.create(user=student, course_id=self.course.id, whitelist=True)
task_input = {'student_set': 'all_whitelisted'}
expected_results = {
'action_name': 'certificates generated',
'total': 5,
'attempted': 5,
'succeeded': 5,
'failed': 0,
'skipped': 0
}
self.assertCertificatesGenerated(task_input, expected_results)
def test_certificate_generation_whitelist_already_generated(self):
"""
Verify that certificates generated for all white-listed students having certifcates already when using
semantic task_input as `all_whitelisted`.
"""
# create 5 students
students = self._create_students(5)
# white-list 5 students
for student in students:
CertificateWhitelistFactory.create(user=student, course_id=self.course.id, whitelist=True)
# mark 5 students to have certificates generated already
for student in students:
GeneratedCertificateFactory.create(
user=student,
course_id=self.course.id,
status=CertificateStatuses.downloadable,
mode='honor'
)
task_input = {'student_set': 'all_whitelisted'}
expected_results = {
'action_name': 'certificates generated',
'total': 5,
'attempted': 5,
'succeeded': 5,
'failed': 0,
'skipped': 0
}
self.assertCertificatesGenerated(task_input, expected_results)
def test_certificate_generation_whitelisted_not_generated(self):
"""
Verify that certificates only generated for those students which does not have certificates yet when
using semantic task_input as `whitelisted_not_generated`.
"""
# create 5 students
students = self._create_students(5)
# mark 2 students to have certificates generated already
for student in students[:2]:
GeneratedCertificateFactory.create(
user=student,
course_id=self.course.id,
status=CertificateStatuses.downloadable,
mode='honor'
)
# white-list 5 students
for student in students:
CertificateWhitelistFactory.create(user=student, course_id=self.course.id, whitelist=True)
task_input = {'student_set': 'whitelisted_not_generated'}
expected_results = {
'action_name': 'certificates generated',
'total': 3,
'attempted': 3,
'succeeded': 3,
'failed': 0,
'skipped': 0
}
self.assertCertificatesGenerated(
task_input,
expected_results
)
def test_certificate_generation_specific_student(self):
"""
Tests generating a certificate for a specific student.
"""
student = self.create_student(username="Hamnet", email="ham@ardenforest.co.uk")
CertificateWhitelistFactory.create(user=student, course_id=self.course.id, whitelist=True)
task_input = {
'student_set': 'specific_student',
'specific_student_id': student.id
}
expected_results = {
'action_name': 'certificates generated',
'total': 1,
'attempted': 1,
'succeeded': 1,
'failed': 0,
'skipped': 0,
}
self.assertCertificatesGenerated(task_input, expected_results)
def test_specific_student_not_enrolled(self):
"""
Tests generating a certificate for a specific student if that student
is not enrolled in the course.
"""
student = self.create_student(username="jacques", email="antlers@ardenforest.co.uk")
task_input = {
'student_set': 'specific_student',
'specific_student_id': student.id
}
expected_results = {
'action_name': 'certificates generated',
'total': 1,
'attempted': 1,
'succeeded': 0,
'failed': 1,
'skipped': 0,
}
self.assertCertificatesGenerated(task_input, expected_results)
def test_certificate_regeneration_for_statuses_to_regenerate(self):
"""
Verify that certificates are regenerated for all eligible students enrolled in a course whose generated
certificate statuses lies in the list 'statuses_to_regenerate' given in task_input.
"""
# create 10 students
students = [self.create_student(username='student_{}'.format(i), email='student_{}@example.com'.format(i))
for i in xrange(1, 11)]
students = self._create_students(10)
# mark 2 students to have certificates generated already
for student in students[:2]:
......@@ -1710,31 +1826,22 @@ class TestCertificateGeneration(InstructorTaskModuleTestCase):
for student in students[:7]:
CertificateWhitelistFactory.create(user=student, course_id=self.course.id, whitelist=True)
current_task = Mock()
current_task.update_state = Mock()
# Certificates should be regenerated for students having generated certificates with status
# 'downloadable' or 'error' which are total of 5 students in this test case
task_input = {'statuses_to_regenerate': [CertificateStatuses.downloadable, CertificateStatuses.error]}
with patch('instructor_task.tasks_helper._get_current_task') as mock_current_task:
mock_current_task.return_value = current_task
with patch('capa.xqueue_interface.XQueueInterface.send_to_queue') as mock_queue:
mock_queue.return_value = (0, "Successfully queued")
result = generate_students_certificates(
None, None, self.course.id, task_input, 'certificates generated'
)
expected_results = {
'action_name': 'certificates generated',
'total': 10,
'attempted': 5,
'succeeded': 5,
'failed': 0,
'skipped': 5
}
self.assertDictContainsSubset(
{
'action_name': 'certificates generated',
'total': 10,
'attempted': 5,
'succeeded': 5,
'failed': 0,
'skipped': 5
},
result
self.assertCertificatesGenerated(
task_input,
expected_results
)
def test_certificate_regeneration_with_expected_failures(self):
......@@ -1746,8 +1853,7 @@ class TestCertificateGeneration(InstructorTaskModuleTestCase):
default_grade = '-1'
# create 10 students
students = [self.create_student(username='student_{}'.format(i), email='student_{}@example.com'.format(i))
for i in xrange(1, 11)]
students = self._create_students(10)
# mark 2 students to have certificates generated already
for student in students[:2]:
......@@ -1796,32 +1902,21 @@ class TestCertificateGeneration(InstructorTaskModuleTestCase):
for student in students[:7]:
CertificateWhitelistFactory.create(user=student, course_id=self.course.id, whitelist=True)
current_task = Mock()
current_task.update_state = Mock()
# Regenerated certificates for students having generated certificates with status
# 'deleted' or 'generating'
task_input = {'statuses_to_regenerate': [CertificateStatuses.deleted, CertificateStatuses.generating]}
with patch('instructor_task.tasks_helper._get_current_task') as mock_current_task:
mock_current_task.return_value = current_task
with patch('capa.xqueue_interface.XQueueInterface.send_to_queue') as mock_queue:
mock_queue.return_value = (0, "Successfully queued")
result = generate_students_certificates(
None, None, self.course.id, task_input, 'certificates generated'
)
expected_results = {
'action_name': 'certificates generated',
'total': 10,
'attempted': 5,
'succeeded': 2,
'failed': 3,
'skipped': 5
}
self.assertCertificatesGenerated(task_input, expected_results)
self.assertDictContainsSubset(
{
'action_name': 'certificates generated',
'total': 10,
'attempted': 5,
'succeeded': 2,
'failed': 3,
'skipped': 5
},
result
)
generated_certificates = GeneratedCertificate.eligible_certificates.filter(
user__in=students,
course_id=self.course.id,
......@@ -1852,8 +1947,7 @@ class TestCertificateGeneration(InstructorTaskModuleTestCase):
default_grade = '-1'
# create 10 students
students = [self.create_student(username='student_{}'.format(i), email='student_{}@example.com'.format(i))
for i in xrange(1, 11)]
students = self._create_students(10)
# mark 2 students to have certificates generated already
for student in students[:2]:
......@@ -1899,9 +1993,6 @@ class TestCertificateGeneration(InstructorTaskModuleTestCase):
for student in students[:]:
CertificateWhitelistFactory.create(user=student, course_id=self.course.id, whitelist=True)
current_task = Mock()
current_task.update_state = Mock()
# Regenerated certificates for students having generated certificates with status
# 'downloadable', 'error' or 'generating'
task_input = {
......@@ -1912,24 +2003,18 @@ class TestCertificateGeneration(InstructorTaskModuleTestCase):
]
}
with patch('instructor_task.tasks_helper._get_current_task') as mock_current_task:
mock_current_task.return_value = current_task
with patch('capa.xqueue_interface.XQueueInterface.send_to_queue') as mock_queue:
mock_queue.return_value = (0, "Successfully queued")
result = generate_students_certificates(
None, None, self.course.id, task_input, 'certificates generated'
)
expected_results = {
'action_name': 'certificates generated',
'total': 10,
'attempted': 8,
'succeeded': 8,
'failed': 0,
'skipped': 2
}
self.assertDictContainsSubset(
{
'action_name': 'certificates generated',
'total': 10,
'attempted': 8,
'succeeded': 8,
'failed': 0,
'skipped': 2
},
result
self.assertCertificatesGenerated(
task_input,
expected_results
)
generated_certificates = GeneratedCertificate.eligible_certificates.filter(
......@@ -1963,8 +2048,7 @@ class TestCertificateGeneration(InstructorTaskModuleTestCase):
Verify that certificates are regenerated for all students passed in task_input.
"""
# create 10 students
students = [self.create_student(username='student_{}'.format(i), email='student_{}@example.com'.format(i))
for i in xrange(1, 11)]
students = self._create_students(10)
# mark 2 students to have certificates generated already
for student in students[:2]:
......@@ -2006,12 +2090,27 @@ class TestCertificateGeneration(InstructorTaskModuleTestCase):
for student in students[:7]:
CertificateWhitelistFactory.create(user=student, course_id=self.course.id, whitelist=True)
current_task = Mock()
current_task.update_state = Mock()
# Certificates should be regenerated for students having generated certificates with status
# 'downloadable' or 'error' which are total of 5 students in this test case
task_input = {'students': [student.id for student in students]}
task_input = {'student_set': "all_whitelisted"}
expected_results = {
'action_name': 'certificates generated',
'total': 7,
'attempted': 7,
'succeeded': 7,
'failed': 0,
'skipped': 0,
}
self.assertCertificatesGenerated(task_input, expected_results)
def assertCertificatesGenerated(self, task_input, expected_results):
"""
Generate certificates for the given task_input and compare with expected_results.
"""
current_task = Mock()
current_task.update_state = Mock()
with patch('instructor_task.tasks_helper._get_current_task') as mock_current_task:
mock_current_task.return_value = current_task
......@@ -2022,17 +2121,22 @@ class TestCertificateGeneration(InstructorTaskModuleTestCase):
)
self.assertDictContainsSubset(
{
'action_name': 'certificates generated',
'total': 10,
'attempted': 10,
'succeeded': 7,
'failed': 3,
'skipped': 0,
},
expected_results,
result
)
def _create_students(self, number_of_students):
"""
Create Students for course.
"""
return [
self.create_student(
username='student_{}'.format(index),
email='student_{}@example.com'.format(index)
)
for index in xrange(number_of_students)
]
class TestInstructorOra2Report(SharedModuleStoreTestCase):
"""
......
......@@ -2,7 +2,8 @@
<p class="under-heading">
<label>
<input type='radio' name='generate-exception-certificates-radio' checked="checked" value='new' aria-describedby='generate-exception-certificates-radio-new-tip'>
<span id='generate-exception-certificates-radio-new-tip'><%- gettext('Generate a Certificate for all ') %><strong><%- gettext('New') %></strong> <%- gettext('additions to the Exception list') %></span>
<span id='generate-exception-certificates-radio-new-tip'><%- gettext('Generate certificates for all users on the Exception list for whom certificates have not yet been run') %></span>
</label>
<br/>
<label>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment