From e05d0f97d82e3961f7374d6c49dfa3ce2bb5b1ad Mon Sep 17 00:00:00 2001 From: Miles Steele Date: Thu, 11 Jul 2013 16:05:58 -0400 Subject: [PATCH] fix permissions, fix ENABLE_INSTRUCTOR_BACKGROUND_TASKS usage, fix csv download --- lms/djangoapps/instructor/views/api.py | 68 +++++--------- .../instructor/views/instructor_dashboard.py | 5 +- lms/envs/common.py | 3 + .../instructor_dashboard/membership.coffee | 1 + .../instructor_dashboard/student_admin.coffee | 50 +++++----- .../sass/course/instructor/_instructor_2.scss | 8 +- .../instructor_dashboard_2/membership.html | 46 ++++----- .../instructor_dashboard_2/student_admin.html | 93 ++++++++++--------- 8 files changed, 137 insertions(+), 137 deletions(-) diff --git a/lms/djangoapps/instructor/views/api.py b/lms/djangoapps/instructor/views/api.py index 9f3538dafb..8741fe6e9e 100644 --- a/lms/djangoapps/instructor/views/api.py +++ b/lms/djangoapps/instructor/views/api.py @@ -3,7 +3,6 @@ Instructor Dashboard API views Non-html views which the instructor dashboard requests. -TODO add tracking TODO a lot of these GETs should be PUTs """ @@ -14,6 +13,7 @@ from django.views.decorators.cache import cache_control from django.core.urlresolvers import reverse from django.http import HttpResponse, HttpResponseBadRequest +from courseware.access import has_access from courseware.courses import get_course_with_access from django.contrib.auth.models import User from django_comment_common.models import (Role, @@ -32,7 +32,10 @@ import analytics.csvs def common_exceptions_400(func): - """ Catches common exceptions and renders matching 400 errors. (decorator) """ + """ + Catches common exceptions and renders matching 400 errors. + (decorator without arguments) + """ def wrapped(*args, **kwargs): try: return func(*args, **kwargs) @@ -44,6 +47,8 @@ def common_exceptions_400(func): def require_query_params(*args, **kwargs): """ Checks for required paremters or renders a 400 error. + (decorator with arguments) + `args` is a *list of required GET parameter names. `kwargs` is a **dict of required GET parameter names to string explanations of the parameter @@ -193,12 +198,12 @@ def access_allow_revoke(request, course_id): def list_course_role_members(request, course_id): """ List instructors and staff. - Requires staff access. + Requires instructor access. rolename is one of ['instructor', 'staff', 'beta'] """ course = get_course_with_access( - request.user, course_id, 'staff', depth=None + request.user, course_id, 'instructor', depth=None ) rolename = request.GET.get('rolename') @@ -232,7 +237,7 @@ def grading_config(request, course_id): """ Respond with json which contains a html formatted grade summary. - TODO maybe this shouldn't be html already + TODO this shouldn't be html already """ course = get_course_with_access( request.user, course_id, 'staff', depth=None @@ -282,7 +287,7 @@ def enrolled_students_features(request, course_id, csv=False): ) return response else: - header, datarows = analytics.csvs.format_dictlist(student_data) + header, datarows = analytics.csvs.format_dictlist(student_data, query_features) return analytics.csvs.create_csv_response("enrolled_profiles.csv", header, datarows) @@ -375,49 +380,19 @@ def get_student_progress_url(request, course_id): return response -@ensure_csrf_cookie -@cache_control(no_cache=True, no_store=True, must_revalidate=True) -@common_exceptions_400 -@require_query_params(student_email="student email") -def redirect_to_student_progress(request, course_id): - """ - Redirects to the specified students progress page - Limited to staff access. - - Takes query parameter student_email - """ - course = get_course_with_access( - request.user, course_id, 'staff', depth=None - ) - - student_email = request.GET.get('student_email') - user = User.objects.get(email=student_email) - - progress_url = reverse('student_progress', kwargs={'course_id': course_id, 'student_id': user.id}) - - response_payload = { - 'course_id': course_id, - 'progress_url': progress_url, - } - response = HttpResponse( - json.dumps(response_payload), content_type="application/json" - ) - return response - - @ensure_csrf_cookie @cache_control(no_cache=True, no_store=True, must_revalidate=True) @common_exceptions_400 def reset_student_attempts(request, course_id): """ Resets a students attempts counter or starts a task to reset all students attempts counters. Optionally deletes student state for a problem. - Limited to staff access. + Limited to staff access. Some sub-methods limited to instructor access. Takes either of the following query paremeters - problem_to_reset is a urlname of a problem - student_email is an email - - all_students is a boolean - - delete_module is a boolean + - all_students is a boolean (requires instructor access) (mutually exclusive with delete_module) + - delete_module is a boolean (requires instructor access) (mutually exclusive with all_students) """ course = get_course_with_access( request.user, course_id, 'staff', depth=None @@ -426,13 +401,18 @@ def reset_student_attempts(request, course_id): problem_to_reset = request.GET.get('problem_to_reset') student_email = request.GET.get('student_email') all_students = request.GET.get('all_students', False) in ['true', 'True', True] - will_delete_module = request.GET.get('delete_module', False) in ['true', 'True', True] + delete_module = request.GET.get('delete_module', False) in ['true', 'True', True] if not (problem_to_reset and (all_students or student_email)): return HttpResponseBadRequest() - if will_delete_module and all_students: + if delete_module and all_students: return HttpResponseBadRequest() + # require instructor access for some queries + if all_students or delete_module: + if not has_access(request.user, course, 'instructor'): + HttpResponseBadRequest("requires instructor accesss.") + module_state_key = _msk_from_problem_urlname(course_id, problem_to_reset) response_payload = {} @@ -441,7 +421,7 @@ def reset_student_attempts(request, course_id): if student_email: try: student = User.objects.get(email=student_email) - enrollment.reset_student_attempts(course_id, student, module_state_key, delete_module=will_delete_module) + enrollment.reset_student_attempts(course_id, student, module_state_key, delete_module=delete_module) except StudentModule.DoesNotExist: return HttpResponseBadRequest("Module does not exist.") elif all_students: @@ -462,7 +442,7 @@ def reset_student_attempts(request, course_id): def rescore_problem(request, course_id): """ Starts a background process a students attempts counter. Optionally deletes student state for a problem. - Limited to staff access. + Limited to instructor access. Takes either of the following query paremeters - problem_to_reset is a urlname of a problem @@ -472,7 +452,7 @@ def rescore_problem(request, course_id): all_students will be ignored if student_email is present """ course = get_course_with_access( - request.user, course_id, 'staff', depth=None + request.user, course_id, 'instructor', depth=None ) problem_to_reset = request.GET.get('problem_to_reset') diff --git a/lms/djangoapps/instructor/views/instructor_dashboard.py b/lms/djangoapps/instructor/views/instructor_dashboard.py index 148a7bd59c..9e106906b1 100644 --- a/lms/djangoapps/instructor/views/instructor_dashboard.py +++ b/lms/djangoapps/instructor/views/instructor_dashboard.py @@ -45,7 +45,7 @@ def instructor_dashboard_2(request, course_id): sections = [ _section_course_info(course_id), _section_membership(course_id, access), - _section_student_admin(course_id), + _section_student_admin(course_id, access), _section_data_download(course_id), _section_analytics(course_id), ] @@ -115,11 +115,12 @@ def _section_membership(course_id, access): return section_data -def _section_student_admin(course_id): +def _section_student_admin(course_id, access): """ Provide data for the corresponding dashboard section """ section_data = { 'section_key': 'student_admin', 'section_display_name': 'Student Admin', + 'access': access, 'get_student_progress_url': reverse('get_student_progress_url', kwargs={'course_id': course_id}), 'enrollment_url': reverse('students_update_enrollment', kwargs={'course_id': course_id}), 'reset_student_attempts_url': reverse('reset_student_attempts', kwargs={'course_id': course_id}), diff --git a/lms/envs/common.py b/lms/envs/common.py index f76298c421..71ebd94de8 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -144,6 +144,9 @@ MITX_FEATURES = { # Enable instructor dash to submit background tasks 'ENABLE_INSTRUCTOR_BACKGROUND_TASKS': True, + # Enable instructor dash beta version link + 'ENABLE_INSTRUCTOR_BETA_DASHBOARD': False, + # Allow use of the hint managment instructor view. 'ENABLE_HINTER_INSTRUCTOR_VIEW': False, diff --git a/lms/static/coffee/src/instructor_dashboard/membership.coffee b/lms/static/coffee/src/instructor_dashboard/membership.coffee index 1998924a1a..9b01262b85 100644 --- a/lms/static/coffee/src/instructor_dashboard/membership.coffee +++ b/lms/static/coffee/src/instructor_dashboard/membership.coffee @@ -272,6 +272,7 @@ class Membership @$list_selector.change => $opt = @$list_selector.children('option:selected') + return unless $opt.length > 0 for auth_list in @auth_lists auth_list.$container.removeClass 'active' auth_list = $opt.data('auth_list') diff --git a/lms/static/coffee/src/instructor_dashboard/student_admin.coffee b/lms/static/coffee/src/instructor_dashboard/student_admin.coffee index badefc0a6c..d3a7d308b1 100644 --- a/lms/static/coffee/src/instructor_dashboard/student_admin.coffee +++ b/lms/static/coffee/src/instructor_dashboard/student_admin.coffee @@ -9,6 +9,14 @@ std_ajax_err = (handler) -> (jqXHR, textStatus, errorThrown) -> errorThrown: #{errorThrown}""" handler.apply this, arguments +# get jquery element and assert its existance +find_and_assert = ($root, selector) -> + item = $root.find selector + if item.length != 1 + console.error "element selection failed for '#{selector}' resulted in length #{item.length}" + throw "Failed Element Selection" + else + item create_task_list_table = ($table_tasks, tasks_data) -> $table_tasks.empty() @@ -61,39 +69,32 @@ class StudentAdmin log "setting up instructor dashboard section - student admin" @$section.data 'wrapper', @ - # get jquery element and assert its existance - # for debugging - find_and_assert = ($root, selector) -> - item = $root.find selector - if item.length != 1 - console.error "element selection failed for '#{selector}' resulted in length #{item.length}" - throw "Failed Element Selection" - else - item - # collect buttons + # some buttons are optional because they can be flipped by the instructor task feature switch @$field_student_select = find_and_assert @$section, "input[name='student-select']" @$progress_link = find_and_assert @$section, "a.progress-link" @$btn_enroll = find_and_assert @$section, "input[name='enroll']" @$btn_unenroll = find_and_assert @$section, "input[name='unenroll']" @$field_problem_select_single = find_and_assert @$section, "input[name='problem-select-single']" @$btn_reset_attempts_single = find_and_assert @$section, "input[name='reset-attempts-single']" - @$btn_delete_state_single = find_and_assert @$section, "input[name='delete-state-single']" - @$btn_rescore_problem_single = find_and_assert @$section, "input[name='rescore-problem-single']" - @$btn_task_history_single = find_and_assert @$section, "input[name='task-history-single']" - @$table_task_history_single = find_and_assert @$section, ".task-history-single-table" + @$btn_delete_state_single = @$section.find "input[name='delete-state-single']" + @$btn_rescore_problem_single = @$section.find "input[name='rescore-problem-single']" + @$btn_task_history_single = @$section.find "input[name='task-history-single']" + @$table_task_history_single = @$section.find ".task-history-single-table" - @$field_problem_select_all = find_and_assert @$section, "input[name='problem-select-all']" - @$btn_reset_attempts_all = find_and_assert @$section, "input[name='reset-attempts-all']" - @$btn_rescore_problem_all = find_and_assert @$section, "input[name='rescore-problem-all']" - @$btn_task_history_all = find_and_assert @$section, "input[name='task-history-all']" - @$table_task_history_all = find_and_assert @$section, ".task-history-all-table" - @$table_running_tasks = find_and_assert @$section, ".running-tasks-table" + # course-specific level buttons + @$field_problem_select_all = @$section.find "input[name='problem-select-all']" + @$btn_reset_attempts_all = @$section.find "input[name='reset-attempts-all']" + @$btn_rescore_problem_all = @$section.find "input[name='rescore-problem-all']" + @$btn_task_history_all = @$section.find "input[name='task-history-all']" + @$table_task_history_all = @$section.find ".task-history-all-table" + @$table_running_tasks = @$section.find ".running-tasks-table" @$request_response_error_single = find_and_assert @$section, ".student-specific-container .request-response-error" - @$request_response_error_all = find_and_assert @$section, ".course-specific-container .request-response-error" + @$request_response_error_all = @$section.find ".course-specific-container .request-response-error" - @start_refresh_running_task_poll_loop() + if @$table_running_tasks.length > 0 + @start_refresh_running_task_poll_loop() # go to student progress page @$progress_link.click (e) => @@ -118,7 +119,7 @@ class StudentAdmin $.ajax dataType: 'json' - url: @$btn_unenroll.data 'endpoint' + url: @$btn_enroll.data 'endpoint' data: send_data success: @clear_errors_then -> console.log "student #{send_data.emails} enrolled" error: std_ajax_err => @$request_response_error_single.text "Error enrolling student '#{send_data.emails}'." @@ -259,7 +260,8 @@ class StudentAdmin cb?.apply this, arguments onClickTitle: -> - @start_refresh_running_task_poll_loop() + if @$table_running_tasks.length > 0 + @start_refresh_running_task_poll_loop() # onExit: -> # clearInterval @reload_running_task_list_slot diff --git a/lms/static/sass/course/instructor/_instructor_2.scss b/lms/static/sass/course/instructor/_instructor_2.scss index 86004f4816..4121926fc2 100644 --- a/lms/static/sass/course/instructor/_instructor_2.scss +++ b/lms/static/sass/course/instructor/_instructor_2.scss @@ -169,14 +169,16 @@ .instructor-dashboard-wrapper-2 section.idash-section#membership { + $half_width: $baseline * 20; + .vert-left { float: left; - width: 47%; + width: $half_width; } .vert-right { float: right; - width: 47%; + width: $half_width; } select { @@ -197,7 +199,7 @@ position: absolute; display: none; padding: $baseline; - width: $baseline * 25; + width: $half_width; border: 1px solid $light-gray; background-color: $white; diff --git a/lms/templates/courseware/instructor_dashboard_2/membership.html b/lms/templates/courseware/instructor_dashboard_2/membership.html index 13da851245..0aa3e374b4 100644 --- a/lms/templates/courseware/instructor_dashboard_2/membership.html +++ b/lms/templates/courseware/instructor_dashboard_2/membership.html @@ -3,7 +3,7 @@

Batch Enrollment

Enter student emails separated by new lines or commas.

- +
@@ -20,41 +20,43 @@
-

Member List Management

+

Administration List Management

-
-
-
- - -
-
-
- %if section_data['access']['instructor']: -
+
- + +
+
+
+ + %if section_data['access']['instructor']: +
+
+
+ + +
+
+
+ %endif + +
+
+
+ +
%endif -
-
-
- - -
-
-
- %if section_data['access']['forum_admin']:
diff --git a/lms/templates/courseware/instructor_dashboard_2/student_admin.html b/lms/templates/courseware/instructor_dashboard_2/student_admin.html index bb1b463d4c..f2f30ad47d 100644 --- a/lms/templates/courseware/instructor_dashboard_2/student_admin.html +++ b/lms/templates/courseware/instructor_dashboard_2/student_admin.html @@ -28,54 +28,63 @@ provide notaproblem/someothername.)

-

You may also delete the entire state of a student for the specified module:

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

You may also delete the entire state of a student for the specified module:

+ + %endif -

- Rescoring runs in the background, and status for active tasks will appear in a table below. - To see status for all tasks submitted for this course and student, click on this button: -

+ %if settings.MITX_FEATURES.get('ENABLE_INSTRUCTOR_BACKGROUND_TASKS') and section_data['access']['instructor']: + + %endif - -
+ %if settings.MITX_FEATURES.get('ENABLE_INSTRUCTOR_BACKGROUND_TASKS') and section_data['access']['instructor']: +

+ Rescoring runs in the background, and status for active tasks will appear in a table below. + To see status for all tasks submitted for this course and student, click on this button: +

+ + +
+ %endif
-
+%if settings.MITX_FEATURES.get('ENABLE_INSTRUCTOR_BACKGROUND_TASKS') and section_data['access']['instructor']: +
-
-

Course-specific grade adjustment

-
+
+

Course-specific grade adjustment

+
-

- Specify a particular problem in the course here by its url: - -

-

- You may use just the "urlname" if a problem, or "modulename/urlname" if not. - (For example, if the location is i4x://university/course/problem/problemname, - then just provide the problemname. - If the location is i4x://university/course/notaproblem/someothername, then - provide notaproblem/someothername.) -

-

- Then select an action: - - -

-

-

These actions run in the background, and status for active tasks will appear in a table below. - To see status for all tasks submitted for this problem, click on this button: -

- -
-

-
+

+ Specify a particular problem in the course here by its url: + +

+

+ You may use just the "urlname" if a problem, or "modulename/urlname" if not. + (For example, if the location is i4x://university/course/problem/problemname, + then just provide the problemname. + If the location is i4x://university/course/notaproblem/someothername, then + provide notaproblem/someothername.) +

+

+ Then select an action: + + +

+

+

These actions run in the background, and status for active tasks will appear in a table below. + To see status for all tasks submitted for this problem, click on this button: +

+ +
+

+
-
+
-
-

Pending Instructor Tasks

-
-
+
+

Pending Instructor Tasks

+
+
+%endif