Merge remote-tracking branch 'origin/release' into merge-release-into-master
This commit is contained in:
@@ -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.
|
||||
Raises SpecificStudentIdMissingError if student_set is 'specific_student' and specific_student_id is 'None'
|
||||
"""
|
||||
if students:
|
||||
task_type = 'generate_certificates_certain_student'
|
||||
students = [student.id for student in students]
|
||||
task_input = {'students': students}
|
||||
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)
|
||||
|
||||
students = task_input.get('students', None)
|
||||
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
|
||||
)
|
||||
|
||||
if students is not None:
|
||||
enrolled_students = enrolled_students.filter(id__in=students)
|
||||
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
|
||||
)
|
||||
|
||||
task_progress = TaskProgress(action_name, enrolled_students.count(), start_time)
|
||||
# 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))
|
||||
|
||||
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>
|
||||
|
||||
Reference in New Issue
Block a user