Files
edx-platform/courseware/views.py
David Ormsbee b54ff42532 add caching to User and UserProfile
--HG--
branch : dormsbee_performance
2012-02-23 20:09:15 -05:00

368 lines
14 KiB
Python

import json
import logging
import os
import random
import sys
import StringIO
import urllib
import uuid
from django.conf import settings
from django.core.context_processors import csrf
from django.contrib.auth.models import User
from django.http import HttpResponse, Http404
from django.shortcuts import redirect
from django.template import Context, loader
from mitxmako.shortcuts import render_to_response, render_to_string
#from django.views.decorators.csrf import ensure_csrf_cookie
from django.db import connection
from django.views.decorators.cache import cache_control
from lxml import etree
from module_render import render_module, modx_dispatch
from models import StudentModule
from student.models import UserProfile
import courseware.content_parser as content_parser
import courseware.modules.capa_module
log = logging.getLogger("mitx.courseware")
etree.set_default_parser(etree.XMLParser(dtd_validation=False, load_dtd=False,
remove_comments = True))
template_imports={'urllib':urllib}
def get_grade(request, problem, cache):
## HACK: assumes max score is fixed per problem
id = problem.get('id')
correct = 0
# If the ID is not in the cache, add the item
if id not in cache:
module = StudentModule(module_type = 'problem', # TODO: Move into StudentModule.__init__?
module_id = id,
student = request.user,
state = None,
grade = 0,
max_grade = None,
done = 'i')
cache[id] = module
# Grab the # correct from cache
if id in cache:
response = cache[id]
if response.grade!=None:
correct=response.grade
# Grab max grade from cache, or if it doesn't exist, compute and save to DB
if id in cache and response.max_grade != None:
total = response.max_grade
else:
total=courseware.modules.capa_module.Module(etree.tostring(problem), "id").max_score()
response.max_grade = total
response.save()
return (correct, total)
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
def profile(request):
''' User profile. Show username, location, etc, as well as grades .
We need to allow the user to change some of these settings .'''
if not request.user.is_authenticated():
return redirect('/')
dom=content_parser.course_file(request.user)
course = dom.xpath('//course/@name')[0]
xmlChapters = dom.xpath('//course[@name=$course]/chapter', course=course)
responses=StudentModule.objects.filter(student=request.user)
response_by_id = {}
for response in responses:
response_by_id[response.module_id] = response
total_scores = {}
chapters=[]
for c in xmlChapters:
sections = []
chname=c.get('name')
for s in dom.xpath('//course[@name=$course]/chapter[@name=$chname]/section',
course=course, chname=chname):
problems=dom.xpath('//course[@name=$course]/chapter[@name=$chname]/section[@name=$section]//problem',
course=course, chname=chname, section=s.get('name'))
graded = True if s.get('graded') == "true" else False
scores=[]
if len(problems)>0:
for p in problems:
(correct,total) = get_grade(request, p, response_by_id)
# id = p.get('id')
# correct = 0
# if id in response_by_id:
# response = response_by_id[id]
# if response.grade!=None:
# correct=response.grade
# total=courseware.modules.capa_module.Module(etree.tostring(p), "id").max_score() # TODO: Add state. Not useful now, but maybe someday problems will have randomized max scores?
# print correct, total
scores.append((int(correct),total, graded ))
section_total = (sum([score[0] for score in scores]),
sum([score[1] for score in scores]))
graded_total = (sum([score[0] for score in scores if score[2]]),
sum([score[1] for score in scores if score[2]]))
#Add the graded total to total_scores
format = s.get('format') if s.get('format') else ""
subtitle = s.get('subtitle') if s.get('subtitle') else format
if format and graded_total[1] > 0:
format_scores = total_scores[ format ] if format in total_scores else []
format_scores.append( graded_total )
total_scores[ format ] = format_scores
score={'section':s.get("name"),
'scores':scores,
'section_total' : section_total,
'format' : format,
'subtitle' : subtitle,
'due' : s.get("due") or "",
'graded' : graded,
}
sections.append(score)
chapters.append({'course':course,
'chapter' : c.get("name"),
'sections' : sections,})
def totalWithDrops(scores, drop_count):
#Note that this key will sort the list descending
sorted_scores = sorted( enumerate(scores), key=lambda x: -x[1]['percentage'] )
# A list of the indices of the dropped scores
dropped_indices = [score[0] for score in sorted_scores[-drop_count:]]
aggregate_score = 0
for index, score in enumerate(scores):
if index not in dropped_indices:
aggregate_score += score['percentage']
aggregate_score /= len(scores) - drop_count
return aggregate_score, dropped_indices
#Figure the homework scores
homework_scores = total_scores['Homework'] if 'Homework' in total_scores else []
homework_percentages = []
for i in range(12):
if i < len(homework_scores):
percentage = homework_scores[i][0] / float(homework_scores[i][1])
summary = "{0:.0%} ({1}/{2})".format( percentage, homework_scores[i][0], homework_scores[i][1] )
else:
percentage = 0
summary = "0% (?/?)"
if settings.GENERATE_PROFILE_SCORES:
points_possible = random.randrange(10, 50)
points_earned = random.randrange(5, points_possible)
percentage = points_earned / float(points_possible)
summary = "{0:.0%} ({1}/{2})".format( percentage, points_earned, points_possible )
summary = "Homework {0} - {1}".format(i + 1, summary)
label = "HW {0:02d}".format(i + 1)
homework_percentages.append( {'percentage': percentage, 'summary': summary, 'label' : label} )
homework_total, homework_dropped_indices = totalWithDrops(homework_percentages, 2)
#Figure the lab scores
lab_scores = total_scores['Lab'] if 'Lab' in total_scores else []
lab_percentages = []
for i in range(12):
if i < len(lab_scores):
percentage = lab_scores[i][0] / float(lab_scores[i][1])
summary = "{0:.0%} ({1}/{2})".format( percentage, lab_scores[i][0], lab_scores[i][1] )
else:
percentage = 0
summary = "0% (?/?)"
if settings.GENERATE_PROFILE_SCORES:
points_possible = random.randrange(10, 50)
points_earned = random.randrange(5, points_possible)
percentage = points_earned / float(points_possible)
summary = "{0:.0%} ({1}/{2})".format( percentage, points_earned, points_possible )
summary = "Lab {0} - {1}".format(i + 1, summary)
label = "Lab {0:02d}".format(i + 1)
lab_percentages.append( {'percentage': percentage, 'summary': summary, 'label' : label} )
lab_total, lab_dropped_indices = totalWithDrops(lab_percentages, 2)
#TODO: Pull this data about the midterm and final from the databse. It should be exactly similar to above, but we aren't sure how exams will be done yet.
midterm_score = ('?', '?')
midterm_percentage = 0
final_score = ('?', '?')
final_percentage = 0
if settings.GENERATE_PROFILE_SCORES:
midterm_score = (random.randrange(50, 150), 150)
midterm_percentage = midterm_score[0] / float(midterm_score[1])
final_score = (random.randrange(100, 300), 300)
final_percentage = final_score[0] / float(final_score[1])
grade_summary = [
{
'category': 'Homework',
'subscores' : homework_percentages,
'dropped_indices' : homework_dropped_indices,
'totalscore' : {'score' : homework_total, 'summary' : "Homework Average - {0:.0%}".format(homework_total)},
'totallabel' : 'HW Avg',
'weight' : 0.15,
},
{
'category': 'Labs',
'subscores' : lab_percentages,
'dropped_indices' : lab_dropped_indices,
'totalscore' : {'score' : lab_total, 'summary' : "Lab Average - {0:.0%}".format(lab_total)},
'totallabel' : 'Lab Avg',
'weight' : 0.15,
},
{
'category': 'Midterm',
'totalscore' : {'score' : midterm_percentage, 'summary' : "Midterm - {0:.0%} ({1}/{2})".format(midterm_percentage, midterm_score[0], midterm_score[1])},
'totallabel' : 'Midterm',
'weight' : 0.30,
},
{
'category': 'Final',
'totalscore' : {'score' : final_percentage, 'summary' : "Final - {0:.0%} ({1}/{2})".format(final_percentage, final_score[0], final_score[1])},
'totallabel' : 'Final',
'weight' : 0.40,
}
]
user_info = request.user.profile_cache # UserProfile.objects.get(user=request.user)
context={'name':user_info.name,
'username':request.user.username,
'location':user_info.location,
'language':user_info.language,
'email':request.user.email,
'chapters':chapters,
'format_url_params' : format_url_params,
'grade_summary' : grade_summary,
'csrf':csrf(request)['csrf_token']
}
return render_to_response('profile.html', context)
def format_url_params(params):
return [ urllib.quote(string.replace(' ','_')) for string in params ]
def render_accordion(request,course,chapter,section):
''' Draws navigation bar. Takes current position in accordion as
parameter. Returns (initialization_javascript, content)'''
toc=content_parser.toc_from_xml(content_parser.course_file(request.user), chapter, section)
active_chapter=1
for i in range(len(toc)):
if toc[i]['active']:
active_chapter=i
context=dict([['active_chapter',active_chapter],
['toc',toc],
['course_name',course],
['format_url_params',format_url_params],
['csrf',csrf(request)['csrf_token']]] + \
template_imports.items())
return {'init_js':render_to_string('accordion_init.js',context),
'content':render_to_string('accordion.html',context)}
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
def render_section(request, section):
''' TODO: Consolidate with index
'''
user = request.user
if not settings.COURSEWARE_ENABLED or not user.is_authenticated():
return redirect('/')
# try:
dom = content_parser.section_file(user, section)
#except:
# raise Http404
accordion=render_accordion(request, '', '', '')
module_ids = dom.xpath("//@id")
module_object_preload = list(StudentModule.objects.filter(student=user,
module_id__in=module_ids))
module=render_module(user, request, dom, module_object_preload)
if 'init_js' not in module:
module['init_js']=''
context={'init':accordion['init_js']+module['init_js'],
'accordion':accordion['content'],
'content':module['content'],
'csrf':csrf(request)['csrf_token']}
result = render_to_response('courseware.html', context)
return result
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
def index(request, course="6.002 Spring 2012", chapter="Using the System", section="Hints"):
''' Displays courseware accordion, and any associated content.
'''
user = request.user
if not settings.COURSEWARE_ENABLED or not user.is_authenticated():
return redirect('/')
# Fixes URLs -- we don't get funny encoding characters from spaces
# so they remain readable
## TODO: Properly replace underscores
course=course.replace("_"," ")
chapter=chapter.replace("_"," ")
section=section.replace("_"," ")
# HACK: Force course to 6.002 for now
# Without this, URLs break
if course!="6.002 Spring 2012":
return redirect('/')
dom = content_parser.course_file(user)
dom_module = dom.xpath("//course[@name=$course]/chapter[@name=$chapter]//section[@name=$section]/*[1]",
course=course, chapter=chapter, section=section)
if len(dom_module) == 0:
module = None
else:
module = dom_module[0]
accordion=render_accordion(request, course, chapter, section)
module_ids = dom.xpath("//course[@name=$course]/chapter[@name=$chapter]//section[@name=$section]//@id",
course=course, chapter=chapter, section=section)
module_object_preload = list(StudentModule.objects.filter(student=user,
module_id__in=module_ids))
module=render_module(user, request, module, module_object_preload)
if 'init_js' not in module:
module['init_js']=''
context={'init':accordion['init_js']+module['init_js'],
'accordion':accordion['content'],
'content':module['content'],
'csrf':csrf(request)['csrf_token']}
result = render_to_response('courseware.html', context)
return result