From 0a657befdbfcfd644b55665a90a4e8823b6b2857 Mon Sep 17 00:00:00 2001 From: Miles Steele Date: Thu, 20 Jun 2013 13:43:28 -0400 Subject: [PATCH] add forum list management --- lms/djangoapps/instructor/access.py | 24 +++++ lms/djangoapps/instructor/views/api.py | 99 +++++++++++++++++-- .../instructor/views/instructor_dashboard.py | 2 + .../instructor_dashboard/membership.coffee | 52 ++++++---- .../sass/course/instructor/_instructor_2.scss | 19 ++-- .../instructor_dashboard_2/membership.html | 27 +++++ lms/urls.py | 4 + 7 files changed, 191 insertions(+), 36 deletions(-) diff --git a/lms/djangoapps/instructor/access.py b/lms/djangoapps/instructor/access.py index 055598c3d9..b95b5b7c19 100644 --- a/lms/djangoapps/instructor/access.py +++ b/lms/djangoapps/instructor/access.py @@ -11,6 +11,10 @@ TODO sync instructor and staff flags from django.contrib.auth.models import User, Group from courseware.access import get_access_group_name +from django_comment_common.models import (Role, + FORUM_ROLE_ADMINISTRATOR, + FORUM_ROLE_MODERATOR, + FORUM_ROLE_COMMUNITY_TA) def list_with_level(course, level): @@ -55,3 +59,23 @@ def _change_access(course, user, level, mode): user.groups.remove(group) else: raise ValueError("unrecognized mode '{}'".format(mode)) + + +def update_forum_role_membership(course_id, user, rolename, mode): + """ + Change forum access of user. + + rolename is one of [FORUM_ROLE_ADMINISTRATOR, FORUM_ROLE_MODERATOR, FORUM_ROLE_COMMUNITY_TA] + + mode is one of ['alow', 'revoke'] + """ + role = Role.objects.get(course_id=course_id, name=rolename) + + if mode == 'allow': + role.users.add(user) + elif mode == 'revoke': + role.users.remove(user) + print "\n" * 5 + print role.users.all() + else: + raise ValueError("unrecognized mode '{}'".format(mode)) diff --git a/lms/djangoapps/instructor/views/api.py b/lms/djangoapps/instructor/views/api.py index d55928833e..a435807adc 100644 --- a/lms/djangoapps/instructor/views/api.py +++ b/lms/djangoapps/instructor/views/api.py @@ -15,11 +15,15 @@ from django.http import HttpResponse, HttpResponseBadRequest from courseware.courses import get_course_with_access from django.contrib.auth.models import User, Group +from django_comment_common.models import (Role, + FORUM_ROLE_ADMINISTRATOR, + FORUM_ROLE_MODERATOR, + FORUM_ROLE_COMMUNITY_TA) from courseware.models import StudentModule import instructor.enrollment as enrollment from instructor.enrollment import split_input_list, enroll_emails, unenroll_emails -from instructor.access import allow_access, revoke_access, list_with_level +import instructor.access as access import analytics.basic import analytics.distributions import analytics.csvs @@ -57,21 +61,21 @@ def access_allow_revoke(request, course_id): Query parameters: email is the target users email - level is one of ['instructor', 'staff'] + rolename is one of ['instructor', 'staff'] mode is one of ['allow', 'revoke'] """ course = get_course_with_access(request.user, course_id, 'instructor', depth=None) email = request.GET.get('email') - level = request.GET.get('level') + rolename = request.GET.get('rolename') mode = request.GET.get('mode') user = User.objects.get(email=email) if mode == 'allow': - allow_access(course, user, level) + access.allow_access(course, user, rolename) elif mode == 'revoke': - revoke_access(course, user, level) + access.revoke_access(course, user, rolename) else: raise ValueError("unrecognized mode '{}'".format(mode)) @@ -88,10 +92,17 @@ def list_instructors_staff(request, course_id): """ List instructors and staff. Requires staff access. + + rolename is one of ['instructor', 'staff'] """ course = get_course_with_access(request.user, course_id, 'staff', depth=None) - def extract_user(user): + rolename = request.GET.get('rolename', '') + + if not rolename in ['instructor', 'staff']: + return HttpResponseBadRequest() + + def extract_user_info(user): return { 'username': user.username, 'email': user.email, @@ -101,8 +112,7 @@ def list_instructors_staff(request, course_id): response_payload = { 'course_id': course_id, - 'instructor': map(extract_user, list_with_level(course, 'instructor')), - 'staff': map(extract_user, list_with_level(course, 'staff')), + rolename: map(extract_user_info, access.list_with_level(course, rolename)), } response = HttpResponse(json.dumps(response_payload), content_type="application/json") return response @@ -301,3 +311,76 @@ def reset_student_attempts(request, course_id): } 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) +def list_forum_members(request, course_id): + """ + Resets a students attempts counter. Optionally deletes student state for a problem. + Limited to staff access. + + Takes query parameter rolename + """ + course = get_course_with_access(request.user, course_id, 'staff', depth=None) + + rolename = request.GET.get('rolename', '') + + if not rolename in [FORUM_ROLE_ADMINISTRATOR, FORUM_ROLE_MODERATOR, FORUM_ROLE_COMMUNITY_TA]: + return HttpResponseBadRequest() + + try: + role = Role.objects.get(name=rolename, course_id=course_id) + users = role.users.all().order_by('username') + except Role.DoesNotExist: + users = [] + + def extract_user_info(user): + return { + 'username': user.username, + 'email': user.email, + 'first_name': user.first_name, + 'last_name': user.last_name, + } + + response_payload = { + 'course_id': course_id, + rolename: map(extract_user_info, users), + } + 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) +def update_forum_role_membership(request, course_id): + """ + Modify forum role access. + + Query parameters: + email is the target users email + rolename is one of [FORUM_ROLE_ADMINISTRATOR, FORUM_ROLE_MODERATOR, FORUM_ROLE_COMMUNITY_TA] + mode is one of ['allow', 'revoke'] + """ + course = get_course_with_access(request.user, course_id, 'instructor', depth=None) + + email = request.GET.get('email', '') + rolename = request.GET.get('rolename', '') + mode = request.GET.get('mode', '') + + if not rolename in [access.FORUM_ROLE_ADMINISTRATOR, access.FORUM_ROLE_MODERATOR, access.FORUM_ROLE_COMMUNITY_TA]: + return HttpResponseBadRequest() + + try: + user = User.objects.get(email=email) + access.update_forum_role_membership(course_id, user, rolename, mode) + except User.DoesNotExist, Role.DoesNotExist: + return HttpResponseBadRequest() + + response_payload = { + 'course_id': course_id, + 'mode': mode, + 'DONE': 'YES', + } + response = HttpResponse(json.dumps(response_payload), content_type="application/json") + return response diff --git a/lms/djangoapps/instructor/views/instructor_dashboard.py b/lms/djangoapps/instructor/views/instructor_dashboard.py index ae318abdce..a3ad6462cd 100644 --- a/lms/djangoapps/instructor/views/instructor_dashboard.py +++ b/lms/djangoapps/instructor/views/instructor_dashboard.py @@ -109,6 +109,8 @@ def _section_membership(course_id): 'unenroll_button_url': reverse('enroll_unenroll', kwargs={'course_id': course_id}), 'list_instructors_staff_url': reverse('list_instructors_staff', kwargs={'course_id': course_id}), 'access_allow_revoke_url': reverse('access_allow_revoke', kwargs={'course_id': course_id}), + 'list_forum_members_url': reverse('list_forum_members', kwargs={'course_id': course_id}), + 'update_forum_role_membership_url': reverse('update_forum_role_membership', kwargs={'course_id': course_id}), } return section_data diff --git a/lms/static/coffee/src/instructor_dashboard/membership.coffee b/lms/static/coffee/src/instructor_dashboard/membership.coffee index fd6f01737b..9240ee75d7 100644 --- a/lms/static/coffee/src/instructor_dashboard/membership.coffee +++ b/lms/static/coffee/src/instructor_dashboard/membership.coffee @@ -94,26 +94,26 @@ class BatchEnrollment # manages a list of instructors or staff and the control of their access. -class AuthorityList - # level is in ['instructor', 'staff'] - constructor: (@$container, @level) -> - log 'setting up instructor dashboard subsection - authlist management for #{@level}' +class AuthList + # rolename is in ['instructor', 'staff'] for instructor_staff endpoints + # rolename is the name of Role for forums for the forum endpoints + constructor: (@$container, @rolename) -> + log "setting up instructor dashboard subsection - authlist management for #{@rolename}" @$display_table = @$container.find('.auth-list-table') - $add_section = @$container.find('.auth-list-add') - $allow_field = $add_section.find("input[name='email']") - $allow_button = $add_section.find("input[name='allow']") - @list_endpoint = @$display_table.data 'endpoint' - @access_change_endpoint = $add_section.data 'endpoint' + @$add_section = @$container.find('.auth-list-add') + $allow_field = @$add_section.find("input[name='email']") + $allow_button = @$add_section.find("input[name='allow']") $allow_button.click => - @access_change($allow_field.val(), @level, 'allow', @reload_auth_list) + @access_change($allow_field.val(), @rolename, 'allow', @reload_auth_list) $allow_field.val '' @reload_auth_list() reload_auth_list: => - $.getJSON @list_endpoint, (data) => + list_endpoint = @$display_table.data 'endpoint' + $.getJSON list_endpoint, {rolename: @rolename}, (data) => log data @$display_table.empty() @@ -138,7 +138,7 @@ class AuthorityList "Revoke Access" ] - table_data = data[@level] + table_data = data[@rolename] log 'table_data', table_data $table_placeholder = $ '
', class: 'slickgrid' @@ -150,11 +150,11 @@ class AuthorityList grid.onClick.subscribe (e, args) => item = args.grid.getDataItem(args.row) if args.cell is 2 - @access_change(item.email, @level, 'revoke', @reload_auth_list) + @access_change(item.email, @rolename, 'revoke', @reload_auth_list) - access_change: (email, level, mode, cb) -> - url = @access_change_endpoint - $.getJSON @access_change_endpoint, {email: email, level: @level, mode: mode}, (data) -> + access_change: (email, rolename, mode, cb) -> + access_change_endpoint = @$add_section.data 'endpoint' + $.getJSON access_change_endpoint, {email: email, rolename: @rolename, mode: mode}, (data) -> log data cb?() @@ -166,15 +166,31 @@ class Membership # isolate sections from each other's errors. plantTimeout 0, => @batchenrollment = new BatchEnrollment @$section.find '.batch-enrollment' - plantTimeout 0, => @stafflist = new AuthorityList (@$section.find '.auth-list-container.auth-list-staff'), 'staff' - plantTimeout 0, => @instructorlist = new AuthorityList (@$section.find '.auth-list-container.auth-list-instructor'), 'instructor' + plantTimeout 0, => @stafflist = new AuthList (@$section.find '.auth-list-container.auth-list-staff'), 'staff' + plantTimeout 0, => @instructorlist = new AuthList (@$section.find '.auth-list-container.auth-list-instructor'), 'instructor' + + # TODO names like 'Administrator' should come from server through template. + plantTimeout 0, => @forum_admin_list = new AuthList (@$section.find '.auth-list-container.auth-list-forum-admin'), 'Administrator' + plantTimeout 0, => @forum_mod_list = new AuthList (@$section.find '.auth-list-container.auth-list-forum-moderator'), 'Moderator' + plantTimeout 0, => @forum_comta_list = new AuthList (@$section.find '.auth-list-container.auth-list-forum-community-ta'), 'Community TA' onClickTitle: -> @stafflist.$display_table.empty() @stafflist.reload_auth_list() + @instructorlist.$display_table.empty() @instructorlist.reload_auth_list() + @forum_admin_list.$display_table.empty() + @forum_admin_list.reload_auth_list() + + @forum_mod_list.$display_table.empty() + @forum_mod_list.reload_auth_list() + + @forum_comta_list.$display_table.empty() + @forum_comta_list.reload_auth_list() + + # exports _.defaults window, InstructorDashboard: {} diff --git a/lms/static/sass/course/instructor/_instructor_2.scss b/lms/static/sass/course/instructor/_instructor_2.scss index a8bbed61e8..ba0f11336a 100644 --- a/lms/static/sass/course/instructor/_instructor_2.scss +++ b/lms/static/sass/course/instructor/_instructor_2.scss @@ -92,21 +92,20 @@ .vert-right { float: right; width: 45%; + } - .auth-list-container { - margin-bottom: 1.5em; + .auth-list-container { + margin-bottom: 1.5em; - .auth-list-table { - .slickgrid { - height: 250px; - } - } - - .auth-list-add { - margin-top: 0.5em; + .auth-list-table { + .slickgrid { + height: 250px; } } + .auth-list-add { + margin-top: 0.5em; + } } .batch-enrollment { diff --git a/lms/templates/courseware/instructor_dashboard_2/membership.html b/lms/templates/courseware/instructor_dashboard_2/membership.html index 37c4d2a2d6..e7b128ec68 100644 --- a/lms/templates/courseware/instructor_dashboard_2/membership.html +++ b/lms/templates/courseware/instructor_dashboard_2/membership.html @@ -8,6 +8,33 @@
+ +
+

Instructor Management

+
+
+ + +
+
+ +
+

Instructor Management

+
+
+ + +
+
+ +
+

Instructor Management

+
+
+ + +
+
diff --git a/lms/urls.py b/lms/urls.py index d42e8628f3..0d5076ec4a 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -271,6 +271,10 @@ if settings.COURSEWARE_ENABLED: 'instructor.views.api.get_student_progress_url', name="get_student_progress_url"), url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/instructor_dashboard/api/reset_student_attempts$', 'instructor.views.api.reset_student_attempts', name="reset_student_attempts"), + url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/instructor_dashboard/api/list_forum_members$', + 'instructor.views.api.list_forum_members', name="list_forum_members"), + url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/instructor_dashboard/api/update_forum_role_membership$', + 'instructor.views.api.update_forum_role_membership', name="update_forum_role_membership"), url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/gradebook$', 'instructor.views.legacy.gradebook', name='gradebook'),