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
a53903c4
Commit
a53903c4
authored
Sep 14, 2015
by
Diana Huang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add team column to grade reports.
parent
d8d49f75
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
170 additions
and
18 deletions
+170
-18
lms/djangoapps/instructor/tests/test_api.py
+20
-0
lms/djangoapps/instructor/views/api.py
+4
-0
lms/djangoapps/instructor_analytics/basic.py
+12
-0
lms/djangoapps/instructor_task/tasks_helper.py
+15
-2
lms/djangoapps/instructor_task/tests/test_tasks_helper.py
+118
-15
lms/djangoapps/teams/tests/factories.py
+1
-1
No files found.
lms/djangoapps/instructor/tests/test_api.py
View file @
a53903c4
...
...
@@ -2424,6 +2424,26 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
self
.
assertEqual
(
'cohort'
in
res_json
[
'feature_names'
],
is_cohorted
)
@ddt.data
(
True
,
False
)
def
test_get_students_features_teams
(
self
,
has_teams
):
"""
Test that get_students_features includes team info when the course is
has teams enabled, and does not when the course does not have teams enabled
"""
if
has_teams
:
self
.
course
=
CourseFactory
.
create
(
teams_configuration
=
{
'max_size'
:
2
,
'topics'
:
[{
'topic-id'
:
'topic'
,
'name'
:
'Topic'
,
'description'
:
'A Topic'
}]
})
course_instructor
=
InstructorFactory
(
course_key
=
self
.
course
.
id
)
self
.
client
.
login
(
username
=
course_instructor
.
username
,
password
=
'test'
)
url
=
reverse
(
'get_students_features'
,
kwargs
=
{
'course_id'
:
unicode
(
self
.
course
.
id
)})
response
=
self
.
client
.
get
(
url
,
{})
res_json
=
json
.
loads
(
response
.
content
)
self
.
assertEqual
(
'team'
in
res_json
[
'feature_names'
],
has_teams
)
def
test_get_students_who_may_enroll
(
self
):
"""
Test whether get_students_who_may_enroll returns an appropriate
...
...
lms/djangoapps/instructor/views/api.py
View file @
a53903c4
...
...
@@ -1138,6 +1138,10 @@ def get_students_features(request, course_id, csv=False): # pylint: disable=red
query_features
.
append
(
'cohort'
)
query_features_names
[
'cohort'
]
=
_
(
'Cohort'
)
if
course
.
teams_enabled
:
query_features
.
append
(
'team'
)
query_features_names
[
'team'
]
=
_
(
'Team'
)
if
not
csv
:
student_data
=
instructor_analytics
.
basic
.
enrolled_students_features
(
course_key
,
query_features
)
response_payload
=
{
...
...
lms/djangoapps/instructor_analytics/basic.py
View file @
a53903c4
...
...
@@ -39,6 +39,8 @@ AVAILABLE_FEATURES = STUDENT_FEATURES + PROFILE_FEATURES
COURSE_REGISTRATION_FEATURES
=
(
'code'
,
'course_id'
,
'created_by'
,
'created_at'
,
'is_valid'
)
COUPON_FEATURES
=
(
'code'
,
'course_id'
,
'percentage_discount'
,
'description'
,
'expiration_date'
,
'is_active'
)
UNAVAILABLE
=
"[unavailable]"
def
sale_order_record_features
(
course_id
,
features
):
"""
...
...
@@ -172,6 +174,7 @@ def enrolled_students_features(course_key, features):
]
"""
include_cohort_column
=
'cohort'
in
features
include_team_column
=
'team'
in
features
students
=
User
.
objects
.
filter
(
courseenrollment__course_id
=
course_key
,
...
...
@@ -181,6 +184,9 @@ def enrolled_students_features(course_key, features):
if
include_cohort_column
:
students
=
students
.
prefetch_related
(
'course_groups'
)
if
include_team_column
:
students
=
students
.
prefetch_related
(
'teams'
)
def
extract_student
(
student
,
features
):
""" convert student to dictionary """
student_features
=
[
x
for
x
in
STUDENT_FEATURES
if
x
in
features
]
...
...
@@ -216,6 +222,12 @@ def enrolled_students_features(course_key, features):
(
cohort
.
name
for
cohort
in
student
.
course_groups
.
all
()
if
cohort
.
course_id
==
course_key
),
"[unassigned]"
)
if
include_team_column
:
student_dict
[
'team'
]
=
next
(
(
team
.
name
for
team
in
student
.
teams
.
all
()
if
team
.
course_id
==
course_key
),
UNAVAILABLE
)
return
student_dict
return
[
extract_student
(
student
,
features
)
for
student
in
students
]
...
...
lms/djangoapps/instructor_task/tasks_helper.py
View file @
a53903c4
...
...
@@ -62,6 +62,7 @@ from openedx.core.djangoapps.content.course_structures.models import CourseStruc
from
opaque_keys.edx.keys
import
UsageKey
from
openedx.core.djangoapps.course_groups.cohorts
import
add_user_to_cohort
,
is_course_cohorted
from
student.models
import
CourseEnrollment
,
CourseAccessRole
from
teams.models
import
CourseTeamMembership
from
verify_student.models
import
SoftwareSecurePhotoVerification
# define different loggers for use within tasks and on client side
...
...
@@ -681,7 +682,9 @@ def upload_grades_csv(_xmodule_instance_args, _entry_id, course_id, _task_input,
course
=
get_course_by_id
(
course_id
)
course_is_cohorted
=
is_course_cohorted
(
course
.
id
)
teams_enabled
=
course
.
teams_enabled
cohorts_header
=
[
'Cohort Name'
]
if
course_is_cohorted
else
[]
teams_header
=
[
'Team Name'
]
if
teams_enabled
else
[]
experiment_partitions
=
get_split_user_partitions
(
course
.
user_partitions
)
group_configs_header
=
[
u'Experiment Group ({})'
.
format
(
partition
.
name
)
for
partition
in
experiment_partitions
]
...
...
@@ -703,6 +706,7 @@ def upload_grades_csv(_xmodule_instance_args, _entry_id, course_id, _task_input,
task_info_string
,
action_name
,
current_step
,
total_enrolled_students
)
for
student
,
gradeset
,
err_msg
in
iterate_grades_for
(
course_id
,
enrolled_students
):
...
...
@@ -730,7 +734,8 @@ def upload_grades_csv(_xmodule_instance_args, _entry_id, course_id, _task_input,
header
=
[
section
[
'label'
]
for
section
in
gradeset
[
u'section_breakdown'
]]
rows
.
append
(
[
"id"
,
"email"
,
"username"
,
"grade"
]
+
header
+
cohorts_header
+
group_configs_header
+
[
'Enrollment Track'
,
'Verification Status'
]
+
certificate_info_header
group_configs_header
+
teams_header
+
[
'Enrollment Track'
,
'Verification Status'
]
+
certificate_info_header
)
percents
=
{
...
...
@@ -749,6 +754,14 @@ def upload_grades_csv(_xmodule_instance_args, _entry_id, course_id, _task_input,
group
=
LmsPartitionService
(
student
,
course_id
)
.
get_group
(
partition
,
assign
=
False
)
group_configs_group_names
.
append
(
group
.
name
if
group
else
''
)
team_name
=
[]
if
teams_enabled
:
try
:
membership
=
CourseTeamMembership
.
objects
.
get
(
user
=
student
,
team__course_id
=
course_id
)
team_name
.
append
(
membership
.
team
.
name
)
except
CourseTeamMembership
.
DoesNotExist
:
team_name
.
append
(
''
)
enrollment_mode
=
CourseEnrollment
.
enrollment_mode_for_user
(
student
,
course_id
)[
0
]
verification_status
=
SoftwareSecurePhotoVerification
.
verification_status_for_user
(
student
,
...
...
@@ -771,7 +784,7 @@ def upload_grades_csv(_xmodule_instance_args, _entry_id, course_id, _task_input,
row_percents
=
[
percents
.
get
(
label
,
0.0
)
for
label
in
header
]
rows
.
append
(
[
student
.
id
,
student
.
email
,
student
.
username
,
gradeset
[
'percent'
]]
+
row_percents
+
cohorts_group_name
+
group_configs_group_names
+
row_percents
+
cohorts_group_name
+
group_configs_group_names
+
team_name
+
[
enrollment_mode
]
+
[
verification_status
]
+
certificate_info
)
else
:
...
...
lms/djangoapps/instructor_task/tests/test_tasks_helper.py
View file @
a53903c4
...
...
@@ -42,11 +42,31 @@ from instructor_task.tasks_helper import (
upload_exec_summary_report
,
generate_students_certificates
,
)
from
instructor_analytics.basic
import
UNAVAILABLE
from
openedx.core.djangoapps.util.testing
import
ContentGroupTestCase
,
TestConditionalContent
from
teams.tests.factories
import
CourseTeamFactory
,
CourseTeamMembershipFactory
class
InstructorGradeReportTestCase
(
TestReportMixin
,
InstructorTaskCourseTestCase
):
""" Base class for grade report tests. """
def
_verify_cell_data_for_user
(
self
,
username
,
course_id
,
column_header
,
expected_cell_content
):
"""
Verify cell data in the grades CSV for a particular user.
"""
with
patch
(
'instructor_task.tasks_helper._get_current_task'
):
result
=
upload_grades_csv
(
None
,
None
,
course_id
,
None
,
'graded'
)
self
.
assertDictContainsSubset
({
'attempted'
:
2
,
'succeeded'
:
2
,
'failed'
:
0
},
result
)
report_store
=
ReportStore
.
from_config
(
config_name
=
'GRADES_DOWNLOAD'
)
report_csv_filename
=
report_store
.
links_for
(
course_id
)[
0
][
0
]
with
open
(
report_store
.
path_to
(
course_id
,
report_csv_filename
))
as
csv_file
:
for
row
in
unicodecsv
.
DictReader
(
csv_file
):
if
row
.
get
(
'username'
)
==
username
:
self
.
assertEqual
(
row
[
column_header
],
expected_cell_content
)
@ddt.ddt
class
TestInstructorGradeReport
(
TestReportMixin
,
InstructorTaskCourse
TestCase
):
class
TestInstructorGradeReport
(
InstructorGradeReport
TestCase
):
"""
Tests that CSV grade report generation works.
"""
...
...
@@ -87,20 +107,6 @@ class TestInstructorGradeReport(TestReportMixin, InstructorTaskCourseTestCase):
report_store
=
ReportStore
.
from_config
(
config_name
=
'GRADES_DOWNLOAD'
)
self
.
assertTrue
(
any
(
'grade_report_err'
in
item
[
0
]
for
item
in
report_store
.
links_for
(
self
.
course
.
id
)))
def
_verify_cell_data_for_user
(
self
,
username
,
course_id
,
column_header
,
expected_cell_content
):
"""
Verify cell data in the grades CSV for a particular user.
"""
with
patch
(
'instructor_task.tasks_helper._get_current_task'
):
result
=
upload_grades_csv
(
None
,
None
,
course_id
,
None
,
'graded'
)
self
.
assertDictContainsSubset
({
'attempted'
:
2
,
'succeeded'
:
2
,
'failed'
:
0
},
result
)
report_store
=
ReportStore
.
from_config
(
config_name
=
'GRADES_DOWNLOAD'
)
report_csv_filename
=
report_store
.
links_for
(
course_id
)[
0
][
0
]
with
open
(
report_store
.
path_to
(
course_id
,
report_csv_filename
))
as
csv_file
:
for
row
in
unicodecsv
.
DictReader
(
csv_file
):
if
row
.
get
(
'username'
)
==
username
:
self
.
assertEqual
(
row
[
column_header
],
expected_cell_content
)
def
test_cohort_data_in_grading
(
self
):
"""
Test that cohort data is included in grades csv if cohort configuration is enabled for course.
...
...
@@ -278,6 +284,43 @@ class TestInstructorGradeReport(TestReportMixin, InstructorTaskCourseTestCase):
self
.
assertDictContainsSubset
({
'attempted'
:
1
,
'succeeded'
:
1
,
'failed'
:
0
},
result
)
class
TestTeamGradeReport
(
InstructorGradeReportTestCase
):
""" Test that teams appear correctly in the grade report when it is enabled for the course. """
def
setUp
(
self
):
super
(
TestTeamGradeReport
,
self
)
.
setUp
()
self
.
course
=
CourseFactory
.
create
(
teams_configuration
=
{
'max_size'
:
2
,
'topics'
:
[{
'topic-id'
:
'topic'
,
'name'
:
'Topic'
,
'description'
:
'A Topic'
}]
})
self
.
student1
=
UserFactory
.
create
()
CourseEnrollment
.
enroll
(
self
.
student1
,
self
.
course
.
id
)
self
.
student2
=
UserFactory
.
create
()
CourseEnrollment
.
enroll
(
self
.
student2
,
self
.
course
.
id
)
def
test_team_in_grade_report
(
self
):
self
.
_verify_cell_data_for_user
(
self
.
student1
.
username
,
self
.
course
.
id
,
'Team Name'
,
''
)
def
test_correct_team_name_in_grade_report
(
self
):
team1
=
CourseTeamFactory
.
create
(
course_id
=
self
.
course
.
id
)
CourseTeamMembershipFactory
.
create
(
team
=
team1
,
user
=
self
.
student1
)
team2
=
CourseTeamFactory
.
create
(
course_id
=
self
.
course
.
id
)
CourseTeamMembershipFactory
.
create
(
team
=
team2
,
user
=
self
.
student2
)
self
.
_verify_cell_data_for_user
(
self
.
student1
.
username
,
self
.
course
.
id
,
'Team Name'
,
team1
.
name
)
self
.
_verify_cell_data_for_user
(
self
.
student2
.
username
,
self
.
course
.
id
,
'Team Name'
,
team2
.
name
)
def
test_team_deleted
(
self
):
team1
=
CourseTeamFactory
.
create
(
course_id
=
self
.
course
.
id
)
membership1
=
CourseTeamMembershipFactory
.
create
(
team
=
team1
,
user
=
self
.
student1
)
team2
=
CourseTeamFactory
.
create
(
course_id
=
self
.
course
.
id
)
CourseTeamMembershipFactory
.
create
(
team
=
team2
,
user
=
self
.
student2
)
team1
.
delete
()
membership1
.
delete
()
self
.
_verify_cell_data_for_user
(
self
.
student1
.
username
,
self
.
course
.
id
,
'Team Name'
,
''
)
self
.
_verify_cell_data_for_user
(
self
.
student2
.
username
,
self
.
course
.
id
,
'Team Name'
,
team2
.
name
)
class
TestProblemResponsesReport
(
TestReportMixin
,
InstructorTaskCourseTestCase
):
"""
Tests that generation of CSV files listing student answers to a
...
...
@@ -912,6 +955,66 @@ class TestStudentReport(TestReportMixin, InstructorTaskCourseTestCase):
self
.
assertDictContainsSubset
({
'attempted'
:
num_students
,
'succeeded'
:
num_students
,
'failed'
:
0
},
result
)
class
TestTeamStudentReport
(
TestReportMixin
,
InstructorTaskCourseTestCase
):
"Test the student report when including teams information. "
def
setUp
(
self
):
super
(
TestTeamStudentReport
,
self
)
.
setUp
()
self
.
course
=
CourseFactory
.
create
(
teams_configuration
=
{
'max_size'
:
2
,
'topics'
:
[{
'topic-id'
:
'topic'
,
'name'
:
'Topic'
,
'description'
:
'A Topic'
}]
})
self
.
student1
=
UserFactory
.
create
()
CourseEnrollment
.
enroll
(
self
.
student1
,
self
.
course
.
id
)
self
.
student2
=
UserFactory
.
create
()
CourseEnrollment
.
enroll
(
self
.
student2
,
self
.
course
.
id
)
def
_generate_and_verify_teams_column
(
self
,
username
,
expected_team
):
""" Run the upload_students_csv task and verify that the correct team was added to the CSV. """
current_task
=
Mock
()
current_task
.
update_state
=
Mock
()
task_input
=
{
'features'
:
[
'id'
,
'username'
,
'name'
,
'email'
,
'language'
,
'location'
,
'year_of_birth'
,
'gender'
,
'level_of_education'
,
'mailing_address'
,
'goals'
,
'team'
]
}
with
patch
(
'instructor_task.tasks_helper._get_current_task'
)
as
mock_current_task
:
mock_current_task
.
return_value
=
current_task
result
=
upload_students_csv
(
None
,
None
,
self
.
course
.
id
,
task_input
,
'calculated'
)
self
.
assertDictContainsSubset
({
'attempted'
:
2
,
'succeeded'
:
2
,
'failed'
:
0
},
result
)
report_store
=
ReportStore
.
from_config
(
config_name
=
'GRADES_DOWNLOAD'
)
report_csv_filename
=
report_store
.
links_for
(
self
.
course
.
id
)[
0
][
0
]
with
open
(
report_store
.
path_to
(
self
.
course
.
id
,
report_csv_filename
))
as
csv_file
:
for
row
in
unicodecsv
.
DictReader
(
csv_file
):
if
row
.
get
(
'username'
)
==
username
:
self
.
assertEqual
(
row
[
'team'
],
expected_team
)
def
test_team_column_no_teams
(
self
):
self
.
_generate_and_verify_teams_column
(
self
.
student1
.
username
,
UNAVAILABLE
)
self
.
_generate_and_verify_teams_column
(
self
.
student2
.
username
,
UNAVAILABLE
)
def
test_team_column_with_teams
(
self
):
team1
=
CourseTeamFactory
.
create
(
course_id
=
self
.
course
.
id
)
CourseTeamMembershipFactory
.
create
(
team
=
team1
,
user
=
self
.
student1
)
team2
=
CourseTeamFactory
.
create
(
course_id
=
self
.
course
.
id
)
CourseTeamMembershipFactory
.
create
(
team
=
team2
,
user
=
self
.
student2
)
self
.
_generate_and_verify_teams_column
(
self
.
student1
.
username
,
team1
.
name
)
self
.
_generate_and_verify_teams_column
(
self
.
student2
.
username
,
team2
.
name
)
def
test_team_column_with_deleted_team
(
self
):
team1
=
CourseTeamFactory
.
create
(
course_id
=
self
.
course
.
id
)
membership1
=
CourseTeamMembershipFactory
.
create
(
team
=
team1
,
user
=
self
.
student1
)
team2
=
CourseTeamFactory
.
create
(
course_id
=
self
.
course
.
id
)
CourseTeamMembershipFactory
.
create
(
team
=
team2
,
user
=
self
.
student2
)
team1
.
delete
()
membership1
.
delete
()
self
.
_generate_and_verify_teams_column
(
self
.
student1
.
username
,
UNAVAILABLE
)
self
.
_generate_and_verify_teams_column
(
self
.
student2
.
username
,
team2
.
name
)
@ddt.ddt
class
TestListMayEnroll
(
TestReportMixin
,
InstructorTaskCourseTestCase
):
"""
...
...
lms/djangoapps/teams/tests/factories.py
View file @
a53903c4
...
...
@@ -24,7 +24,7 @@ class CourseTeamFactory(DjangoModelFactory):
team_id
=
factory
.
Sequence
(
'team-{0}'
.
format
)
discussion_topic_id
=
factory
.
LazyAttribute
(
lambda
a
:
uuid4
()
.
hex
)
name
=
"Awesome Team"
name
=
factory
.
Sequence
(
"Awesome Team {0}"
.
format
)
description
=
"A simple description"
last_activity_at
=
LAST_ACTIVITY_AT
...
...
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