Commit c89031b0 by Victor Shnayder

Export of answer distibutions to csv

- go through all students, all of their problems, save count for each answer
- return csv
- url exists, but no links to it yet
- Will need to integrate with Ike's new dashboard code
parent 77029076
......@@ -4,11 +4,14 @@ from __future__ import division
import random
import logging
from collections import defaultdict
from django.conf import settings
from django.contrib.auth.models import User
from models import StudentModuleCache
from module_render import get_module, get_instance_module
from xmodule import graders
from xmodule.capa_module import CapaModule
from xmodule.course_module import CourseDescriptor
from xmodule.graders import Score
from models import StudentModule
......@@ -24,6 +27,73 @@ def yield_module_descendents(module):
stack.extend( next_module.get_display_items() )
yield next_module
def yield_problems(request, course, student):
"""
Return an iterator over capa_modules that this student has
potentially answered. (all that student has answered will definitely be in
the list, but there may be others as well).
"""
grading_context = course.grading_context
student_module_cache = StudentModuleCache(course.id, student, grading_context['all_descriptors'])
for section_format, sections in grading_context['graded_sections'].iteritems():
for section in sections:
section_descriptor = section['section_descriptor']
# If the student hasn't seen a single problem in the section, skip it.
skip = True
for moduledescriptor in section['xmoduledescriptors']:
if student_module_cache.lookup(
course.id, moduledescriptor.category, moduledescriptor.location.url()):
skip = False
break
if skip:
continue
section_module = get_module(student, request,
section_descriptor.location, student_module_cache,
course.id)
if section_module is None:
# student doesn't have access to this module, or something else
# went wrong.
log.debug("couldn't get module for student {0} for section location {1}"
.format(student.username, section_descriptor.location))
continue
for problem in yield_module_descendents(section_module):
if isinstance(problem, CapaModule):
yield problem
def answer_distributions(request, course):
"""
Given a course_descriptor, compute frequencies of answers for each problem:
Format is:
dict: (problem url_name, problem display_name, problem_id) -> (dict : answer -> count)
TODO (vshnayder): this is currently doing a full linear pass through all
students and all problems. This will be just a little slow.
"""
counts = defaultdict(lambda: defaultdict(int))
enrolled_students = User.objects.filter(courseenrollment__course_id=course.id)
for student in enrolled_students:
for capa_module in yield_problems(request, course, student):
log.debug("looking at problem {0} for {1}. answers {2}".format(
capa_module.display_name, student.username, capa_module.lcp.student_answers))
for problem_id in capa_module.lcp.student_answers:
answer = capa_module.lcp.student_answers[problem_id]
key = (capa_module.url_name, capa_module.display_name, problem_id)
counts[key][answer] += 1
return counts
def grade(student, request, course, student_module_cache=None, keep_raw_scores=False):
"""
This grades a student as quickly as possible. It retuns the
......
import csv
import json
import logging
import urllib
import itertools
import StringIO
from functools import partial
......@@ -219,9 +221,9 @@ def jump_to(request, course_id, location):
# Rely on index to do all error handling and access control.
return redirect('courseware_position',
course_id=course_id,
chapter=chapter,
section=section,
course_id=course_id,
chapter=chapter,
section=section,
position=position)
@ensure_csrf_cookie
def course_info(request, course_id):
......@@ -342,7 +344,7 @@ def progress(request, course_id, student_id=None):
# NOTE: To make sure impersonation by instructor works, use
# student instead of request.user in the rest of the function.
# The pre-fetching of groups is done to make auth checks not require an
# The pre-fetching of groups is done to make auth checks not require an
# additional DB lookup (this kills the Progress page in particular).
student = User.objects.prefetch_related("groups").get(id=student.id)
......@@ -370,3 +372,24 @@ def progress(request, course_id, student_id=None):
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
def answers_export(request, course_id):
"""
Export the distribution of student answers to all problems as a csv file.
- only displayed to course staff
"""
course = get_course_with_access(request.user, course_id, 'staff')
dist = grades.answer_distributions(request, course)
response = HttpResponse(mimetype='text/csv')
response['Content-Disposition'] = 'attachment; filename=%s' % "answer_distribution.csv"
writer = csv.writer(response)
for (url_name, display_name, answer_id), answers in dist.items():
# HEADER?
for a in answers:
writer.writerow([url_name, display_name, answer_id, a, answers[a]])
return response
......@@ -161,6 +161,9 @@ if settings.COURSEWARE_ENABLED:
'instructor.views.grade_summary', name='grade_summary'),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/enroll_students$',
'instructor.views.enroll_students', name='enroll_students'),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/answers_export$',
'courseware.views.answers_export', name='answers_export'),
)
# discussion forums live within courseware, so courseware must be enabled first
......
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