Commit 5ea7ced9 by cahrens Committed by Andy Armstrong

Create a RESTful handle_cohort view method for GET and PUT/POST.

parent 9b85ac1f
......@@ -3,7 +3,21 @@
idAttribute: 'id',
defaults: {
name: '',
user_count: 0
user_count: 0,
/**
* Indicates how students are added to the cohort. Will be "none" (signifying manual assignment) or
* "random" (indicating students are randomly assigned).
*/
assignment_type: '',
/**
* If this cohort is associated with a user partition group, the ID of the user partition.
*/
user_partition_id: null,
/**
* If this cohort is associated with a user partition group, the ID of the group within the
* partition associated with user_partition_id.
*/
group_id: null
}
});
......
......@@ -345,11 +345,8 @@ if settings.COURSEWARE_ENABLED:
'open_ended_grading.views.take_action_on_flags', name='open_ended_flagged_problems_take_action'),
# Cohorts management
url(r'^courses/{}/cohorts$'.format(settings.COURSE_KEY_PATTERN),
'openedx.core.djangoapps.course_groups.views.list_cohorts', name="cohorts"),
url(r'^courses/{}/cohorts/add$'.format(settings.COURSE_KEY_PATTERN),
'openedx.core.djangoapps.course_groups.views.add_cohort',
name="add_cohort"),
url(r'^courses/{}/cohorts/(?P<cohort_id>[0-9]+)?$'.format(settings.COURSE_KEY_PATTERN),
'openedx.core.djangoapps.course_groups.views.cohort_handler', name="cohorts"),
url(r'^courses/{}/cohorts/(?P<cohort_id>[0-9]+)$'.format(settings.COURSE_KEY_PATTERN),
'openedx.core.djangoapps.course_groups.views.users_in_cohort',
name="list_cohort"),
......
......@@ -21,6 +21,7 @@ from opaque_keys.edx.locations import SlashSeparatedCourseKey
from openedx.core.djangoapps.user_api.partition_schemes import RandomUserPartitionScheme
from ..partition_scheme import CohortPartitionScheme, get_cohorted_user_partition
from ..models import CourseUserGroupPartitionGroup
from ..views import link_cohort_to_partition_group, unlink_cohort_partition_group
from ..cohorts import add_user_to_cohort, get_course_cohorts
from .helpers import CohortFactory, config_course_cohorts
......@@ -55,22 +56,6 @@ class TestCohortPartitionScheme(django.test.TestCase):
)
self.student = UserFactory.create()
def link_cohort_partition_group(self, cohort, partition, group):
"""
Utility for creating cohort -> partition group links
"""
CourseUserGroupPartitionGroup(
course_user_group=cohort,
partition_id=partition.id,
group_id=group.id,
).save()
def unlink_cohort_partition_group(self, cohort):
"""
Utility for removing cohort -> partition group links
"""
CourseUserGroupPartitionGroup.objects.filter(course_user_group=cohort).delete()
def assert_student_in_group(self, group, partition=None):
"""
Utility for checking that our test student comes up as assigned to the
......@@ -99,16 +84,16 @@ class TestCohortPartitionScheme(django.test.TestCase):
self.assert_student_in_group(None)
# link first cohort to group 0 in the partition
self.link_cohort_partition_group(
link_cohort_to_partition_group(
first_cohort,
self.user_partition,
self.groups[0],
self.user_partition.id,
self.groups[0].id,
)
# link second cohort to to group 1 in the partition
self.link_cohort_partition_group(
link_cohort_to_partition_group(
second_cohort,
self.user_partition,
self.groups[1],
self.user_partition.id,
self.groups[1].id,
)
self.assert_student_in_group(self.groups[0])
......@@ -133,26 +118,26 @@ class TestCohortPartitionScheme(django.test.TestCase):
self.assert_student_in_group(None)
# link cohort to group 0
self.link_cohort_partition_group(
link_cohort_to_partition_group(
test_cohort,
self.user_partition,
self.groups[0],
self.user_partition.id,
self.groups[0].id,
)
# now the scheme should find a link
self.assert_student_in_group(self.groups[0])
# link cohort to group 1 (first unlink it from group 0)
self.unlink_cohort_partition_group(test_cohort)
self.link_cohort_partition_group(
unlink_cohort_partition_group(test_cohort)
link_cohort_to_partition_group(
test_cohort,
self.user_partition,
self.groups[1],
self.user_partition.id,
self.groups[1].id,
)
# scheme should pick up the link
self.assert_student_in_group(self.groups[1])
# unlink cohort from anywhere
self.unlink_cohort_partition_group(
unlink_cohort_partition_group(
test_cohort,
)
# scheme should now return nothing
......@@ -171,10 +156,10 @@ class TestCohortPartitionScheme(django.test.TestCase):
cohort = get_course_cohorts(self.course)[0]
# map that cohort to a group in our partition
self.link_cohort_partition_group(
link_cohort_to_partition_group(
cohort,
self.user_partition,
self.groups[0],
self.user_partition.id,
self.groups[0].id,
)
# The student will be lazily assigned to the default cohort
......@@ -190,10 +175,10 @@ class TestCohortPartitionScheme(django.test.TestCase):
test_cohort = CohortFactory(course_id=self.course_key)
# link cohort to group 0
self.link_cohort_partition_group(
link_cohort_to_partition_group(
test_cohort,
self.user_partition,
self.groups[0],
self.user_partition.id,
self.groups[0].id,
)
# place student into cohort
add_user_to_cohort(test_cohort, self.student.username)
......
......@@ -4,6 +4,9 @@ from django.contrib.auth.models import User
from django.core.paginator import Paginator, EmptyPage
from django.core.urlresolvers import reverse
from django.http import Http404, HttpResponse, HttpResponseBadRequest
from django.views.decorators.http import require_http_methods
from util.json_request import expect_json, JsonResponse
from django.contrib.auth.decorators import login_required
import json
import logging
import re
......@@ -12,9 +15,8 @@ from opaque_keys.edx.locations import SlashSeparatedCourseKey
from courseware.courses import get_course_with_access
from edxmako.shortcuts import render_to_response
from util.json_request import JsonResponse
from . import cohorts
from .models import CourseUserGroup
from .models import CourseUserGroup, CourseUserGroupPartitionGroup
log = logging.getLogger(__name__)
......@@ -29,76 +31,119 @@ def json_http_response(data):
def split_by_comma_and_whitespace(cstr):
"""
Split a string both by commas and whitespice. Returns a list.
Split a string both by commas and whitespace. Returns a list.
"""
return re.split(r'[\s,]+', cstr)
@ensure_csrf_cookie
def list_cohorts(request, course_key_string):
def link_cohort_to_partition_group(cohort, partition_id, group_id):
"""
Return json dump of dict:
{'success': True,
'cohorts': [{'name': name, 'id': id}, ...]}
Create cohort to partition_id/group_id link.
"""
CourseUserGroupPartitionGroup(
course_user_group=cohort,
partition_id=partition_id,
group_id=group_id,
).save()
# this is a string when we get it here
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_key_string)
course = get_course_with_access(request.user, 'staff', course_key)
def unlink_cohort_partition_group(cohort):
"""
Remove any existing cohort to partition_id/group_id link.
"""
CourseUserGroupPartitionGroup.objects.filter(course_user_group=cohort).delete()
all_cohorts = [
{
'name': c.name,
'id': c.id,
'user_count': c.users.count(),
'assignment_type': cohorts.CohortAssignmentType.get(c, course)
}
for c in cohorts.get_course_cohorts(course)
]
return json_http_response({'success': True,
'cohorts': all_cohorts})
def _get_cohort_representation(cohort, course):
"""
Returns a JSON representation of a cohort.
"""
group_id, partition_id = cohorts.get_group_info_for_cohort(cohort)
return {
'name': cohort.name,
'id': cohort.id,
'user_count': cohort.users.count(),
'assignment_type': cohorts.CohortAssignmentType.get(cohort, course),
'user_partition_id': partition_id,
'group_id': group_id
}
@require_http_methods(("GET", "PUT", "POST", "PATCH"))
@ensure_csrf_cookie
@require_POST
def add_cohort(request, course_key_string):
@expect_json
@login_required
def cohort_handler(request, course_key_string, cohort_id=None):
"""
Return json of dict:
{'success': True,
'cohort': {'id': id,
'name': name}}
or
{'success': False,
'msg': error_msg} if there's an error
The restful handler for cohort requests. Requires JSON.
GET
If a cohort ID is specified, returns a JSON representation of the cohort
(name, id, user_count, assignment_type, user_partition_id, group_id).
If no cohort ID is specified, returns the JSON representation of all cohorts.
This is returned as a dict with the list of cohort information stored under the
key `cohorts`.
PUT or POST or PATCH
If a cohort ID is specified, updates the cohort with the specified ID. Currently the only
properties that can be updated are `name`, `user_partition_id` and `group_id`.
Returns the JSON representation of the updated cohort.
If no cohort ID is specified, creates a new cohort and returns the JSON representation of the updated
cohort.
"""
# this is a string when we get it here
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_key_string)
get_course_with_access(request.user, 'staff', course_key)
name = request.POST.get("name")
if not name:
return json_http_response({'success': False,
'msg': "No name specified"})
try:
cohort = cohorts.add_cohort(course_key, name)
except ValueError as err:
return json_http_response({'success': False,
'msg': str(err)})
return json_http_response({
'success': 'True',
'cohort': {
'id': cohort.id,
'name': cohort.name
}
})
course = get_course_with_access(request.user, 'staff', course_key)
if request.method == 'GET':
if not cohort_id:
all_cohorts = [
_get_cohort_representation(c, course)
for c in cohorts.get_course_cohorts(course)
]
# TODO: change to just directly returning the lists.
return JsonResponse({'cohorts': all_cohorts})
else:
cohort = cohorts.get_cohort_by_id(course_key, cohort_id)
return JsonResponse(_get_cohort_representation(cohort, course))
else:
# If cohort_id is specified, update the existing cohort. Otherwise, create a new cohort.
if cohort_id:
cohort = cohorts.get_cohort_by_id(course_key, cohort_id)
name = request.json.get('name')
if name != cohort.name:
if cohorts.CohortAssignmentType.get(cohort, course) == cohorts.CohortAssignmentType.RANDOM:
return JsonResponse(
# Note: error message not translated because it is not exposed to the user (UI prevents).
{"error": "Renaming of random cohorts is not supported at this time."}, 400
)
cohort.name = name
cohort.save()
else:
name = request.json.get('name')
if not name:
# Note: error message not translated because it is not exposed to the user (UI prevents this state).
return JsonResponse({"error": "In order to create a cohort, a name must be specified."}, 400)
try:
cohort = cohorts.add_cohort(course_key, name)
except ValueError as err:
return JsonResponse({"error": unicode(err)}, 400)
group_id = request.json.get('group_id')
if group_id is not None:
user_partition_id = request.json.get('user_partition_id')
if user_partition_id is None:
# Note: error message not translated because it is not exposed to the user (UI prevents this state).
return JsonResponse(
{"error": "If group_id is specified, user_partition_id must also be specified."}, 400
)
existing_group_id, existing_partition_id = cohorts.get_group_info_for_cohort(cohort)
if group_id != existing_group_id or user_partition_id != existing_partition_id:
unlink_cohort_partition_group(cohort)
link_cohort_to_partition_group(cohort, user_partition_id, group_id)
else:
# If group_id was specified as None, unlink the cohort if it previously was associated with a group.
existing_group_id, _ = cohorts.get_group_info_for_cohort(cohort)
if existing_group_id is not None:
unlink_cohort_partition_group(cohort)
return JsonResponse(_get_cohort_representation(cohort, course))
@ensure_csrf_cookie
......@@ -121,7 +166,7 @@ def users_in_cohort(request, course_key_string, cohort_id):
get_course_with_access(request.user, 'staff', course_key)
# this will error if called with a non-int cohort_id. That's ok--it
# shoudn't happen for valid clients.
# shouldn't happen for valid clients.
cohort = cohorts.get_cohort_by_id(course_key, int(cohort_id))
paginator = Paginator(cohort.users.all(), 100)
......
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