Commit 630d1ce0 by Chris Rossi Committed by cewing

MIT: CCX. Implement coach interactions with student grades

Story #4: Coaches sees grades.

Story #9: Coach downloads grades.
parent cabb1962
...@@ -4,6 +4,8 @@ import re ...@@ -4,6 +4,8 @@ import re
import pytz import pytz
from mock import patch from mock import patch
from capa.tests.response_xml_factory import StringResponseXMLFactory
from courseware.tests.factories import StudentModuleFactory
from courseware.tests.helpers import LoginEnrollmentTestCase from courseware.tests.helpers import LoginEnrollmentTestCase
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from edxmako.shortcuts import render_to_response from edxmako.shortcuts import render_to_response
...@@ -11,7 +13,9 @@ from student.roles import CoursePocCoachRole ...@@ -11,7 +13,9 @@ from student.roles import CoursePocCoachRole
from student.tests.factories import ( from student.tests.factories import (
AdminFactory, AdminFactory,
CourseEnrollmentFactory, CourseEnrollmentFactory,
UserFactory,
) )
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import ( from xmodule.modulestore.tests.factories import (
CourseFactory, CourseFactory,
...@@ -295,6 +299,86 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -295,6 +299,86 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
).exists() ).exists()
) )
USER_COUNT = 2
class TestPocGrades(ModuleStoreTestCase, LoginEnrollmentTestCase):
"""
Tests for Personal Online Courses views.
"""
def setUp(self):
"""
Set up tests
"""
self.course = course = CourseFactory.create()
# Create instructor account
self.coach = coach = AdminFactory.create()
self.client.login(username=coach.username, password="test")
# Create a course outline
self.mooc_start = start = datetime.datetime(
2010, 5, 12, 2, 42, tzinfo=pytz.UTC)
chapter = ItemFactory.create(
start=start, parent=course, category='sequential')
section = ItemFactory.create(
parent=chapter,
category="sequential",
metadata={'graded': True, 'format': 'Homework'}
)
role = CoursePocCoachRole(self.course.id)
role.add_users(coach)
self.poc = poc = PocFactory(course_id=self.course.id, coach=self.coach)
self.users = [UserFactory.create() for _ in xrange(USER_COUNT)]
for user in self.users:
CourseEnrollmentFactory.create(user=user, course_id=self.course.id)
PocMembershipFactory(poc=poc, student=user, active=True)
for i in xrange(USER_COUNT - 1):
category = "problem"
item = ItemFactory.create(
parent_location=section.location,
category=category,
data=StringResponseXMLFactory().build_xml(answer='foo'),
metadata={'rerandomize': 'always'}
)
for j, user in enumerate(self.users):
StudentModuleFactory.create(
grade=1 if i < j else 0,
max_grade=1,
student=user,
course_id=self.course.id,
module_state_key=item.location
)
@patch('pocs.views.render_to_response', intercept_renderer)
def test_gradebook(self):
url = reverse(
'poc_gradebook',
kwargs={'course_id': self.course.id.to_deprecated_string()}
)
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
student_info = response.mako_context['students']
self.assertEqual(len(student_info), USER_COUNT)
self.assertEqual(student_info[0]['grade_summary']['percent'], 0.0)
self.assertEqual(student_info[1]['grade_summary']['percent'], 0.02)
self.assertEqual(
student_info[1]['grade_summary']['grade_breakdown'][0]['percent'],
0.015)
def test_grades_csv(self):
url = reverse(
'poc_grades_csv',
kwargs={'course_id': self.course.id.to_deprecated_string()}
)
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertEqual(
len(response.content.strip().split('\n')), USER_COUNT + 1)
def flatten(seq): def flatten(seq):
......
import csv
import datetime import datetime
import functools import functools
import json import json
import logging import logging
import pytz import pytz
from cStringIO import StringIO
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.http import HttpResponse, HttpResponseForbidden from django.http import HttpResponse, HttpResponseForbidden
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
...@@ -16,10 +19,12 @@ from django.contrib.auth.models import User ...@@ -16,10 +19,12 @@ from django.contrib.auth.models import User
from courseware.courses import get_course_by_id from courseware.courses import get_course_by_id
from courseware.field_overrides import disable_overrides from courseware.field_overrides import disable_overrides
from courseware.grades import iterate_grades_for
from edxmako.shortcuts import render_to_response from edxmako.shortcuts import render_to_response
from opaque_keys.edx.locations import SlashSeparatedCourseKey from opaque_keys.edx.locations import SlashSeparatedCourseKey
from student.roles import CoursePocCoachRole from student.roles import CoursePocCoachRole
from instructor.offline_gradecalc import student_grades
from instructor.views.api import _split_input_list from instructor.views.api import _split_input_list
from instructor.views.tools import get_student_from_identifier from instructor.views.tools import get_student_from_identifier
...@@ -68,6 +73,10 @@ def dashboard(request, course): ...@@ -68,6 +73,10 @@ def dashboard(request, course):
'schedule': json.dumps(schedule, indent=4), 'schedule': json.dumps(schedule, indent=4),
'save_url': reverse('save_poc', kwargs={'course_id': course.id}), 'save_url': reverse('save_poc', kwargs={'course_id': course.id}),
'poc_members': PocMembership.objects.filter(poc=poc), 'poc_members': PocMembership.objects.filter(poc=poc),
'gradebook_url': reverse('poc_gradebook',
kwargs={'course_id': course.id}),
'grades_csv_url': reverse('poc_grades_csv',
kwargs={'course_id': course.id}),
} }
if not poc: if not poc:
context['create_poc_url'] = reverse( context['create_poc_url'] = reverse(
...@@ -242,3 +251,77 @@ def poc_invite(request, course): ...@@ -242,3 +251,77 @@ def poc_invite(request, course):
pass # maybe log this? pass # maybe log this?
url = reverse('poc_coach_dashboard', kwargs={'course_id': course.id}) url = reverse('poc_coach_dashboard', kwargs={'course_id': course.id})
return redirect(url) return redirect(url)
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
@coach_dashboard
def poc_gradebook(request, course):
"""
Show the gradebook for this POC.
"""
poc = get_poc_for_coach(course, request.user)
enrolled_students = User.objects.filter(
pocmembership__poc=poc,
pocmembership__active=1
).order_by('username').select_related("profile")
student_info = [
{
'username': student.username,
'id': student.id,
'email': student.email,
'grade_summary': student_grades(student, request, course),
'realname': student.profile.name,
}
for student in enrolled_students
]
return render_to_response('courseware/gradebook.html', {
'students': student_info,
'course': course,
'course_id': course.id,
'staff_access': request.user.is_staff,
'ordered_grades': sorted(
course.grade_cutoffs.items(), key=lambda i: i[1], reverse=True),
})
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
@coach_dashboard
def poc_grades_csv(request, course):
poc = get_poc_for_coach(course, request.user)
enrolled_students = User.objects.filter(
pocmembership__poc=poc,
pocmembership__active=1
).order_by('username').select_related("profile")
grades = iterate_grades_for(course.id, enrolled_students)
header = None
rows = []
for student, gradeset, err_msg in grades:
if gradeset:
# We were able to successfully grade this student for this course.
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)
percents = {
section['label']: section.get('percent', 0.0)
for section in gradeset[u'section_breakdown']
if 'label' in section
}
row_percents = [percents.get(label, 0.0) for label in header]
rows.append([student.id, student.email, student.username,
gradeset['percent']] + row_percents)
buffer = StringIO()
writer = csv.writer(buffer)
for row in rows:
writer.writerow(row)
return HttpResponse(buffer.getvalue(), content_type='text/csv')
...@@ -39,6 +39,9 @@ ...@@ -39,6 +39,9 @@
<li class="nav-item"> <li class="nav-item">
<a href="#" data-section="schedule">${_("Schedule")}</a> <a href="#" data-section="schedule">${_("Schedule")}</a>
</li> </li>
<li class="nav-item">
<a href="#" data-section="student_admin">${_("Student Admin")}</a>
</li>
</ul> </ul>
<section id="membership" class="idash-section"> <section id="membership" class="idash-section">
<%include file="enrollment.html" args="" /> <%include file="enrollment.html" args="" />
...@@ -46,6 +49,9 @@ ...@@ -46,6 +49,9 @@
<section id="schedule" class="idash-section"> <section id="schedule" class="idash-section">
<%include file="schedule.html" args="" /> <%include file="schedule.html" args="" />
</section> </section>
<section id="student_admin" class="idash-section">
<%include file="student_admin.html" args="" />
</section>
%endif %endif
</section> </section>
......
<%! from django.utils.translation import ugettext as _ %>
<section>
<h2>${_('Student Grades')}</h2>
<p>
<a href="${gradebook_url}">${_('View gradebook')}</a>
</p>
<p>
<a href="${grades_csv_url}">${_('Download student grades')}</a>
</p>
</section>
...@@ -351,6 +351,10 @@ if settings.COURSEWARE_ENABLED: ...@@ -351,6 +351,10 @@ if settings.COURSEWARE_ENABLED:
'pocs.views.save_poc', name='save_poc'), 'pocs.views.save_poc', name='save_poc'),
url(r'^courses/{}/poc_invite$'.format(settings.COURSE_ID_PATTERN), url(r'^courses/{}/poc_invite$'.format(settings.COURSE_ID_PATTERN),
'pocs.views.poc_invite', name='poc_invite'), 'pocs.views.poc_invite', name='poc_invite'),
url(r'^courses/{}/poc_gradebook$'.format(settings.COURSE_ID_PATTERN),
'pocs.views.poc_gradebook', name='poc_gradebook'),
url(r'^courses/{}/poc_grades.csv$'.format(settings.COURSE_ID_PATTERN),
'pocs.views.poc_grades_csv', name='poc_grades_csv'),
url(r'^courses/{}/set_course_mode_price$'.format(settings.COURSE_ID_PATTERN), url(r'^courses/{}/set_course_mode_price$'.format(settings.COURSE_ID_PATTERN),
'instructor.views.instructor_dashboard.set_course_mode_price', name="set_course_mode_price"), 'instructor.views.instructor_dashboard.set_course_mode_price', name="set_course_mode_price"),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment