Make course ids and usage ids opaque to LMS and Studio [partial commit]
This commit updates common/djangoapps. These keys are now objects with a limited interface, and the particular internal representation is managed by the data storage layer (the modulestore). For the LMS, there should be no outward-facing changes to the system. The keys are, for now, a change to internal representation only. For Studio, the new serialized form of the keys is used in urls, to allow for further migration in the future. Co-Author: Andy Armstrong <andya@edx.org> Co-Author: Christina Roberts <christina@edx.org> Co-Author: David Baumgold <db@edx.org> Co-Author: Diana Huang <dkh@edx.org> Co-Author: Don Mitchell <dmitchell@edx.org> Co-Author: Julia Hansbrough <julia@edx.org> Co-Author: Nimisha Asthagiri <nasthagiri@edx.org> Co-Author: Sarina Canelake <sarina@edx.org> [LMS-2370]
This commit is contained in:
@@ -32,30 +32,30 @@ def local_random():
|
||||
|
||||
return _local_random
|
||||
|
||||
def is_course_cohorted(course_id):
|
||||
def is_course_cohorted(course_key):
|
||||
"""
|
||||
Given a course id, return a boolean for whether or not the course is
|
||||
Given a course key, return a boolean for whether or not the course is
|
||||
cohorted.
|
||||
|
||||
Raises:
|
||||
Http404 if the course doesn't exist.
|
||||
"""
|
||||
return courses.get_course_by_id(course_id).is_cohorted
|
||||
return courses.get_course_by_id(course_key).is_cohorted
|
||||
|
||||
|
||||
def get_cohort_id(user, course_id):
|
||||
def get_cohort_id(user, course_key):
|
||||
"""
|
||||
Given a course id and a user, return the id of the cohort that user is
|
||||
Given a course key and a user, return the id of the cohort that user is
|
||||
assigned to in that course. If they don't have a cohort, return None.
|
||||
"""
|
||||
cohort = get_cohort(user, course_id)
|
||||
cohort = get_cohort(user, course_key)
|
||||
return None if cohort is None else cohort.id
|
||||
|
||||
|
||||
def is_commentable_cohorted(course_id, commentable_id):
|
||||
def is_commentable_cohorted(course_key, commentable_id):
|
||||
"""
|
||||
Args:
|
||||
course_id: string
|
||||
course_key: CourseKey
|
||||
commentable_id: string
|
||||
|
||||
Returns:
|
||||
@@ -64,7 +64,7 @@ def is_commentable_cohorted(course_id, commentable_id):
|
||||
Raises:
|
||||
Http404 if the course doesn't exist.
|
||||
"""
|
||||
course = courses.get_course_by_id(course_id)
|
||||
course = courses.get_course_by_id(course_key)
|
||||
|
||||
if not course.is_cohorted:
|
||||
# this is the easy case :)
|
||||
@@ -77,18 +77,18 @@ def is_commentable_cohorted(course_id, commentable_id):
|
||||
# inline discussions are cohorted by default
|
||||
ans = True
|
||||
|
||||
log.debug(u"is_commentable_cohorted({0}, {1}) = {2}".format(course_id,
|
||||
commentable_id,
|
||||
ans))
|
||||
log.debug(u"is_commentable_cohorted({0}, {1}) = {2}".format(
|
||||
course_key, commentable_id, ans
|
||||
))
|
||||
return ans
|
||||
|
||||
|
||||
def get_cohorted_commentables(course_id):
|
||||
def get_cohorted_commentables(course_key):
|
||||
"""
|
||||
Given a course_id return a list of strings representing cohorted commentables
|
||||
Given a course_key return a list of strings representing cohorted commentables
|
||||
"""
|
||||
|
||||
course = courses.get_course_by_id(course_id)
|
||||
course = courses.get_course_by_id(course_key)
|
||||
|
||||
if not course.is_cohorted:
|
||||
# this is the easy case :)
|
||||
@@ -99,34 +99,34 @@ def get_cohorted_commentables(course_id):
|
||||
return ans
|
||||
|
||||
|
||||
def get_cohort(user, course_id):
|
||||
def get_cohort(user, course_key):
|
||||
"""
|
||||
Given a django User and a course_id, return the user's cohort in that
|
||||
Given a django User and a CourseKey, return the user's cohort in that
|
||||
cohort.
|
||||
|
||||
Arguments:
|
||||
user: a Django User object.
|
||||
course_id: string in the format 'org/course/run'
|
||||
course_key: CourseKey
|
||||
|
||||
Returns:
|
||||
A CourseUserGroup object if the course is cohorted and the User has a
|
||||
cohort, else None.
|
||||
|
||||
Raises:
|
||||
ValueError if the course_id doesn't exist.
|
||||
ValueError if the CourseKey doesn't exist.
|
||||
"""
|
||||
# First check whether the course is cohorted (users shouldn't be in a cohort
|
||||
# in non-cohorted courses, but settings can change after course starts)
|
||||
try:
|
||||
course = courses.get_course_by_id(course_id)
|
||||
course = courses.get_course_by_id(course_key)
|
||||
except Http404:
|
||||
raise ValueError("Invalid course_id")
|
||||
raise ValueError("Invalid course_key")
|
||||
|
||||
if not course.is_cohorted:
|
||||
return None
|
||||
|
||||
try:
|
||||
return CourseUserGroup.objects.get(course_id=course_id,
|
||||
return CourseUserGroup.objects.get(course_id=course_key,
|
||||
group_type=CourseUserGroup.COHORT,
|
||||
users__id=user.id)
|
||||
except CourseUserGroup.DoesNotExist:
|
||||
@@ -142,72 +142,81 @@ def get_cohort(user, course_id):
|
||||
# Nowhere to put user
|
||||
log.warning("Course %s is auto-cohorted, but there are no"
|
||||
" auto_cohort_groups specified",
|
||||
course_id)
|
||||
course_key)
|
||||
return None
|
||||
|
||||
# Put user in a random group, creating it if needed
|
||||
group_name = local_random().choice(choices)
|
||||
|
||||
group, created = CourseUserGroup.objects.get_or_create(
|
||||
course_id=course_id,
|
||||
course_id=course_key,
|
||||
group_type=CourseUserGroup.COHORT,
|
||||
name=group_name)
|
||||
name=group_name
|
||||
)
|
||||
|
||||
user.course_groups.add(group)
|
||||
return group
|
||||
|
||||
|
||||
def get_course_cohorts(course_id):
|
||||
def get_course_cohorts(course_key):
|
||||
"""
|
||||
Get a list of all the cohorts in the given course.
|
||||
|
||||
Arguments:
|
||||
course_id: string in the format 'org/course/run'
|
||||
course_key: CourseKey
|
||||
|
||||
Returns:
|
||||
A list of CourseUserGroup objects. Empty if there are no cohorts. Does
|
||||
not check whether the course is cohorted.
|
||||
"""
|
||||
return list(CourseUserGroup.objects.filter(course_id=course_id,
|
||||
group_type=CourseUserGroup.COHORT))
|
||||
return list(CourseUserGroup.objects.filter(
|
||||
course_id=course_key,
|
||||
group_type=CourseUserGroup.COHORT
|
||||
))
|
||||
|
||||
### Helpers for cohort management views
|
||||
|
||||
|
||||
def get_cohort_by_name(course_id, name):
|
||||
def get_cohort_by_name(course_key, 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)
|
||||
return CourseUserGroup.objects.get(
|
||||
course_id=course_key,
|
||||
group_type=CourseUserGroup.COHORT,
|
||||
name=name
|
||||
)
|
||||
|
||||
|
||||
def get_cohort_by_id(course_id, cohort_id):
|
||||
def get_cohort_by_id(course_key, cohort_id):
|
||||
"""
|
||||
Return the CourseUserGroup object for the given cohort. Raises DoesNotExist
|
||||
it isn't present. Uses the course_id for extra validation...
|
||||
it isn't present. Uses the course_key for extra validation...
|
||||
"""
|
||||
return CourseUserGroup.objects.get(course_id=course_id,
|
||||
group_type=CourseUserGroup.COHORT,
|
||||
id=cohort_id)
|
||||
return CourseUserGroup.objects.get(
|
||||
course_id=course_key,
|
||||
group_type=CourseUserGroup.COHORT,
|
||||
id=cohort_id
|
||||
)
|
||||
|
||||
|
||||
def add_cohort(course_id, name):
|
||||
def add_cohort(course_key, 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,
|
||||
log.debug("Adding cohort %s to %s", name, course_key)
|
||||
if CourseUserGroup.objects.filter(course_id=course_key,
|
||||
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)
|
||||
return CourseUserGroup.objects.create(
|
||||
course_id=course_key,
|
||||
group_type=CourseUserGroup.COHORT,
|
||||
name=name
|
||||
)
|
||||
|
||||
|
||||
class CohortConflict(Exception):
|
||||
@@ -237,9 +246,10 @@ def add_user_to_cohort(cohort, username_or_email):
|
||||
previous_cohort = None
|
||||
|
||||
course_cohorts = CourseUserGroup.objects.filter(
|
||||
course_id=cohort.course_id,
|
||||
course_id=cohort.course_key,
|
||||
users__id=user.id,
|
||||
group_type=CourseUserGroup.COHORT)
|
||||
group_type=CourseUserGroup.COHORT
|
||||
)
|
||||
if course_cohorts.exists():
|
||||
if course_cohorts[0] == cohort:
|
||||
raise ValueError("User {0} already present in cohort {1}".format(
|
||||
@@ -253,21 +263,21 @@ def add_user_to_cohort(cohort, username_or_email):
|
||||
return (user, previous_cohort)
|
||||
|
||||
|
||||
def get_course_cohort_names(course_id):
|
||||
def get_course_cohort_names(course_key):
|
||||
"""
|
||||
Return a list of the cohort names in a course.
|
||||
"""
|
||||
return [c.name for c in get_course_cohorts(course_id)]
|
||||
return [c.name for c in get_course_cohorts(course_key)]
|
||||
|
||||
|
||||
def delete_empty_cohort(course_id, name):
|
||||
def delete_empty_cohort(course_key, name):
|
||||
"""
|
||||
Remove an empty cohort. Raise ValueError if cohort is not empty.
|
||||
"""
|
||||
cohort = get_cohort_by_name(course_id, name)
|
||||
cohort = get_cohort_by_name(course_key, name)
|
||||
if cohort.users.exists():
|
||||
raise ValueError(
|
||||
"Can't delete non-empty cohort {0} in course {1}".format(
|
||||
name, course_id))
|
||||
name, course_key))
|
||||
|
||||
cohort.delete()
|
||||
|
||||
@@ -2,6 +2,7 @@ import logging
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.db import models
|
||||
from xmodule_django.models import CourseKeyField
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -23,7 +24,8 @@ class CourseUserGroup(models.Model):
|
||||
|
||||
# Note: groups associated with particular runs of a course. E.g. Fall 2012 and Spring
|
||||
# 2013 versions of 6.00x will have separate groups.
|
||||
course_id = models.CharField(max_length=255, db_index=True,
|
||||
# TODO change field name to course_key
|
||||
course_id = CourseKeyField(max_length=255, db_index=True,
|
||||
help_text="Which course is this group associated with?")
|
||||
|
||||
# For now, only have group type 'cohort', but adding a type field to support
|
||||
|
||||
@@ -9,6 +9,7 @@ from course_groups.cohorts import (get_cohort, get_course_cohorts,
|
||||
is_commentable_cohorted, get_cohort_by_name)
|
||||
|
||||
from xmodule.modulestore.django import modulestore, clear_existing_modulestores
|
||||
from xmodule.modulestore.locations import SlashSeparatedCourseKey
|
||||
|
||||
from xmodule.modulestore.tests.django_utils import mixed_store_config
|
||||
|
||||
@@ -84,13 +85,14 @@ class TestCohorts(django.test.TestCase):
|
||||
Make sure that course is reloaded every time--clear out the modulestore.
|
||||
"""
|
||||
clear_existing_modulestores()
|
||||
self.toy_course_key = SlashSeparatedCourseKey("edX", "toy", "2012_Fall")
|
||||
|
||||
def test_get_cohort(self):
|
||||
"""
|
||||
Make sure get_cohort() does the right thing when the course is cohorted
|
||||
"""
|
||||
course = modulestore().get_course("edX/toy/2012_Fall")
|
||||
self.assertEqual(course.id, "edX/toy/2012_Fall")
|
||||
course = modulestore().get_course(self.toy_course_key)
|
||||
self.assertEqual(course.id, self.toy_course_key)
|
||||
self.assertFalse(course.is_cohorted)
|
||||
|
||||
user = User.objects.create(username="test", email="a@b.com")
|
||||
@@ -120,8 +122,7 @@ class TestCohorts(django.test.TestCase):
|
||||
"""
|
||||
Make sure get_cohort() does the right thing when the course is auto_cohorted
|
||||
"""
|
||||
course = modulestore().get_course("edX/toy/2012_Fall")
|
||||
self.assertEqual(course.id, "edX/toy/2012_Fall")
|
||||
course = modulestore().get_course(self.toy_course_key)
|
||||
self.assertFalse(course.is_cohorted)
|
||||
|
||||
user1 = User.objects.create(username="test", email="a@b.com")
|
||||
@@ -168,8 +169,7 @@ class TestCohorts(django.test.TestCase):
|
||||
"""
|
||||
Make sure get_cohort() randomizes properly.
|
||||
"""
|
||||
course = modulestore().get_course("edX/toy/2012_Fall")
|
||||
self.assertEqual(course.id, "edX/toy/2012_Fall")
|
||||
course = modulestore().get_course(self.toy_course_key)
|
||||
self.assertFalse(course.is_cohorted)
|
||||
|
||||
groups = ["group_{0}".format(n) for n in range(5)]
|
||||
@@ -194,26 +194,26 @@ class TestCohorts(django.test.TestCase):
|
||||
self.assertLess(num_users, 50)
|
||||
|
||||
def test_get_course_cohorts(self):
|
||||
course1_id = 'a/b/c'
|
||||
course2_id = 'e/f/g'
|
||||
course1_key = SlashSeparatedCourseKey('a', 'b', 'c')
|
||||
course2_key = SlashSeparatedCourseKey('e', 'f', 'g')
|
||||
|
||||
# add some cohorts to course 1
|
||||
cohort = CourseUserGroup.objects.create(name="TestCohort",
|
||||
course_id=course1_id,
|
||||
course_id=course1_key,
|
||||
group_type=CourseUserGroup.COHORT)
|
||||
|
||||
cohort = CourseUserGroup.objects.create(name="TestCohort2",
|
||||
course_id=course1_id,
|
||||
course_id=course1_key,
|
||||
group_type=CourseUserGroup.COHORT)
|
||||
|
||||
# second course should have no cohorts
|
||||
self.assertEqual(get_course_cohorts(course2_id), [])
|
||||
self.assertEqual(get_course_cohorts(course2_key), [])
|
||||
|
||||
cohorts = sorted([c.name for c in get_course_cohorts(course1_id)])
|
||||
cohorts = sorted([c.name for c in get_course_cohorts(course1_key)])
|
||||
self.assertEqual(cohorts, ['TestCohort', 'TestCohort2'])
|
||||
|
||||
def test_is_commentable_cohorted(self):
|
||||
course = modulestore().get_course("edX/toy/2012_Fall")
|
||||
course = modulestore().get_course(self.toy_course_key)
|
||||
self.assertFalse(course.is_cohorted)
|
||||
|
||||
def to_id(name):
|
||||
|
||||
@@ -33,25 +33,25 @@ def split_by_comma_and_whitespace(s):
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
def list_cohorts(request, course_id):
|
||||
def list_cohorts(request, course_key):
|
||||
"""
|
||||
Return json dump of dict:
|
||||
|
||||
{'success': True,
|
||||
'cohorts': [{'name': name, 'id': id}, ...]}
|
||||
"""
|
||||
get_course_with_access(request.user, course_id, 'staff')
|
||||
get_course_with_access(request.user, 'staff', course_key)
|
||||
|
||||
all_cohorts = [{'name': c.name, 'id': c.id}
|
||||
for c in cohorts.get_course_cohorts(course_id)]
|
||||
for c in cohorts.get_course_cohorts(course_key)]
|
||||
|
||||
return json_http_response({'success': True,
|
||||
'cohorts': all_cohorts})
|
||||
'cohorts': all_cohorts})
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@require_POST
|
||||
def add_cohort(request, course_id):
|
||||
def add_cohort(request, course_key):
|
||||
"""
|
||||
Return json of dict:
|
||||
{'success': True,
|
||||
@@ -63,7 +63,7 @@ def add_cohort(request, course_id):
|
||||
{'success': False,
|
||||
'msg': error_msg} if there's an error
|
||||
"""
|
||||
get_course_with_access(request.user, course_id, 'staff')
|
||||
get_course_with_access(request.user, 'staff', course_key)
|
||||
|
||||
name = request.POST.get("name")
|
||||
if not name:
|
||||
@@ -71,7 +71,7 @@ def add_cohort(request, course_id):
|
||||
'msg': "No name specified"})
|
||||
|
||||
try:
|
||||
cohort = cohorts.add_cohort(course_id, name)
|
||||
cohort = cohorts.add_cohort(course_key, name)
|
||||
except ValueError as err:
|
||||
return json_http_response({'success': False,
|
||||
'msg': str(err)})
|
||||
@@ -84,7 +84,7 @@ def add_cohort(request, course_id):
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
def users_in_cohort(request, course_id, cohort_id):
|
||||
def users_in_cohort(request, course_key, cohort_id):
|
||||
"""
|
||||
Return users in the cohort. Show up to 100 per page, and page
|
||||
using the 'page' GET attribute in the call. Format:
|
||||
@@ -97,11 +97,11 @@ def users_in_cohort(request, course_id, cohort_id):
|
||||
'users': [{'username': ..., 'email': ..., 'name': ...}]
|
||||
}
|
||||
"""
|
||||
get_course_with_access(request.user, course_id, 'staff')
|
||||
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.
|
||||
cohort = cohorts.get_cohort_by_id(course_id, int(cohort_id))
|
||||
cohort = cohorts.get_cohort_by_id(course_key, int(cohort_id))
|
||||
|
||||
paginator = Paginator(cohort.users.all(), 100)
|
||||
page = request.GET.get('page')
|
||||
@@ -119,17 +119,17 @@ def users_in_cohort(request, course_id, cohort_id):
|
||||
user_info = [{'username': u.username,
|
||||
'email': u.email,
|
||||
'name': '{0} {1}'.format(u.first_name, u.last_name)}
|
||||
for u in users]
|
||||
for u in users]
|
||||
|
||||
return json_http_response({'success': True,
|
||||
'page': page,
|
||||
'num_pages': paginator.num_pages,
|
||||
'users': user_info})
|
||||
'page': page,
|
||||
'num_pages': paginator.num_pages,
|
||||
'users': user_info})
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@require_POST
|
||||
def add_users_to_cohort(request, course_id, cohort_id):
|
||||
def add_users_to_cohort(request, course_key, cohort_id):
|
||||
"""
|
||||
Return json dict of:
|
||||
|
||||
@@ -144,9 +144,9 @@ def add_users_to_cohort(request, course_id, cohort_id):
|
||||
'present': [str1, str2, ...], # already there
|
||||
'unknown': [str1, str2, ...]}
|
||||
"""
|
||||
get_course_with_access(request.user, course_id, 'staff')
|
||||
get_course_with_access(request.user, 'staff', course_key)
|
||||
|
||||
cohort = cohorts.get_cohort_by_id(course_id, cohort_id)
|
||||
cohort = cohorts.get_cohort_by_id(course_key, cohort_id)
|
||||
|
||||
users = request.POST.get('users', '')
|
||||
added = []
|
||||
@@ -175,15 +175,15 @@ def add_users_to_cohort(request, course_id, cohort_id):
|
||||
unknown.append(username_or_email)
|
||||
|
||||
return json_http_response({'success': True,
|
||||
'added': added,
|
||||
'changed': changed,
|
||||
'present': present,
|
||||
'unknown': unknown})
|
||||
'added': added,
|
||||
'changed': changed,
|
||||
'present': present,
|
||||
'unknown': unknown})
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@require_POST
|
||||
def remove_user_from_cohort(request, course_id, cohort_id):
|
||||
def remove_user_from_cohort(request, course_key, cohort_id):
|
||||
"""
|
||||
Expects 'username': username in POST data.
|
||||
|
||||
@@ -193,14 +193,14 @@ def remove_user_from_cohort(request, course_id, cohort_id):
|
||||
{'success': False,
|
||||
'msg': error_msg}
|
||||
"""
|
||||
get_course_with_access(request.user, course_id, 'staff')
|
||||
get_course_with_access(request.user, 'staff', course_key)
|
||||
|
||||
username = request.POST.get('username')
|
||||
if username is None:
|
||||
return json_http_response({'success': False,
|
||||
'msg': 'No username specified'})
|
||||
'msg': 'No username specified'})
|
||||
|
||||
cohort = cohorts.get_cohort_by_id(course_id, cohort_id)
|
||||
cohort = cohorts.get_cohort_by_id(course_key, cohort_id)
|
||||
try:
|
||||
user = User.objects.get(username=username)
|
||||
cohort.users.remove(user)
|
||||
@@ -208,16 +208,18 @@ def remove_user_from_cohort(request, course_id, cohort_id):
|
||||
except User.DoesNotExist:
|
||||
log.debug('no user')
|
||||
return json_http_response({'success': False,
|
||||
'msg': "No user '{0}'".format(username)})
|
||||
'msg': "No user '{0}'".format(username)})
|
||||
|
||||
|
||||
def debug_cohort_mgmt(request, course_id):
|
||||
def debug_cohort_mgmt(request, course_key):
|
||||
"""
|
||||
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')
|
||||
get_course_with_access(request.user, 'staff', course_key)
|
||||
|
||||
context = {'cohorts_ajax_url': reverse('cohorts',
|
||||
kwargs={'course_id': course_id})}
|
||||
context = {'cohorts_ajax_url': reverse(
|
||||
'cohorts',
|
||||
kwargs={'course_id': course_key.to_deprecated_string()}
|
||||
)}
|
||||
return render_to_response('/course_groups/debug.html', context)
|
||||
|
||||
Reference in New Issue
Block a user