Skip to content

Commit

Permalink
Implement coach customization of grading policy for a CCX
Browse files Browse the repository at this point in the history
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

I am not certain this is the right way to do this, might be better to test the session facility by setting the session for self.client, but this will do for now.
  • Loading branch information
Chris Rossi authored and cewing committed Apr 6, 2015
1 parent 396fa09 commit 0bdcde3
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 20 deletions.
61 changes: 47 additions & 14 deletions lms/djangoapps/pocs/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@
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,
Expand Down Expand Up @@ -73,13 +77,18 @@ def setUp(self):
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)
Expand Down Expand Up @@ -140,7 +149,7 @@ def test_create_poc(self):
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.
Expand All @@ -166,14 +175,23 @@ def test_edit_schedule(self, today):
'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(
Expand All @@ -186,6 +204,18 @@ def test_edit_schedule(self, today):
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
"""
Expand Down Expand Up @@ -498,12 +528,15 @@ def test_student_progress(self):
'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):
Expand Down
53 changes: 48 additions & 5 deletions lms/djangoapps/pocs/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import logging
import pytz

from copy import deepcopy
from cStringIO import StringIO

from django.core.urlresolvers import reverse
Expand Down Expand Up @@ -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,
Expand All @@ -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(
Expand Down Expand Up @@ -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
Expand All @@ -161,18 +167,55 @@ def override_fields(parent, data, earliest=None):
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):
Expand Down
6 changes: 6 additions & 0 deletions lms/templates/pocs/coach_dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ <h1>${_("POC Coach Dashboard")}</h1>
<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="" />
Expand All @@ -52,6 +55,9 @@ <h1>${_("POC Coach Dashboard")}</h1>
<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>
Expand Down
10 changes: 10 additions & 0 deletions lms/templates/pocs/grading_policy.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<%! 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>
6 changes: 5 additions & 1 deletion lms/templates/pocs/schedule.html
Original file line number Diff line number Diff line change
Expand Up @@ -436,10 +436,14 @@ <h2>${_('Schedule a Unit')}</h2>
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);
Expand Down
2 changes: 2 additions & 0 deletions lms/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,8 @@
'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),
Expand Down

0 comments on commit 0bdcde3

Please sign in to comment.