Commit 0a657bef by Miles Steele

add forum list management

parent bc9cce57
...@@ -11,6 +11,10 @@ TODO sync instructor and staff flags ...@@ -11,6 +11,10 @@ TODO sync instructor and staff flags
from django.contrib.auth.models import User, Group from django.contrib.auth.models import User, Group
from courseware.access import get_access_group_name 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): def list_with_level(course, level):
...@@ -55,3 +59,23 @@ def _change_access(course, user, level, mode): ...@@ -55,3 +59,23 @@ def _change_access(course, user, level, mode):
user.groups.remove(group) user.groups.remove(group)
else: else:
raise ValueError("unrecognized mode '{}'".format(mode)) 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))
...@@ -15,11 +15,15 @@ from django.http import HttpResponse, HttpResponseBadRequest ...@@ -15,11 +15,15 @@ from django.http import HttpResponse, HttpResponseBadRequest
from courseware.courses import get_course_with_access from courseware.courses import get_course_with_access
from django.contrib.auth.models import User, Group 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 from courseware.models import StudentModule
import instructor.enrollment as enrollment import instructor.enrollment as enrollment
from instructor.enrollment import split_input_list, enroll_emails, unenroll_emails 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.basic
import analytics.distributions import analytics.distributions
import analytics.csvs import analytics.csvs
...@@ -57,21 +61,21 @@ def access_allow_revoke(request, course_id): ...@@ -57,21 +61,21 @@ def access_allow_revoke(request, course_id):
Query parameters: Query parameters:
email is the target users email email is the target users email
level is one of ['instructor', 'staff'] rolename is one of ['instructor', 'staff']
mode is one of ['allow', 'revoke'] mode is one of ['allow', 'revoke']
""" """
course = get_course_with_access(request.user, course_id, 'instructor', depth=None) course = get_course_with_access(request.user, course_id, 'instructor', depth=None)
email = request.GET.get('email') email = request.GET.get('email')
level = request.GET.get('level') rolename = request.GET.get('rolename')
mode = request.GET.get('mode') mode = request.GET.get('mode')
user = User.objects.get(email=email) user = User.objects.get(email=email)
if mode == 'allow': if mode == 'allow':
allow_access(course, user, level) access.allow_access(course, user, rolename)
elif mode == 'revoke': elif mode == 'revoke':
revoke_access(course, user, level) access.revoke_access(course, user, rolename)
else: else:
raise ValueError("unrecognized mode '{}'".format(mode)) raise ValueError("unrecognized mode '{}'".format(mode))
...@@ -88,10 +92,17 @@ def list_instructors_staff(request, course_id): ...@@ -88,10 +92,17 @@ def list_instructors_staff(request, course_id):
""" """
List instructors and staff. List instructors and staff.
Requires staff access. Requires staff access.
rolename is one of ['instructor', 'staff']
""" """
course = get_course_with_access(request.user, course_id, 'staff', depth=None) 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 { return {
'username': user.username, 'username': user.username,
'email': user.email, 'email': user.email,
...@@ -101,8 +112,7 @@ def list_instructors_staff(request, course_id): ...@@ -101,8 +112,7 @@ def list_instructors_staff(request, course_id):
response_payload = { response_payload = {
'course_id': course_id, 'course_id': course_id,
'instructor': map(extract_user, list_with_level(course, 'instructor')), rolename: map(extract_user_info, access.list_with_level(course, rolename)),
'staff': map(extract_user, list_with_level(course, 'staff')),
} }
response = HttpResponse(json.dumps(response_payload), content_type="application/json") response = HttpResponse(json.dumps(response_payload), content_type="application/json")
return response return response
...@@ -301,3 +311,76 @@ def reset_student_attempts(request, course_id): ...@@ -301,3 +311,76 @@ def reset_student_attempts(request, course_id):
} }
response = HttpResponse(json.dumps(response_payload), content_type="application/json") response = HttpResponse(json.dumps(response_payload), content_type="application/json")
return response 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
...@@ -109,6 +109,8 @@ def _section_membership(course_id): ...@@ -109,6 +109,8 @@ def _section_membership(course_id):
'unenroll_button_url': reverse('enroll_unenroll', kwargs={'course_id': 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}), '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}), '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 return section_data
......
...@@ -94,26 +94,26 @@ class BatchEnrollment ...@@ -94,26 +94,26 @@ class BatchEnrollment
# manages a list of instructors or staff and the control of their access. # manages a list of instructors or staff and the control of their access.
class AuthorityList class AuthList
# level is in ['instructor', 'staff'] # rolename is in ['instructor', 'staff'] for instructor_staff endpoints
constructor: (@$container, @level) -> # rolename is the name of Role for forums for the forum endpoints
log 'setting up instructor dashboard subsection - authlist management for #{@level}' constructor: (@$container, @rolename) ->
log "setting up instructor dashboard subsection - authlist management for #{@rolename}"
@$display_table = @$container.find('.auth-list-table') @$display_table = @$container.find('.auth-list-table')
$add_section = @$container.find('.auth-list-add') @$add_section = @$container.find('.auth-list-add')
$allow_field = $add_section.find("input[name='email']") $allow_field = @$add_section.find("input[name='email']")
$allow_button = $add_section.find("input[name='allow']") $allow_button = @$add_section.find("input[name='allow']")
@list_endpoint = @$display_table.data 'endpoint'
@access_change_endpoint = $add_section.data 'endpoint'
$allow_button.click => $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 '' $allow_field.val ''
@reload_auth_list() @reload_auth_list()
reload_auth_list: => reload_auth_list: =>
$.getJSON @list_endpoint, (data) => list_endpoint = @$display_table.data 'endpoint'
$.getJSON list_endpoint, {rolename: @rolename}, (data) =>
log data log data
@$display_table.empty() @$display_table.empty()
...@@ -138,7 +138,7 @@ class AuthorityList ...@@ -138,7 +138,7 @@ class AuthorityList
"<span class='revoke-link'>Revoke Access</span>" "<span class='revoke-link'>Revoke Access</span>"
] ]
table_data = data[@level] table_data = data[@rolename]
log 'table_data', table_data log 'table_data', table_data
$table_placeholder = $ '<div/>', class: 'slickgrid' $table_placeholder = $ '<div/>', class: 'slickgrid'
...@@ -150,11 +150,11 @@ class AuthorityList ...@@ -150,11 +150,11 @@ class AuthorityList
grid.onClick.subscribe (e, args) => grid.onClick.subscribe (e, args) =>
item = args.grid.getDataItem(args.row) item = args.grid.getDataItem(args.row)
if args.cell is 2 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) -> access_change: (email, rolename, mode, cb) ->
url = @access_change_endpoint access_change_endpoint = @$add_section.data 'endpoint'
$.getJSON @access_change_endpoint, {email: email, level: @level, mode: mode}, (data) -> $.getJSON access_change_endpoint, {email: email, rolename: @rolename, mode: mode}, (data) ->
log data log data
cb?() cb?()
...@@ -166,15 +166,31 @@ class Membership ...@@ -166,15 +166,31 @@ class Membership
# isolate sections from each other's errors. # isolate sections from each other's errors.
plantTimeout 0, => @batchenrollment = new BatchEnrollment @$section.find '.batch-enrollment' 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, => @stafflist = new AuthList (@$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, => @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: -> onClickTitle: ->
@stafflist.$display_table.empty() @stafflist.$display_table.empty()
@stafflist.reload_auth_list() @stafflist.reload_auth_list()
@instructorlist.$display_table.empty() @instructorlist.$display_table.empty()
@instructorlist.reload_auth_list() @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 # exports
_.defaults window, InstructorDashboard: {} _.defaults window, InstructorDashboard: {}
......
...@@ -92,21 +92,20 @@ ...@@ -92,21 +92,20 @@
.vert-right { .vert-right {
float: right; float: right;
width: 45%; width: 45%;
}
.auth-list-container { .auth-list-container {
margin-bottom: 1.5em; margin-bottom: 1.5em;
.auth-list-table {
.slickgrid {
height: 250px;
}
}
.auth-list-add { .auth-list-table {
margin-top: 0.5em; .slickgrid {
height: 250px;
} }
} }
.auth-list-add {
margin-top: 0.5em;
}
} }
.batch-enrollment { .batch-enrollment {
......
...@@ -8,6 +8,33 @@ ...@@ -8,6 +8,33 @@
<input type="button" name="enroll" value="Enroll" data-endpoint="${ section_data['enroll_button_url'] }" > <input type="button" name="enroll" value="Enroll" data-endpoint="${ section_data['enroll_button_url'] }" >
<input type="button" name="unenroll" value="Unenroll" data-endpoint="${ section_data['unenroll_button_url'] }" > <input type="button" name="unenroll" value="Unenroll" data-endpoint="${ section_data['unenroll_button_url'] }" >
<div class="task-response"></div> <div class="task-response"></div>
<div class="auth-list-container auth-list-forum-admin">
<h2>Instructor Management</h2>
<div class="auth-list-table" data-endpoint="${ section_data['list_forum_members_url'] }"></div>
<div class="auth-list-add" data-endpoint="${ section_data['update_forum_role_membership_url'] }">
<input type="text" name="email" placeholder="Enter Email" spellcheck="false">
<input type="button" name="allow" value="Grant Forum Admin">
</div>
</div>
<div class="auth-list-container auth-list-forum-moderator">
<h2>Instructor Management</h2>
<div class="auth-list-table" data-endpoint="${ section_data['list_forum_members_url'] }"></div>
<div class="auth-list-add" data-endpoint="${ section_data['update_forum_role_membership_url'] }">
<input type="text" name="email" placeholder="Enter Email" spellcheck="false">
<input type="button" name="allow" value="Grant Forum Moderator">
</div>
</div>
<div class="auth-list-container auth-list-forum-community-ta">
<h2>Instructor Management</h2>
<div class="auth-list-table" data-endpoint="${ section_data['list_forum_members_url'] }"></div>
<div class="auth-list-add" data-endpoint="${ section_data['update_forum_role_membership_url'] }">
<input type="text" name="email" placeholder="Enter Email" spellcheck="false">
<input type="button" name="allow" value="Grant Community TA">
</div>
</div>
</div> </div>
<div class="vert-right instructor-staff-management"> <div class="vert-right instructor-staff-management">
......
...@@ -271,6 +271,10 @@ if settings.COURSEWARE_ENABLED: ...@@ -271,6 +271,10 @@ if settings.COURSEWARE_ENABLED:
'instructor.views.api.get_student_progress_url', name="get_student_progress_url"), 'instructor.views.api.get_student_progress_url', name="get_student_progress_url"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/instructor_dashboard/api/reset_student_attempts$', url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/instructor_dashboard/api/reset_student_attempts$',
'instructor.views.api.reset_student_attempts', name="reset_student_attempts"), 'instructor.views.api.reset_student_attempts', name="reset_student_attempts"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/instructor_dashboard/api/list_forum_members$',
'instructor.views.api.list_forum_members', name="list_forum_members"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/instructor_dashboard/api/update_forum_role_membership$',
'instructor.views.api.update_forum_role_membership', name="update_forum_role_membership"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/gradebook$', url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/gradebook$',
'instructor.views.legacy.gradebook', name='gradebook'), 'instructor.views.legacy.gradebook', name='gradebook'),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment