Merge pull request #8167 from itsjeyd/tim/TNL-1652
Instructor Dashboard: Add functionality for obtaining list of students who may enroll in a course but haven't signed up yet
This commit is contained in:
@@ -1380,6 +1380,19 @@ class CourseEnrollmentAllowed(models.Model):
|
||||
def __unicode__(self):
|
||||
return "[CourseEnrollmentAllowed] %s: %s (%s)" % (self.email, self.course_id, self.created)
|
||||
|
||||
@classmethod
|
||||
def may_enroll_and_unenrolled(cls, course_id):
|
||||
"""
|
||||
Return QuerySet of students who are allowed to enroll in a course.
|
||||
|
||||
Result excludes students who have already enrolled in the
|
||||
course.
|
||||
|
||||
`course_id` identifies the course for which to compute the QuerySet.
|
||||
"""
|
||||
enrolled = CourseEnrollment.objects.users_enrolled_in(course_id=course_id).values_list('email', flat=True)
|
||||
return CourseEnrollmentAllowed.objects.filter(course_id=course_id).exclude(email__in=enrolled)
|
||||
|
||||
|
||||
@total_ordering
|
||||
class CourseAccessRole(models.Model):
|
||||
|
||||
@@ -96,6 +96,12 @@ REPORTS_DATA = (
|
||||
'instructor_api_endpoint': 'get_enrollment_report',
|
||||
'task_api_endpoint': 'instructor_task.api.submit_detailed_enrollment_features_csv',
|
||||
'extra_instructor_api_kwargs': {}
|
||||
},
|
||||
{
|
||||
'report_type': 'students who may enroll',
|
||||
'instructor_api_endpoint': 'get_students_who_may_enroll',
|
||||
'task_api_endpoint': 'instructor_task.api.submit_calculate_may_enroll_csv',
|
||||
'extra_instructor_api_kwargs': {},
|
||||
}
|
||||
)
|
||||
|
||||
@@ -208,6 +214,7 @@ class TestInstructorAPIDenyLevels(ModuleStoreTestCase, LoginEnrollmentTestCase):
|
||||
('calculate_grades_csv', {}),
|
||||
('get_students_features', {}),
|
||||
('get_enrollment_report', {}),
|
||||
('get_students_who_may_enroll', {}),
|
||||
]
|
||||
# Endpoints that only Instructors can access
|
||||
self.instructor_level_endpoints = [
|
||||
@@ -1977,6 +1984,12 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa
|
||||
for student in self.students:
|
||||
CourseEnrollment.enroll(student, self.course.id)
|
||||
|
||||
self.students_who_may_enroll = self.students + [UserFactory() for _ in range(5)]
|
||||
for student in self.students_who_may_enroll:
|
||||
CourseEnrollmentAllowed.objects.create(
|
||||
email=student.email, course_id=self.course.id
|
||||
)
|
||||
|
||||
def register_with_redemption_code(self, user, code):
|
||||
"""
|
||||
enroll user using a registration code
|
||||
@@ -2271,6 +2284,30 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa
|
||||
|
||||
self.assertEqual('cohort' in res_json['feature_names'], is_cohorted)
|
||||
|
||||
def test_get_students_who_may_enroll(self):
|
||||
"""
|
||||
Test whether get_students_who_may_enroll returns an appropriate
|
||||
status message when users request a CSV file of students who
|
||||
may enroll in a course.
|
||||
"""
|
||||
url = reverse(
|
||||
'get_students_who_may_enroll',
|
||||
kwargs={'course_id': unicode(self.course.id)}
|
||||
)
|
||||
# Successful case:
|
||||
response = self.client.get(url, {})
|
||||
res_json = json.loads(response.content)
|
||||
self.assertIn('status', res_json)
|
||||
self.assertNotIn('already in progress', res_json['status'])
|
||||
# CSV generation already in progress:
|
||||
with patch('instructor_task.api.submit_calculate_may_enroll_csv') as submit_task_function:
|
||||
error = AlreadyRunningError()
|
||||
submit_task_function.side_effect = error
|
||||
response = self.client.get(url, {})
|
||||
res_json = json.loads(response.content)
|
||||
self.assertIn('status', res_json)
|
||||
self.assertIn('already in progress', res_json['status'])
|
||||
|
||||
def test_access_course_finance_admin_with_invalid_course_key(self):
|
||||
"""
|
||||
Test assert require_course fiance_admin before generating
|
||||
|
||||
@@ -1112,6 +1112,36 @@ def get_students_features(request, course_id, csv=False): # pylint: disable=red
|
||||
return JsonResponse({"status": already_running_status})
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_level('staff')
|
||||
def get_students_who_may_enroll(request, course_id):
|
||||
"""
|
||||
Initiate generation of a CSV file containing information about
|
||||
students who may enroll in a course.
|
||||
|
||||
Responds with JSON
|
||||
{"status": "... status message ..."}
|
||||
|
||||
"""
|
||||
course_key = CourseKey.from_string(course_id)
|
||||
query_features = ['email']
|
||||
try:
|
||||
instructor_task.api.submit_calculate_may_enroll_csv(request, course_key, query_features)
|
||||
success_status = _(
|
||||
"Your students who may enroll report is being generated! "
|
||||
"You can view the status of the generation task in the 'Pending Instructor Tasks' section."
|
||||
)
|
||||
return JsonResponse({"status": success_status})
|
||||
except AlreadyRunningError:
|
||||
already_running_status = _(
|
||||
"A students who may enroll report generation task is already in progress. "
|
||||
"Check the 'Pending Instructor Tasks' table for the status of the task. "
|
||||
"When completed, the report will be available for download in the table below."
|
||||
)
|
||||
return JsonResponse({"status": already_running_status})
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_POST
|
||||
|
||||
@@ -21,6 +21,8 @@ urlpatterns = patterns(
|
||||
'instructor.views.api.get_grading_config', name="get_grading_config"),
|
||||
url(r'^get_students_features(?P<csv>/csv)?$',
|
||||
'instructor.views.api.get_students_features', name="get_students_features"),
|
||||
url(r'^get_students_who_may_enroll$',
|
||||
'instructor.views.api.get_students_who_may_enroll', name="get_students_who_may_enroll"),
|
||||
url(r'^get_user_invoice_preference$',
|
||||
'instructor.views.api.get_user_invoice_preference', name="get_user_invoice_preference"),
|
||||
url(r'^get_sale_records(?P<csv>/csv)?$',
|
||||
|
||||
@@ -445,6 +445,9 @@ def _section_data_download(course, access):
|
||||
'access': access,
|
||||
'get_grading_config_url': reverse('get_grading_config', kwargs={'course_id': unicode(course_key)}),
|
||||
'get_students_features_url': reverse('get_students_features', kwargs={'course_id': unicode(course_key)}),
|
||||
'get_students_who_may_enroll_url': reverse(
|
||||
'get_students_who_may_enroll', kwargs={'course_id': unicode(course_key)}
|
||||
),
|
||||
'get_anon_ids_url': reverse('get_anon_ids', kwargs={'course_id': unicode(course_key)}),
|
||||
'list_instructor_tasks_url': reverse('list_instructor_tasks', kwargs={'course_id': unicode(course_key)}),
|
||||
'list_report_downloads_url': reverse('list_report_downloads', kwargs={'course_id': unicode(course_key)}),
|
||||
|
||||
@@ -307,12 +307,6 @@ def instructor_dashboard(request, course_id):
|
||||
#----------------------------------------
|
||||
# enrollment
|
||||
|
||||
elif action == 'List students who may enroll but may not have yet signed up':
|
||||
ceaset = CourseEnrollmentAllowed.objects.filter(course_id=course_key)
|
||||
datatable = {'header': ['StudentEmail']}
|
||||
datatable['data'] = [[x.email] for x in ceaset]
|
||||
datatable['title'] = action
|
||||
|
||||
elif action == 'Enroll multiple students':
|
||||
|
||||
is_shib_course = uses_shib(course)
|
||||
|
||||
@@ -15,6 +15,7 @@ from django.core.urlresolvers import reverse
|
||||
import xmodule.graders as xmgraders
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from microsite_configuration import microsite
|
||||
from student.models import CourseEnrollmentAllowed
|
||||
|
||||
|
||||
STUDENT_FEATURES = ('id', 'username', 'first_name', 'last_name', 'is_staff', 'email')
|
||||
@@ -209,6 +210,31 @@ def enrolled_students_features(course_key, features):
|
||||
return [extract_student(student, features) for student in students]
|
||||
|
||||
|
||||
def list_may_enroll(course_key, features):
|
||||
"""
|
||||
Return info about students who may enroll in a course as a dict.
|
||||
|
||||
list_may_enroll(course_key, ['email'])
|
||||
would return [
|
||||
{'email': 'email1'}
|
||||
{'email': 'email2'}
|
||||
{'email': 'email3'}
|
||||
]
|
||||
|
||||
Note that result does not include students who may enroll and have
|
||||
already done so.
|
||||
"""
|
||||
may_enroll_and_unenrolled = CourseEnrollmentAllowed.may_enroll_and_unenrolled(course_key)
|
||||
|
||||
def extract_student(student, features):
|
||||
"""
|
||||
Build dict containing information about a single student.
|
||||
"""
|
||||
return dict((feature, getattr(student, feature)) for feature in features)
|
||||
|
||||
return [extract_student(student, features) for student in may_enroll_and_unenrolled]
|
||||
|
||||
|
||||
def coupon_codes_features(features, coupons_list):
|
||||
"""
|
||||
Return list of Coupon Codes as dictionaries.
|
||||
|
||||
@@ -3,7 +3,7 @@ Tests for instructor.basic
|
||||
"""
|
||||
|
||||
import json
|
||||
from student.models import CourseEnrollment
|
||||
from student.models import CourseEnrollment, CourseEnrollmentAllowed
|
||||
from django.core.urlresolvers import reverse
|
||||
from mock import patch
|
||||
from student.roles import CourseSalesAdminRole
|
||||
@@ -14,8 +14,9 @@ from shoppingcart.models import (
|
||||
)
|
||||
from course_modes.models import CourseMode
|
||||
from instructor_analytics.basic import (
|
||||
sale_record_features, sale_order_record_features, enrolled_students_features, course_registration_features,
|
||||
coupon_codes_features, AVAILABLE_FEATURES, STUDENT_FEATURES, PROFILE_FEATURES
|
||||
sale_record_features, sale_order_record_features, enrolled_students_features,
|
||||
course_registration_features, coupon_codes_features, list_may_enroll,
|
||||
AVAILABLE_FEATURES, STUDENT_FEATURES, PROFILE_FEATURES
|
||||
)
|
||||
from openedx.core.djangoapps.course_groups.tests.helpers import CohortFactory
|
||||
from courseware.tests.factories import InstructorFactory
|
||||
@@ -43,6 +44,11 @@ class TestAnalyticsBasic(ModuleStoreTestCase):
|
||||
"company": "Open edX Inc {}".format(user.id),
|
||||
})
|
||||
user.profile.save()
|
||||
self.students_who_may_enroll = list(self.users) + [UserFactory() for _ in range(5)]
|
||||
for student in self.students_who_may_enroll:
|
||||
CourseEnrollmentAllowed.objects.create(
|
||||
email=student.email, course_id=self.course_key
|
||||
)
|
||||
|
||||
def test_enrolled_students_features_username(self):
|
||||
self.assertIn('username', AVAILABLE_FEATURES)
|
||||
@@ -113,6 +119,14 @@ class TestAnalyticsBasic(ModuleStoreTestCase):
|
||||
self.assertEqual(len(AVAILABLE_FEATURES), len(STUDENT_FEATURES + PROFILE_FEATURES))
|
||||
self.assertEqual(set(AVAILABLE_FEATURES), set(STUDENT_FEATURES + PROFILE_FEATURES))
|
||||
|
||||
def test_list_may_enroll(self):
|
||||
may_enroll = list_may_enroll(self.course_key, ['email'])
|
||||
self.assertEqual(len(may_enroll), len(self.students_who_may_enroll) - len(self.users))
|
||||
email_adresses = [student.email for student in self.students_who_may_enroll]
|
||||
for student in may_enroll:
|
||||
self.assertEqual(student.keys(), ['email'])
|
||||
self.assertIn(student['email'], email_adresses)
|
||||
|
||||
|
||||
@patch.dict('django.conf.settings.FEATURES', {'ENABLE_PAID_COURSE_REGISTRATION': True})
|
||||
class TestCourseSaleRecordsAnalyticsBasic(ModuleStoreTestCase):
|
||||
|
||||
@@ -22,7 +22,9 @@ from instructor_task.tasks import (
|
||||
calculate_problem_grade_report,
|
||||
calculate_students_features_csv,
|
||||
cohort_students,
|
||||
enrollment_report_features_csv)
|
||||
enrollment_report_features_csv,
|
||||
calculate_may_enroll_csv,
|
||||
)
|
||||
|
||||
from instructor_task.api_helper import (
|
||||
check_arguments_for_rescoring,
|
||||
@@ -375,6 +377,21 @@ def submit_detailed_enrollment_features_csv(request, course_key): # pylint: dis
|
||||
return submit_task(request, task_type, task_class, course_key, task_input, task_key)
|
||||
|
||||
|
||||
def submit_calculate_may_enroll_csv(request, course_key, features):
|
||||
"""
|
||||
Submits a task to generate a CSV file containing information about
|
||||
invited students who have not enrolled in a given course yet.
|
||||
|
||||
Raises AlreadyRunningError if said file is already being updated.
|
||||
"""
|
||||
task_type = 'may_enroll_info_csv'
|
||||
task_class = calculate_may_enroll_csv
|
||||
task_input = {'features': features}
|
||||
task_key = ""
|
||||
|
||||
return submit_task(request, task_type, task_class, course_key, task_input, task_key)
|
||||
|
||||
|
||||
def submit_cohort_students(request, course_key, file_name):
|
||||
"""
|
||||
Request to have students cohorted in bulk.
|
||||
|
||||
@@ -38,7 +38,9 @@ from instructor_task.tasks_helper import (
|
||||
upload_problem_grade_report,
|
||||
upload_students_csv,
|
||||
cohort_students_and_upload,
|
||||
upload_enrollment_report)
|
||||
upload_enrollment_report,
|
||||
upload_may_enroll_csv,
|
||||
)
|
||||
|
||||
|
||||
TASK_LOG = logging.getLogger('edx.celery.task')
|
||||
@@ -197,6 +199,19 @@ def enrollment_report_features_csv(entry_id, xmodule_instance_args):
|
||||
return run_main_task(entry_id, task_fn, action_name)
|
||||
|
||||
|
||||
@task(base=BaseInstructorTask, routing_key=settings.GRADES_DOWNLOAD_ROUTING_KEY) # pylint: disable=not-callable
|
||||
def calculate_may_enroll_csv(entry_id, xmodule_instance_args):
|
||||
"""
|
||||
Compute information about invited students who have not enrolled
|
||||
in a given course yet and upload the CSV to an S3 bucket for
|
||||
download.
|
||||
"""
|
||||
# Translators: This is a past-tense verb that is inserted into task progress messages as {action}.
|
||||
action_name = ugettext_noop('generated')
|
||||
task_fn = partial(upload_may_enroll_csv, xmodule_instance_args)
|
||||
return run_main_task(entry_id, task_fn, action_name)
|
||||
|
||||
|
||||
@task(base=BaseInstructorTask) # pylint: disable=E1102
|
||||
def cohort_students(entry_id, xmodule_instance_args):
|
||||
"""
|
||||
|
||||
@@ -32,7 +32,7 @@ from courseware.grades import iterate_grades_for
|
||||
from courseware.models import StudentModule
|
||||
from courseware.model_data import FieldDataCache
|
||||
from courseware.module_render import get_module_for_descriptor_internal
|
||||
from instructor_analytics.basic import enrolled_students_features
|
||||
from instructor_analytics.basic import enrolled_students_features, list_may_enroll
|
||||
from instructor_analytics.csvs import format_dictlist
|
||||
from instructor_task.models import ReportStore, InstructorTask, PROGRESS
|
||||
from lms.djangoapps.lms_xblock.runtime import LmsPartitionService
|
||||
@@ -991,6 +991,38 @@ def upload_enrollment_report(_xmodule_instance_args, _entry_id, course_id, _task
|
||||
return task_progress.update_task_state(extra_meta=current_step)
|
||||
|
||||
|
||||
def upload_may_enroll_csv(_xmodule_instance_args, _entry_id, course_id, task_input, action_name):
|
||||
"""
|
||||
For a given `course_id`, generate a CSV file containing
|
||||
information about students who may enroll but have not done so
|
||||
yet, and store using a `ReportStore`.
|
||||
"""
|
||||
start_time = time()
|
||||
start_date = datetime.now(UTC)
|
||||
num_reports = 1
|
||||
task_progress = TaskProgress(action_name, num_reports, start_time)
|
||||
current_step = {'step': 'Calculating info about students who may enroll'}
|
||||
task_progress.update_task_state(extra_meta=current_step)
|
||||
|
||||
# Compute result table and format it
|
||||
query_features = task_input.get('features')
|
||||
student_data = list_may_enroll(course_id, query_features)
|
||||
header, rows = format_dictlist(student_data, query_features)
|
||||
|
||||
task_progress.attempted = task_progress.succeeded = len(rows)
|
||||
task_progress.skipped = task_progress.total - task_progress.attempted
|
||||
|
||||
rows.insert(0, header)
|
||||
|
||||
current_step = {'step': 'Uploading CSV'}
|
||||
task_progress.update_task_state(extra_meta=current_step)
|
||||
|
||||
# Perform the upload
|
||||
upload_csv_to_report_store(rows, 'may_enroll_info', course_id, start_date)
|
||||
|
||||
return task_progress.update_task_state(extra_meta=current_step)
|
||||
|
||||
|
||||
def cohort_students_and_upload(_xmodule_instance_args, _entry_id, course_id, task_input, action_name):
|
||||
"""
|
||||
Within a given course, cohort students in bulk, then upload the results
|
||||
|
||||
@@ -16,7 +16,9 @@ from instructor_task.api import (
|
||||
submit_bulk_course_email,
|
||||
submit_calculate_students_features_csv,
|
||||
submit_cohort_students,
|
||||
submit_detailed_enrollment_features_csv)
|
||||
submit_detailed_enrollment_features_csv,
|
||||
submit_calculate_may_enroll_csv,
|
||||
)
|
||||
|
||||
from instructor_task.api_helper import AlreadyRunningError
|
||||
from instructor_task.models import InstructorTask, PROGRESS
|
||||
@@ -212,6 +214,14 @@ class InstructorTaskCourseSubmitTest(TestReportMixin, InstructorTaskCourseTestCa
|
||||
self.course.id)
|
||||
self._test_resubmission(api_call)
|
||||
|
||||
def test_submit_calculate_may_enroll(self):
|
||||
api_call = lambda: submit_calculate_may_enroll_csv(
|
||||
self.create_task_request(self.instructor),
|
||||
self.course.id,
|
||||
features=[]
|
||||
)
|
||||
self._test_resubmission(api_call)
|
||||
|
||||
def test_submit_cohort_students(self):
|
||||
api_call = lambda: submit_cohort_students(
|
||||
self.create_task_request(self.instructor),
|
||||
|
||||
@@ -27,13 +27,20 @@ from openedx.core.djangoapps.user_api.partition_schemes import RandomUserPartiti
|
||||
from shoppingcart.models import Order, PaidCourseRegistration, CourseRegistrationCode, Invoice, \
|
||||
CourseRegistrationCodeInvoiceItem, InvoiceTransaction
|
||||
from student.tests.factories import UserFactory
|
||||
from student.models import CourseEnrollment, ManualEnrollmentAudit, ALLOWEDTOENROLL_TO_ENROLLED
|
||||
from student.models import (
|
||||
CourseEnrollment, CourseEnrollmentAllowed, ManualEnrollmentAudit,
|
||||
ALLOWEDTOENROLL_TO_ENROLLED
|
||||
)
|
||||
from verify_student.tests.factories import SoftwareSecurePhotoVerificationFactory
|
||||
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
|
||||
from xmodule.partitions.partitions import Group, UserPartition
|
||||
from instructor_task.models import ReportStore
|
||||
from instructor_task.tasks_helper import (
|
||||
cohort_students_and_upload, upload_grades_csv, upload_problem_grade_report, upload_students_csv
|
||||
cohort_students_and_upload,
|
||||
upload_grades_csv,
|
||||
upload_problem_grade_report,
|
||||
upload_students_csv,
|
||||
upload_may_enroll_csv,
|
||||
)
|
||||
from openedx.core.djangoapps.util.testing import ContentGroupTestCase, TestConditionalContent
|
||||
|
||||
@@ -753,6 +760,51 @@ class TestStudentReport(TestReportMixin, InstructorTaskCourseTestCase):
|
||||
self.assertDictContainsSubset({'attempted': num_students, 'succeeded': num_students, 'failed': 0}, result)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestListMayEnroll(TestReportMixin, InstructorTaskCourseTestCase):
|
||||
"""
|
||||
Tests that generation of CSV files containing information about
|
||||
students who may enroll in a given course (but have not signed up
|
||||
for it yet) works.
|
||||
"""
|
||||
def _create_enrollment(self, email):
|
||||
"Factory method for creating CourseEnrollmentAllowed objects."
|
||||
return CourseEnrollmentAllowed.objects.create(
|
||||
email=email, course_id=self.course.id
|
||||
)
|
||||
|
||||
def setUp(self):
|
||||
super(TestListMayEnroll, self).setUp()
|
||||
self.course = CourseFactory.create()
|
||||
|
||||
def test_success(self):
|
||||
self._create_enrollment('user@example.com')
|
||||
task_input = {'features': []}
|
||||
with patch('instructor_task.tasks_helper._get_current_task'):
|
||||
result = upload_may_enroll_csv(None, None, self.course.id, task_input, 'calculated')
|
||||
report_store = ReportStore.from_config(config_name='GRADES_DOWNLOAD')
|
||||
links = report_store.links_for(self.course.id)
|
||||
|
||||
self.assertEquals(len(links), 1)
|
||||
self.assertDictContainsSubset({'attempted': 1, 'succeeded': 1, 'failed': 0}, result)
|
||||
|
||||
def test_unicode_email_addresses(self):
|
||||
"""
|
||||
Test handling of unicode characters in email addresses of students
|
||||
who may enroll in a course.
|
||||
"""
|
||||
enrollments = [u'student@example.com', u'ni\xf1o@example.com']
|
||||
for email in enrollments:
|
||||
self._create_enrollment(email)
|
||||
|
||||
task_input = {'features': ['email']}
|
||||
with patch('instructor_task.tasks_helper._get_current_task'):
|
||||
result = upload_may_enroll_csv(None, None, self.course.id, task_input, 'calculated')
|
||||
# This assertion simply confirms that the generation completed with no errors
|
||||
num_enrollments = len(enrollments)
|
||||
self.assertDictContainsSubset({'attempted': num_enrollments, 'succeeded': num_enrollments, 'failed': 0}, result)
|
||||
|
||||
|
||||
class MockDefaultStorage(object):
|
||||
"""Mock django's DefaultStorage"""
|
||||
def __init__(self):
|
||||
|
||||
@@ -20,6 +20,7 @@ class DataDownload
|
||||
# gather elements
|
||||
@$list_studs_btn = @$section.find("input[name='list-profiles']'")
|
||||
@$list_studs_csv_btn = @$section.find("input[name='list-profiles-csv']'")
|
||||
@$list_may_enroll_csv_btn = @$section.find("input[name='list-may-enroll-csv']")
|
||||
@$list_anon_btn = @$section.find("input[name='list-anon-ids']'")
|
||||
@$grade_config_btn = @$section.find("input[name='dump-gradeconf']'")
|
||||
@$calculate_grades_csv_btn = @$section.find("input[name='calculate-grades-csv']'")
|
||||
@@ -96,6 +97,20 @@ class DataDownload
|
||||
grid = new Slick.Grid($table_placeholder, grid_data, columns, options)
|
||||
# grid.autosizeColumns()
|
||||
|
||||
@$list_may_enroll_csv_btn.click (e) =>
|
||||
@clear_display()
|
||||
|
||||
url = @$list_may_enroll_csv_btn.data 'endpoint'
|
||||
$.ajax
|
||||
dataType: 'json'
|
||||
url: url
|
||||
error: (std_ajax_err) =>
|
||||
@$reports_request_response_error.text gettext("Error generating list of students who may enroll. Please try again.")
|
||||
$(".msg-error").css({"display":"block"})
|
||||
success: (data) =>
|
||||
@$reports_request_response.text data['status']
|
||||
$(".msg-confirm").css({"display":"block"})
|
||||
|
||||
@$grade_config_btn.click (e) =>
|
||||
url = @$grade_config_btn.data 'endpoint'
|
||||
# display html from grading config endpoint
|
||||
|
||||
@@ -330,8 +330,14 @@ function goto( mode)
|
||||
</div>
|
||||
% endif
|
||||
|
||||
<input type="submit" name="action" value="List enrolled students" class="${'is-disabled' if disable_buttons else ''}" aria-disabled="${'true' if disable_buttons else 'false'}">
|
||||
<input type="submit" name="action" value="List students who may enroll but may not have yet signed up" class="${'is-disabled' if disable_buttons else ''}" aria-disabled="${'true' if disable_buttons else 'false'}" >
|
||||
<p class="is-deprecated">
|
||||
${_("To download a CSV file containing profile information for students who are enrolled in this course, visit the Data Download section of the Instructor Dashboard.")}
|
||||
</p>
|
||||
|
||||
<p class="is-deprecated">
|
||||
${_("To download a list of students who may enroll in this course but have not yet signed up for it, visit the Data Download section of the Instructor Dashboard.")}
|
||||
</p>
|
||||
|
||||
<hr width="40%" style="align:left">
|
||||
|
||||
%if settings.FEATURES.get('REMOTE_GRADEBOOK_URL','') and instructor_access:
|
||||
|
||||
@@ -31,6 +31,10 @@
|
||||
|
||||
<p><input type="button" name="list-profiles-csv" value="${_("Download profile information as a CSV")}" data-endpoint="${ section_data['get_students_features_url'] }" data-csv="true"></p>
|
||||
|
||||
<p>${_("Click to generate a CSV file that lists learners who can enroll in the course but have not yet done so.")}</p>
|
||||
|
||||
<p><input type="button" name="list-may-enroll-csv" value="${_("Download a CSV of learners who can enroll")}" data-endpoint="${ section_data['get_students_who_may_enroll_url'] }" data-csv="true"></p>
|
||||
|
||||
% if not disable_buttons:
|
||||
<p>${_("For smaller courses, click to list profile information for enrolled students directly on this page:")}</p>
|
||||
<p><input type="button" name="list-profiles" value="${_("List enrolled students' profile information")}" data-endpoint="${ section_data['get_students_features_url'] }"></p>
|
||||
|
||||
Reference in New Issue
Block a user