From d7c9491f233f3fcc1f7db514e2478f6e070770ed Mon Sep 17 00:00:00 2001 From: Adam Palay Date: Fri, 7 Mar 2014 14:37:16 -0500 Subject: [PATCH] Bulk beta tester add/remove on instructor dashboard LMS-1287 --- lms/djangoapps/instructor/views/api.py | 48 +++++++++ lms/djangoapps/instructor/views/api_urls.py | 2 + .../instructor/views/instructor_dashboard.py | 1 + .../instructor_dashboard/membership.coffee | 99 +++++++++++++------ .../sass/course/instructor/_instructor_2.scss | 2 +- .../instructor_dashboard_2/membership.html | 35 ++++++- 6 files changed, 154 insertions(+), 33 deletions(-) diff --git a/lms/djangoapps/instructor/views/api.py b/lms/djangoapps/instructor/views/api.py index b02396429a..f6ac3fe1ca 100644 --- a/lms/djangoapps/instructor/views/api.py +++ b/lms/djangoapps/instructor/views/api.py @@ -278,6 +278,54 @@ def students_update_enrollment(request, course_id): return JsonResponse(response_payload) +@ensure_csrf_cookie +@cache_control(no_cache=True, no_store=True, must_revalidate=True) +@require_level('instructor') +@common_exceptions_400 +@require_query_params( + emails="stringified list of emails", + action="add or remove", +) +def bulk_modify_access(request, course_id): + action = request.GET.get('action') + emails_raw = request.GET.get('emails') + emails = _split_input_list(emails_raw) + results = [] + rolename = 'beta' + course = get_course_by_id(course_id) + for email in emails: + try: + user = User.objects.get(email=email) + if action == 'add': + allow_access(course, user, rolename) + elif action == 'remove': + revoke_access(course, user, rolename) + else: + return HttpResponseBadRequest(strip_tags( + "Unrecognized action '{}'".format(action) + )) + results.append({ + 'email': email, + 'error': False, + }) + + # catch and log any exceptions + # so that one error doesn't cause a 500. + except Exception as exc: # pylint: disable=W0703 + log.exception("Error while #{}ing student") + log.exception(exc) + results.append({ + 'email': email, + 'error': True, + }) + + response_payload = { + 'action': action, + 'results': results, + } + return JsonResponse(response_payload) + + @ensure_csrf_cookie @cache_control(no_cache=True, no_store=True, must_revalidate=True) @require_level('instructor') diff --git a/lms/djangoapps/instructor/views/api_urls.py b/lms/djangoapps/instructor/views/api_urls.py index 4ab668fb24..e2fbeba3c8 100644 --- a/lms/djangoapps/instructor/views/api_urls.py +++ b/lms/djangoapps/instructor/views/api_urls.py @@ -11,6 +11,8 @@ urlpatterns = patterns('', # nopep8 'instructor.views.api.list_course_role_members', name="list_course_role_members"), url(r'^modify_access$', 'instructor.views.api.modify_access', name="modify_access"), + url(r'^bulk_modify_access$', + 'instructor.views.api.bulk_modify_access', name="bulk_modify_access"), url(r'^get_grading_config$', 'instructor.views.api.get_grading_config', name="get_grading_config"), url(r'^get_students_features(?P/csv)?$', diff --git a/lms/djangoapps/instructor/views/instructor_dashboard.py b/lms/djangoapps/instructor/views/instructor_dashboard.py index 56ec3f7325..ce532c1e94 100644 --- a/lms/djangoapps/instructor/views/instructor_dashboard.py +++ b/lms/djangoapps/instructor/views/instructor_dashboard.py @@ -146,6 +146,7 @@ def _section_membership(course_id, access): 'access': access, 'enroll_button_url': reverse('students_update_enrollment', kwargs={'course_id': course_id}), 'unenroll_button_url': reverse('students_update_enrollment', kwargs={'course_id': course_id}), + 'modify_beta_testers_button_url': reverse('bulk_modify_access', kwargs={'course_id': course_id}), 'list_course_role_members_url': reverse('list_course_role_members', kwargs={'course_id': course_id}), 'modify_access_url': reverse('modify_access', kwargs={'course_id': course_id}), 'list_forum_members_url': reverse('list_forum_members', kwargs={'course_id': course_id}), diff --git a/lms/static/coffee/src/instructor_dashboard/membership.coffee b/lms/static/coffee/src/instructor_dashboard/membership.coffee index 64d860e474..849ecd92c7 100644 --- a/lms/static/coffee/src/instructor_dashboard/membership.coffee +++ b/lms/static/coffee/src/instructor_dashboard/membership.coffee @@ -160,50 +160,90 @@ class AuthListWidget extends MemberListWidget error: std_ajax_err => cb? gettext "Error changing user's permissions." +class BetaTesterBulkAddition + constructor: (@$container) -> + # gather elements + @$emails_input = @$container.find("textarea[name='student-emails-for-beta']") + @$btn_beta_testers = @$container.find("input[name='beta-testers']") + # @$checkbox_emailstudents = @$container.find("input[name='email-students']") + @$task_response = @$container.find(".request-response") + @$request_response_error = @$container.find(".request-response-error") + + # click handlers + @$btn_beta_testers.click => + # emailStudents = @$checkbox_emailstudents.is(':checked') + send_data = + action: $(event.target).data('action') + emails: @$emails_input.val() + email_students: emailStudents + + $.ajax + dataType: 'json' + url: @$btn_beta_testers.data 'endpoint' + data: send_data + success: (data) => @display_response data + # error: std_ajax_err => @fail_with_error "Error enrolling/unenrolling students." + + # success: (data) => @display_response data + + # fail_with_error: (msg) -> + # console.warn msg + # @$task_response.empty() + # @$request_response_error.empty() + # @$request_response_error.text msg + + display_response: (data_from_server) -> + @$task_response.empty() + # @$request_response_error.empty() + errors = [] + sucesses = [] + for student_results in data_from_server.results + if student_results.error + errors.push student_results + else + sucesses.push student_results + + console.log(sr.email for sr in sucesses) + + render_list = (label, emails) => + task_res_section = $ '
', class: 'request-res-section' + task_res_section.append $ '

', text: label + email_list = $ '
    ' + task_res_section.append email_list + + for email in emails + email_list.append $ '
  • ', text: email + + @$task_response.append task_res_section + + render_list gettext("these students were added as beta testers"), (sr.email for sr in sucesses) + render_list gettext("these students were not added as beta testers"), (sr.email for sr in errors) + # Wrapper for the batch enrollment subsection. # This object handles buttons, success and failure reporting, # and server communication. class BatchEnrollment constructor: (@$container) -> # gather elements - @$emails_input = @$container.find("textarea[name='student-emails']'") - @$btn_enroll = @$container.find("input[name='enroll']'") - @$btn_unenroll = @$container.find("input[name='unenroll']'") - @$checkbox_autoenroll = @$container.find("input[name='auto-enroll']'") - @$checkbox_emailstudents = @$container.find("input[name='email-students']'") + @$emails_input = @$container.find("textarea[name='student-emails']") + @$enrollment_button = @$container.find(".enrollment-button") + @$checkbox_autoenroll = @$container.find("input[name='auto-enroll']") + @$checkbox_emailstudents = @$container.find("input[name='email-students']") @$task_response = @$container.find(".request-response") @$request_response_error = @$container.find(".request-response-error") - # attach click handlers - - @$btn_enroll.click => - emailStudents = @$checkbox_emailstudents.is(':checked') - + # attach click handler for enrollment buttons + @$enrollment_button.click => + emailStudents: @$checkbox_emailstudents.is(':checked') send_data = - action: 'enroll' + action: $(event.target).data('action') # 'enroll' or 'unenroll' emails: @$emails_input.val() auto_enroll: @$checkbox_autoenroll.is(':checked') email_students: emailStudents $.ajax dataType: 'json' - url: @$btn_enroll.data 'endpoint' - data: send_data - success: (data) => @display_response data - error: std_ajax_err => @fail_with_error "Error enrolling/unenrolling students." - - @$btn_unenroll.click => - emailStudents = @$checkbox_emailstudents.is(':checked') - - send_data = - action: 'unenroll' - emails: @$emails_input.val() - auto_enroll: @$checkbox_autoenroll.is(':checked') - email_students: emailStudents - - $.ajax - dataType: 'json' - url: @$btn_unenroll.data 'endpoint' + url: $(event.target).data 'endpoint' data: send_data success: (data) => @display_response data error: std_ajax_err => @fail_with_error gettext "Error enrolling/unenrolling students." @@ -475,6 +515,9 @@ class Membership # isolate # initialize BatchEnrollment subsection plantTimeout 0, => new BatchEnrollment @$section.find '.batch-enrollment' + + # initialize BetaTesterBulkAddition subsection + plantTimeout 0, => new BetaTesterBulkAddition @$section.find '.batch-beta-testers' # gather elements @$list_selector = @$section.find 'select#member-lists-selector' diff --git a/lms/static/sass/course/instructor/_instructor_2.scss b/lms/static/sass/course/instructor/_instructor_2.scss index eff6a56c4a..ab73566b32 100644 --- a/lms/static/sass/course/instructor/_instructor_2.scss +++ b/lms/static/sass/course/instructor/_instructor_2.scss @@ -292,7 +292,7 @@ section.instructor-dashboard-content-2 { margin-right: 0; } - .batch-enrollment { + .batch-enrollment, .batch-beta-testers { textarea { margin-top: 0.2em; height: auto; diff --git a/lms/templates/instructor/instructor_dashboard_2/membership.html b/lms/templates/instructor/instructor_dashboard_2/membership.html index 5fb595add2..14634892ba 100644 --- a/lms/templates/instructor/instructor_dashboard_2/membership.html +++ b/lms/templates/instructor/instructor_dashboard_2/membership.html @@ -26,7 +26,8 @@

-
+
+

${_("Batch Enrollment")}

@@ -51,15 +52,41 @@

${_("If email students is checked students will receive an email notification.")}

+ +
+ + +
+
+
+
-
- - +%if section_data['access']['instructor']: +
+

${_("Add Beta Testers")}

+

${_("Enter student emails separated by new lines or commas.")}

+ +
+ +
+ + + +
+ +
+ +
+%endif +

${_("Administration List Management")}