Conflicts: cms/djangoapps/contentstore/tests/test_contentstore.py cms/djangoapps/contentstore/views.py common/lib/xmodule/xmodule/combined_open_ended_module.py common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py common/lib/xmodule/xmodule/open_ended_grading_classes/open_ended_module.py common/lib/xmodule/xmodule/open_ended_grading_classes/openendedchild.py common/lib/xmodule/xmodule/peer_grading_module.py common/lib/xmodule/xmodule/tests/test_combined_open_ended.py common/lib/xmodule/xmodule/tests/test_self_assessment.py lms/djangoapps/open_ended_grading/tests.py
277 lines
10 KiB
Python
277 lines
10 KiB
Python
import django.test
|
|
from django.contrib.auth.models import User
|
|
from django.conf import settings
|
|
|
|
from django.test.utils import override_settings
|
|
|
|
from course_groups.models import CourseUserGroup
|
|
from course_groups.cohorts import (get_cohort, get_course_cohorts,
|
|
is_commentable_cohorted, get_cohort_by_name)
|
|
|
|
from xmodule.modulestore.django import modulestore, _MODULESTORES
|
|
|
|
# NOTE: running this with the lms.envs.test config works without
|
|
# manually overriding the modulestore. However, running with
|
|
# cms.envs.test doesn't.
|
|
|
|
|
|
def xml_store_config(data_dir):
|
|
return {
|
|
'default': {
|
|
'ENGINE': 'xmodule.modulestore.xml.XMLModuleStore',
|
|
'OPTIONS': {
|
|
'data_dir': data_dir,
|
|
'default_class': 'xmodule.hidden_module.HiddenDescriptor',
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT
|
|
TEST_DATA_XML_MODULESTORE = xml_store_config(TEST_DATA_DIR)
|
|
|
|
|
|
@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE)
|
|
class TestCohorts(django.test.TestCase):
|
|
|
|
|
|
@staticmethod
|
|
def topic_name_to_id(course, name):
|
|
"""
|
|
Given a discussion topic name, return an id for that name (includes
|
|
course and url_name).
|
|
"""
|
|
return "{course}_{run}_{name}".format(course=course.location.course,
|
|
run=course.url_name,
|
|
name=name)
|
|
|
|
|
|
@staticmethod
|
|
def config_course_cohorts(course, discussions,
|
|
cohorted,
|
|
cohorted_discussions=None,
|
|
auto_cohort=None,
|
|
auto_cohort_groups=None):
|
|
"""
|
|
Given a course with no discussion set up, add the discussions and set
|
|
the cohort config appropriately.
|
|
|
|
Arguments:
|
|
course: CourseDescriptor
|
|
discussions: list of topic names strings. Picks ids and sort_keys
|
|
automatically.
|
|
cohorted: bool.
|
|
cohorted_discussions: optional list of topic names. If specified,
|
|
converts them to use the same ids as topic names.
|
|
auto_cohort: optional bool.
|
|
auto_cohort_groups: optional list of strings
|
|
(names of groups to put students into).
|
|
|
|
Returns:
|
|
Nothing -- modifies course in place.
|
|
"""
|
|
def to_id(name):
|
|
return TestCohorts.topic_name_to_id(course, name)
|
|
|
|
topics = dict((name, {"sort_key": "A",
|
|
"id": to_id(name)})
|
|
for name in discussions)
|
|
|
|
course.discussion_topics = topics
|
|
|
|
d = {"cohorted": cohorted}
|
|
if cohorted_discussions is not None:
|
|
d["cohorted_discussions"] = [to_id(name)
|
|
for name in cohorted_discussions]
|
|
|
|
if auto_cohort is not None:
|
|
d["auto_cohort"] = auto_cohort
|
|
if auto_cohort_groups is not None:
|
|
d["auto_cohort_groups"] = auto_cohort_groups
|
|
|
|
course.cohort_config = d
|
|
|
|
|
|
def setUp(self):
|
|
"""
|
|
Make sure that course is reloaded every time--clear out the modulestore.
|
|
"""
|
|
# don't like this, but don't know a better way to undo all changes made
|
|
# to course. We don't have a course.clone() method.
|
|
_MODULESTORES.clear()
|
|
|
|
|
|
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")
|
|
self.assertFalse(course.is_cohorted)
|
|
|
|
user = User.objects.create(username="test", email="a@b.com")
|
|
other_user = User.objects.create(username="test2", email="a2@b.com")
|
|
|
|
self.assertIsNone(get_cohort(user, course.id), "No cohort created yet")
|
|
|
|
cohort = CourseUserGroup.objects.create(name="TestCohort",
|
|
course_id=course.id,
|
|
group_type=CourseUserGroup.COHORT)
|
|
|
|
cohort.users.add(user)
|
|
|
|
self.assertIsNone(get_cohort(user, course.id),
|
|
"Course isn't cohorted, so shouldn't have a cohort")
|
|
|
|
# Make the course cohorted...
|
|
self.config_course_cohorts(course, [], cohorted=True)
|
|
|
|
self.assertEquals(get_cohort(user, course.id).id, cohort.id,
|
|
"Should find the right cohort")
|
|
|
|
self.assertEquals(get_cohort(other_user, course.id), None,
|
|
"other_user shouldn't have a cohort")
|
|
|
|
def test_auto_cohorting(self):
|
|
"""
|
|
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")
|
|
self.assertFalse(course.is_cohorted)
|
|
|
|
user1 = User.objects.create(username="test", email="a@b.com")
|
|
user2 = User.objects.create(username="test2", email="a2@b.com")
|
|
user3 = User.objects.create(username="test3", email="a3@b.com")
|
|
|
|
cohort = CourseUserGroup.objects.create(name="TestCohort",
|
|
course_id=course.id,
|
|
group_type=CourseUserGroup.COHORT)
|
|
|
|
# user1 manually added to a cohort
|
|
cohort.users.add(user1)
|
|
|
|
# Make the course auto cohorted...
|
|
self.config_course_cohorts(course, [], cohorted=True,
|
|
auto_cohort=True,
|
|
auto_cohort_groups=["AutoGroup"])
|
|
|
|
self.assertEquals(get_cohort(user1, course.id).id, cohort.id,
|
|
"user1 should stay put")
|
|
|
|
self.assertEquals(get_cohort(user2, course.id).name, "AutoGroup",
|
|
"user2 should be auto-cohorted")
|
|
|
|
# Now make the group list empty
|
|
self.config_course_cohorts(course, [], cohorted=True,
|
|
auto_cohort=True,
|
|
auto_cohort_groups=[])
|
|
|
|
self.assertEquals(get_cohort(user3, course.id), None,
|
|
"No groups->no auto-cohorting")
|
|
|
|
# Now make it different
|
|
self.config_course_cohorts(course, [], cohorted=True,
|
|
auto_cohort=True,
|
|
auto_cohort_groups=["OtherGroup"])
|
|
|
|
self.assertEquals(get_cohort(user3, course.id).name, "OtherGroup",
|
|
"New list->new group")
|
|
self.assertEquals(get_cohort(user2, course.id).name, "AutoGroup",
|
|
"user2 should still be in originally placed cohort")
|
|
|
|
|
|
def test_auto_cohorting_randomization(self):
|
|
"""
|
|
Make sure get_cohort() randomizes properly.
|
|
"""
|
|
course = modulestore().get_course("edX/toy/2012_Fall")
|
|
self.assertEqual(course.id, "edX/toy/2012_Fall")
|
|
self.assertFalse(course.is_cohorted)
|
|
|
|
groups = ["group_{0}".format(n) for n in range(5)]
|
|
self.config_course_cohorts(course, [], cohorted=True,
|
|
auto_cohort=True,
|
|
auto_cohort_groups=groups)
|
|
|
|
# Assign 100 users to cohorts
|
|
for i in range(100):
|
|
user = User.objects.create(username="test_{0}".format(i),
|
|
email="a@b{0}.com".format(i))
|
|
get_cohort(user, course.id)
|
|
|
|
# Now make sure that the assignment was at least vaguely random:
|
|
# each cohort should have at least 1, and fewer than 50 students.
|
|
# (with 5 groups, probability of 0 users in any group is about
|
|
# .8**100= 2.0e-10)
|
|
for cohort_name in groups:
|
|
cohort = get_cohort_by_name(course.id, cohort_name)
|
|
num_users = cohort.users.count()
|
|
self.assertGreater(num_users, 1)
|
|
self.assertLess(num_users, 50)
|
|
|
|
|
|
|
|
def test_get_course_cohorts(self):
|
|
course1_id = 'a/b/c'
|
|
course2_id = 'e/f/g'
|
|
|
|
# add some cohorts to course 1
|
|
cohort = CourseUserGroup.objects.create(name="TestCohort",
|
|
course_id=course1_id,
|
|
group_type=CourseUserGroup.COHORT)
|
|
|
|
cohort = CourseUserGroup.objects.create(name="TestCohort2",
|
|
course_id=course1_id,
|
|
group_type=CourseUserGroup.COHORT)
|
|
|
|
|
|
# second course should have no cohorts
|
|
self.assertEqual(get_course_cohorts(course2_id), [])
|
|
|
|
cohorts = sorted([c.name for c in get_course_cohorts(course1_id)])
|
|
self.assertEqual(cohorts, ['TestCohort', 'TestCohort2'])
|
|
|
|
|
|
def test_is_commentable_cohorted(self):
|
|
course = modulestore().get_course("edX/toy/2012_Fall")
|
|
self.assertFalse(course.is_cohorted)
|
|
|
|
def to_id(name):
|
|
return self.topic_name_to_id(course, name)
|
|
|
|
# no topics
|
|
self.assertFalse(is_commentable_cohorted(course.id, to_id("General")),
|
|
"Course doesn't even have a 'General' topic")
|
|
|
|
# not cohorted
|
|
self.config_course_cohorts(course, ["General", "Feedback"],
|
|
cohorted=False)
|
|
|
|
self.assertFalse(is_commentable_cohorted(course.id, to_id("General")),
|
|
"Course isn't cohorted")
|
|
|
|
# cohorted, but top level topics aren't
|
|
self.config_course_cohorts(course, ["General", "Feedback"],
|
|
cohorted=True)
|
|
|
|
self.assertTrue(course.is_cohorted)
|
|
self.assertFalse(is_commentable_cohorted(course.id, to_id("General")),
|
|
"Course is cohorted, but 'General' isn't.")
|
|
|
|
self.assertTrue(
|
|
is_commentable_cohorted(course.id, to_id("random")),
|
|
"Non-top-level discussion is always cohorted in cohorted courses.")
|
|
|
|
# cohorted, including "Feedback" top-level topics aren't
|
|
self.config_course_cohorts(course, ["General", "Feedback"],
|
|
cohorted=True,
|
|
cohorted_discussions=["Feedback"])
|
|
|
|
self.assertTrue(course.is_cohorted)
|
|
self.assertFalse(is_commentable_cohorted(course.id, to_id("General")),
|
|
"Course is cohorted, but 'General' isn't.")
|
|
|
|
self.assertTrue(
|
|
is_commentable_cohorted(course.id, to_id("Feedback")),
|
|
"Feedback was listed as cohorted. Should be.")
|