Get students experiments, groups, and cohorts
TNL-498
This commit is contained in:
@@ -4,7 +4,6 @@ running state of a course.
|
||||
|
||||
"""
|
||||
import json
|
||||
import urllib
|
||||
from datetime import datetime
|
||||
from time import time
|
||||
import unicodecsv
|
||||
@@ -22,8 +21,7 @@ from track.views import task_track
|
||||
from util.file import course_filename_prefix_generator, UniversalNewlineIterator
|
||||
from xmodule.modulestore.django import modulestore
|
||||
|
||||
from openedx.core.djangoapps.course_groups.models import CourseUserGroup
|
||||
from openedx.core.djangoapps.course_groups.cohorts import add_user_to_cohort
|
||||
from courseware.courses import get_course_by_id
|
||||
from courseware.grades import iterate_grades_for
|
||||
from courseware.models import StudentModule
|
||||
from courseware.model_data import FieldDataCache
|
||||
@@ -31,8 +29,13 @@ from courseware.module_render import get_module_for_descriptor_internal
|
||||
from instructor_analytics.basic import enrolled_students_features
|
||||
from instructor_analytics.csvs import format_dictlist
|
||||
from instructor_task.models import ReportStore, InstructorTask, PROGRESS
|
||||
from lms.djangoapps.lms_xblock.runtime import LmsPartitionService
|
||||
from openedx.core.djangoapps.course_groups.cohorts import get_cohort
|
||||
from openedx.core.djangoapps.course_groups.models import CourseUserGroup
|
||||
from openedx.core.djangoapps.course_groups.cohorts import add_user_to_cohort
|
||||
from student.models import CourseEnrollment
|
||||
|
||||
|
||||
# define different loggers for use within tasks and on client side
|
||||
TASK_LOG = get_task_logger(__name__)
|
||||
|
||||
@@ -547,6 +550,13 @@ def upload_grades_csv(_xmodule_instance_args, _entry_id, course_id, _task_input,
|
||||
enrolled_students = CourseEnrollment.users_enrolled_in(course_id)
|
||||
task_progress = TaskProgress(action_name, enrolled_students.count(), start_time)
|
||||
|
||||
course = get_course_by_id(course_id)
|
||||
cohorts_header = ['Cohort Group Name'] if course.is_cohorted else []
|
||||
|
||||
partition_service = LmsPartitionService(user=None, course_id=course_id)
|
||||
partitions = partition_service.course_partitions
|
||||
group_configs_header = ['Group Configuration Group Name ({})'.format(partition.name) for partition in partitions]
|
||||
|
||||
# Loop over all our students and build our CSV lists in memory
|
||||
header = None
|
||||
rows = []
|
||||
@@ -564,7 +574,9 @@ def upload_grades_csv(_xmodule_instance_args, _entry_id, course_id, _task_input,
|
||||
if not header:
|
||||
# Encode the header row in utf-8 encoding in case there are unicode characters
|
||||
header = [section['label'].encode('utf-8') for section in gradeset[u'section_breakdown']]
|
||||
rows.append(["id", "email", "username", "grade"] + header)
|
||||
rows.append(
|
||||
["id", "email", "username", "grade"] + header + cohorts_header + group_configs_header
|
||||
)
|
||||
|
||||
percents = {
|
||||
section['label']: section.get('percent', 0.0)
|
||||
@@ -572,6 +584,16 @@ def upload_grades_csv(_xmodule_instance_args, _entry_id, course_id, _task_input,
|
||||
if 'label' in section
|
||||
}
|
||||
|
||||
cohorts_group_name = []
|
||||
if course.is_cohorted:
|
||||
group = get_cohort(student, course_id, assign=False)
|
||||
cohorts_group_name.append(group.name if group else '')
|
||||
|
||||
group_configs_group_names = []
|
||||
for partition in partitions:
|
||||
group = LmsPartitionService(student, course_id).get_group(partition, assign=False)
|
||||
group_configs_group_names.append(group.name if group else '')
|
||||
|
||||
# Not everybody has the same gradable items. If the item is not
|
||||
# found in the user's gradeset, just assume it's a 0. The aggregated
|
||||
# grades for their sections and overall course will be calculated
|
||||
@@ -579,7 +601,10 @@ def upload_grades_csv(_xmodule_instance_args, _entry_id, course_id, _task_input,
|
||||
# possible for a student to have a 0.0 show up in their row but
|
||||
# still have 100% for the course.
|
||||
row_percents = [percents.get(label, 0.0) for label in header]
|
||||
rows.append([student.id, student.email, student.username, gradeset['percent']] + row_percents)
|
||||
rows.append(
|
||||
[student.id, student.email, student.username, gradeset['percent']] +
|
||||
row_percents + cohorts_group_name + group_configs_group_names
|
||||
)
|
||||
else:
|
||||
# An empty gradeset means we failed to grade a student.
|
||||
task_progress.failed += 1
|
||||
|
||||
@@ -611,15 +611,30 @@ class TestGradeReportConditionalContent(TestReportMixin, TestIntegrationTask):
|
||||
representing their grades we expect to see in the CSV.
|
||||
For example: [student_a: {'grade': 1.0, 'HW': 1.0}]
|
||||
"""
|
||||
def merge_dicts(dict_1, dict_2):
|
||||
"""Return the union of dict_1 and dict_2"""
|
||||
return dict(dict_1.items() + dict_2.items())
|
||||
def merge_dicts(*dicts):
|
||||
"""
|
||||
Return the union of dicts
|
||||
|
||||
Arguments:
|
||||
dicts: tuple of dicts
|
||||
"""
|
||||
return dict([item for d in dicts for item in d.items()])
|
||||
|
||||
def user_partition_group(user):
|
||||
"""Return a dict having single key with value equals to students group in partition"""
|
||||
group_config_hdr_tpl = 'Group Configuration Group Name ({})'
|
||||
return {
|
||||
group_config_hdr_tpl.format(self.partition.name): self.partition.scheme.get_group_for_user( # pylint: disable=E1101
|
||||
self.course.id, user, self.partition, track_function=None
|
||||
).name
|
||||
}
|
||||
|
||||
self.verify_rows_in_csv(
|
||||
[
|
||||
merge_dicts(
|
||||
{'id': str(student.id), 'username': student.username, 'email': student.email},
|
||||
grades
|
||||
grades,
|
||||
user_partition_group(student)
|
||||
)
|
||||
for student_grades in students_grades for student, grades in student_grades.iteritems()
|
||||
]
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Unit tests for LMS instructor-initiated background tasks helper functions.
|
||||
|
||||
@@ -7,8 +9,12 @@ Tests that CSV grade report generation works with unicode emails.
|
||||
import ddt
|
||||
from mock import Mock, patch
|
||||
import tempfile
|
||||
import unicodecsv
|
||||
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
from student.tests.factories import UserFactory
|
||||
from student.models import CourseEnrollment
|
||||
from xmodule.partitions.partitions import Group, UserPartition
|
||||
|
||||
from openedx.core.djangoapps.course_groups.tests.helpers import CohortFactory
|
||||
from instructor_task.models import ReportStore
|
||||
@@ -57,6 +63,82 @@ class TestInstructorGradeReport(TestReportMixin, InstructorTaskCourseTestCase):
|
||||
report_store = ReportStore.from_config()
|
||||
self.assertTrue(any('grade_report_err' in item[0] for item in report_store.links_for(self.course.id)))
|
||||
|
||||
def _verify_cohort_data(self, course_id, expected_cohort_groups):
|
||||
"""
|
||||
Verify cohort data.
|
||||
"""
|
||||
cohort_groups_in_csv = []
|
||||
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()
|
||||
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):
|
||||
cohort_groups_in_csv.append(row['Cohort Group Name'])
|
||||
|
||||
self.assertEqual(cohort_groups_in_csv, expected_cohort_groups)
|
||||
|
||||
def test_cohort_data_in_grading(self):
|
||||
"""
|
||||
Test that cohort data is included in grades csv if cohort configuration is enabled for course.
|
||||
"""
|
||||
cohort_groups = ['cohort 1', 'cohort 2']
|
||||
course = CourseFactory.create(cohort_config={'cohorted': True, 'auto_cohort': True,
|
||||
'auto_cohort_groups': cohort_groups})
|
||||
for _ in range(2):
|
||||
CourseEnrollment.enroll(UserFactory.create(), course.id)
|
||||
|
||||
# In auto cohorting a group will be assigned to a user only when user visits a problem
|
||||
# In grading calculation we only add a group in csv if group is already assigned to
|
||||
# user rather than creating a group automatically at runtime
|
||||
expected_groups = ['', '']
|
||||
self._verify_cohort_data(course.id, expected_groups)
|
||||
|
||||
def test_unicode_cohort_data_in_grading(self):
|
||||
"""
|
||||
Test that cohort groups can contain unicode characters.
|
||||
"""
|
||||
cohort_groups = [u'ÞrÖfessÖr X', u'MàgnëtÖ']
|
||||
course = CourseFactory.create(cohort_config={'cohorted': True})
|
||||
|
||||
# Create users and manually assign cohort groups
|
||||
user1 = UserFactory.create(username='user1')
|
||||
user2 = UserFactory.create(username='user2')
|
||||
CourseEnrollment.enroll(user1, course.id)
|
||||
CourseEnrollment.enroll(user2, course.id)
|
||||
cohort1 = CohortFactory(course_id=course.id, name=u'ÞrÖfessÖr X')
|
||||
cohort2 = CohortFactory(course_id=course.id, name=u'MàgnëtÖ')
|
||||
cohort1.users.add(user1)
|
||||
cohort2.users.add(user2)
|
||||
|
||||
self._verify_cohort_data(course.id, cohort_groups)
|
||||
|
||||
def test_unicode_user_partitions(self):
|
||||
"""
|
||||
Test that user partition groups can contain unicode characters.
|
||||
"""
|
||||
user_groups = [u'ÞrÖfessÖr X', u'MàgnëtÖ']
|
||||
user_partition = UserPartition(
|
||||
0,
|
||||
'x_man',
|
||||
'X Man',
|
||||
[
|
||||
Group(0, user_groups[0]),
|
||||
Group(1, user_groups[1])
|
||||
]
|
||||
)
|
||||
|
||||
# Create course with group configurations
|
||||
self.initialize_course(
|
||||
course_factory_kwargs={
|
||||
'user_partitions': [user_partition]
|
||||
}
|
||||
)
|
||||
|
||||
_groups = [group.name for group in self.course.user_partitions[0].groups]
|
||||
self.assertEqual(_groups, user_groups)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestStudentReport(TestReportMixin, InstructorTaskCourseTestCase):
|
||||
|
||||
Reference in New Issue
Block a user