Commit eecefec8 by Chris Rossi Committed by cewing

MIT: CCX. Implement coach customization of grading policy for a CCX

Story #35 As a coach I can see and edit the json for the grading policy.

Story #34 Recalculate grading policy

Repair the broken test for grading by providing an explicit POC context for the request to run in
parent 2c586ddb
......@@ -28,7 +28,11 @@ from ..models import (
PocMembership,
PocFutureMembership,
)
from ..overrides import get_override_for_poc, override_field_for_poc
from ..overrides import (
get_override_for_poc,
override_field_for_poc,
poc_context,
)
from .factories import (
PocFactory,
PocMembershipFactory,
......@@ -73,13 +77,18 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
for _ in xrange(2)]
sequentials = flatten([
[ItemFactory.create(parent=chapter) for _ in xrange(2)]
for chapter in chapters])
for chapter in chapters]
)
verticals = flatten([
[ItemFactory.create(due=due, parent=sequential) for _ in xrange(2)]
for sequential in sequentials])
[ItemFactory.create(
due=due, parent=sequential, graded=True, format='Homework')
for _ in xrange(2)]
for sequential in sequentials]
)
blocks = flatten([
[ItemFactory.create(parent=vertical) for _ in xrange(2)]
for vertical in verticals])
for vertical in verticals]
)
def make_coach(self):
role = CoursePocCoachRole(self.course.id)
......@@ -140,7 +149,7 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
self.assertTrue(re.search('id="poc-schedule"', response.content))
@patch('pocs.views.render_to_response', intercept_renderer)
@patch('pocs.views.today')
@patch('pocs.views.TODAY')
def test_edit_schedule(self, today):
"""
Get POC schedule, modify it, save it.
......@@ -166,14 +175,23 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
'save_poc',
kwargs={'course_id': self.course.id.to_deprecated_string()})
schedule[0]['hidden'] = False
def unhide(unit):
"""
Recursively unhide a unit and all of its children in the POC
schedule.
"""
unit['hidden'] = False
for child in unit.get('children', ()):
unhide(child)
unhide(schedule[0])
schedule[0]['start'] = u'2014-11-20 00:00'
schedule[0]['children'][0]['due'] = u'2014-12-25 00:00' # what a jerk!
response = self.client.post(
url, json.dumps(schedule), content_type='application/json'
)
schedule = json.loads(response.content)
schedule = json.loads(response.content)['schedule']
self.assertEqual(schedule[0]['hidden'], False)
self.assertEqual(schedule[0]['start'], u'2014-11-20 00:00')
self.assertEqual(
......@@ -186,6 +204,18 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
course_start = get_override_for_poc(poc, self.course, 'start')
self.assertEqual(str(course_start)[:-9], u'2014-11-20 00:00')
# Make sure grading policy adjusted
policy = get_override_for_poc(poc, self.course, 'grading_policy',
self.course.grading_policy)
self.assertEqual(policy['GRADER'][0]['type'], 'Homework')
self.assertEqual(policy['GRADER'][0]['min_count'], 4)
self.assertEqual(policy['GRADER'][1]['type'], 'Lab')
self.assertEqual(policy['GRADER'][1]['min_count'], 0)
self.assertEqual(policy['GRADER'][2]['type'], 'Midterm Exam')
self.assertEqual(policy['GRADER'][2]['min_count'], 0)
self.assertEqual(policy['GRADER'][3]['type'], 'Final Exam')
self.assertEqual(policy['GRADER'][3]['min_count'], 0)
def test_enroll_member_student(self):
"""enroll a list of students who are members of the class
"""
......@@ -498,12 +528,15 @@ class TestPocGrades(ModuleStoreTestCase, LoginEnrollmentTestCase):
'progress',
kwargs={'course_id': self.course.id.to_deprecated_string()}
)
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
grades = response.mako_context['grade_summary']
self.assertEqual(grades['percent'], 0.5)
self.assertEqual(grades['grade_breakdown'][0]['percent'], 0.5)
self.assertEqual(len(grades['section_breakdown']), 4)
with poc_context(self.poc):
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
grades = response.mako_context['grade_summary']
self.assertEqual(grades['percent'], 0.5)
self.assertEqual(grades['grade_breakdown'][0]['percent'], 0.5)
self.assertEqual(len(grades['section_breakdown']), 4)
def flatten(seq):
......
......@@ -8,6 +8,7 @@ import json
import logging
import pytz
from copy import deepcopy
from cStringIO import StringIO
from django.core.urlresolvers import reverse
......@@ -77,6 +78,8 @@ def dashboard(request, course):
"""
poc = get_poc_for_coach(course, request.user)
schedule = get_poc_schedule(course, poc)
grading_policy = get_override_for_poc(poc, course, 'grading_policy',
course.grading_policy)
context = {
'course': course,
'poc': poc,
......@@ -87,6 +90,9 @@ def dashboard(request, course):
kwargs={'course_id': course.id}),
'grades_csv_url': reverse('poc_grades_csv',
kwargs={'course_id': course.id}),
'grading_policy': json.dumps(grading_policy, indent=4),
'grading_policy_url': reverse('poc_set_grading_policy',
kwargs={'course_id': course.id}),
}
if not poc:
context['create_poc_url'] = reverse(
......@@ -135,7 +141,7 @@ def save_poc(request, course):
"""
poc = get_poc_for_coach(course, request.user)
def override_fields(parent, data, earliest=None):
def override_fields(parent, data, graded, earliest=None):
"""
Recursively apply POC schedule data to POC by overriding the
`visible_to_staff_only`, `start` and `due` fields for units in the
......@@ -161,18 +167,55 @@ def save_poc(request, course):
else:
clear_override_for_poc(poc, block, 'due')
if not unit['hidden'] and block.graded:
graded[block.format] = graded.get(block.format, 0) + 1
children = unit.get('children', None)
if children:
override_fields(block, children, earliest)
override_fields(block, children, graded, earliest)
return earliest
earliest = override_fields(course, json.loads(request.body))
graded = {}
earliest = override_fields(course, json.loads(request.body), graded)
if earliest:
override_field_for_poc(poc, course, 'start', earliest)
# Attempt to automatically adjust grading policy
changed = False
policy = get_override_for_poc(
poc, course, 'grading_policy', course.grading_policy
)
policy = deepcopy(policy)
grader = policy['GRADER']
for section in grader:
count = graded.get(section.get('type'), 0)
if count < section['min_count']:
changed = True
section['min_count'] = count
if changed:
override_field_for_poc(poc, course, 'grading_policy', policy)
return HttpResponse(
json.dumps(get_poc_schedule(course, poc)),
content_type='application/json')
json.dumps({
'schedule': get_poc_schedule(course, poc),
'grading_policy': json.dumps(policy, indent=4)}),
content_type='application/json',
)
@ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
@coach_dashboard
def set_grading_policy(request, course):
"""
Set grading policy for the POC.
"""
poc = get_poc_for_coach(course, request.user)
override_field_for_poc(
poc, course, 'grading_policy', json.loads(request.POST['policy']))
url = reverse('poc_coach_dashboard', kwargs={'course_id': course.id})
return redirect(url)
def parse_date(datestring):
......
......@@ -42,6 +42,9 @@
<li class="nav-item">
<a href="#" data-section="student_admin">${_("Student Admin")}</a>
</li>
<li class="nav-item">
<a href="#" data-section="grading_policy">${_("Grading Policy")}</a>
</li>
</ul>
<section id="membership" class="idash-section">
<%include file="enrollment.html" args="" />
......@@ -52,6 +55,9 @@
<section id="student_admin" class="idash-section">
<%include file="student_admin.html" args="" />
</section>
<section id="grading_policy" class="idash-section">
<%include file="grading_policy.html" args="" />
</section>
%endif
</section>
......
<%! from django.utils.translation import ugettext as _ %>
<h2>${_("Grading Policy")}</h2>
<form action="${grading_policy_url}" method="POST">
<input type="hidden" name="csrfmiddlewaretoken" value="${csrf_token}"/>
<textarea cols="80" style="height: 500px;"
name="policy" id="grading-policy">${grading_policy}</textarea><br/>
<button type="submit">${_("Save Grading Policy")}</button>
</form>
......@@ -436,10 +436,14 @@ var poc_schedule = (function () {
contentType: 'application/json',
data: JSON.stringify(this.schedule),
success: function(data, textStatus, jqXHR) {
self.schedule = data;
self.schedule = data.schedule;
self.dirty = false;
self.render();
button.prop('disabled', false).text('${_("Save changes")}');
// Update textarea with grading policy JSON, since grading policy
// may have changed.
$('#grading-policy').text(data.grading_policy);
},
error: function(jqXHR, textStatus, error) {
console.log(jqXHR.responseText);
......
......@@ -357,6 +357,8 @@ if settings.COURSEWARE_ENABLED:
'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/{}/poc_set_grading_policy$'.format(settings.COURSE_ID_PATTERN),
'pocs.views.set_grading_policy', name='poc_set_grading_policy'),
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"),
url(r'^courses/{}/instructor/api/'.format(settings.COURSE_ID_PATTERN),
......
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