Add enrollment status check for instructor

To the Student Admin tab of the instructor dashboard, add the ability to
check a specific learner's enrollment status.
This commit is contained in:
John Hensley
2018-02-09 17:26:59 -05:00
committed by Jillian Vogel
parent 7517eb918b
commit efc3057fe1
6 changed files with 189 additions and 6 deletions

View File

@@ -180,6 +180,7 @@ INSTRUCTOR_POST_ENDPOINTS = set([
'get_problem_responses',
'get_proctored_exam_results',
'get_registration_codes',
'get_student_enrollment_status',
'get_student_progress_url',
'get_students_features',
'get_students_who_may_enroll',
@@ -1911,6 +1912,65 @@ class TestInstructorAPIEnrollment(SharedModuleStoreTestCase, LoginEnrollmentTest
self.assertEqual(response.status_code, 200)
return response
def test_get_enrollment_status(self):
"""Check that enrollment states are reported correctly."""
# enrolled, active
url = reverse(
'get_student_enrollment_status',
kwargs={'course_id': self.course.id.to_deprecated_string()},
)
params = {
'unique_student_identifier': 'EnrolledStudent'
}
response = self.client.post(url, params)
self.assertEqual(response.status_code, 200)
res_json = json.loads(response.content)
self.assertEqual(
res_json['enrollment_status'],
'Enrollment status for EnrolledStudent: active'
)
# unenrolled, inactive
CourseEnrollment.unenroll(
self.enrolled_student,
self.course.id
)
response = self.client.post(url, params)
self.assertEqual(response.status_code, 200)
res_json = json.loads(response.content)
self.assertEqual(
res_json['enrollment_status'],
'Enrollment status for EnrolledStudent: inactive'
)
# invited, not yet registered
params = {
'unique_student_identifier': 'robot-allowed@robot.org'
}
response = self.client.post(url, params)
self.assertEqual(response.status_code, 200)
res_json = json.loads(response.content)
self.assertEqual(
res_json['enrollment_status'],
'Enrollment status for robot-allowed@robot.org: pending'
)
# never enrolled or invited
params = {
'unique_student_identifier': 'nonotever@example.com'
}
response = self.client.post(url, params)
self.assertEqual(response.status_code, 200)
res_json = json.loads(response.content)
self.assertEqual(
res_json['enrollment_status'],
'Enrollment status for nonotever@example.com: never enrolled'
)
@attr(shard=5)
@ddt.ddt

View File

@@ -104,6 +104,7 @@ from student.models import (
UNENROLLED_TO_ENROLLED,
UNENROLLED_TO_UNENROLLED,
CourseEnrollment,
CourseEnrollmentAllowed,
EntranceExamConfiguration,
ManualEnrollmentAudit,
Registration,
@@ -1870,6 +1871,63 @@ def get_anon_ids(request, course_id): # pylint: disable=unused-argument
@ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
@require_level('staff')
@require_post_params(
unique_student_identifier="email or username of student for whom to get enrollment status"
)
def get_student_enrollment_status(request, course_id):
"""
Get the enrollment status of a student.
Limited to staff access.
Takes query parameter unique_student_identifier
"""
error = ''
user = None
mode = None
is_active = None
course_id = CourseKey.from_string(course_id)
unique_student_identifier = request.POST.get('unique_student_identifier')
try:
user = get_student_from_identifier(unique_student_identifier)
mode, is_active = CourseEnrollment.enrollment_mode_for_user(user, course_id)
except User.DoesNotExist:
# The student could have been invited to enroll without having
# registered. We'll also look at CourseEnrollmentAllowed
# records, so let the lack of a User slide.
pass
enrollment_status = _('Enrollment status for {student}: unknown').format(student=unique_student_identifier)
if user and mode:
if is_active:
enrollment_status = _('Enrollment status for {student}: active').format(student=user)
else:
enrollment_status = _('Enrollment status for {student}: inactive').format(student=user)
else:
email = user.email if user else unique_student_identifier
allowed = CourseEnrollmentAllowed.may_enroll_and_unenrolled(course_id)
if allowed and email in [cea.email for cea in allowed]:
enrollment_status = _('Enrollment status for {student}: pending').format(student=email)
else:
enrollment_status = _('Enrollment status for {student}: never enrolled').format(student=email)
response_payload = {
'course_id': course_id.to_deprecated_string(),
'error': error,
'enrollment_status': enrollment_status
}
return JsonResponse(response_payload)
@require_POST
@ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
@common_exceptions_400
@require_level('staff')
@require_post_params(
unique_student_identifier="email or username of student for whom to get progress url"
)

View File

@@ -22,6 +22,7 @@ urlpatterns = [
url(r'^get_sale_order_records$', api.get_sale_order_records, name='get_sale_order_records'),
url(r'^sale_validation_url$', api.sale_validation, name='sale_validation'),
url(r'^get_anon_ids$', api.get_anon_ids, name='get_anon_ids'),
url(r'^get_student_enrollment_status$', api.get_student_enrollment_status, name="get_student_enrollment_status"),
url(r'^get_student_progress_url$', api.get_student_progress_url, name='get_student_progress_url'),
url(r'^reset_student_attempts$', api.reset_student_attempts, name='reset_student_attempts'),
url(r'^rescore_problem$', api.rescore_problem, name='rescore_problem'),

View File

@@ -565,6 +565,10 @@ def _section_student_admin(course, access):
'section_display_name': _('Student Admin'),
'access': access,
'is_small_course': is_small_course,
'get_student_enrollment_status_url': reverse(
'get_student_enrollment_status',
kwargs={'course_id': unicode(course_key)}
),
'get_student_progress_url_url': reverse('get_student_progress_url', kwargs={'course_id': unicode(course_key)}),
'enrollment_url': reverse('students_update_enrollment', kwargs={'course_id': unicode(course_key)}),
'reset_student_attempts_url': reverse('reset_student_attempts', kwargs={'course_id': unicode(course_key)}),

View File

@@ -32,6 +32,7 @@
var studentadmin = this;
this.$section = $section;
this.$section.data('wrapper', this);
this.$field_student_select_enrollment_status = findAndAssert(this.$section, "input[name='student-select-enrollment-status']");
this.$field_student_select_progress = findAndAssert(this.$section, "input[name='student-select-progress']");
this.$field_student_select_grade = findAndAssert(this.$section, "input[name='student-select-grade']");
this.$progress_link = findAndAssert(this.$section, 'a.progress-link');
@@ -65,16 +66,51 @@
this.$btn_task_history_all = this.$section.find("input[name='task-history-all']");
this.$table_task_history_all = this.$section.find('.task-history-all-table');
this.instructor_tasks = new (PendingInstructorTasks())(this.$section);
this.$request_err = findAndAssert(this.$section, '.student-specific-container .request-response-error');
this.$request_err_enrollment_status = findAndAssert(this.$section, '.student-enrollment-status-container .request-response-error');
this.$request_err_progress = findAndAssert(this.$section, '.student-progress-container .request-response-error');
this.$request_err_grade = findAndAssert(this.$section, '.student-grade-container .request-response-error');
this.$request_err_ee = this.$section.find('.entrance-exam-grade-container .request-response-error');
this.$request_response_error_all = this.$section.find('.course-specific-container .request-response-error');
this.$enrollment_status_link = findAndAssert(this.$section, 'a.enrollment-status-link');
this.$enrollment_status = findAndAssert(this.$section, '.student-enrollment-status');
this.$enrollment_status_link.click(function(e) {
var errorMessage, fullErrorMessage, uniqStudentIdentifier;
e.preventDefault();
uniqStudentIdentifier = studentadmin.$field_student_select_enrollment_status.val();
if (!uniqStudentIdentifier) {
studentadmin.$enrollment_status.text('');
return studentadmin.$request_err_enrollment_status.text(
gettext('Please enter a student email address or username.')
);
}
errorMessage = gettext("Error getting enrollment status for '<%- student_id %>'. Make sure that the student identifier is spelled correctly."); // eslint-disable-line max-len
fullErrorMessage = _.template(errorMessage)({
student_id: uniqStudentIdentifier
});
studentadmin.$enrollment_status.text(gettext("Retrieving enrollment status..."));
return $.ajax({
type: 'POST',
dataType: 'json',
url: studentadmin.$enrollment_status_link.data('endpoint'),
data: {
course_id: studentadmin.$enrollment_status_link.data('course-id'),
unique_student_identifier: uniqStudentIdentifier
},
success: studentadmin.clear_errors_then(function(data) {
return studentadmin.$enrollment_status.text(data.enrollment_status);
}),
error: statusAjaxError(function() {
studentadmin.$enrollment_status.text('');
return studentadmin.$request_err_enrollment_status.text(fullErrorMessage);
})
});
});
this.$progress_link.click(function(e) {
var errorMessage, fullErrorMessage, uniqStudentIdentifier;
e.preventDefault();
uniqStudentIdentifier = studentadmin.$field_student_select_progress.val();
if (!uniqStudentIdentifier) {
return studentadmin.$request_err.text(
return studentadmin.$request_err_progress.text(
gettext('Please enter a student email address or username.')
);
}
@@ -94,7 +130,7 @@
return window.location;
}),
error: statusAjaxError(function() {
return studentadmin.$request_err.text(fullErrorMessage);
return studentadmin.$request_err_progress.text(fullErrorMessage);
})
});
});
@@ -631,7 +667,8 @@
};
StudentAdmin.prototype.clear_errors_then = function(cb) {
this.$request_err.empty();
this.$request_err_enrollment_status.empty();
this.$request_err_progress.empty();
this.$request_err_grade.empty();
this.$request_err_ee.empty();
this.$request_response_error_all.empty();
@@ -641,7 +678,8 @@
};
StudentAdmin.prototype.clear_errors = function() {
this.$request_err.empty();
this.$request_err_enrollment_status.empty();
this.$request_err_progress.empty();
this.$request_err_grade.empty();
this.$request_err_ee.empty();
return this.$request_response_error_all.empty();

View File

@@ -14,7 +14,29 @@
%endif
</div>
<div class="student-specific-container action-type-container">
<div class="student-enrollment-status-container action-type-container">
<h4 class="hd hd-4">${_("View a specific learner's enrollment status")}</h4>
<div class="request-response-error"></div>
<label for="student-select-enrollment-status">
${_("Learner's {platform_name} email address or username *").format(platform_name=settings.PLATFORM_NAME)}
</label>
<br>
<input type="text" name="student-select-enrollment-status" placeholder="${_('Learner email address or username')}" >
<blockquote class="student-enrollment-status"></blockquote>
<br><br>
<div class="enrollment-status-link-wrapper">
<span name="enrollment-status-link">
<a href="" class="enrollment-status-link" data-endpoint="${ section_data['get_student_enrollment_status_url'] }">
${_("View Enrollment Status")}
</a>
</span>
</div>
<hr>
</div>
<div class="student-progress-container action-type-container">
<h4 class="hd hd-4">${_("View a specific learner's grades and progress")}</h4>
<div class="request-response-error"></div>
<label for="student-select-progress">