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 @@ ...@@ -3,7 +3,21 @@
idAttribute: 'id', idAttribute: 'id',
defaults: { defaults: {
name: '', 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: ...@@ -345,11 +345,8 @@ if settings.COURSEWARE_ENABLED:
'open_ended_grading.views.take_action_on_flags', name='open_ended_flagged_problems_take_action'), 'open_ended_grading.views.take_action_on_flags', name='open_ended_flagged_problems_take_action'),
# Cohorts management # Cohorts management
url(r'^courses/{}/cohorts$'.format(settings.COURSE_KEY_PATTERN), url(r'^courses/{}/cohorts/(?P<cohort_id>[0-9]+)?$'.format(settings.COURSE_KEY_PATTERN),
'openedx.core.djangoapps.course_groups.views.list_cohorts', name="cohorts"), 'openedx.core.djangoapps.course_groups.views.cohort_handler', 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), url(r'^courses/{}/cohorts/(?P<cohort_id>[0-9]+)$'.format(settings.COURSE_KEY_PATTERN),
'openedx.core.djangoapps.course_groups.views.users_in_cohort', 'openedx.core.djangoapps.course_groups.views.users_in_cohort',
name="list_cohort"), name="list_cohort"),
......
...@@ -21,6 +21,7 @@ from opaque_keys.edx.locations import SlashSeparatedCourseKey ...@@ -21,6 +21,7 @@ from opaque_keys.edx.locations import SlashSeparatedCourseKey
from openedx.core.djangoapps.user_api.partition_schemes import RandomUserPartitionScheme from openedx.core.djangoapps.user_api.partition_schemes import RandomUserPartitionScheme
from ..partition_scheme import CohortPartitionScheme, get_cohorted_user_partition from ..partition_scheme import CohortPartitionScheme, get_cohorted_user_partition
from ..models import CourseUserGroupPartitionGroup 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 ..cohorts import add_user_to_cohort, get_course_cohorts
from .helpers import CohortFactory, config_course_cohorts from .helpers import CohortFactory, config_course_cohorts
...@@ -55,22 +56,6 @@ class TestCohortPartitionScheme(django.test.TestCase): ...@@ -55,22 +56,6 @@ class TestCohortPartitionScheme(django.test.TestCase):
) )
self.student = UserFactory.create() 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): def assert_student_in_group(self, group, partition=None):
""" """
Utility for checking that our test student comes up as assigned to the Utility for checking that our test student comes up as assigned to the
...@@ -99,16 +84,16 @@ class TestCohortPartitionScheme(django.test.TestCase): ...@@ -99,16 +84,16 @@ class TestCohortPartitionScheme(django.test.TestCase):
self.assert_student_in_group(None) self.assert_student_in_group(None)
# link first cohort to group 0 in the partition # link first cohort to group 0 in the partition
self.link_cohort_partition_group( link_cohort_to_partition_group(
first_cohort, first_cohort,
self.user_partition, self.user_partition.id,
self.groups[0], self.groups[0].id,
) )
# link second cohort to to group 1 in the partition # link second cohort to to group 1 in the partition
self.link_cohort_partition_group( link_cohort_to_partition_group(
second_cohort, second_cohort,
self.user_partition, self.user_partition.id,
self.groups[1], self.groups[1].id,
) )
self.assert_student_in_group(self.groups[0]) self.assert_student_in_group(self.groups[0])
...@@ -133,26 +118,26 @@ class TestCohortPartitionScheme(django.test.TestCase): ...@@ -133,26 +118,26 @@ class TestCohortPartitionScheme(django.test.TestCase):
self.assert_student_in_group(None) self.assert_student_in_group(None)
# link cohort to group 0 # link cohort to group 0
self.link_cohort_partition_group( link_cohort_to_partition_group(
test_cohort, test_cohort,
self.user_partition, self.user_partition.id,
self.groups[0], self.groups[0].id,
) )
# now the scheme should find a link # now the scheme should find a link
self.assert_student_in_group(self.groups[0]) self.assert_student_in_group(self.groups[0])
# link cohort to group 1 (first unlink it from group 0) # link cohort to group 1 (first unlink it from group 0)
self.unlink_cohort_partition_group(test_cohort) unlink_cohort_partition_group(test_cohort)
self.link_cohort_partition_group( link_cohort_to_partition_group(
test_cohort, test_cohort,
self.user_partition, self.user_partition.id,
self.groups[1], self.groups[1].id,
) )
# scheme should pick up the link # scheme should pick up the link
self.assert_student_in_group(self.groups[1]) self.assert_student_in_group(self.groups[1])
# unlink cohort from anywhere # unlink cohort from anywhere
self.unlink_cohort_partition_group( unlink_cohort_partition_group(
test_cohort, test_cohort,
) )
# scheme should now return nothing # scheme should now return nothing
...@@ -171,10 +156,10 @@ class TestCohortPartitionScheme(django.test.TestCase): ...@@ -171,10 +156,10 @@ class TestCohortPartitionScheme(django.test.TestCase):
cohort = get_course_cohorts(self.course)[0] cohort = get_course_cohorts(self.course)[0]
# map that cohort to a group in our partition # map that cohort to a group in our partition
self.link_cohort_partition_group( link_cohort_to_partition_group(
cohort, cohort,
self.user_partition, self.user_partition.id,
self.groups[0], self.groups[0].id,
) )
# The student will be lazily assigned to the default cohort # The student will be lazily assigned to the default cohort
...@@ -190,10 +175,10 @@ class TestCohortPartitionScheme(django.test.TestCase): ...@@ -190,10 +175,10 @@ class TestCohortPartitionScheme(django.test.TestCase):
test_cohort = CohortFactory(course_id=self.course_key) test_cohort = CohortFactory(course_id=self.course_key)
# link cohort to group 0 # link cohort to group 0
self.link_cohort_partition_group( link_cohort_to_partition_group(
test_cohort, test_cohort,
self.user_partition, self.user_partition.id,
self.groups[0], self.groups[0].id,
) )
# place student into cohort # place student into cohort
add_user_to_cohort(test_cohort, self.student.username) add_user_to_cohort(test_cohort, self.student.username)
......
...@@ -4,6 +4,9 @@ from django.contrib.auth.models import User ...@@ -4,6 +4,9 @@ from django.contrib.auth.models import User
from django.core.paginator import Paginator, EmptyPage from django.core.paginator import Paginator, EmptyPage
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.http import Http404, HttpResponse, HttpResponseBadRequest 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 json
import logging import logging
import re import re
...@@ -12,9 +15,8 @@ from opaque_keys.edx.locations import SlashSeparatedCourseKey ...@@ -12,9 +15,8 @@ from opaque_keys.edx.locations import SlashSeparatedCourseKey
from courseware.courses import get_course_with_access from courseware.courses import get_course_with_access
from edxmako.shortcuts import render_to_response from edxmako.shortcuts import render_to_response
from util.json_request import JsonResponse
from . import cohorts from . import cohorts
from .models import CourseUserGroup from .models import CourseUserGroup, CourseUserGroupPartitionGroup
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -29,76 +31,119 @@ def json_http_response(data): ...@@ -29,76 +31,119 @@ def json_http_response(data):
def split_by_comma_and_whitespace(cstr): 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) return re.split(r'[\s,]+', cstr)
@ensure_csrf_cookie def link_cohort_to_partition_group(cohort, partition_id, group_id):
def list_cohorts(request, course_key_string):
""" """
Return json dump of dict: Create cohort to partition_id/group_id link.
{'success': True,
'cohorts': [{'name': name, 'id': id}, ...]}
""" """
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, def _get_cohort_representation(cohort, course):
'cohorts': all_cohorts}) """
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 @ensure_csrf_cookie
@require_POST @expect_json
def add_cohort(request, course_key_string): @login_required
def cohort_handler(request, course_key_string, cohort_id=None):
""" """
Return json of dict: The restful handler for cohort requests. Requires JSON.
{'success': True, GET
'cohort': {'id': id, If a cohort ID is specified, returns a JSON representation of the cohort
'name': name}} (name, id, user_count, assignment_type, user_partition_id, group_id).
If no cohort ID is specified, returns the JSON representation of all cohorts.
or This is returned as a dict with the list of cohort information stored under the
key `cohorts`.
{'success': False, PUT or POST or PATCH
'msg': error_msg} if there's an error 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) course_key = SlashSeparatedCourseKey.from_deprecated_string(course_key_string)
course = get_course_with_access(request.user, 'staff', course_key)
get_course_with_access(request.user, 'staff', course_key) if request.method == 'GET':
if not cohort_id:
name = request.POST.get("name") all_cohorts = [
if not name: _get_cohort_representation(c, course)
return json_http_response({'success': False, for c in cohorts.get_course_cohorts(course)
'msg': "No name specified"}) ]
# TODO: change to just directly returning the lists.
try: return JsonResponse({'cohorts': all_cohorts})
cohort = cohorts.add_cohort(course_key, name) else:
except ValueError as err: cohort = cohorts.get_cohort_by_id(course_key, cohort_id)
return json_http_response({'success': False, return JsonResponse(_get_cohort_representation(cohort, course))
'msg': str(err)}) else:
# If cohort_id is specified, update the existing cohort. Otherwise, create a new cohort.
return json_http_response({ if cohort_id:
'success': 'True', cohort = cohorts.get_cohort_by_id(course_key, cohort_id)
'cohort': { name = request.json.get('name')
'id': cohort.id, if name != cohort.name:
'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 @ensure_csrf_cookie
...@@ -121,7 +166,7 @@ def users_in_cohort(request, course_key_string, cohort_id): ...@@ -121,7 +166,7 @@ def users_in_cohort(request, course_key_string, cohort_id):
get_course_with_access(request.user, 'staff', course_key) get_course_with_access(request.user, 'staff', course_key)
# this will error if called with a non-int cohort_id. That's ok--it # 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)) cohort = cohorts.get_cohort_by_id(course_key, int(cohort_id))
paginator = Paginator(cohort.users.all(), 100) 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