Commit bab3b0c3 by Victor Shnayder Committed by Victor Shnayder

Add initial set of views for managing cohorts

- lets user see and add cohorts
- needs styling
- needs to allow them to actually manage membership!
parent ac8c5912
import logging
from django.contrib.auth.models import User
from django.db import models
log = logging.getLogger(__name__)
class CourseUserGroup(models.Model):
"""
This model represents groups of users in a course. Groups may have different types,
......@@ -27,7 +31,6 @@ class CourseUserGroup(models.Model):
GROUP_TYPE_CHOICES = ((COHORT, 'Cohort'),)
group_type = models.CharField(max_length=20, choices=GROUP_TYPE_CHOICES)
def get_cohort(user, course_id):
"""
Given a django User and a course_id, return the user's cohort. In classes with
......@@ -65,3 +68,49 @@ def get_course_cohorts(course_id):
"""
return list(CourseUserGroup.objects.filter(course_id=course_id,
group_type=CourseUserGroup.COHORT))
### Helpers for cohor management views
def get_cohort_by_name(course_id, name):
"""
Return the CourseUserGroup object for the given cohort. Raises DoesNotExist
it isn't present.
"""
return CourseUserGroup.objects.get(course_id=course_id,
group_type=CourseUserGroup.COHORT,
name=name)
def add_cohort(course_id, name):
"""
Add a cohort to a course. Raises ValueError if a cohort of the same name already
exists.
"""
log.debug("Adding cohort %s to %s", name, course_id)
if CourseUserGroup.objects.filter(course_id=course_id,
group_type=CourseUserGroup.COHORT,
name=name).exists():
raise ValueError("Can't create two cohorts with the same name")
return CourseUserGroup.objects.create(course_id=course_id,
group_type=CourseUserGroup.COHORT,
name=name)
def get_course_cohort_names(course_id):
"""
Return a list of the cohort names in a course.
"""
return [c.name for c in get_course_cohorts(course_id)]
def delete_empty_cohort(course_id, name):
"""
Remove an empty cohort. Raise ValueError if cohort is not empty.
"""
cohort = get_cohort_by_name(course_id, name)
if cohort.users.exists():
raise ValueError(
"Can't delete non-empty cohort {0} in course {1}".format(
name, course_id))
cohort.delete()
import json
from django_future.csrf import ensure_csrf_cookie
from django.contrib.auth.decorators import login_required
from django.core.context_processors import csrf
from django.core.urlresolvers import reverse
from django.http import HttpResponse, HttpResponseForbidden, Http404
from django.shortcuts import redirect
import logging
from courseware.courses import get_course_with_access
from mitxmako.shortcuts import render_to_response, render_to_string
from .models import CourseUserGroup
from . import models
import track.views
log = logging.getLogger(__name__)
def JsonHttpReponse(data):
"""
Return an HttpResponse with the data json-serialized and the right content type
header. Named to look like a class.
"""
return HttpResponse(json.dumps(data), content_type="application/json")
@ensure_csrf_cookie
def list_cohorts(request, course_id):
"""
Return json dump of dict:
{'success': True,
'cohorts': [{'name': name, 'id': id}, ...]}
"""
get_course_with_access(request.user, course_id, 'staff')
cohorts = [{'name': c.name, 'id': c.id}
for c in models.get_course_cohorts(course_id)]
return JsonHttpReponse({'success': True,
'cohorts': cohorts})
@ensure_csrf_cookie
def add_cohort(request, course_id):
"""
Return json of dict:
{'success': True,
'cohort': {'id': id,
'name': name}}
or
{'success': False,
'msg': error_msg} if there's an error
"""
get_course_with_access(request.user, course_id, 'staff')
if request.method != "POST":
raise Http404("Must POST to add cohorts")
name = request.POST.get("name")
if not name:
return JsonHttpReponse({'success': False,
'msg': "No name specified"})
try:
cohort = models.add_cohort(course_id, name)
except ValueError as err:
return JsonHttpReponse({'success': False,
'msg': str(err)})
return JsonHttpReponse({'success': 'True',
'cohort': {
'id': cohort.id,
'name': cohort.name
}})
@ensure_csrf_cookie
def users_in_cohort(request, course_id, cohort_id):
"""
"""
get_course_with_access(request.user, course_id, 'staff')
return JsonHttpReponse({'error': 'Not implemented'})
@ensure_csrf_cookie
def add_users_to_cohort(request, course_id):
"""
"""
get_course_with_access(request.user, course_id, 'staff')
return JsonHttpReponse({'error': 'Not implemented'})
def debug_cohort_mgmt(request, course_id):
"""
Debugging view for dev.
"""
# add staff check to make sure it's safe if it's accidentally deployed.
get_course_with_access(request.user, course_id, 'staff')
context = {'cohorts_ajax_url': reverse('cohorts',
kwargs={'course_id': course_id})}
return render_to_response('/course_groups/debug.html', context)
// structure stolen from http://briancray.com/posts/javascript-module-pattern
var CohortManager = (function ($) {
// private variables and functions
// using jQuery
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie != '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = $.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) == (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
var csrftoken = getCookie('csrftoken');
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
crossDomain: false, // obviates need for sameOrigin test
beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type)) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
}
});
// constructor
var module = function () {
var url = $(".cohort_manager").data('ajax_url');
var self = this;
var error_list = $(".cohort_errors");
var cohort_list = $(".cohort_list");
var cohorts_display = $(".cohorts_display");
var show_cohorts_button = $(".cohort_controls .show_cohorts");
var add_cohort_input = $("#cohort-name");
var add_cohort_button = $(".add_cohort");
function show_cohort(item) {
// item is a li that has a data-href link to the cohort base url
var el = $(this);
alert("would show you data about " + el.text() + " from " + el.data('href'));
}
function add_to_cohorts_list(item) {
var li = $('<li><a></a></li>');
$("a", li).text(item.name)
.data('href', url + '/' + item.id)
.addClass('link')
.click(show_cohort);
cohort_list.append(li);
};
function log_error(msg) {
error_list.empty();
error_list.append($("<li />").text(msg).addClass("error"));
};
function load_cohorts(response) {
cohort_list.empty();
if (response && response.success) {
response.cohorts.forEach(add_to_cohorts_list);
} else {
log_error(response.msg || "There was an error loading cohorts");
}
cohorts_display.show();
};
function added_cohort(response) {
if (response && response.success) {
add_to_cohorts_list(response.cohort);
} else {
log_error(response.msg || "There was an error adding a cohort");
}
}
show_cohorts_button.click(function() {
$.ajax(url).done(load_cohorts);
});
add_cohort_input.change(function() {
if (!$(this).val()) {
add_cohort_button.removeClass('button').addClass('button-disabled');
} else {
add_cohort_button.removeClass('button-disabled').addClass('button');
}
});
add_cohort_button.click(function() {
var add_url = url + '/add';
data = {'name': add_cohort_input.val()}
$.post(add_url, data).done(added_cohort);
});
};
// prototype
module.prototype = {
constructor: module,
};
// return module
return module;
})(jQuery);
$(window).load(function () {
var my_module = new CohortManager();
})
......@@ -562,6 +562,7 @@ def instructor_dashboard(request, course_id):
'djangopid' : os.getpid(),
'mitx_version' : getattr(settings,'MITX_VERSION_STRING',''),
'offline_grade_log' : offline_grades_available(course_id),
'cohorts_ajax_url' : reverse('cohorts', kwargs={'course_id': course_id}),
}
return render_to_response('courseware/instructor_dashboard.html', context)
......
<section class="cohort_manager" data-ajax_url="${cohorts_ajax_url}">
<h3>Cohort groups</h3>
<div class="cohort_controls">
<a href="#" class="button show_cohorts">Show cohorts</a>
</div>
<ul class="cohort_errors">
</ul>
<div class="cohorts_display" style="display:none">
<h3>Cohorts in the course</h3>
<ul class="cohort_list">
</ul>
<p>
<input id="cohort-name"/>
<a href="#" class="button add_cohort">Add cohort</a>
</p>
</div>
</section>
<!DOCTYPE html>
<html>
<head>
<%block name="title"><title>edX</title></%block>
<script type="text/javascript" src="/static/js/vendor/jquery.min.js"></script>
<script type="text/javascript" src="/static/js/course_groups/cohorts.js"></script>
</head>
<body class="<%block name='bodyclass'/>">
<%include file="/course_groups/cohort_management.html" />
</body>
</html>
......@@ -6,6 +6,8 @@
<%static:css group='course'/>
<script type="text/javascript" src="${static.url('js/vendor/flot/jquery.flot.js')}"></script>
<script type="text/javascript" src="${static.url('js/vendor/flot/jquery.flot.axislabels.js')}"></script>
<script type="text/javascript" src="${static.url('js/course_groups/cohorts.js')}"></script>
</%block>
<%include file="/courseware/course_navigation.html" args="active_page='instructor'" />
......@@ -282,16 +284,19 @@ function goto( mode)
<input type="submit" name="action" value="Add beta testers">
</p>
<hr width="40%" style="align:left">
<%include file="/course_groups/cohort_management.html" />
%endif
%endif
</form>
##-----------------------------------------------------------------------------
%if msg:
<p></p><p>${msg}</p>
%endif
##-----------------------------------------------------------------------------
##-----------------------------------------------------------------------------
%if datatable and modeflag.get('Psychometrics') is None:
......
......@@ -274,6 +274,23 @@ if settings.COURSEWARE_ENABLED:
'open_ended_grading.peer_grading_service.save_grade', name='peer_grading_save_grade'),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/peer_grading/save_calibration_essay$',
'open_ended_grading.peer_grading_service.save_calibration_essay', name='peer_grading_save_calibration_essay'),
# Cohorts management
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/cohorts$',
'course_groups.views.list_cohorts', name="cohorts"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/cohorts/add$',
'course_groups.views.add_cohort',
name="add_cohort"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/cohorts/(?P<cohort_id>[0-9]+)$',
'course_groups.views.users_in_cohort',
name="list_cohort"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/cohorts/(?P<cohort_id>[0-9]+)/add$',
'course_groups.views.add_users_to_cohort',
name="add_to_cohort"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/cohorts/debug$',
'course_groups.views.debug_cohort_mgmt',
name="debug_cohort_mgmt"),
)
# discussion forums live within courseware, so courseware must be enabled first
......
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