add tests
This commit is contained in:
@@ -8,8 +8,9 @@ from django.contrib.auth.models import User
|
||||
import xmodule.graders as xmgraders
|
||||
|
||||
|
||||
AVAILABLE_STUDENT_FEATURES = ['username', 'first_name', 'last_name', 'is_staff', 'email']
|
||||
AVAILABLE_PROFILE_FEATURES = ['name', 'language', 'location', 'year_of_birth', 'gender', 'level_of_education', 'mailing_address', 'goals']
|
||||
STUDENT_FEATURES = ('username', 'first_name', 'last_name', 'is_staff', 'email')
|
||||
PROFILE_FEATURES = ('name', 'language', 'location', 'year_of_birth', 'gender', 'level_of_education', 'mailing_address', 'goals')
|
||||
AVAILABLE_FEATURES = STUDENT_FEATURES + PROFILE_FEATURES
|
||||
|
||||
|
||||
def enrolled_students_profiles(course_id, features):
|
||||
@@ -19,10 +20,12 @@ def enrolled_students_profiles(course_id, features):
|
||||
# enrollments = CourseEnrollment.objects.filter(course_id=course_id)
|
||||
# students = [enrollment.user for enrollment in enrollments]
|
||||
students = User.objects.filter(courseenrollment__course_id=course_id).order_by('username').select_related('profile')
|
||||
print len(students)
|
||||
print students
|
||||
|
||||
def extract_student(student):
|
||||
student_features = [feature for feature in features if feature in AVAILABLE_STUDENT_FEATURES]
|
||||
profile_features = [feature for feature in features if feature in AVAILABLE_PROFILE_FEATURES]
|
||||
student_features = [feature for feature in features if feature in STUDENT_FEATURES]
|
||||
profile_features = [feature for feature in features if feature in PROFILE_FEATURES]
|
||||
|
||||
student_dict = dict((feature, getattr(student, feature)) for feature in student_features)
|
||||
profile = student.profile
|
||||
@@ -35,7 +38,7 @@ def enrolled_students_profiles(course_id, features):
|
||||
|
||||
def dump_grading_context(course):
|
||||
"""
|
||||
Dump information about course grading context (eg which problems are graded in what assignments)
|
||||
Render information about course grading context (eg which problems are graded in what assignments)
|
||||
Useful for debugging grading_policy.json and policy.json
|
||||
|
||||
Returns HTML string
|
||||
|
||||
@@ -27,30 +27,34 @@ def create_csv_response(filename, header, datarows):
|
||||
|
||||
def format_dictlist(dictlist):
|
||||
"""
|
||||
Convert from [
|
||||
Convert FROM [
|
||||
{
|
||||
'label1': 'value1,1',
|
||||
'label2': 'value2,1',
|
||||
'label3': 'value3,1',
|
||||
'label4': 'value4,1',
|
||||
'label1': 'value-1,1',
|
||||
'label2': 'value-1,2',
|
||||
'label3': 'value-1,3',
|
||||
'label4': 'value-1,4',
|
||||
},
|
||||
{
|
||||
'label1': 'value1,2',
|
||||
'label2': 'value2,2',
|
||||
'label3': 'value3,2',
|
||||
'label4': 'value4,2',
|
||||
'label1': 'value-2,1',
|
||||
'label2': 'value-2,2',
|
||||
'label3': 'value-2,3',
|
||||
'label4': 'value-2,4',
|
||||
}
|
||||
]
|
||||
|
||||
to {
|
||||
TO {
|
||||
'header': ['label1', 'label2', 'label3', 'label4'],
|
||||
'datarows': ['value1,1', 'value2,1', 'value3,1', 'value4,1'], ['value1,2', 'value2,2', 'value3,2', 'value4,2']
|
||||
'datarows': [['value-1,1', 'value-1,2', 'value-1,3', 'value-1,4'],
|
||||
['value-2,1', 'value-2,2', 'value-2,3', 'value-2,4']]
|
||||
}
|
||||
|
||||
Do not handle empty lists.
|
||||
Assumes all keys for input dicts are the same.
|
||||
"""
|
||||
|
||||
header = dictlist[0].keys()
|
||||
if len(dictlist) > 0:
|
||||
header = dictlist[0].keys()
|
||||
else:
|
||||
header = []
|
||||
|
||||
def dict_to_entry(d):
|
||||
ordered = sorted(d.items(), key=lambda (k, v): header.index(k))
|
||||
|
||||
@@ -14,7 +14,9 @@ def profile_distribution(course_id, feature):
|
||||
Retrieve distribution of students over a given feature.
|
||||
feature is one of AVAILABLE_PROFILE_FEATURES.
|
||||
|
||||
Returna dictionary {'type': 'SOME_TYPE', 'data': {'key': 'val'}}
|
||||
Returna dictionary {'type': 'SOME_TYPE', 'data': {'key': 'val'}, 'display_names': {'key': 'displaynameval'}}
|
||||
display_names is only return for EASY_CHOICE type eatuers
|
||||
note no_data instead of None to be compatible with the json spec.
|
||||
data types e.g.
|
||||
EASY_CHOICE - choices with a restricted domain, e.g. level_of_education
|
||||
OPEN_CHOICE - choices with a larger domain e.g. year_of_birth
|
||||
@@ -23,18 +25,23 @@ def profile_distribution(course_id, feature):
|
||||
EASY_CHOICE_FEATURES = ['gender', 'level_of_education']
|
||||
OPEN_CHOICE_FEATURES = ['year_of_birth']
|
||||
|
||||
def raise_not_implemented():
|
||||
raise NotImplementedError("feature requested not implemented but is advertised in AVAILABLE_PROFILE_FEATURES {}" .format(feature))
|
||||
|
||||
feature_results = {}
|
||||
|
||||
if not feature in AVAILABLE_PROFILE_FEATURES:
|
||||
raise ValueError("unsupported feature requested for distribution '%s'" % feature)
|
||||
raise ValueError("unsupported feature requested for distribution '{}'".format(feature))
|
||||
|
||||
if feature in EASY_CHOICE_FEATURES:
|
||||
if feature == 'gender':
|
||||
choices = [(short, full) for (short, full) in UserProfile.GENDER_CHOICES] + [(None, 'No Data')]
|
||||
raw_choices = UserProfile.GENDER_CHOICES
|
||||
elif feature == 'level_of_education':
|
||||
choices = [(short, full) for (short, full) in UserProfile.LEVEL_OF_EDUCATION_CHOICES] + [(None, 'No Data')]
|
||||
raw_choices = UserProfile.LEVEL_OF_EDUCATION_CHOICES
|
||||
else:
|
||||
raise ValueError("feature request not implemented for feature %s" % feature)
|
||||
raise raise_not_implemented()
|
||||
|
||||
choices = [(short, full) for (short, full) in raw_choices] + [('no_data', 'No Data')]
|
||||
|
||||
data = {}
|
||||
for (short, full) in choices:
|
||||
@@ -43,21 +50,30 @@ def profile_distribution(course_id, feature):
|
||||
elif feature == 'level_of_education':
|
||||
count = CourseEnrollment.objects.filter(course_id=course_id, user__profile__level_of_education=short).count()
|
||||
else:
|
||||
raise ValueError("feature request not implemented for feature %s" % feature)
|
||||
data[full] = count
|
||||
raise raise_not_implemented()
|
||||
data[short] = count
|
||||
|
||||
feature_results['data'] = data
|
||||
feature_results['type'] = 'EASY_CHOICE'
|
||||
feature_results['display_names'] = dict(choices)
|
||||
elif feature in OPEN_CHOICE_FEATURES:
|
||||
profiles = UserProfile.objects.filter(user__courseenrollment__course_id=course_id)
|
||||
query_distribution = profiles.values(feature).annotate(Count(feature)).order_by()
|
||||
# query_distribution is of the form [{'attribute': 'value1', 'attribute__count': 4}, {'attribute': 'value2', 'attribute__count': 2}, ...]
|
||||
# query_distribution is of the form [{'featureval': 'value1', 'featureval__count': 4}, {'featureval': 'value2', 'featureval__count': 2}, ...]
|
||||
|
||||
distribution = dict((vald[feature], vald[feature + '__count']) for vald in query_distribution)
|
||||
# distribution is of the form {'value1': 4, 'value2': 2, ...}
|
||||
|
||||
# change none to no_data for valid json key
|
||||
if None in distribution:
|
||||
distribution['no_data'] = distribution.pop(None)
|
||||
# django does not properly count NULL values, so the above will alwasy be 0.
|
||||
# this correctly counts null values
|
||||
distribution['no_data'] = profiles.filter(**{feature: None}).count()
|
||||
|
||||
feature_results['data'] = distribution
|
||||
feature_results['type'] = 'OPEN_CHOICE'
|
||||
else:
|
||||
raise ValueError("feature requested for distribution has not been implemented but is advertised in AVAILABLE_PROFILE_FEATURES! '%s'" % feature)
|
||||
raise raise_not_implemented()
|
||||
|
||||
return feature_results
|
||||
|
||||
40
lms/djangoapps/analytics/tests/test_basic.py
Normal file
40
lms/djangoapps/analytics/tests/test_basic.py
Normal file
@@ -0,0 +1,40 @@
|
||||
from django.test import TestCase
|
||||
from django.contrib.auth.models import User, Group
|
||||
from student.models import CourseEnrollment
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
from student.tests.factories import UserFactory
|
||||
|
||||
from analytics.basic import enrolled_students_profiles, AVAILABLE_FEATURES, STUDENT_FEATURES, PROFILE_FEATURES
|
||||
|
||||
|
||||
class TestAnalyticsBasic(TestCase):
|
||||
'''Test basic analytics functions.'''
|
||||
|
||||
def setUp(self):
|
||||
self.course_id = 'some/robot/course/id'
|
||||
self.users = tuple(UserFactory() for _ in xrange(30))
|
||||
self.ces = tuple(CourseEnrollment.objects.create(course_id=self.course_id, user=user) for user in self.users)
|
||||
|
||||
def test_enrolled_students_profiles_username(self):
|
||||
self.assertIn('username', AVAILABLE_FEATURES)
|
||||
userreports = enrolled_students_profiles(self.course_id, ['username'])
|
||||
self.assertEqual(len(userreports), len(self.users))
|
||||
for userreport in userreports:
|
||||
self.assertEqual(userreport.keys(), ['username'])
|
||||
self.assertIn(userreport['username'], [user.username for user in self.users])
|
||||
|
||||
def test_enrolled_students_profiles_keys(self):
|
||||
query_features = ('username', 'name', 'email')
|
||||
for feature in query_features:
|
||||
self.assertIn(feature, AVAILABLE_FEATURES)
|
||||
userreports = enrolled_students_profiles(self.course_id, query_features)
|
||||
self.assertEqual(len(userreports), len(self.users))
|
||||
for userreport in userreports:
|
||||
self.assertEqual(set(userreport.keys()), set(query_features))
|
||||
self.assertIn(userreport['username'], [user.username for user in self.users])
|
||||
self.assertIn(userreport['email'], [user.email for user in self.users])
|
||||
self.assertIn(userreport['name'], [user.profile.name for user in self.users])
|
||||
|
||||
def test_available_features(self):
|
||||
self.assertEqual(len(AVAILABLE_FEATURES), len(STUDENT_FEATURES + PROFILE_FEATURES))
|
||||
self.assertEqual(set(AVAILABLE_FEATURES), set(STUDENT_FEATURES + PROFILE_FEATURES))
|
||||
65
lms/djangoapps/analytics/tests/test_csvs.py
Normal file
65
lms/djangoapps/analytics/tests/test_csvs.py
Normal file
@@ -0,0 +1,65 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from analytics.csvs import create_csv_response, format_dictlist
|
||||
|
||||
|
||||
class TestAnalyticsCSVS(TestCase):
|
||||
'''Test analytics rendering of csv files.'''
|
||||
|
||||
def test_create_csv_response_nodata(self):
|
||||
header = ['Name', 'Email']
|
||||
datarows = []
|
||||
|
||||
res = create_csv_response('robot.csv', header, datarows)
|
||||
self.assertEqual(res['Content-Type'], 'text/csv')
|
||||
self.assertEqual(res['Content-Disposition'], 'attachment; filename={0}'.format('robot.csv'))
|
||||
self.assertEqual(res.content.strip(), '"Name","Email"')
|
||||
|
||||
def test_create_csv_response(self):
|
||||
header = ['Name', 'Email']
|
||||
datarows = [['Jim', 'jim@edy.org'], ['Jake', 'jake@edy.org'], ['Jeeves', 'jeeves@edy.org']]
|
||||
|
||||
res = create_csv_response('robot.csv', header, datarows)
|
||||
self.assertEqual(res['Content-Type'], 'text/csv')
|
||||
self.assertEqual(res['Content-Disposition'], 'attachment; filename={0}'.format('robot.csv'))
|
||||
self.assertEqual(res.content.strip(), '"Name","Email"\r\n"Jim","jim@edy.org"\r\n"Jake","jake@edy.org"\r\n"Jeeves","jeeves@edy.org"')
|
||||
|
||||
def test_create_csv_response_empty(self):
|
||||
header = []
|
||||
datarows = []
|
||||
|
||||
res = create_csv_response('robot.csv', header, datarows)
|
||||
self.assertEqual(res['Content-Type'], 'text/csv')
|
||||
self.assertEqual(res['Content-Disposition'], 'attachment; filename={0}'.format('robot.csv'))
|
||||
self.assertEqual(res.content.strip(), '')
|
||||
|
||||
def test_format_dictlist(self):
|
||||
data_in = [
|
||||
{
|
||||
'label1': 'value-1,1',
|
||||
'label2': 'value-1,2',
|
||||
'label3': 'value-1,3',
|
||||
'label4': 'value-1,4',
|
||||
},
|
||||
{
|
||||
'label1': 'value-2,1',
|
||||
'label2': 'value-2,2',
|
||||
'label3': 'value-2,3',
|
||||
'label4': 'value-2,4',
|
||||
},
|
||||
]
|
||||
|
||||
data_out = {
|
||||
'header': ['label1', 'label2', 'label3', 'label4'],
|
||||
'datarows': [['value-1,1', 'value-1,2', 'value-1,3', 'value-1,4'],
|
||||
['value-2,1', 'value-2,2', 'value-2,3', 'value-2,4']],
|
||||
}
|
||||
|
||||
self.assertEqual(format_dictlist(data_in), data_out)
|
||||
|
||||
|
||||
def test_format_dictlist_empty(self):
|
||||
self.assertEqual(format_dictlist([]), {
|
||||
'header': [],
|
||||
'datarows': [],
|
||||
})
|
||||
83
lms/djangoapps/analytics/tests/test_distributions.py
Normal file
83
lms/djangoapps/analytics/tests/test_distributions.py
Normal file
@@ -0,0 +1,83 @@
|
||||
from django.test import TestCase
|
||||
from nose.tools import raises
|
||||
from django.contrib.auth.models import User, Group
|
||||
from student.models import CourseEnrollment
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
from student.tests.factories import UserFactory
|
||||
|
||||
from analytics.distributions import profile_distribution, AVAILABLE_PROFILE_FEATURES
|
||||
|
||||
|
||||
class TestAnalyticsDistributions(TestCase):
|
||||
'''Test analytics distribution gathering.'''
|
||||
|
||||
def setUp(self):
|
||||
self.course_id = 'some/robot/course/id'
|
||||
|
||||
self.users = tuple(UserFactory(
|
||||
profile__gender=['m', 'f', 'o'][i % 3],
|
||||
profile__year_of_birth=i + 1930
|
||||
) for i in xrange(30))
|
||||
|
||||
self.ces = tuple(CourseEnrollment.objects.create(course_id=self.course_id, user=user) for user in self.users)
|
||||
|
||||
@raises(ValueError)
|
||||
def test_profile_distribution_bad_feature(self):
|
||||
feature = 'robot-not-a-real-feature'
|
||||
self.assertNotIn(feature, AVAILABLE_PROFILE_FEATURES)
|
||||
profile_distribution(self.course_id, feature)
|
||||
|
||||
@raises(NotImplementedError)
|
||||
def test_profile_distribution_not_implemented_feature(self):
|
||||
feature = 'ROBOT_DO_NOT_USE_FEATURE'
|
||||
AVAILABLE_PROFILE_FEATURES.append(feature)
|
||||
self.assertIn(feature, AVAILABLE_PROFILE_FEATURES)
|
||||
profile_distribution(self.course_id, feature)
|
||||
|
||||
def test_profile_distribution_easy_choice(self):
|
||||
feature = 'gender'
|
||||
self.assertIn(feature, AVAILABLE_PROFILE_FEATURES)
|
||||
distribution = profile_distribution(self.course_id, feature)
|
||||
self.assertEqual(distribution['type'], 'EASY_CHOICE')
|
||||
self.assertEqual(distribution['data']['no_data'], 0)
|
||||
self.assertEqual(distribution['data']['m'], len(self.users) / 3)
|
||||
self.assertEqual(distribution['display_names']['m'], 'Male')
|
||||
|
||||
def test_profile_distribution_open_choice(self):
|
||||
feature = 'year_of_birth'
|
||||
self.assertIn(feature, AVAILABLE_PROFILE_FEATURES)
|
||||
distribution = profile_distribution(self.course_id, feature)
|
||||
print distribution
|
||||
self.assertEqual(distribution['type'], 'OPEN_CHOICE')
|
||||
self.assertNotIn('display_names', distribution)
|
||||
self.assertNotIn('no_data', distribution['data'])
|
||||
self.assertEqual(distribution['data'][1930], 1)
|
||||
|
||||
|
||||
class TestAnalyticsDistributionsNoData(TestCase):
|
||||
'''Test analytics distribution gathering.'''
|
||||
|
||||
def setUp(self):
|
||||
self.course_id = 'some/robot/course/id'
|
||||
|
||||
self.users = tuple(UserFactory(
|
||||
profile__year_of_birth=i + 1930,
|
||||
) for i in xrange(5))
|
||||
|
||||
self.nodata_users = tuple(UserFactory(
|
||||
profile__year_of_birth=None,
|
||||
) for _ in xrange(4))
|
||||
|
||||
self.users += self.nodata_users
|
||||
|
||||
self.ces = tuple(CourseEnrollment.objects.create(course_id=self.course_id, user=user) for user in self.users)
|
||||
|
||||
def test_profile_distribution_open_choice_nodata(self):
|
||||
feature = 'year_of_birth'
|
||||
self.assertIn(feature, AVAILABLE_PROFILE_FEATURES)
|
||||
distribution = profile_distribution(self.course_id, feature)
|
||||
print distribution
|
||||
self.assertEqual(distribution['type'], 'OPEN_CHOICE')
|
||||
self.assertNotIn('display_names', distribution)
|
||||
self.assertIn('no_data', distribution['data'])
|
||||
self.assertEqual(distribution['data']['no_data'], len(self.nodata_users))
|
||||
@@ -88,7 +88,5 @@ def update_forum_role_membership(course_id, user, rolename, mode):
|
||||
role.users.add(user)
|
||||
elif mode == 'revoke':
|
||||
role.users.remove(user)
|
||||
print "\n" * 5
|
||||
print role.users.all()
|
||||
else:
|
||||
raise ValueError("unrecognized mode '{}'".format(mode))
|
||||
|
||||
217
lms/djangoapps/instructor/tests/test_access.py
Normal file
217
lms/djangoapps/instructor/tests/test_access.py
Normal file
@@ -0,0 +1,217 @@
|
||||
from django.test import TestCase
|
||||
from nose.tools import raises
|
||||
from django.contrib.auth.models import User, Group
|
||||
from student.tests.factories import UserFactory
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
|
||||
from django.test.utils import override_settings
|
||||
from django.conf import settings
|
||||
from uuid import uuid4
|
||||
|
||||
from student.models import CourseEnrollment, CourseEnrollmentAllowed
|
||||
from courseware.access import get_access_group_name
|
||||
from django_comment_common.models import (Role,
|
||||
FORUM_ROLE_ADMINISTRATOR,
|
||||
FORUM_ROLE_MODERATOR,
|
||||
FORUM_ROLE_COMMUNITY_TA)
|
||||
from instructor.access import allow_access, revoke_access, list_with_level, update_forum_role_membership
|
||||
|
||||
# mock dependency
|
||||
# get_access_group_name = lambda course, role: '{0}_{1}'.format(course.course_id, role)
|
||||
|
||||
|
||||
# moved here from old courseware/tests/tests.py
|
||||
# when it disappeared this test broke.
|
||||
def mongo_store_config(data_dir):
|
||||
'''
|
||||
Defines default module store using MongoModuleStore
|
||||
|
||||
Use of this config requires mongo to be running
|
||||
'''
|
||||
store = {
|
||||
'default': {
|
||||
'ENGINE': 'xmodule.modulestore.mongo.MongoModuleStore',
|
||||
'OPTIONS': {
|
||||
'default_class': 'xmodule.raw_module.RawDescriptor',
|
||||
'host': 'localhost',
|
||||
'db': 'test_xmodule',
|
||||
'collection': 'modulestore_%s' % uuid4().hex,
|
||||
'fs_root': data_dir,
|
||||
'render_template': 'mitxmako.shortcuts.render_to_string',
|
||||
}
|
||||
}
|
||||
}
|
||||
store['direct'] = store['default']
|
||||
return store
|
||||
|
||||
TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT
|
||||
TEST_DATA_MONGO_MODULESTORE = mongo_store_config(TEST_DATA_DIR)
|
||||
# TEST_DATA_XML_MODULESTORE = xml_store_config(TEST_DATA_DIR)
|
||||
|
||||
|
||||
@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE)
|
||||
class TestInstructorAccessControlDB(ModuleStoreTestCase):
|
||||
'''Test instructor access administration against database effects'''
|
||||
|
||||
def setUp(self):
|
||||
# self.course_id = 'jus:/a/fake/c::rse/id'
|
||||
# self.course = MockCourse('jus:/a/fake/c::rse/id')
|
||||
self.course = CourseFactory.create()
|
||||
|
||||
def test_allow(self):
|
||||
user = UserFactory()
|
||||
level = 'staff'
|
||||
|
||||
allow_access(self.course, user, level)
|
||||
|
||||
self.assertIn(user, Group.objects.get(name=get_access_group_name(self.course, 'staff')).user_set.all())
|
||||
|
||||
def test_allow_twice(self):
|
||||
user = UserFactory()
|
||||
level = 'staff'
|
||||
|
||||
allow_access(self.course, user, level)
|
||||
self.assertIn(user, Group.objects.get(name=get_access_group_name(self.course, 'staff')).user_set.all())
|
||||
allow_access(self.course, user, level)
|
||||
self.assertIn(user, Group.objects.get(name=get_access_group_name(self.course, 'staff')).user_set.all())
|
||||
|
||||
def test_allow_revoke(self):
|
||||
user = UserFactory()
|
||||
level = 'staff'
|
||||
|
||||
allow_access(self.course, user, level)
|
||||
self.assertIn(user, Group.objects.get(name=get_access_group_name(self.course, 'staff')).user_set.all())
|
||||
revoke_access(self.course, user, level)
|
||||
self.assertNotIn(user, Group.objects.get(name=get_access_group_name(self.course, 'staff')).user_set.all())
|
||||
allow_access(self.course, user, level)
|
||||
self.assertIn(user, Group.objects.get(name=get_access_group_name(self.course, 'staff')).user_set.all())
|
||||
revoke_access(self.course, user, level)
|
||||
self.assertNotIn(user, Group.objects.get(name=get_access_group_name(self.course, 'staff')).user_set.all())
|
||||
|
||||
def test_revoke_without_group(self):
|
||||
user = UserFactory()
|
||||
level = 'staff'
|
||||
|
||||
revoke_access(self.course, user, level)
|
||||
self.assertNotIn(user, Group.objects.get(name=get_access_group_name(self.course, 'staff')).user_set.all())
|
||||
|
||||
def test_revoke_with_group(self):
|
||||
user = UserFactory()
|
||||
level = 'staff'
|
||||
|
||||
Group(name=get_access_group_name(self.course, level))
|
||||
revoke_access(self.course, user, level)
|
||||
self.assertNotIn(user, Group.objects.get(name=get_access_group_name(self.course, 'staff')).user_set.all())
|
||||
|
||||
def test_allow_disallow_multiuser(self):
|
||||
users = [UserFactory() for _ in xrange(3)]
|
||||
levels = ['staff', 'instructor', 'staff']
|
||||
antilevels = ['instructor', 'staff', 'instructor']
|
||||
|
||||
allow_access(self.course, users[0], levels[0])
|
||||
allow_access(self.course, users[1], levels[1])
|
||||
allow_access(self.course, users[2], levels[2])
|
||||
self.assertIn(users[0], Group.objects.get(name=get_access_group_name(self.course, levels[0])).user_set.all())
|
||||
self.assertIn(users[1], Group.objects.get(name=get_access_group_name(self.course, levels[1])).user_set.all())
|
||||
self.assertIn(users[2], Group.objects.get(name=get_access_group_name(self.course, levels[2])).user_set.all())
|
||||
|
||||
revoke_access(self.course, users[0], levels[0])
|
||||
revoke_access(self.course, users[0], antilevels[0])
|
||||
self.assertNotIn(users[0], Group.objects.get(name=get_access_group_name(self.course, levels[0])).user_set.all())
|
||||
self.assertIn(users[1], Group.objects.get(name=get_access_group_name(self.course, levels[1])).user_set.all())
|
||||
self.assertIn(users[2], Group.objects.get(name=get_access_group_name(self.course, levels[2])).user_set.all())
|
||||
|
||||
revoke_access(self.course, users[1], levels[1])
|
||||
allow_access(self.course, users[0], antilevels[0])
|
||||
self.assertNotIn(users[0], Group.objects.get(name=get_access_group_name(self.course, levels[0])).user_set.all())
|
||||
self.assertIn(users[0], Group.objects.get(name=get_access_group_name(self.course, antilevels[0])).user_set.all())
|
||||
self.assertNotIn(users[1], Group.objects.get(name=get_access_group_name(self.course, levels[1])).user_set.all())
|
||||
self.assertIn(users[2], Group.objects.get(name=get_access_group_name(self.course, levels[2])).user_set.all())
|
||||
|
||||
|
||||
@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE)
|
||||
class TestInstructorAccessControlPrefilledDB(ModuleStoreTestCase):
|
||||
def setUp(self):
|
||||
self.course = CourseFactory.create()
|
||||
|
||||
# setup instructors
|
||||
self.instructors = set([UserFactory.create(), UserFactory.create()])
|
||||
[allow_access(self.course, user, 'instructor') for user in self.instructors]
|
||||
|
||||
def test_list_with_level(self):
|
||||
instructors = set(list_with_level(self.course, 'instructor'))
|
||||
self.assertEqual(instructors, self.instructors)
|
||||
|
||||
def test_list_with_level_not_yet_group(self):
|
||||
instructors = set(list_with_level(self.course, 'staff'))
|
||||
self.assertEqual(instructors, set())
|
||||
|
||||
def test_list_with_level_bad_group(self):
|
||||
self.assertEqual(set(list_with_level(self.course, 'robot-definitely-not-a-group')), set())
|
||||
|
||||
def test_list_with_level_beta(self):
|
||||
beta_testers_result = set(list_with_level(self.course, 'beta'))
|
||||
self.assertEqual(set(), beta_testers_result)
|
||||
|
||||
beta_testers = set([UserFactory.create(), UserFactory.create()])
|
||||
[allow_access(self.course, user, 'beta') for user in beta_testers]
|
||||
beta_testers_result = set(list_with_level(self.course, 'beta'))
|
||||
self.assertEqual(beta_testers, beta_testers_result)
|
||||
|
||||
|
||||
@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE)
|
||||
class TestInstructorAccessForumDB(ModuleStoreTestCase):
|
||||
def setUp(self):
|
||||
self.course = CourseFactory.create()
|
||||
|
||||
self.moderators = set([UserFactory.create() for _ in xrange(4)])
|
||||
self.mod_role = Role.objects.create(course_id=self.course.id, name=FORUM_ROLE_MODERATOR)
|
||||
[self.mod_role.users.add(user) for user in self.moderators]
|
||||
|
||||
def test_update_forum_role_membership_allow_existing_role(self):
|
||||
user = UserFactory.create()
|
||||
update_forum_role_membership(self.course.id, user, FORUM_ROLE_MODERATOR, 'allow')
|
||||
self.assertIn(user, self.mod_role.users.all())
|
||||
|
||||
def test_update_forum_role_membership_allow_existing_role_allowed_user(self):
|
||||
user = UserFactory.create()
|
||||
update_forum_role_membership(self.course.id, user, FORUM_ROLE_MODERATOR, 'allow')
|
||||
update_forum_role_membership(self.course.id, user, FORUM_ROLE_MODERATOR, 'allow')
|
||||
self.assertIn(user, self.mod_role.users.all())
|
||||
|
||||
@raises(Role.DoesNotExist)
|
||||
def test_update_forum_role_membership_allow_not_existing_role(self):
|
||||
user = UserFactory.create()
|
||||
update_forum_role_membership(self.course.id, user, FORUM_ROLE_COMMUNITY_TA, 'allow')
|
||||
|
||||
def test_update_forum_role_membership_revoke_existing_role(self):
|
||||
user = iter(self.moderators).next()
|
||||
update_forum_role_membership(self.course.id, user, FORUM_ROLE_MODERATOR, 'revoke')
|
||||
self.assertNotIn(user, self.mod_role.users.all())
|
||||
|
||||
def test_update_forum_role_membership_revoke_existing_role_revoked_user(self):
|
||||
user = iter(self.moderators).next()
|
||||
update_forum_role_membership(self.course.id, user, FORUM_ROLE_MODERATOR, 'revoke')
|
||||
update_forum_role_membership(self.course.id, user, FORUM_ROLE_MODERATOR, 'revoke')
|
||||
self.assertNotIn(user, self.mod_role.users.all())
|
||||
|
||||
@raises(Role.DoesNotExist)
|
||||
def test_update_forum_role_membership_revoke_not_existing_role(self):
|
||||
user = iter(self.moderators).next()
|
||||
update_forum_role_membership(self.course.id, user, FORUM_ROLE_COMMUNITY_TA, 'revoke')
|
||||
|
||||
@raises(Role.DoesNotExist)
|
||||
def test_update_forum_role_membership_bad_role_allow(self):
|
||||
user = UserFactory.create()
|
||||
update_forum_role_membership(self.course.id, user, 'robot-definitely-not-a-forum-role', 'allow')
|
||||
|
||||
@raises(Role.DoesNotExist)
|
||||
def test_update_forum_role_membership_bad_role_revoke(self):
|
||||
user = UserFactory.create()
|
||||
update_forum_role_membership(self.course.id, user, 'robot-definitely-not-a-forum-role', 'revoke')
|
||||
|
||||
@raises(ValueError)
|
||||
def test_update_forum_role_membership_bad_mode(self):
|
||||
user = iter(self.moderators).next()
|
||||
update_forum_role_membership(self.course.id, user, FORUM_ROLE_MODERATOR, 'robot-not-a-mode')
|
||||
159
lms/djangoapps/instructor/tests/test_enrollment.py
Normal file
159
lms/djangoapps/instructor/tests/test_enrollment.py
Normal file
@@ -0,0 +1,159 @@
|
||||
"""
|
||||
Unit tests for instructor.enrollment methods.
|
||||
"""
|
||||
|
||||
import json
|
||||
from django.contrib.auth.models import Group, User
|
||||
# from courseware.access import _course_staff_group_name
|
||||
from courseware.models import StudentModule
|
||||
from django.test import TestCase
|
||||
from student.tests.factories import UserFactory
|
||||
|
||||
from student.models import CourseEnrollment, CourseEnrollmentAllowed
|
||||
from instructor.enrollment import (enroll_emails, unenroll_emails,
|
||||
split_input_list, reset_student_attempts)
|
||||
|
||||
|
||||
class TestInstructorEnrollmentDB(TestCase):
|
||||
'''Test instructor enrollment administration against database effects'''
|
||||
def setUp(self):
|
||||
self.course_id = 'robot:/a/fake/c::rse/id'
|
||||
|
||||
def test_split_input_list(self):
|
||||
strings = []
|
||||
lists = []
|
||||
strings.append("Lorem@ipsum.dolor, sit@amet.consectetur\nadipiscing@elit.Aenean\r convallis@at.lacus\r, ut@lacinia.Sed")
|
||||
lists.append(['Lorem@ipsum.dolor', 'sit@amet.consectetur', 'adipiscing@elit.Aenean', 'convallis@at.lacus', 'ut@lacinia.Sed'])
|
||||
|
||||
for (s, l) in zip(strings, lists):
|
||||
self.assertEqual(split_input_list(s), l)
|
||||
|
||||
def test_enroll_emails_userexists_alreadyenrolled(self):
|
||||
user = UserFactory()
|
||||
ce = CourseEnrollment(course_id=self.course_id, user=user)
|
||||
ce.save()
|
||||
|
||||
self.assertEqual(CourseEnrollment.objects.filter(course_id=self.course_id, user__email=user.email).count(), 1)
|
||||
|
||||
enroll_emails(self.course_id, [user.email])
|
||||
|
||||
self.assertEqual(CourseEnrollment.objects.filter(course_id=self.course_id, user__email=user.email).count(), 1)
|
||||
|
||||
def test_enroll_emails_userexists_succeedenrolling(self):
|
||||
user = UserFactory()
|
||||
|
||||
self.assertEqual(CourseEnrollment.objects.filter(course_id=self.course_id, user__email=user.email).count(), 0)
|
||||
|
||||
enroll_emails(self.course_id, [user.email])
|
||||
|
||||
self.assertEqual(CourseEnrollment.objects.filter(course_id=self.course_id, user__email=user.email).count(), 1)
|
||||
|
||||
def test_enroll_emails_nouser_alreadyallowed(self):
|
||||
email_without_user = 'test_enroll_emails_nouser_alreadyallowed@test.org'
|
||||
|
||||
self.assertEqual(User.objects.filter(email=email_without_user).count(), 0)
|
||||
self.assertEqual(CourseEnrollment.objects.filter(course_id=self.course_id, user__email=email_without_user).count(), 0)
|
||||
self.assertEqual(CourseEnrollmentAllowed.objects.filter(course_id=self.course_id, email=email_without_user).count(), 0)
|
||||
|
||||
cea = CourseEnrollmentAllowed(course_id=self.course_id, email=email_without_user, auto_enroll=False)
|
||||
cea.save()
|
||||
|
||||
enroll_emails(self.course_id, [email_without_user])
|
||||
|
||||
self.assertEqual(CourseEnrollment.objects.filter(course_id=self.course_id, user__email=email_without_user).count(), 0)
|
||||
self.assertEqual(CourseEnrollmentAllowed.objects.filter(course_id=self.course_id, email=email_without_user).count(), 1)
|
||||
self.assertEqual(CourseEnrollmentAllowed.objects.get(course_id=self.course_id, email=email_without_user).auto_enroll, False)
|
||||
|
||||
def test_enroll_emails_nouser_suceedallow(self):
|
||||
email_without_user = 'test_enroll_emails_nouser_suceedallow@test.org'
|
||||
|
||||
self.assertEqual(User.objects.filter(email=email_without_user).count(), 0)
|
||||
self.assertEqual(CourseEnrollment.objects.filter(course_id=self.course_id, user__email=email_without_user).count(), 0)
|
||||
self.assertEqual(CourseEnrollmentAllowed.objects.filter(course_id=self.course_id, email=email_without_user).count(), 0)
|
||||
|
||||
enroll_emails(self.course_id, [email_without_user])
|
||||
|
||||
self.assertEqual(CourseEnrollment.objects.filter(course_id=self.course_id, user__email=email_without_user).count(), 0)
|
||||
self.assertEqual(CourseEnrollmentAllowed.objects.filter(course_id=self.course_id, email=email_without_user).count(), 1)
|
||||
self.assertEqual(CourseEnrollmentAllowed.objects.get(course_id=self.course_id, email=email_without_user).auto_enroll, False)
|
||||
|
||||
def test_enroll_multiple(self):
|
||||
user1 = UserFactory()
|
||||
user2 = UserFactory()
|
||||
user3 = UserFactory()
|
||||
email_without_user1 = 'test_enroll_emails_nouser_suceedallow_1@test.org'
|
||||
email_without_user2 = 'test_enroll_emails_nouser_suceedallow_2@test.org'
|
||||
email_without_user3 = 'test_enroll_emails_nouser_suceedallow_3@test.org'
|
||||
|
||||
def test_db(auto_enroll):
|
||||
for user in [user1, user2, user3]:
|
||||
self.assertEqual(CourseEnrollment.objects.filter(course_id=self.course_id, user=user).count(), 1)
|
||||
self.assertEqual(CourseEnrollmentAllowed.objects.filter(course_id=self.course_id, email=user.email).count(), 0)
|
||||
|
||||
for email in [email_without_user1, email_without_user2, email_without_user3]:
|
||||
self.assertEqual(CourseEnrollment.objects.filter(course_id=self.course_id, user__email=email).count(), 0)
|
||||
self.assertEqual(CourseEnrollmentAllowed.objects.filter(course_id=self.course_id, email=email).count(), 1)
|
||||
self.assertEqual(CourseEnrollmentAllowed.objects.get(course_id=self.course_id, email=email).auto_enroll, auto_enroll)
|
||||
|
||||
enroll_emails(self.course_id, [user1.email, user2.email, user3.email, email_without_user1, email_without_user2, email_without_user3], auto_enroll=True)
|
||||
test_db(True)
|
||||
enroll_emails(self.course_id, [user1.email, user2.email, user3.email, email_without_user1, email_without_user2, email_without_user3], auto_enroll=False)
|
||||
test_db(False)
|
||||
|
||||
def test_unenroll_alreadyallowed(self):
|
||||
email_without_user = 'test_unenroll_alreadyallowed@test.org'
|
||||
cea = CourseEnrollmentAllowed(course_id=self.course_id, email=email_without_user, auto_enroll=False)
|
||||
cea.save()
|
||||
|
||||
unenroll_emails(self.course_id, [email_without_user])
|
||||
|
||||
self.assertEqual(User.objects.filter(email=email_without_user).count(), 0)
|
||||
self.assertEqual(CourseEnrollment.objects.filter(course_id=self.course_id, user__email=email_without_user).count(), 0)
|
||||
self.assertEqual(CourseEnrollmentAllowed.objects.filter(course_id=self.course_id, email=email_without_user).count(), 0)
|
||||
|
||||
def test_unenroll_alreadyenrolled(self):
|
||||
user = UserFactory()
|
||||
ce = CourseEnrollment(course_id=self.course_id, user=user)
|
||||
ce.save()
|
||||
|
||||
unenroll_emails(self.course_id, [user.email])
|
||||
|
||||
self.assertEqual(CourseEnrollment.objects.filter(course_id=self.course_id, user=user).count(), 0)
|
||||
self.assertEqual(CourseEnrollmentAllowed.objects.filter(course_id=self.course_id, email=user.email).count(), 0)
|
||||
|
||||
def test_unenroll_notenrolled(self):
|
||||
user = UserFactory()
|
||||
|
||||
unenroll_emails(self.course_id, [user.email])
|
||||
|
||||
self.assertEqual(CourseEnrollment.objects.filter(course_id=self.course_id, user=user).count(), 0)
|
||||
self.assertEqual(CourseEnrollmentAllowed.objects.filter(course_id=self.course_id, email=user.email).count(), 0)
|
||||
|
||||
def test_unenroll_nosuchuser(self):
|
||||
email_without_user = 'test_unenroll_nosuchuser@test.org'
|
||||
|
||||
unenroll_emails(self.course_id, [email_without_user])
|
||||
|
||||
self.assertEqual(User.objects.filter(email=email_without_user).count(), 0)
|
||||
self.assertEqual(CourseEnrollment.objects.filter(course_id=self.course_id, user__email=email_without_user).count(), 0)
|
||||
self.assertEqual(CourseEnrollmentAllowed.objects.filter(course_id=self.course_id, email=email_without_user).count(), 0)
|
||||
|
||||
def test_reset_student_attempts(self):
|
||||
user = UserFactory()
|
||||
msk = 'robot/module/state/key'
|
||||
original_state = json.dumps({'attempts': 32, 'otherstuff': 'alsorobots'})
|
||||
module = StudentModule.objects.create(student=user, course_id=self.course_id, module_state_key=msk, state=original_state)
|
||||
# lambda to reload the module state from the database
|
||||
module = lambda: StudentModule.objects.get(student=user, course_id=self.course_id, module_state_key=msk)
|
||||
self.assertEqual(json.loads(module().state)['attempts'], 32)
|
||||
reset_student_attempts(self.course_id, user, msk)
|
||||
self.assertEqual(json.loads(module().state)['attempts'], 0)
|
||||
|
||||
def test_delete_student_attempts(self):
|
||||
user = UserFactory()
|
||||
msk = 'robot/module/state/key'
|
||||
original_state = json.dumps({'attempts': 32, 'otherstuff': 'alsorobots'})
|
||||
StudentModule.objects.create(student=user, course_id=self.course_id, module_state_key=msk, state=original_state)
|
||||
self.assertEqual(StudentModule.objects.filter(student=user, course_id=self.course_id, module_state_key=msk).count(), 1)
|
||||
reset_student_attempts(self.course_id, user, msk, delete_module=True)
|
||||
self.assertEqual(StudentModule.objects.filter(student=user, course_id=self.course_id, module_state_key=msk).count(), 0)
|
||||
@@ -172,7 +172,7 @@ def enrolled_students_profiles(request, course_id, csv=False):
|
||||
"""
|
||||
course = get_course_with_access(request.user, course_id, 'staff', depth=None)
|
||||
|
||||
available_features = analytics.basic.AVAILABLE_STUDENT_FEATURES + analytics.basic.AVAILABLE_PROFILE_FEATURES
|
||||
available_features = analytics.basic.AVAILABLE_FEATURES
|
||||
query_features = ['username', 'name', 'email', 'language', 'location', 'year_of_birth', 'gender',
|
||||
'level_of_education', 'mailing_address', 'goals']
|
||||
|
||||
@@ -226,7 +226,7 @@ def profile_distribution(request, course_id):
|
||||
try:
|
||||
feature_results[feature] = analytics.distributions.profile_distribution(course_id, feature)
|
||||
except Exception as e:
|
||||
feature_results[feature] = {'error': "can not find distribution for '%s'" % feature}
|
||||
feature_results[feature] = {'error': "Error finding distribution for distribution for '{}'.".format(feature)}
|
||||
raise e
|
||||
|
||||
response_payload = {
|
||||
|
||||
Reference in New Issue
Block a user