Skip to content

Commit

Permalink
Implement coach interactions with student grades
Browse files Browse the repository at this point in the history
Story #4: Coaches sees grades.

Story #9: Coach downloads grades.
  • Loading branch information
Chris Rossi authored and cewing committed Apr 6, 2015
1 parent 700c243 commit be580d3
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 0 deletions.
84 changes: 84 additions & 0 deletions lms/djangoapps/pocs/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@
import pytz
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 django.core.urlresolvers import reverse
from edxmako.shortcuts import render_to_response
from student.roles import CoursePocCoachRole
from student.tests.factories import (
AdminFactory,
CourseEnrollmentFactory,
UserFactory,
)

from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import (
CourseFactory,
Expand Down Expand Up @@ -295,6 +299,86 @@ def test_unenroll_non_user_student(self):
).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):
Expand Down
83 changes: 83 additions & 0 deletions lms/djangoapps/pocs/views.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import csv
import datetime
import functools
import json
import logging
import pytz

from cStringIO import StringIO

from django.core.urlresolvers import reverse
from django.http import HttpResponse, HttpResponseForbidden
from django.core.exceptions import ValidationError
Expand All @@ -16,10 +19,12 @@

from courseware.courses import get_course_by_id
from courseware.field_overrides import disable_overrides
from courseware.grades import iterate_grades_for
from edxmako.shortcuts import render_to_response
from opaque_keys.edx.locations import SlashSeparatedCourseKey
from student.roles import CoursePocCoachRole

from instructor.offline_gradecalc import student_grades
from instructor.views.api import _split_input_list
from instructor.views.tools import get_student_from_identifier

Expand Down Expand Up @@ -68,6 +73,10 @@ def dashboard(request, course):
'schedule': json.dumps(schedule, indent=4),
'save_url': reverse('save_poc', kwargs={'course_id': course.id}),
'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:
context['create_poc_url'] = reverse(
Expand Down Expand Up @@ -242,3 +251,77 @@ def poc_invite(request, course):
pass # maybe log this?
url = reverse('poc_coach_dashboard', kwargs={'course_id': course.id})
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')

6 changes: 6 additions & 0 deletions lms/templates/pocs/coach_dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,19 @@ <h1>${_("POC Coach Dashboard")}</h1>
<li class="nav-item">
<a href="#" data-section="schedule">${_("Schedule")}</a>
</li>
<li class="nav-item">
<a href="#" data-section="student_admin">${_("Student Admin")}</a>
</li>
</ul>
<section id="membership" class="idash-section">
<%include file="enrollment.html" args="" />
</section>
<section id="schedule" class="idash-section">
<%include file="schedule.html" args="" />
</section>
<section id="student_admin" class="idash-section">
<%include file="student_admin.html" args="" />
</section>
%endif

</section>
Expand Down
11 changes: 11 additions & 0 deletions lms/templates/pocs/student_admin.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<%! 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>
4 changes: 4 additions & 0 deletions lms/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,10 @@
'pocs.views.save_poc', name='save_poc'),
url(r'^courses/{}/poc_invite$'.format(settings.COURSE_ID_PATTERN),
'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),
'instructor.views.instructor_dashboard.set_course_mode_price', name="set_course_mode_price"),
Expand Down

0 comments on commit be580d3

Please sign in to comment.