diff --git a/lms/djangoapps/courseware/access.py b/lms/djangoapps/courseware/access.py index f4e4a95e07..64bef8fa1a 100644 --- a/lms/djangoapps/courseware/access.py +++ b/lms/djangoapps/courseware/access.py @@ -329,7 +329,7 @@ def _course_staff_group_name(location): """ return 'staff_%s' % Location(location).course -def _course_beta_test_group_name(location): +def course_beta_test_group_name(location): """ Get the name of the beta tester group for a location. Right now, that's beta_testers_COURSE. @@ -388,7 +388,7 @@ def _adjust_start_date_for_beta_testers(user, descriptor): user_groups = [g.name for g in user.groups.all()] - beta_group = _course_beta_test_group_name(descriptor.location) + beta_group = course_beta_test_group_name(descriptor.location) if beta_group in user_groups: debug("Adjust start time: user in group %s", beta_group) # time_structs don't support subtraction, so convert to datetimes, diff --git a/lms/djangoapps/courseware/tests/tests.py b/lms/djangoapps/courseware/tests/tests.py index 6972ded307..eeb304b193 100644 --- a/lms/djangoapps/courseware/tests/tests.py +++ b/lms/djangoapps/courseware/tests/tests.py @@ -18,7 +18,7 @@ import xmodule.modulestore.django # Need access to internal func to put users in the right group from courseware import grades from courseware.access import (has_access, _course_staff_group_name, - _course_beta_test_group_name) + course_beta_test_group_name) from courseware.models import StudentModuleCache from student.models import Registration @@ -645,7 +645,7 @@ class TestViewAuth(PageLoader): self.assertFalse(has_access(student_user, self.toy, 'load')) # now add the student to the beta test group - group_name = _course_beta_test_group_name(self.toy.location) + group_name = course_beta_test_group_name(self.toy.location) g = Group.objects.create(name=group_name) g.user_set.add(student_user) diff --git a/lms/djangoapps/instructor/tests.py b/lms/djangoapps/instructor/tests.py index 2d17cee47d..57c0436921 100644 --- a/lms/djangoapps/instructor/tests.py +++ b/lms/djangoapps/instructor/tests.py @@ -179,7 +179,7 @@ class TestInstructorDashboardForumAdmin(ct.PageLoader): self.assertTrue(response.content.find('Removed "{0}" from "{1}" forum role = "{2}"'.format(username, course.id, rolename))>=0) self.assertFalse(has_forum_access(username, course.id, rolename)) - def test_add_and_readd_forum_admin_users(self): + def test_add_and_read_forum_admin_users(self): course = self.toy self.initialize_roles(course.id) url = reverse('instructor_dashboard', kwargs={'course_id': course.id}) diff --git a/lms/djangoapps/instructor/views.py b/lms/djangoapps/instructor/views.py index 2d58799efe..1519d876bb 100644 --- a/lms/djangoapps/instructor/views.py +++ b/lms/djangoapps/instructor/views.py @@ -19,9 +19,13 @@ from mitxmako.shortcuts import render_to_response from django.core.urlresolvers import reverse from courseware import grades -from courseware.access import has_access, get_access_group_name -from courseware.courses import get_course_with_access -from django_comment_client.models import Role, FORUM_ROLE_ADMINISTRATOR, FORUM_ROLE_MODERATOR, FORUM_ROLE_COMMUNITY_TA +from courseware.access import (has_access, get_access_group_name, + course_beta_test_group_name) +from courseware.courses import get_course_with_access +from django_comment_client.models import (Role, + FORUM_ROLE_ADMINISTRATOR, + FORUM_ROLE_MODERATOR, + FORUM_ROLE_COMMUNITY_TA) from django_comment_client.utils import has_forum_access from psychometrics import psychoanalyze from student.models import CourseEnrollment, CourseEnrollmentAllowed @@ -44,13 +48,12 @@ FORUM_ROLE_REMOVE = 'remove' @ensure_csrf_cookie @cache_control(no_cache=True, no_store=True, must_revalidate=True) - def instructor_dashboard(request, course_id): """Display the instructor dashboard for a course.""" course = get_course_with_access(request.user, course_id, 'staff') instructor_access = has_access(request.user, course, 'instructor') # an instructor can manage staff lists - + forum_admin_access = has_forum_access(request.user, course_id, FORUM_ROLE_ADMINISTRATOR) msg = '' @@ -105,6 +108,16 @@ def instructor_dashboard(request, course_id): except Group.DoesNotExist: group = Group(name=grpname) # create the group group.save() + + def get_beta_group(course): + """ + Get the group for beta testers of course. + """ + # Not using get_group because there is no access control action called + # 'beta', so adding it to get_access_group_name doesn't really make + # sense. + name = course_beta_test_group_name(course.location) + (group, created) = Group.objects.get_or_create(name=name) return group # process actions from form POST @@ -310,26 +323,64 @@ def instructor_dashboard(request, course_id): user.groups.remove(group) track.views.server_track(request, 'remove-instructor {0}'.format(user), {}, page='idashboard') + #---------------------------------------- + # Group management + + elif 'List beta testers' in action: + group = get_beta_group(course) + msg += 'Beta test group = {0}'.format(group.name) + datatable = _group_members_table(group, "List of beta_testers", course_id) + track.views.server_track(request, 'list-beta-testers', {}, page='idashboard') + + elif action == 'Add beta testers': + uname = request.POST['betausers'] + try: + user = User.objects.get(username=uname) + except User.DoesNotExist: + msg += 'Error: unknown username "{0}"'.format(uname) + user = None + if user is not None: + group = get_beta_group(course) + msg += 'Added {0} to beta testers group = {1}'.format(user, group.name) + log.debug('staffgrp={0}'.format(group.name)) + user.groups.add(group) + track.views.server_track(request, 'add-beta-tester {0}'.format(user), {}, page='idashboard') + + elif action == 'Remove beta testers': + uname = request.POST['betausers'] + try: + user = User.objects.get(username=uname) + except User.DoesNotExist: + msg += 'Error: unknown username "{0}"'.format(uname) + user = None + if user is not None: + group = get_beta_group(course) + msg += 'Removed {0} from beta tester group = {1}'.format(user, group.name) + log.debug('staffgrp={0}'.format(group.name)) + user.groups.remove(group) + track.views.server_track(request, 'remove-beta-tester {0}'.format(user), {}, page='idashboard') + + #---------------------------------------- # forum administration - + elif action == 'List course forum admins': rolename = FORUM_ROLE_ADMINISTRATOR datatable = {} msg += _list_course_forum_members(course_id, rolename, datatable) track.views.server_track(request, 'list-{0}'.format(rolename), {}, page='idashboard') - - + + elif action == 'Remove forum admin': uname = request.POST['forumadmin'] msg += _update_forum_role_membership(uname, course, FORUM_ROLE_ADMINISTRATOR, FORUM_ROLE_REMOVE) - track.views.server_track(request, '{0} {1} as {2} for {3}'.format(FORUM_ROLE_REMOVE, uname, FORUM_ROLE_ADMINISTRATOR, course_id), + track.views.server_track(request, '{0} {1} as {2} for {3}'.format(FORUM_ROLE_REMOVE, uname, FORUM_ROLE_ADMINISTRATOR, course_id), {}, page='idashboard') elif action == 'Add forum admin': uname = request.POST['forumadmin'] msg += _update_forum_role_membership(uname, course, FORUM_ROLE_ADMINISTRATOR, FORUM_ROLE_ADD) - track.views.server_track(request, '{0} {1} as {2} for {3}'.format(FORUM_ROLE_ADD, uname, FORUM_ROLE_ADMINISTRATOR, course_id), + track.views.server_track(request, '{0} {1} as {2} for {3}'.format(FORUM_ROLE_ADD, uname, FORUM_ROLE_ADMINISTRATOR, course_id), {}, page='idashboard') elif action == 'List course forum moderators': @@ -337,35 +388,35 @@ def instructor_dashboard(request, course_id): datatable = {} msg += _list_course_forum_members(course_id, rolename, datatable) track.views.server_track(request, 'list-{0}'.format(rolename), {}, page='idashboard') - + elif action == 'Remove forum moderator': uname = request.POST['forummoderator'] msg += _update_forum_role_membership(uname, course, FORUM_ROLE_MODERATOR, FORUM_ROLE_REMOVE) - track.views.server_track(request, '{0} {1} as {2} for {3}'.format(FORUM_ROLE_REMOVE, uname, FORUM_ROLE_MODERATOR, course_id), + track.views.server_track(request, '{0} {1} as {2} for {3}'.format(FORUM_ROLE_REMOVE, uname, FORUM_ROLE_MODERATOR, course_id), {}, page='idashboard') - + elif action == 'Add forum moderator': uname = request.POST['forummoderator'] msg += _update_forum_role_membership(uname, course, FORUM_ROLE_MODERATOR, FORUM_ROLE_ADD) - track.views.server_track(request, '{0} {1} as {2} for {3}'.format(FORUM_ROLE_ADD, uname, FORUM_ROLE_MODERATOR, course_id), + track.views.server_track(request, '{0} {1} as {2} for {3}'.format(FORUM_ROLE_ADD, uname, FORUM_ROLE_MODERATOR, course_id), {}, page='idashboard') - + elif action == 'List course forum community TAs': rolename = FORUM_ROLE_COMMUNITY_TA datatable = {} msg += _list_course_forum_members(course_id, rolename, datatable) track.views.server_track(request, 'list-{0}'.format(rolename), {}, page='idashboard') - + elif action == 'Remove forum community TA': uname = request.POST['forummoderator'] msg += _update_forum_role_membership(uname, course, FORUM_ROLE_COMMUNITY_TA, FORUM_ROLE_REMOVE) - track.views.server_track(request, '{0} {1} as {2} for {3}'.format(FORUM_ROLE_REMOVE, uname, FORUM_ROLE_COMMUNITY_TA, course_id), + track.views.server_track(request, '{0} {1} as {2} for {3}'.format(FORUM_ROLE_REMOVE, uname, FORUM_ROLE_COMMUNITY_TA, course_id), {}, page='idashboard') - + elif action == 'Add forum community TA': uname = request.POST['forummoderator'] msg += _update_forum_role_membership(uname, course, FORUM_ROLE_COMMUNITY_TA, FORUM_ROLE_ADD) - track.views.server_track(request, '{0} {1} as {2} for {3}'.format(FORUM_ROLE_ADD, uname, FORUM_ROLE_COMMUNITY_TA, course_id), + track.views.server_track(request, '{0} {1} as {2} for {3}'.format(FORUM_ROLE_ADD, uname, FORUM_ROLE_COMMUNITY_TA, course_id), {}, page='idashboard') #---------------------------------------- @@ -418,7 +469,7 @@ def instructor_dashboard(request, course_id): msg2, datatable = _do_remote_gradebook(request.user, course, 'get-sections') msg += msg2 - elif action in ['List students in section in remote gradebook', + elif action in ['List students in section in remote gradebook', 'Overload enrollment list using remote gradebook', 'Merge enrollment list with remote gradebook']: @@ -431,7 +482,7 @@ def instructor_dashboard(request, course_id): overload = 'Overload' in action ret = _do_enroll_students(course, course_id, students, overload=overload) datatable = ret['datatable'] - + #---------------------------------------- # psychometrics @@ -448,7 +499,7 @@ def instructor_dashboard(request, course_id): #---------------------------------------- # offline grades? - + if use_offline: msg += "
Grades from %s" % offline_grades_available(course_id) @@ -482,17 +533,17 @@ def _do_remote_gradebook(user, course, action, args=None, files=None): if not rg: msg = "No remote gradebook defined in course metadata" return msg, {} - + rgurl = settings.MITX_FEATURES.get('REMOTE_GRADEBOOK_URL','') if not rgurl: msg = "No remote gradebook url defined in settings.MITX_FEATURES" return msg, {} - + rgname = rg.get('name','') if not rgname: msg = "No gradebook name defined in course remote_gradebook metadata" return msg, {} - + if args is None: args = {} data = dict(submit=action, gradebook=rgname, user=user.email) @@ -522,15 +573,15 @@ def _do_remote_gradebook(user, course, action, args=None, files=None): return msg, datatable def _list_course_forum_members(course_id, rolename, datatable): - ''' + """ Fills in datatable with forum membership information, for a given role, so that it will be displayed on instructor dashboard. - + course_ID = the ID string for a course rolename = one of "Administrator", "Moderator", "Community TA" - + Returns message status string to append to displayed message, if role is unknown. - ''' + """ # make sure datatable is set up properly for display first, before checking for errors datatable['header'] = ['Username', 'Full name', 'Roles'] datatable['title'] = 'List of Forum {0}s in course {1}'.format(rolename, course_id) @@ -549,13 +600,13 @@ def _list_course_forum_members(course_id, rolename, datatable): def _update_forum_role_membership(uname, course, rolename, add_or_remove): ''' Supports adding a user to a course's forum role - + uname = username string for user - course = course object + course = course object rolename = one of "Administrator", "Moderator", "Community TA" add_or_remove = one of "add" or "remove" - - Returns message status string to append to displayed message, Status is returned if user + + Returns message status string to append to displayed message, Status is returned if user or role is unknown, or if entry already exists when adding, or if entry doesn't exist when removing. ''' # check that username and rolename are valid: @@ -575,21 +626,42 @@ def _update_forum_role_membership(uname, course, rolename, add_or_remove): if add_or_remove == FORUM_ROLE_REMOVE: if not alreadyexists: msg ='Error: user "{0}" does not have rolename "{1}", cannot remove'.format(uname, rolename) - else: + else: user.roles.remove(role) msg = 'Removed "{0}" from "{1}" forum role = "{2}"'.format(user, course.id, rolename) else: if alreadyexists: msg = 'Error: user "{0}" already has rolename "{1}", cannot add'.format(uname, rolename) - else: - if (rolename == FORUM_ROLE_ADMINISTRATOR and not has_access(user, course, 'staff')): + else: + if (rolename == FORUM_ROLE_ADMINISTRATOR and not has_access(user, course, 'staff')): msg = 'Error: user "{0}" should first be added as staff before adding as a forum administrator, cannot add'.format(uname) else: user.roles.add(role) msg = 'Added "{0}" to "{1}" forum role = "{2}"'.format(user, course.id, rolename) return msg - + +def _group_members_table(group, title, course_id): + """ + Return a data table of usernames and names of users in group_name. + + Arguments: + group -- a django group. + title -- a descriptive title to show the user + + Returns: + a dictionary with keys + 'header': ['Username', 'Full name'], + 'data': [[username, name] for all users] + 'title': "{title} in course {course}" + """ + uset = group.user_set.all() + datatable = {'header': ['Username', 'Full name']} + datatable['data'] = [[x.username, x.profile.name] for x in uset] + datatable['title'] = '{0} in course {1}'.format(title, course_id) + return datatable + + def get_student_grade_summary_data(request, course, course_id, get_grades=True, get_raw_scores=False, use_offline=False): ''' @@ -750,7 +822,7 @@ def _do_enroll_students(course, course_id, students, overload=False): def sf(stat): return [x for x in status if status[x]==stat] - data = dict(added=sf('added'), rejected=sf('rejected')+sf('exists'), + data = dict(added=sf('added'), rejected=sf('rejected')+sf('exists'), deleted=sf('deleted'), datatable=datatable) return data diff --git a/lms/templates/courseware/instructor_dashboard.html b/lms/templates/courseware/instructor_dashboard.html index 7f1912cd45..04248336e5 100644 --- a/lms/templates/courseware/instructor_dashboard.html +++ b/lms/templates/courseware/instructor_dashboard.html @@ -59,7 +59,7 @@ function goto( mode) Admin | Forum Admin | Enrollment - ] + Manage Groups ]
${djangopid} @@ -168,7 +168,8 @@ function goto( mode)

- + +


%endif @@ -258,6 +259,21 @@ function goto( mode) ##----------------------------------------------------------------------------- +%if modeflag.get('Manage Groups'): + %if instructor_access: +
+

+ +

+ Enter usernames or emails for students who should be beta-testers. They will get to see course materials early, as configured via the days_early_for_beta option in the course policy. +

+ + + +
+ %endif +%endif + ##-----------------------------------------------------------------------------