Commit 7bdb5c23 by Victor Shnayder Committed by Victor Shnayder

Allow listing of users in cohorts and adding users

- still needs style, more features, but basic functionality works
parent 64f82082
...@@ -80,6 +80,15 @@ def get_cohort_by_name(course_id, name): ...@@ -80,6 +80,15 @@ def get_cohort_by_name(course_id, name):
group_type=CourseUserGroup.COHORT, group_type=CourseUserGroup.COHORT,
name=name) name=name)
def get_cohort_by_id(course_id, cohort_id):
"""
Return the CourseUserGroup object for the given cohort. Raises DoesNotExist
it isn't present. Uses the course_id for extra validation...
"""
return CourseUserGroup.objects.get(course_id=course_id,
group_type=CourseUserGroup.COHORT,
id=cohort_id)
def add_cohort(course_id, name): def add_cohort(course_id, name):
""" """
Add a cohort to a course. Raises ValueError if a cohort of the same name already Add a cohort to a course. Raises ValueError if a cohort of the same name already
...@@ -95,6 +104,34 @@ def add_cohort(course_id, name): ...@@ -95,6 +104,34 @@ def add_cohort(course_id, name):
group_type=CourseUserGroup.COHORT, group_type=CourseUserGroup.COHORT,
name=name) name=name)
def add_user_to_cohort(cohort, username_or_email):
"""
Look up the given user, and if successful, add them to the specified cohort.
Arguments:
cohort: CourseUserGroup
username_or_email: string. Treated as email if has '@'
Returns:
User object.
Raises:
User.DoesNotExist if can't find user.
ValueError if user already present.
"""
if '@' in username_or_email:
user = User.objects.get(email=username_or_email)
else:
user = User.objects.get(username=username_or_email)
if cohort.users.filter(id=user.id).exists():
raise ValueError("User {0} already present".format(user.username))
cohort.users.add(user)
return user
def get_course_cohort_names(course_id): def get_course_cohort_names(course_id):
""" """
Return a list of the cohort names in a course. Return a list of the cohort names in a course.
......
import json import json
from django_future.csrf import ensure_csrf_cookie from django_future.csrf import ensure_csrf_cookie
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.core.context_processors import csrf from django.core.context_processors import csrf
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.http import HttpResponse, HttpResponseForbidden, Http404 from django.http import HttpResponse, HttpResponseForbidden, Http404
from django.shortcuts import redirect from django.shortcuts import redirect
...@@ -9,6 +11,8 @@ import logging ...@@ -9,6 +11,8 @@ import logging
from courseware.courses import get_course_with_access from courseware.courses import get_course_with_access
from mitxmako.shortcuts import render_to_response, render_to_string from mitxmako.shortcuts import render_to_response, render_to_string
from string_util import split_by_comma_and_whitespace
from .models import CourseUserGroup from .models import CourseUserGroup
from . import models from . import models
...@@ -81,19 +85,84 @@ def add_cohort(request, course_id): ...@@ -81,19 +85,84 @@ def add_cohort(request, course_id):
@ensure_csrf_cookie @ensure_csrf_cookie
def users_in_cohort(request, course_id, cohort_id): def users_in_cohort(request, course_id, cohort_id):
""" """
Return users in the cohort. Show up to 100 per page, and page
using the 'page' GET attribute in the call. Format:
Returns:
Json dump of dictionary in the following format:
{'success': True,
'page': page,
'num_pages': paginator.num_pages,
'users': [{'username': ..., 'email': ..., 'name': ...}]
}
""" """
get_course_with_access(request.user, course_id, 'staff') get_course_with_access(request.user, course_id, 'staff')
return JsonHttpReponse({'error': 'Not implemented'}) cohort = models.get_cohort_by_id(course_id, int(cohort_id))
paginator = Paginator(cohort.users.all(), 100)
page = request.GET.get('page')
try:
users = paginator.page(page)
except PageNotAnInteger:
# return the first page
page = 1
users = paginator.page(page)
except EmptyPage:
# Page is out of range. Return last page
page = paginator.num_pages
contacts = paginator.page(page)
user_info = [{'username': u.username,
'email': u.email,
'name': '{0} {1}'.format(u.first_name, u.last_name)}
for u in users]
return JsonHttpReponse({'success': True,
'page': page,
'num_pages': paginator.num_pages,
'users': user_info})
@ensure_csrf_cookie @ensure_csrf_cookie
def add_users_to_cohort(request, course_id): def add_users_to_cohort(request, course_id, cohort_id):
""" """
Return json dict of:
{'success': True,
'added': [{'username': username,
'name': name,
'email': email}, ...],
'present': [str1, str2, ...], # already there
'unknown': [str1, str2, ...]}
""" """
get_course_with_access(request.user, course_id, 'staff') get_course_with_access(request.user, course_id, 'staff')
return JsonHttpReponse({'error': 'Not implemented'}) if request.method != "POST":
raise Http404("Must POST to add users to cohorts")
cohort = models.get_cohort_by_id(course_id, cohort_id)
users = request.POST.get('users', '')
added = []
present = []
unknown = []
for username_or_email in split_by_comma_and_whitespace(users):
try:
user = models.add_user_to_cohort(cohort, username_or_email)
added.append({'username': user.username,
'name': "{0} {1}".format(user.first_name, user.last_name),
'email': user.email,
})
except ValueError:
present.append(username_or_email)
except User.DoesNotExist:
unknown.append(username_or_email)
return JsonHttpReponse({'success': True,
'added': added,
'present': present,
'unknown': unknown})
def debug_cohort_mgmt(request, course_id): def debug_cohort_mgmt(request, course_id):
...@@ -102,7 +171,7 @@ def debug_cohort_mgmt(request, course_id): ...@@ -102,7 +171,7 @@ def debug_cohort_mgmt(request, course_id):
""" """
# add staff check to make sure it's safe if it's accidentally deployed. # add staff check to make sure it's safe if it's accidentally deployed.
get_course_with_access(request.user, course_id, 'staff') get_course_with_access(request.user, course_id, 'staff')
context = {'cohorts_ajax_url': reverse('cohorts', context = {'cohorts_ajax_url': reverse('cohorts',
kwargs={'course_id': course_id})} kwargs={'course_id': course_id})}
return render_to_response('/course_groups/debug.html', context) return render_to_response('/course_groups/debug.html', context)
import itertools
def split_by_comma_and_whitespace(s):
"""
Split a string both by on commas and whitespice.
"""
# Note: split() with no args removes empty strings from output
lists = [x.split() for x in s.split(',')]
# return all of them
return itertools.chain(*lists)
...@@ -36,19 +36,51 @@ var CohortManager = (function ($) { ...@@ -36,19 +36,51 @@ var CohortManager = (function ($) {
// constructor // constructor
var module = function () { var module = function () {
var url = $(".cohort_manager").data('ajax_url'); var el = $(".cohort_manager");
// localized jquery
var $$ = function (selector) {
return $(selector, el)
}
var state_init = "init";
var state_summary = "summary";
var state_detail = "detail";
var state = state_init;
var url = el.data('ajax_url');
var self = this; var self = this;
var error_list = $(".cohort_errors");
var cohort_list = $(".cohort_list"); // Pull out the relevant parts of the html
var cohorts_display = $(".cohorts_display"); // global stuff
var show_cohorts_button = $(".cohort_controls .show_cohorts"); var errors = $$(".errors");
var add_cohort_input = $("#cohort-name");
var add_cohort_button = $(".add_cohort"); // cohort summary display
var summary = $$(".summary");
var cohorts = $$(".cohorts");
var show_cohorts_button = $$(".controls .show_cohorts");
var add_cohort_input = $$(".cohort_name");
var add_cohort_button = $$(".add_cohort");
// single cohort user display
var detail = $$(".detail");
var detail_header = $(".header", detail);
var detail_users = $$(".users");
var detail_page_num = $$(".page_num");
var users_area = $$(".users_area");
var add_members_button = $$(".add_members");
var op_results = $$("op_results");
var cohort_title = null;
var detail_url = null;
var page = null;
// *********** Summary view methods
function show_cohort(item) { function show_cohort(item) {
// item is a li that has a data-href link to the cohort base url // item is a li that has a data-href link to the cohort base url
var el = $(this); var el = $(this);
alert("would show you data about " + el.text() + " from " + el.data('href')); cohort_title = el.text();
detail_url = el.data('href');
state = state_detail;
render();
} }
function add_to_cohorts_list(item) { function add_to_cohorts_list(item) {
...@@ -57,24 +89,25 @@ var CohortManager = (function ($) { ...@@ -57,24 +89,25 @@ var CohortManager = (function ($) {
.data('href', url + '/' + item.id) .data('href', url + '/' + item.id)
.addClass('link') .addClass('link')
.click(show_cohort); .click(show_cohort);
cohort_list.append(li); cohorts.append(li);
}; };
function log_error(msg) { function log_error(msg) {
error_list.empty(); errors.empty();
error_list.append($("<li />").text(msg).addClass("error")); errors.append($("<li />").text(msg).addClass("error"));
}; };
function load_cohorts(response) { function load_cohorts(response) {
cohort_list.empty(); cohorts.empty();
if (response && response.success) { if (response && response.success) {
response.cohorts.forEach(add_to_cohorts_list); response.cohorts.forEach(add_to_cohorts_list);
} else { } else {
log_error(response.msg || "There was an error loading cohorts"); log_error(response.msg || "There was an error loading cohorts");
} }
cohorts_display.show(); summary.show();
}; };
function added_cohort(response) { function added_cohort(response) {
if (response && response.success) { if (response && response.success) {
add_to_cohorts_list(response.cohort); add_to_cohorts_list(response.cohort);
...@@ -83,8 +116,75 @@ var CohortManager = (function ($) { ...@@ -83,8 +116,75 @@ var CohortManager = (function ($) {
} }
} }
// *********** Detail view methods
function add_to_users_list(item) {
var tr = $('<tr><td class="name"></td><td class="username"></td>' +
'<td class="email"></td></tr>');
$(".name", tr).text(item.name);
$(".username", tr).text(item.username);
$(".email", tr).text(item.email);
detail_users.append(tr);
};
function show_users(response) {
detail_users.html("<tr><th>Name</th><th>Username</th><th>Email</th></tr>");
if (response && response.success) {
response.users.forEach(add_to_users_list);
detail_page_num.text("Page " + response.page + " of " + response.num_pages);
} else {
log_error(response.msg ||
"There was an error loading users for " + cohort.title);
}
detail.show();
}
function added_users(response) {
function adder(note, color) {
return function(item) {
var li = $('<li></li>')
li.text(note + ' ' + item.name + ', ' + item.username + ', ' + item.email);
li.css('color', color);
op_results.append(li);
}
}
if (response && response.success) {
response.added.forEach(adder("Added", "green"));
response.present.forEach(adder("Already present:", "black"));
response.unknown.forEach(adder("Already present:", "red"));
} else {
log_error(response.msg || "There was an error adding users");
}
}
// ******* Rendering
function render() {
// Load and render the right thing based on the state
// start with both divs hidden
summary.hide();
detail.hide();
// and clear out the errors
errors.empty();
if (state == state_summary) {
$.ajax(url).done(load_cohorts).fail(function() {
log_error("Error trying to load cohorts");
});
} else if (state == state_detail) {
detail_header.text("Members of " + cohort_title);
$.ajax(detail_url).done(show_users).fail(function() {
log_error("Error trying to load users in cohort");
});
}
}
show_cohorts_button.click(function() { show_cohorts_button.click(function() {
$.ajax(url).done(load_cohorts); state = state_summary;
render();
}); });
add_cohort_input.change(function() { add_cohort_input.change(function() {
...@@ -101,6 +201,13 @@ var CohortManager = (function ($) { ...@@ -101,6 +201,13 @@ var CohortManager = (function ($) {
$.post(add_url, data).done(added_cohort); $.post(add_url, data).done(added_cohort);
}); });
add_members_button.click(function() {
var add_url = detail_url + '/add';
data = {'users': users_area.val()}
$.post(add_url, data).done(added_users);
});
}; };
// prototype // prototype
......
...@@ -24,14 +24,15 @@ from courseware import grades ...@@ -24,14 +24,15 @@ from courseware import grades
from courseware.access import (has_access, get_access_group_name, from courseware.access import (has_access, get_access_group_name,
course_beta_test_group_name) course_beta_test_group_name)
from courseware.courses import get_course_with_access from courseware.courses import get_course_with_access
from courseware.models import StudentModule
from django_comment_client.models import (Role, from django_comment_client.models import (Role,
FORUM_ROLE_ADMINISTRATOR, FORUM_ROLE_ADMINISTRATOR,
FORUM_ROLE_MODERATOR, FORUM_ROLE_MODERATOR,
FORUM_ROLE_COMMUNITY_TA) FORUM_ROLE_COMMUNITY_TA)
from django_comment_client.utils import has_forum_access from django_comment_client.utils import has_forum_access
from psychometrics import psychoanalyze from psychometrics import psychoanalyze
from string_util import split_by_comma_and_whitespace
from student.models import CourseEnrollment, CourseEnrollmentAllowed from student.models import CourseEnrollment, CourseEnrollmentAllowed
from courseware.models import StudentModule
from xmodule.course_module import CourseDescriptor from xmodule.course_module import CourseDescriptor
from xmodule.modulestore import Location from xmodule.modulestore import Location
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
...@@ -392,14 +393,14 @@ def instructor_dashboard(request, course_id): ...@@ -392,14 +393,14 @@ def instructor_dashboard(request, course_id):
users = request.POST['betausers'] users = request.POST['betausers']
log.debug("users: {0!r}".format(users)) log.debug("users: {0!r}".format(users))
group = get_beta_group(course) group = get_beta_group(course)
for username_or_email in _split_by_comma_and_whitespace(users): for username_or_email in split_by_comma_and_whitespace(users):
msg += "<p>{0}</p>".format( msg += "<p>{0}</p>".format(
add_user_to_group(request, username_or_email, group, 'beta testers', 'beta-tester')) add_user_to_group(request, username_or_email, group, 'beta testers', 'beta-tester'))
elif action == 'Remove beta testers': elif action == 'Remove beta testers':
users = request.POST['betausers'] users = request.POST['betausers']
group = get_beta_group(course) group = get_beta_group(course)
for username_or_email in _split_by_comma_and_whitespace(users): for username_or_email in split_by_comma_and_whitespace(users):
msg += "<p>{0}</p>".format( msg += "<p>{0}</p>".format(
remove_user_from_group(request, username_or_email, group, 'beta testers', 'beta-tester')) remove_user_from_group(request, username_or_email, group, 'beta testers', 'beta-tester'))
...@@ -871,21 +872,11 @@ def grade_summary(request, course_id): ...@@ -871,21 +872,11 @@ def grade_summary(request, course_id):
#----------------------------------------------------------------------------- #-----------------------------------------------------------------------------
# enrollment # enrollment
def _split_by_comma_and_whitespace(s):
"""
Split a string both by on commas and whitespice.
"""
# Note: split() with no args removes empty strings from output
lists = [x.split() for x in s.split(',')]
# return all of them
return itertools.chain(*lists)
def _do_enroll_students(course, course_id, students, overload=False): def _do_enroll_students(course, course_id, students, overload=False):
"""Do the actual work of enrolling multiple students, presented as a string """Do the actual work of enrolling multiple students, presented as a string
of emails separated by commas or returns""" of emails separated by commas or returns"""
new_students = _split_by_comma_and_whitespace(students) new_students = split_by_comma_and_whitespace(students)
new_students = [str(s.strip()) for s in new_students] new_students = [str(s.strip()) for s in new_students]
new_students_lc = [x.lower() for x in new_students] new_students_lc = [x.lower() for x in new_students]
......
<section class="cohort_manager" data-ajax_url="${cohorts_ajax_url}"> <section class="cohort_manager" data-ajax_url="${cohorts_ajax_url}">
<h3>Cohort groups</h3> <h3>Cohort groups</h3>
<div class="cohort_controls"> <div class="controls">
<a href="#" class="button show_cohorts">Show cohorts</a> <a href="#" class="button show_cohorts">Show cohorts</a>
</div> </div>
<ul class="cohort_errors"> <ul class="errors">
</ul> </ul>
<div class="cohorts_display" style="display:none"> <div class="summary" style="display:none">
<h3>Cohorts in the course</h3> <h3>Cohorts in the course</h3>
<ul class="cohort_list"> <ul class="cohorts">
</ul> </ul>
<p> <p>
<input id="cohort-name"/> <input class="cohort_name"/>
<a href="#" class="button add_cohort">Add cohort</a> <a href="#" class="button add_cohort">Add cohort</a>
</p> </p>
</div>
<div class="detail" style="display:none">
<h3 class="header"></h3>
<table class="users">
</table>
<span class="page_num"></span>
<p>
Add users by username or email. One per line or comma-separated.
</p>
<textarea cols="50" row="30" class="users_area"></textarea>
<a href="#" class="button add_members">Add cohort members</a>
<ul class="op_results">
</ul>
</div> </div>
......
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