Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
f77ebc82
Commit
f77ebc82
authored
Jun 27, 2013
by
Miles Steele
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
add tests
parent
a751702a
Show whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
615 additions
and
30 deletions
+615
-30
lms/djangoapps/analytics/basic.py
+8
-5
lms/djangoapps/analytics/csvs.py
+16
-12
lms/djangoapps/analytics/distributions.py
+25
-9
lms/djangoapps/analytics/tests/test_basic.py
+40
-0
lms/djangoapps/analytics/tests/test_csvs.py
+65
-0
lms/djangoapps/analytics/tests/test_distributions.py
+83
-0
lms/djangoapps/instructor/access.py
+0
-2
lms/djangoapps/instructor/tests/test_access.py
+217
-0
lms/djangoapps/instructor/tests/test_enrollment.py
+159
-0
lms/djangoapps/instructor/views/api.py
+2
-2
No files found.
lms/djangoapps/analytics/basic.py
View file @
f77ebc82
...
@@ -8,8 +8,9 @@ from django.contrib.auth.models import User
...
@@ -8,8 +8,9 @@ from django.contrib.auth.models import User
import
xmodule.graders
as
xmgraders
import
xmodule.graders
as
xmgraders
AVAILABLE_STUDENT_FEATURES
=
[
'username'
,
'first_name'
,
'last_name'
,
'is_staff'
,
'email'
]
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'
]
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
):
def
enrolled_students_profiles
(
course_id
,
features
):
...
@@ -19,10 +20,12 @@ 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)
# enrollments = CourseEnrollment.objects.filter(course_id=course_id)
# students = [enrollment.user for enrollment in enrollments]
# students = [enrollment.user for enrollment in enrollments]
students
=
User
.
objects
.
filter
(
courseenrollment__course_id
=
course_id
)
.
order_by
(
'username'
)
.
select_related
(
'profile'
)
students
=
User
.
objects
.
filter
(
courseenrollment__course_id
=
course_id
)
.
order_by
(
'username'
)
.
select_related
(
'profile'
)
print
len
(
students
)
print
students
def
extract_student
(
student
):
def
extract_student
(
student
):
student_features
=
[
feature
for
feature
in
features
if
feature
in
AVAILABLE_
STUDENT_FEATURES
]
student_features
=
[
feature
for
feature
in
features
if
feature
in
STUDENT_FEATURES
]
profile_features
=
[
feature
for
feature
in
features
if
feature
in
AVAILABLE_
PROFILE_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
)
student_dict
=
dict
((
feature
,
getattr
(
student
,
feature
))
for
feature
in
student_features
)
profile
=
student
.
profile
profile
=
student
.
profile
...
@@ -35,7 +38,7 @@ def enrolled_students_profiles(course_id, features):
...
@@ -35,7 +38,7 @@ def enrolled_students_profiles(course_id, features):
def
dump_grading_context
(
course
):
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
Useful for debugging grading_policy.json and policy.json
Returns HTML string
Returns HTML string
...
...
lms/djangoapps/analytics/csvs.py
View file @
f77ebc82
...
@@ -27,30 +27,34 @@ def create_csv_response(filename, header, datarows):
...
@@ -27,30 +27,34 @@ def create_csv_response(filename, header, datarows):
def
format_dictlist
(
dictlist
):
def
format_dictlist
(
dictlist
):
"""
"""
Convert
from
[
Convert
FROM
[
{
{
'label1': 'value1,1',
'label1': 'value
-
1,1',
'label2': 'value
2,1
',
'label2': 'value
-1,2
',
'label3': 'value
3,1
',
'label3': 'value
-1,3
',
'label4': 'value
4,1
',
'label4': 'value
-1,4
',
},
},
{
{
'label1': 'value
1,2
',
'label1': 'value
-2,1
',
'label2': 'value2,2',
'label2': 'value
-
2,2',
'label3': 'value
3,2
',
'label3': 'value
-2,3
',
'label4': 'value
4,2
',
'label4': 'value
-2,4
',
}
}
]
]
to
{
TO
{
'header': ['label1', 'label2', 'label3', 'label4'],
'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
.
"""
"""
if
len
(
dictlist
)
>
0
:
header
=
dictlist
[
0
]
.
keys
()
header
=
dictlist
[
0
]
.
keys
()
else
:
header
=
[]
def
dict_to_entry
(
d
):
def
dict_to_entry
(
d
):
ordered
=
sorted
(
d
.
items
(),
key
=
lambda
(
k
,
v
):
header
.
index
(
k
))
ordered
=
sorted
(
d
.
items
(),
key
=
lambda
(
k
,
v
):
header
.
index
(
k
))
...
...
lms/djangoapps/analytics/distributions.py
View file @
f77ebc82
...
@@ -14,7 +14,9 @@ def profile_distribution(course_id, feature):
...
@@ -14,7 +14,9 @@ def profile_distribution(course_id, feature):
Retrieve distribution of students over a given feature.
Retrieve distribution of students over a given feature.
feature is one of AVAILABLE_PROFILE_FEATURES.
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.
data types e.g.
EASY_CHOICE - choices with a restricted domain, e.g. level_of_education
EASY_CHOICE - choices with a restricted domain, e.g. level_of_education
OPEN_CHOICE - choices with a larger domain e.g. year_of_birth
OPEN_CHOICE - choices with a larger domain e.g. year_of_birth
...
@@ -23,18 +25,23 @@ def profile_distribution(course_id, feature):
...
@@ -23,18 +25,23 @@ def profile_distribution(course_id, feature):
EASY_CHOICE_FEATURES
=
[
'gender'
,
'level_of_education'
]
EASY_CHOICE_FEATURES
=
[
'gender'
,
'level_of_education'
]
OPEN_CHOICE_FEATURES
=
[
'year_of_birth'
]
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
=
{}
feature_results
=
{}
if
not
feature
in
AVAILABLE_PROFILE_FEATURES
:
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
in
EASY_CHOICE_FEATURES
:
if
feature
==
'gender'
:
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'
:
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
:
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
=
{}
data
=
{}
for
(
short
,
full
)
in
choices
:
for
(
short
,
full
)
in
choices
:
...
@@ -43,21 +50,30 @@ def profile_distribution(course_id, feature):
...
@@ -43,21 +50,30 @@ def profile_distribution(course_id, feature):
elif
feature
==
'level_of_education'
:
elif
feature
==
'level_of_education'
:
count
=
CourseEnrollment
.
objects
.
filter
(
course_id
=
course_id
,
user__profile__level_of_education
=
short
)
.
count
()
count
=
CourseEnrollment
.
objects
.
filter
(
course_id
=
course_id
,
user__profile__level_of_education
=
short
)
.
count
()
else
:
else
:
raise
ValueError
(
"feature request not implemented for feature
%
s"
%
feature
)
raise
raise_not_implemented
(
)
data
[
full
]
=
count
data
[
short
]
=
count
feature_results
[
'data'
]
=
data
feature_results
[
'data'
]
=
data
feature_results
[
'type'
]
=
'EASY_CHOICE'
feature_results
[
'type'
]
=
'EASY_CHOICE'
feature_results
[
'display_names'
]
=
dict
(
choices
)
elif
feature
in
OPEN_CHOICE_FEATURES
:
elif
feature
in
OPEN_CHOICE_FEATURES
:
profiles
=
UserProfile
.
objects
.
filter
(
user__courseenrollment__course_id
=
course_id
)
profiles
=
UserProfile
.
objects
.
filter
(
user__courseenrollment__course_id
=
course_id
)
query_distribution
=
profiles
.
values
(
feature
)
.
annotate
(
Count
(
feature
))
.
order_by
()
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
=
dict
((
vald
[
feature
],
vald
[
feature
+
'__count'
])
for
vald
in
query_distribution
)
# distribution is of the form {'value1': 4, 'value2': 2, ...}
# 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
[
'data'
]
=
distribution
feature_results
[
'type'
]
=
'OPEN_CHOICE'
feature_results
[
'type'
]
=
'OPEN_CHOICE'
else
:
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
return
feature_results
lms/djangoapps/analytics/tests/test_basic.py
0 → 100644
View file @
f77ebc82
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
))
lms/djangoapps/analytics/tests/test_csvs.py
0 → 100644
View file @
f77ebc82
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'
:
[],
})
lms/djangoapps/analytics/tests/test_distributions.py
0 → 100644
View file @
f77ebc82
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
))
lms/djangoapps/instructor/access.py
View file @
f77ebc82
...
@@ -88,7 +88,5 @@ def update_forum_role_membership(course_id, user, rolename, mode):
...
@@ -88,7 +88,5 @@ def update_forum_role_membership(course_id, user, rolename, mode):
role
.
users
.
add
(
user
)
role
.
users
.
add
(
user
)
elif
mode
==
'revoke'
:
elif
mode
==
'revoke'
:
role
.
users
.
remove
(
user
)
role
.
users
.
remove
(
user
)
print
"
\n
"
*
5
print
role
.
users
.
all
()
else
:
else
:
raise
ValueError
(
"unrecognized mode '{}'"
.
format
(
mode
))
raise
ValueError
(
"unrecognized mode '{}'"
.
format
(
mode
))
lms/djangoapps/instructor/tests/test_access.py
0 → 100644
View file @
f77ebc82
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'
)
lms/djangoapps/instructor/tests/test_enrollment.py
0 → 100644
View file @
f77ebc82
"""
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
\n
adipiscing@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
)
lms/djangoapps/instructor/views/api.py
View file @
f77ebc82
...
@@ -172,7 +172,7 @@ def enrolled_students_profiles(request, course_id, csv=False):
...
@@ -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
)
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'
,
query_features
=
[
'username'
,
'name'
,
'email'
,
'language'
,
'location'
,
'year_of_birth'
,
'gender'
,
'level_of_education'
,
'mailing_address'
,
'goals'
]
'level_of_education'
,
'mailing_address'
,
'goals'
]
...
@@ -226,7 +226,7 @@ def profile_distribution(request, course_id):
...
@@ -226,7 +226,7 @@ def profile_distribution(request, course_id):
try
:
try
:
feature_results
[
feature
]
=
analytics
.
distributions
.
profile_distribution
(
course_id
,
feature
)
feature_results
[
feature
]
=
analytics
.
distributions
.
profile_distribution
(
course_id
,
feature
)
except
Exception
as
e
:
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
raise
e
response_payload
=
{
response_payload
=
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment