Commit ad448828 by Isaac Chuang Committed by Piotr Mitros

Ike's changes to enable multicourse, new response types, etc.

parent e45eb9fb
...@@ -4,6 +4,9 @@ ...@@ -4,6 +4,9 @@
*.swp *.swp
*.orig *.orig
*.DS_Store *.DS_Store
:2e_*
:2e#
.AppleDouble
database.sqlite database.sqlite
courseware/static/js/mathjax/* courseware/static/js/mathjax/*
db.newaskbot db.newaskbot
......
...@@ -25,7 +25,7 @@ from mako.template import Template ...@@ -25,7 +25,7 @@ from mako.template import Template
from util import contextualize_text from util import contextualize_text
import inputtypes import inputtypes
from responsetypes import NumericalResponse, FormulaResponse, CustomResponse, SchematicResponse, MultipleChoiceResponse, StudentInputError, TrueFalseResponse, ExternalResponse,ImageResponse from responsetypes import NumericalResponse, FormulaResponse, CustomResponse, SchematicResponse, MultipleChoiceResponse, StudentInputError, TrueFalseResponse, ExternalResponse,ImageResponse,OptionResponse
import calc import calc
import eia import eia
...@@ -40,8 +40,9 @@ response_types = {'numericalresponse':NumericalResponse, ...@@ -40,8 +40,9 @@ response_types = {'numericalresponse':NumericalResponse,
'multiplechoiceresponse':MultipleChoiceResponse, 'multiplechoiceresponse':MultipleChoiceResponse,
'truefalseresponse':TrueFalseResponse, 'truefalseresponse':TrueFalseResponse,
'imageresponse':ImageResponse, 'imageresponse':ImageResponse,
'optionresponse':OptionResponse,
} }
entry_types = ['textline', 'schematic', 'choicegroup','textbox','imageinput'] entry_types = ['textline', 'schematic', 'choicegroup','textbox','imageinput','optioninput']
solution_types = ['solution'] # extra things displayed after "show answers" is pressed solution_types = ['solution'] # extra things displayed after "show answers" is pressed
response_properties = ["responseparam", "answer"] # these get captured as student responses response_properties = ["responseparam", "answer"] # these get captured as student responses
...@@ -186,6 +187,13 @@ class LoncapaProblem(object): ...@@ -186,6 +187,13 @@ class LoncapaProblem(object):
if answer: if answer:
answer_map[entry.get('id')] = contextualize_text(answer, self.context) answer_map[entry.get('id')] = contextualize_text(answer, self.context)
# include solutions from <solution>...</solution> stanzas
# Tentative merge; we should figure out how we want to handle hints and solutions
for entry in self.tree.xpath("//"+"|//".join(solution_types)):
answer = etree.tostring(entry)
if answer:
answer_map[entry.get('id')] = answer
return answer_map return answer_map
# ======= Private ======== # ======= Private ========
...@@ -241,7 +249,24 @@ class LoncapaProblem(object): ...@@ -241,7 +249,24 @@ class LoncapaProblem(object):
if self.student_answers and problemid in self.student_answers: if self.student_answers and problemid in self.student_answers:
value = self.student_answers[problemid] value = self.student_answers[problemid]
return getattr(inputtypes, problemtree.tag)(problemtree, value, status) #TODO #### This code is a hack. It was merged to help bring two branches
#### in sync, but should be replaced. msg should be passed in a
#### response_type
# prepare the response message, if it exists in correct_map
if 'msg' in self.correct_map:
msg = self.correct_map['msg']
elif ('msg_%s' % problemid) in self.correct_map:
msg = self.correct_map['msg_%s' % problemid]
else:
msg = ''
#if settings.DEBUG:
# print "[courseware.capa.capa_problem.extract_html] msg = ",msg
# do the rendering
#render_function = html_special_response[problemtree.tag]
render_function = getattr(inputtypes, problemtree.tag)
return render_function(problemtree, value, status, msg) # render the special response (textline, schematic,...)
tree=Element(problemtree.tag) tree=Element(problemtree.tag)
for item in problemtree: for item in problemtree:
...@@ -287,6 +312,7 @@ class LoncapaProblem(object): ...@@ -287,6 +312,7 @@ class LoncapaProblem(object):
answer_id = 1 answer_id = 1
for entry in tree.xpath("|".join(['//'+response.tag+'[@id=$id]//'+x for x in (entry_types + solution_types)]), for entry in tree.xpath("|".join(['//'+response.tag+'[@id=$id]//'+x for x in (entry_types + solution_types)]),
id=response_id_str): id=response_id_str):
# assign one answer_id for each entry_type or solution_type
entry.attrib['response_id'] = str(response_id) entry.attrib['response_id'] = str(response_id)
entry.attrib['answer_id'] = str(answer_id) entry.attrib['answer_id'] = str(answer_id)
entry.attrib['id'] = "%s_%i_%i"%(self.problem_id, response_id, answer_id) entry.attrib['id'] = "%s_%i_%i"%(self.problem_id, response_id, answer_id)
......
...@@ -6,11 +6,16 @@ ...@@ -6,11 +6,16 @@
Module containing the problem elements which render into input objects Module containing the problem elements which render into input objects
- textline - textline
- textbox (change this to textarea?) - textbox (change this to textarea?)
- schemmatic - schemmatic
- choicegroup (for multiplechoice: checkbox, radio, or select option)
- imageinput (for clickable image)
- optioninput (for option list)
These are matched by *.html files templates/*.html which are mako templates with the actual html. These are matched by *.html files templates/*.html which are mako templates with the actual html.
Each input type takes the xml tree as 'element', the previous answer as 'value', and the graded status as 'status'
''' '''
# TODO: rename "state" to "status" for all below # TODO: rename "state" to "status" for all below
...@@ -18,6 +23,7 @@ These are matched by *.html files templates/*.html which are mako templates with ...@@ -18,6 +23,7 @@ These are matched by *.html files templates/*.html which are mako templates with
# but it will turn into a dict containing both the answer and any associated message for the problem ID for the input element. # but it will turn into a dict containing both the answer and any associated message for the problem ID for the input element.
import re import re
import shlex # for splitting quoted strings
from django.conf import settings from django.conf import settings
...@@ -27,9 +33,42 @@ from lxml import etree ...@@ -27,9 +33,42 @@ from lxml import etree
from mitxmako.shortcuts import render_to_string from mitxmako.shortcuts import render_to_string
#----------------------------------------------------------------------------- #-----------------------------------------------------------------------------
#takes the xml tree as 'element', the student's previous answer as 'value', and the graded status as 'state'
def choicegroup(element, value, state, msg=""): def optioninput(element, value, status, msg=''):
'''
Select option input type.
Example:
<optioninput options="('Up','Down')" correct="Up"/><text>The location of the sky</text>
'''
eid=element.get('id')
options = element.get('options')
if not options:
raise Exception,"[courseware.capa.inputtypes.optioninput] Missing options specification in " + etree.tostring(element)
oset = shlex.shlex(options[1:-1])
oset.quotes = "'"
oset.whitespace = ","
oset = [x[1:-1] for x in list(oset)]
# osetdict = dict([('option_%s_%s' % (eid,x),oset[x]) for x in range(len(oset)) ]) # make dict with IDs
osetdict = dict([(oset[x],oset[x]) for x in range(len(oset)) ]) # make dict with key,value same
if settings.DEBUG:
print '[courseware.capa.inputtypes.optioninput] osetdict=',osetdict
context={'id':eid,
'value':value,
'state':status,
'msg':msg,
'options':osetdict,
}
html=render_to_string("optioninput.html", context)
return etree.XML(html)
#-----------------------------------------------------------------------------
def choicegroup(element, value, status, msg=''):
''' '''
Radio button inputs: multiple choice or true/false Radio button inputs: multiple choice or true/false
...@@ -47,7 +86,7 @@ def choicegroup(element, value, state, msg=""): ...@@ -47,7 +86,7 @@ def choicegroup(element, value, state, msg=""):
for choice in element: for choice in element:
assert choice.tag =="choice", "only <choice> tags should be immediate children of a <choicegroup>" assert choice.tag =="choice", "only <choice> tags should be immediate children of a <choicegroup>"
choices[choice.get("name")] = etree.tostring(choice[0]) # TODO: what if choice[0] has math tags in it? choices[choice.get("name")] = etree.tostring(choice[0]) # TODO: what if choice[0] has math tags in it?
context={'id':eid, 'value':value, 'state':state, 'type':type, 'choices':choices} context={'id':eid, 'value':value, 'state':status, 'type':type, 'choices':choices}
html=render_to_string("choicegroup.html", context) html=render_to_string("choicegroup.html", context)
return etree.XML(html) return etree.XML(html)
...@@ -60,9 +99,9 @@ def textline(element, value, state, msg=""): ...@@ -60,9 +99,9 @@ def textline(element, value, state, msg=""):
return etree.XML(html) return etree.XML(html)
#----------------------------------------------------------------------------- #-----------------------------------------------------------------------------
# TODO: Make a wrapper for <formulainput>
# TODO: Make an AJAX loop to confirm equation is okay in real-time as user types def js_textline(element, value, status, msg=''):
def jstextline(element, value, state, msg=""): ## TODO: Code should follow PEP8 (4 spaces per indentation level)
''' '''
textline is used for simple one-line inputs, like formularesponse and symbolicresponse. textline is used for simple one-line inputs, like formularesponse and symbolicresponse.
''' '''
...@@ -72,7 +111,7 @@ def jstextline(element, value, state, msg=""): ...@@ -72,7 +111,7 @@ def jstextline(element, value, state, msg=""):
dojs = element.get('dojs') # dojs is used for client-side javascript display & return dojs = element.get('dojs') # dojs is used for client-side javascript display & return
# when dojs=='math', a <span id=display_eid>`{::}`</span> # when dojs=='math', a <span id=display_eid>`{::}`</span>
# and a hidden textarea with id=input_eid_fromjs will be output # and a hidden textarea with id=input_eid_fromjs will be output
context = {'id':eid, 'value':value, 'state':state, 'count':count, 'size': size, context = {'id':eid, 'value':value, 'state':status, 'count':count, 'size': size,
'dojs':dojs, 'dojs':dojs,
'msg':msg, 'msg':msg,
} }
...@@ -81,7 +120,7 @@ def jstextline(element, value, state, msg=""): ...@@ -81,7 +120,7 @@ def jstextline(element, value, state, msg=""):
#----------------------------------------------------------------------------- #-----------------------------------------------------------------------------
## TODO: Make a wrapper for <codeinput> ## TODO: Make a wrapper for <codeinput>
def textbox(element, value, state, msg=''): def textbox(element, value, status, msg=''):
''' '''
The textbox is used for code input. The message is the return HTML string from The textbox is used for code input. The message is the return HTML string from
evaluating the code, eg error messages, and output from the code tests. evaluating the code, eg error messages, and output from the code tests.
...@@ -91,12 +130,12 @@ def textbox(element, value, state, msg=''): ...@@ -91,12 +130,12 @@ def textbox(element, value, state, msg=''):
eid=element.get('id') eid=element.get('id')
count = int(eid.split('_')[-2])-1 # HACK count = int(eid.split('_')[-2])-1 # HACK
size = element.get('size') size = element.get('size')
context = {'id':eid, 'value':value, 'state':state, 'count':count, 'size': size, 'msg':msg} context = {'id':eid, 'value':value, 'state':status, 'count':count, 'size': size, 'msg':msg}
html=render_to_string("textbox.html", context) html=render_to_string("textbox.html", context)
return etree.XML(html) return etree.XML(html)
#----------------------------------------------------------------------------- #-----------------------------------------------------------------------------
def schematic(element, value, state): def schematic(element, value, status, msg=''):
eid = element.get('id') eid = element.get('id')
height = element.get('height') height = element.get('height')
width = element.get('width') width = element.get('width')
...@@ -120,7 +159,7 @@ def schematic(element, value, state): ...@@ -120,7 +159,7 @@ def schematic(element, value, state):
#----------------------------------------------------------------------------- #-----------------------------------------------------------------------------
### TODO: Move out of inputtypes ### TODO: Move out of inputtypes
def math(element, value, state, msg=''): def math(element, value, status, msg=''):
''' '''
This is not really an input type. It is a convention from Lon-CAPA, used for This is not really an input type. It is a convention from Lon-CAPA, used for
displaying a math equation. displaying a math equation.
...@@ -134,21 +173,27 @@ def math(element, value, state, msg=''): ...@@ -134,21 +173,27 @@ def math(element, value, state, msg=''):
TODO: use shorter tags (but this will require converting problem XML files!) TODO: use shorter tags (but this will require converting problem XML files!)
''' '''
mathstr = element.text[1:-1] mathstr = re.sub('\$(.*)\$','[mathjaxinline]\\1[/mathjaxinline]',element.text)
if '\\displaystyle' in mathstr: mtag = 'mathjax'
isinline = False if not '\\displaystyle' in mathstr: mtag += 'inline'
mathstr = mathstr.replace('\\displaystyle','') else: mathstr = mathstr.replace('\\displaystyle','')
else: mathstr = mathstr.replace('mathjaxinline]','%s]'%mtag)
isinline = True
#if '\\displaystyle' in mathstr:
html=render_to_string("mathstring.html",{'mathstr':mathstr,'isinline':isinline,'tail':element.tail}) # isinline = False
# mathstr = mathstr.replace('\\displaystyle','')
#else:
# isinline = True
# html=render_to_string("mathstring.html",{'mathstr':mathstr,'isinline':isinline,'tail':element.tail})
html = '<html><html>%s</html><html>%s</html></html>' % (mathstr,element.tail)
xhtml = etree.XML(html) xhtml = etree.XML(html)
# xhtml.tail = element.tail # don't forget to include the tail! # xhtml.tail = element.tail # don't forget to include the tail!
return xhtml return xhtml
#----------------------------------------------------------------------------- #-----------------------------------------------------------------------------
def solution(element, value, state, msg=''): def solution(element, value, status, msg=''):
''' '''
This is not really an input type. It is just a <span>...</span> which is given an ID, This is not really an input type. It is just a <span>...</span> which is given an ID,
that is used for displaying an extended answer (a problem "solution") after "show answers" that is used for displaying an extended answer (a problem "solution") after "show answers"
...@@ -159,7 +204,7 @@ def solution(element, value, state, msg=''): ...@@ -159,7 +204,7 @@ def solution(element, value, state, msg=''):
size = element.get('size') size = element.get('size')
context = {'id':eid, context = {'id':eid,
'value':value, 'value':value,
'state':state, 'state':status,
'size': size, 'size': size,
'msg':msg, 'msg':msg,
} }
......
...@@ -24,7 +24,9 @@ try: # This lets us do __name__ == ='__main__' ...@@ -24,7 +24,9 @@ try: # This lets us do __name__ == ='__main__'
from student.models import UserTestGroup from student.models import UserTestGroup
from mitxmako.shortcuts import render_to_string from mitxmako.shortcuts import render_to_string
from util.cache import cache from util.cache import cache
from multicourse import multicourse_settings
except: except:
print "Could not import/content_parser"
settings = None settings = None
''' This file will eventually form an abstraction layer between the ''' This file will eventually form an abstraction layer between the
...@@ -181,7 +183,7 @@ def course_xml_process(tree): ...@@ -181,7 +183,7 @@ def course_xml_process(tree):
propogate_downward_tag(tree, "rerandomize") propogate_downward_tag(tree, "rerandomize")
return tree return tree
def course_file(user): def course_file(user,coursename=None):
''' Given a user, return course.xml''' ''' Given a user, return course.xml'''
if user.is_authenticated(): if user.is_authenticated():
...@@ -189,6 +191,11 @@ def course_file(user): ...@@ -189,6 +191,11 @@ def course_file(user):
else: else:
filename = 'guest_course.xml' filename = 'guest_course.xml'
# if a specific course is specified, then use multicourse to get the right path to the course XML directory
if coursename and settings.ENABLE_MULTICOURSE:
xp = multicourse_settings.get_course_xmlpath(coursename)
filename = xp + filename # prefix the filename with the path
groups = user_groups(user) groups = user_groups(user)
options = {'dev_content':settings.DEV_CONTENT, options = {'dev_content':settings.DEV_CONTENT,
'groups' : groups} 'groups' : groups}
...@@ -210,13 +217,24 @@ def course_file(user): ...@@ -210,13 +217,24 @@ def course_file(user):
return tree return tree
def section_file(user, section): def section_file(user, section, coursename=None, dironly=False):
''' Given a user and the name of a section, return that section '''
Given a user and the name of a section, return that section.
This is done specific to each course.
If dironly=True then return the sections directory.
''' '''
filename = section+".xml" filename = section+".xml"
if filename not in os.listdir(settings.DATA_DIR + '/sections/'): # if a specific course is specified, then use multicourse to get the right path to the course XML directory
print filename+" not in "+str(os.listdir(settings.DATA_DIR + '/sections/')) xp = ''
if coursename and settings.ENABLE_MULTICOURSE: xp = multicourse_settings.get_course_xmlpath(coursename)
dirname = settings.DATA_DIR + xp + '/sections/'
if dironly: return dirname
if filename not in os.listdir(dirname):
print filename+" not in "+str(os.listdir(dirname))
return None return None
options = {'dev_content':settings.DEV_CONTENT, options = {'dev_content':settings.DEV_CONTENT,
...@@ -226,7 +244,7 @@ def section_file(user, section): ...@@ -226,7 +244,7 @@ def section_file(user, section):
return tree return tree
def module_xml(user, module, id_tag, module_id): def module_xml(user, module, id_tag, module_id, coursename=None):
''' Get XML for a module based on module and module_id. Assumes ''' Get XML for a module based on module and module_id. Assumes
module occurs once in courseware XML file or hidden section. ''' module occurs once in courseware XML file or hidden section. '''
# Sanitize input # Sanitize input
...@@ -239,14 +257,15 @@ def module_xml(user, module, id_tag, module_id): ...@@ -239,14 +257,15 @@ def module_xml(user, module, id_tag, module_id):
id_tag=id_tag, id_tag=id_tag,
id=module_id) id=module_id)
#result_set=doc.xpathEval(xpath_search) #result_set=doc.xpathEval(xpath_search)
doc = course_file(user) doc = course_file(user,coursename)
section_list = (s[:-4] for s in os.listdir(settings.DATA_DIR+'/sections') if s[-4:]=='.xml') sdirname = section_file(user,'',coursename,True) # get directory where sections information is stored
section_list = (s[:-4] for s in os.listdir(sdirname) if s[-4:]=='.xml')
result_set=doc.xpath(xpath_search) result_set=doc.xpath(xpath_search)
if len(result_set)<1: if len(result_set)<1:
for section in section_list: for section in section_list:
try: try:
s = section_file(user, section) s = section_file(user, section, coursename)
except etree.XMLSyntaxError: except etree.XMLSyntaxError:
ex= sys.exc_info() ex= sys.exc_info()
raise ContentException("Malformed XML in " + section+ "("+str(ex[1].msg)+")") raise ContentException("Malformed XML in " + section+ "("+str(ex[1].msg)+")")
......
...@@ -67,7 +67,7 @@ course_settings = Settings() ...@@ -67,7 +67,7 @@ course_settings = Settings()
def grade_sheet(student): def grade_sheet(student,coursename=None):
""" """
This pulls a summary of all problems in the course. It returns a dictionary with two datastructures: This pulls a summary of all problems in the course. It returns a dictionary with two datastructures:
...@@ -77,7 +77,7 @@ def grade_sheet(student): ...@@ -77,7 +77,7 @@ def grade_sheet(student):
- grade_summary is the output from the course grader. More information on the format is in the docstring for CourseGrader. - grade_summary is the output from the course grader. More information on the format is in the docstring for CourseGrader.
""" """
dom=content_parser.course_file(student) dom=content_parser.course_file(student,coursename)
course = dom.xpath('//course/@name')[0] course = dom.xpath('//course/@name')[0]
xmlChapters = dom.xpath('//course[@name=$course]/chapter', course=course) xmlChapters = dom.xpath('//course[@name=$course]/chapter', course=course)
...@@ -103,7 +103,7 @@ def grade_sheet(student): ...@@ -103,7 +103,7 @@ def grade_sheet(student):
scores=[] scores=[]
if len(problems)>0: if len(problems)>0:
for p in problems: for p in problems:
(correct,total) = get_score(student, p, response_by_id) (correct,total) = get_score(student, p, response_by_id, coursename=coursename)
if settings.GENERATE_PROFILE_SCORES: if settings.GENERATE_PROFILE_SCORES:
if total > 1: if total > 1:
...@@ -167,7 +167,7 @@ def aggregate_scores(scores, section_name = "summary"): ...@@ -167,7 +167,7 @@ def aggregate_scores(scores, section_name = "summary"):
return all_total, graded_total return all_total, graded_total
def get_score(user, problem, cache): def get_score(user, problem, cache, coursename=None):
## HACK: assumes max score is fixed per problem ## HACK: assumes max score is fixed per problem
id = problem.get('id') id = problem.get('id')
correct = 0.0 correct = 0.0
...@@ -196,7 +196,7 @@ def get_score(user, problem, cache): ...@@ -196,7 +196,7 @@ def get_score(user, problem, cache):
## HACK 1: We shouldn't specifically reference capa_module ## HACK 1: We shouldn't specifically reference capa_module
## HACK 2: Backwards-compatibility: This should be written when a grade is saved, and removed from the system ## HACK 2: Backwards-compatibility: This should be written when a grade is saved, and removed from the system
from module_render import I4xSystem from module_render import I4xSystem
system = I4xSystem(None, None, None) system = I4xSystem(None, None, None, coursename=coursename)
total=float(courseware.modules.capa_module.Module(system, etree.tostring(problem), "id").max_score()) total=float(courseware.modules.capa_module.Module(system, etree.tostring(problem), "id").max_score())
response.max_grade = total response.max_grade = total
response.save() response.save()
......
...@@ -6,9 +6,9 @@ from django.core.management.base import BaseCommand ...@@ -6,9 +6,9 @@ from django.core.management.base import BaseCommand
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
from courseware.content_parser import course_file from mitx.courseware.content_parser import course_file
import courseware.module_render import mitx.courseware.module_render
import courseware.modules import mitx.courseware.modules
class Command(BaseCommand): class Command(BaseCommand):
help = "Does basic validity tests on course.xml." help = "Does basic validity tests on course.xml."
...@@ -25,15 +25,15 @@ class Command(BaseCommand): ...@@ -25,15 +25,15 @@ class Command(BaseCommand):
check = False check = False
print "Confirming all modules render. Nothing should print during this step. " print "Confirming all modules render. Nothing should print during this step. "
for module in course.xpath('//problem|//html|//video|//vertical|//sequential|/tab'): for module in course.xpath('//problem|//html|//video|//vertical|//sequential|/tab'):
module_class = courseware.modules.modx_modules[module.tag] module_class=mitx.courseware.modules.modx_modules[module.tag]
# TODO: Abstract this out in render_module.py # TODO: Abstract this out in render_module.py
try: try:
module_class(etree.tostring(module), instance=module_class(etree.tostring(module),
module.get('id'), module.get('id'),
ajax_url='', ajax_url='',
state=None, state=None,
track_function = lambda x,y,z:None, track_function = lambda x,y,z:None,
render_function = lambda x: {'content':'','destroy_js':'','init_js':'','type':'video'}) render_function = lambda x: {'content':'','destroy_js':'','init_js':'','type':'video'})
except: except:
print "==============> Error in ", etree.tostring(module) print "==============> Error in ", etree.tostring(module)
check = False check = False
......
...@@ -22,6 +22,11 @@ import courseware.modules ...@@ -22,6 +22,11 @@ import courseware.modules
log = logging.getLogger("mitx.courseware") log = logging.getLogger("mitx.courseware")
class I4xSystem(object): class I4xSystem(object):
'''
This is an abstraction such that x_modules can function independent
of the courseware (e.g. import into other types of courseware, LMS,
or if we want to have a sandbox server for user-contributed content)
'''
def __init__(self, ajax_url, track_function, render_function, filestore=None): def __init__(self, ajax_url, track_function, render_function, filestore=None):
self.ajax_url = ajax_url self.ajax_url = ajax_url
self.track_function = track_function self.track_function = track_function
...@@ -29,6 +34,10 @@ class I4xSystem(object): ...@@ -29,6 +34,10 @@ class I4xSystem(object):
self.filestore = OSFS(settings.DATA_DIR) self.filestore = OSFS(settings.DATA_DIR)
self.render_function = render_function self.render_function = render_function
self.exception404 = Http404 self.exception404 = Http404
def __repr__(self):
return repr(self.__dict__)
def __str__(self):
return str(self.__dict__)
def object_cache(cache, user, module_type, module_id): def object_cache(cache, user, module_type, module_id):
# We don't look up on user -- all queries include user # We don't look up on user -- all queries include user
...@@ -50,6 +59,7 @@ def make_track_function(request): ...@@ -50,6 +59,7 @@ def make_track_function(request):
def f(event_type, event): def f(event_type, event):
return track.views.server_track(request, event_type, event, page='x_module') return track.views.server_track(request, event_type, event, page='x_module')
return f return f
def grade_histogram(module_id): def grade_histogram(module_id):
''' Print out a histogram of grades on a given problem. ''' Print out a histogram of grades on a given problem.
Part of staff member debug info. Part of staff member debug info.
...@@ -83,6 +93,10 @@ def render_x_module(user, request, xml_module, module_object_preload): ...@@ -83,6 +93,10 @@ def render_x_module(user, request, xml_module, module_object_preload):
else: else:
state = smod.state state = smod.state
# get coursename if stored
if 'coursename' in request.session: coursename = request.session['coursename']
else: coursename = None
# Create a new instance # Create a new instance
ajax_url = settings.MITX_ROOT_URL + '/modx/'+module_type+'/'+module_id+'/' ajax_url = settings.MITX_ROOT_URL + '/modx/'+module_type+'/'+module_id+'/'
system = I4xSystem(track_function = make_track_function(request), system = I4xSystem(track_function = make_track_function(request),
...@@ -104,6 +118,7 @@ def render_x_module(user, request, xml_module, module_object_preload): ...@@ -104,6 +118,7 @@ def render_x_module(user, request, xml_module, module_object_preload):
state=instance.get_state()) state=instance.get_state())
smod.save() smod.save()
module_object_preload.append(smod) module_object_preload.append(smod)
# Grab content # Grab content
content = instance.get_html() content = instance.get_html()
init_js = instance.get_init_js() init_js = instance.get_init_js()
......
...@@ -21,6 +21,7 @@ from mitxmako.shortcuts import render_to_string ...@@ -21,6 +21,7 @@ from mitxmako.shortcuts import render_to_string
from x_module import XModule from x_module import XModule
from courseware.capa.capa_problem import LoncapaProblem, StudentInputError from courseware.capa.capa_problem import LoncapaProblem, StudentInputError
import courseware.content_parser as content_parser import courseware.content_parser as content_parser
from multicourse import multicourse_settings
log = logging.getLogger("mitx.courseware") log = logging.getLogger("mitx.courseware")
...@@ -115,18 +116,19 @@ class Module(XModule): ...@@ -115,18 +116,19 @@ class Module(XModule):
if len(explain) == 0: if len(explain) == 0:
explain = False explain = False
html=render_to_string('problem.html', context = {'problem' : content,
{'problem' : content, 'id' : self.item_id,
'id' : self.item_id, 'check_button' : check_button,
'check_button' : check_button, 'reset_button' : reset_button,
'reset_button' : reset_button, 'save_button' : save_button,
'save_button' : save_button, 'answer_available' : self.answer_available(),
'answer_available' : self.answer_available(), 'ajax_url' : self.ajax_url,
'ajax_url' : self.ajax_url, 'attempts_used': self.attempts,
'attempts_used': self.attempts, 'attempts_allowed': self.max_attempts,
'attempts_allowed': self.max_attempts, 'explain': explain,
'explain': explain }
})
html=render_to_string('problem.html', context)
if encapsulate: if encapsulate:
html = '<div id="main_{id}">'.format(id=self.item_id)+html+"</div>" html = '<div id="main_{id}">'.format(id=self.item_id)+html+"</div>"
...@@ -193,7 +195,12 @@ class Module(XModule): ...@@ -193,7 +195,12 @@ class Module(XModule):
seed = 1 seed = 1
else: else:
seed = None seed = None
self.lcp=LoncapaProblem(self.filestore.open(self.filename), self.item_id, state, seed = seed) try:
fp = self.filestore.open(self.filename)
except Exception,err:
print '[courseware.capa.capa_module.Module.init] error %s: cannot open file %s' % (err,self.filename)
raise Exception,err
self.lcp=LoncapaProblem(fp, self.item_id, state, seed = seed)
def handle_ajax(self, dispatch, get): def handle_ajax(self, dispatch, get):
''' '''
...@@ -306,7 +313,7 @@ class Module(XModule): ...@@ -306,7 +313,7 @@ class Module(XModule):
except: except:
self.lcp = LoncapaProblem(self.filestore.open(self.filename), id=lcp_id, state=old_state) self.lcp = LoncapaProblem(self.filestore.open(self.filename), id=lcp_id, state=old_state)
traceback.print_exc() traceback.print_exc()
raise raise Exception,"error in capa_module"
return json.dumps({'success':'Unknown Error'}) return json.dumps({'success':'Unknown Error'})
self.attempts = self.attempts + 1 self.attempts = self.attempts + 1
......
<problem>
<text>
<p>
Why do bicycles benefit from having larger wheels when going up a bump as shown in the picture? <br/>
Assume that for both bicycles:<br/>
1.) The tires have equal air pressure.<br/>
2.) The bicycles never leave the contact with the bump.<br/>
3.) The bicycles have the same mass. The bicycle tires (regardless of size) have the same mass.<br/>
</p>
</text>
<optionresponse texlayout="horizontal" max="10" randomize="yes">
<ul>
<li>
<text>
<p>The bicycles with larger wheels have more time to go over the bump. This decreases the magnitude of the force needed to lift the bicycle.</p>
</text>
<optioninput name="Foil1" location="random" options="('True','False')" correct="True">
</optioninput>
</li>
<li>
<text>
<p>The bicycles with larger wheels always have a smaller vertical displacement regardless of speed.</p>
</text>
<optioninput name="Foil2" location="random" options="('True','False')" correct="False">
</optioninput>
</li>
<li>
<text>
<p>The bicycles with larger wheels experience a force backward with less magnitude for the same amount of time.</p>
</text>
<optioninput name="Foil3" location="random" options="('True','False')" correct="False">
</optioninput>
</li>
<li>
<text>
<p>The bicycles with larger wheels experience a force backward with less magnitude for a greater amount of time.</p>
</text>
<optioninput name="Foil4" location="random" options="('True','False')" correct="True">
</optioninput>
</li>
<li>
<text>
<p>The bicycles with larger wheels have more kinetic energy turned into gravitational potential energy.</p>
</text>
<optioninput name="Foil5" location="random" options="('True','False')" correct="False">
</optioninput>
</li>
<li>
<text>
<p>The bicycles with larger wheels have more rotational kinetic energy, so the horizontal velocity of the biker changes less.</p>
</text>
<optioninput name="Foil6" location="random" options="('True','False')" correct="False">
</optioninput>
</li>
</ul>
<hintgroup showoncorrect="no">
<text>
<br/>
<br/>
</text>
</hintgroup>
</optionresponse>
</problem>
...@@ -63,6 +63,9 @@ class ModelsTest(unittest.TestCase): ...@@ -63,6 +63,9 @@ class ModelsTest(unittest.TestCase):
exception_happened = True exception_happened = True
self.assertTrue(exception_happened) self.assertTrue(exception_happened)
#-----------------------------------------------------------------------------
# tests of capa_problem inputtypes
class MultiChoiceTest(unittest.TestCase): class MultiChoiceTest(unittest.TestCase):
def test_MC_grade(self): def test_MC_grade(self):
multichoice_file = os.path.dirname(__file__)+"/test_files/multichoice.xml" multichoice_file = os.path.dirname(__file__)+"/test_files/multichoice.xml"
...@@ -93,6 +96,38 @@ class MultiChoiceTest(unittest.TestCase): ...@@ -93,6 +96,38 @@ class MultiChoiceTest(unittest.TestCase):
self.assertEquals(test_lcp.grade_answers(false_answers)['1_2_1'], 'incorrect') self.assertEquals(test_lcp.grade_answers(false_answers)['1_2_1'], 'incorrect')
false_answers = {'1_2_1':['choice_foil1', 'choice_foil2', 'choice_foil3']} false_answers = {'1_2_1':['choice_foil1', 'choice_foil2', 'choice_foil3']}
self.assertEquals(test_lcp.grade_answers(false_answers)['1_2_1'], 'incorrect') self.assertEquals(test_lcp.grade_answers(false_answers)['1_2_1'], 'incorrect')
class ImageResponseTest(unittest.TestCase):
def test_ir_grade(self):
imageresponse_file = os.path.dirname(__file__)+"/test_files/imageresponse.xml"
test_lcp = lcp.LoncapaProblem(open(imageresponse_file), '1')
correct_answers = {'1_2_1':'(490,11)-(556,98)',
'1_2_2':'(242,202)-(296,276)'}
test_answers = {'1_2_1':'[500,20]',
'1_2_2':'[250,300]',
}
self.assertEquals(test_lcp.grade_answers(test_answers)['1_2_1'], 'correct')
self.assertEquals(test_lcp.grade_answers(test_answers)['1_2_2'], 'incorrect')
class OptionResponseTest(unittest.TestCase):
'''
Run this with
python manage.py test courseware.OptionResponseTest
'''
def test_or_grade(self):
optionresponse_file = os.path.dirname(__file__)+"/test_files/optionresponse.xml"
test_lcp = lcp.LoncapaProblem(open(optionresponse_file), '1')
correct_answers = {'1_2_1':'True',
'1_2_2':'False'}
test_answers = {'1_2_1':'True',
'1_2_2':'True',
}
self.assertEquals(test_lcp.grade_answers(test_answers)['1_2_1'], 'correct')
self.assertEquals(test_lcp.grade_answers(test_answers)['1_2_2'], 'incorrect')
#-----------------------------------------------------------------------------
# Grading tests
class GradesheetTest(unittest.TestCase): class GradesheetTest(unittest.TestCase):
...@@ -118,7 +153,7 @@ class GradesheetTest(unittest.TestCase): ...@@ -118,7 +153,7 @@ class GradesheetTest(unittest.TestCase):
all, graded = aggregate_scores(scores) all, graded = aggregate_scores(scores)
self.assertAlmostEqual(all, Score(earned=5, possible=15, graded=False, section="summary")) self.assertAlmostEqual(all, Score(earned=5, possible=15, graded=False, section="summary"))
self.assertAlmostEqual(graded, Score(earned=5, possible=10, graded=True, section="summary")) self.assertAlmostEqual(graded, Score(earned=5, possible=10, graded=True, section="summary"))
class GraderTest(unittest.TestCase): class GraderTest(unittest.TestCase):
empty_gradesheet = { empty_gradesheet = {
......
...@@ -16,6 +16,7 @@ from lxml import etree ...@@ -16,6 +16,7 @@ from lxml import etree
from module_render import render_module, make_track_function, I4xSystem from module_render import render_module, make_track_function, I4xSystem
from models import StudentModule from models import StudentModule
from student.models import UserProfile from student.models import UserProfile
from multicourse import multicourse_settings
import courseware.content_parser as content_parser import courseware.content_parser as content_parser
import courseware.modules import courseware.modules
...@@ -33,11 +34,16 @@ template_imports={'urllib':urllib} ...@@ -33,11 +34,16 @@ template_imports={'urllib':urllib}
def gradebook(request): def gradebook(request):
if 'course_admin' not in content_parser.user_groups(request.user): if 'course_admin' not in content_parser.user_groups(request.user):
raise Http404 raise Http404
# TODO: This should be abstracted out. We repeat this logic many times.
if 'coursename' in request.session: coursename = request.session['coursename']
else: coursename = None
student_objects = User.objects.all()[:100] student_objects = User.objects.all()[:100]
student_info = [{'username' :s.username, student_info = [{'username' :s.username,
'id' : s.id, 'id' : s.id,
'email': s.email, 'email': s.email,
'grade_info' : grades.grade_sheet(s), 'grade_info' : grades.grade_sheet(s,coursename),
'realname' : UserProfile.objects.get(user = s).name 'realname' : UserProfile.objects.get(user = s).name
} for s in student_objects] } for s in student_objects]
...@@ -59,6 +65,9 @@ def profile(request, student_id = None): ...@@ -59,6 +65,9 @@ def profile(request, student_id = None):
user_info = UserProfile.objects.get(user=student) # request.user.profile_cache # user_info = UserProfile.objects.get(user=student) # request.user.profile_cache #
if 'coursename' in request.session: coursename = request.session['coursename']
else: coursename = None
context={'name':user_info.name, context={'name':user_info.name,
'username':student.username, 'username':student.username,
'location':user_info.location, 'location':user_info.location,
...@@ -67,7 +76,7 @@ def profile(request, student_id = None): ...@@ -67,7 +76,7 @@ def profile(request, student_id = None):
'format_url_params' : content_parser.format_url_params, 'format_url_params' : content_parser.format_url_params,
'csrf':csrf(request)['csrf_token'] 'csrf':csrf(request)['csrf_token']
} }
context.update(grades.grade_sheet(student)) context.update(grades.grade_sheet(student,coursename))
return render_to_response('profile.html', context) return render_to_response('profile.html', context)
...@@ -77,7 +86,7 @@ def render_accordion(request,course,chapter,section): ...@@ -77,7 +86,7 @@ def render_accordion(request,course,chapter,section):
if not course: if not course:
course = "6.002 Spring 2012" course = "6.002 Spring 2012"
toc=content_parser.toc_from_xml(content_parser.course_file(request.user), chapter, section) toc=content_parser.toc_from_xml(content_parser.course_file(request.user,course), chapter, section)
active_chapter=1 active_chapter=1
for i in range(len(toc)): for i in range(len(toc)):
if toc[i]['active']: if toc[i]['active']:
...@@ -98,8 +107,11 @@ def render_section(request, section): ...@@ -98,8 +107,11 @@ def render_section(request, section):
if not settings.COURSEWARE_ENABLED: if not settings.COURSEWARE_ENABLED:
return redirect('/') return redirect('/')
if 'coursename' in request.session: coursename = request.session['coursename']
else: coursename = None
# try: # try:
dom = content_parser.section_file(user, section) dom = content_parser.section_file(user, section, coursename)
#except: #except:
# raise Http404 # raise Http404
...@@ -128,13 +140,21 @@ def render_section(request, section): ...@@ -128,13 +140,21 @@ def render_section(request, section):
@cache_control(no_cache=True, no_store=True, must_revalidate=True) @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"): def index(request, course=None, chapter="Using the System", section="Hints"):
''' Displays courseware accordion, and any associated content. ''' Displays courseware accordion, and any associated content.
''' '''
user = request.user user = request.user
if not settings.COURSEWARE_ENABLED: if not settings.COURSEWARE_ENABLED:
return redirect('/') return redirect('/')
if course==None:
if not settings.ENABLE_MULTICOURSE:
course = "6.002 Spring 2012"
elif 'coursename' in request.session:
course = request.session['coursename']
else:
course = settings.COURSE_DEFAULT
# Fixes URLs -- we don't get funny encoding characters from spaces # Fixes URLs -- we don't get funny encoding characters from spaces
# so they remain readable # so they remain readable
## TODO: Properly replace underscores ## TODO: Properly replace underscores
...@@ -142,16 +162,18 @@ def index(request, course="6.002 Spring 2012", chapter="Using the System", secti ...@@ -142,16 +162,18 @@ def index(request, course="6.002 Spring 2012", chapter="Using the System", secti
chapter=chapter.replace("_"," ") chapter=chapter.replace("_"," ")
section=section.replace("_"," ") section=section.replace("_"," ")
# HACK: Force course to 6.002 for now # use multicourse module to determine if "course" is valid
# Without this, URLs break #if course!=settings.COURSE_NAME.replace('_',' '):
if course!="6.002 Spring 2012": if not multicourse_settings.is_valid_course(course):
return redirect('/') return redirect('/')
#import logging #import logging
#log = logging.getLogger("mitx") #log = logging.getLogger("mitx")
#log.info( "DEBUG: "+str(user) ) #log.info( "DEBUG: "+str(user) )
dom = content_parser.course_file(user) request.session['coursename'] = course # keep track of current course being viewed in django's request.session
dom = content_parser.course_file(user,course) # also pass course to it, for course-specific XML path
dom_module = dom.xpath("//course[@name=$course]/chapter[@name=$chapter]//section[@name=$section]/*[1]", dom_module = dom.xpath("//course[@name=$course]/chapter[@name=$chapter]//section[@name=$section]/*[1]",
course=course, chapter=chapter, section=section) course=course, chapter=chapter, section=section)
if len(dom_module) == 0: if len(dom_module) == 0:
...@@ -179,6 +201,7 @@ def index(request, course="6.002 Spring 2012", chapter="Using the System", secti ...@@ -179,6 +201,7 @@ def index(request, course="6.002 Spring 2012", chapter="Using the System", secti
context={'init':module['init_js'], context={'init':module['init_js'],
'accordion':accordion, 'accordion':accordion,
'content':module['content'], 'content':module['content'],
'COURSE_TITLE':multicourse_settings.get_course_title(course),
'csrf':csrf(request)['csrf_token']} 'csrf':csrf(request)['csrf_token']}
result = render_to_response('courseware.html', context) result = render_to_response('courseware.html', context)
...@@ -206,8 +229,12 @@ def modx_dispatch(request, module=None, dispatch=None, id=None): ...@@ -206,8 +229,12 @@ def modx_dispatch(request, module=None, dispatch=None, id=None):
ajax_url = settings.MITX_ROOT_URL + '/modx/'+module+'/'+id+'/' ajax_url = settings.MITX_ROOT_URL + '/modx/'+module+'/'+id+'/'
# get coursename if stored
if 'coursename' in request.session: coursename = request.session['coursename']
else: coursename = None
# Grab the XML corresponding to the request from course.xml # Grab the XML corresponding to the request from course.xml
xml = content_parser.module_xml(request.user, module, 'id', id) xml = content_parser.module_xml(request.user, module, 'id', id, coursename)
# Create the module # Create the module
system = I4xSystem(track_function = make_track_function(request), system = I4xSystem(track_function = make_track_function(request),
...@@ -229,3 +256,98 @@ def modx_dispatch(request, module=None, dispatch=None, id=None): ...@@ -229,3 +256,98 @@ def modx_dispatch(request, module=None, dispatch=None, id=None):
s.save() s.save()
# Return whatever the module wanted to return to the client/caller # Return whatever the module wanted to return to the client/caller
return HttpResponse(ajax_return) return HttpResponse(ajax_return)
def quickedit(request, id=None):
'''
quick-edit capa problem.
Maybe this should be moved into capa/views.py
Or this should take a "module" argument, and the quickedit moved into capa_module.
'''
print "WARNING: UNDEPLOYABLE CODE. FOR DEV USE ONLY."
print "In deployed use, this will only edit on one server"
print "We need a setting to disable for production where there is"
print "a load balanacer"
if not request.user.is_staff():
return redirect('/')
# get coursename if stored
if 'coursename' in request.session: coursename = request.session['coursename']
else: coursename = None
def get_lcp(coursename,id):
# Grab the XML corresponding to the request from course.xml
module = 'problem'
xml = content_parser.module_xml(request.user, module, 'id', id, coursename)
ajax_url = settings.MITX_ROOT_URL + '/modx/'+module+'/'+id+'/'
# Create the module (instance of capa_module.Module)
system = I4xSystem(track_function = make_track_function(request),
render_function = None,
ajax_url = ajax_url,
filestore = None,
coursename = coursename,
role = 'staff' if request.user.is_staff else 'student', # TODO: generalize this
)
instance=courseware.modules.get_module_class(module)(system,
xml,
id,
state=None)
lcp = instance.lcp
pxml = lcp.tree
pxmls = etree.tostring(pxml,pretty_print=True)
return instance, pxmls
instance, pxmls = get_lcp(coursename,id)
# if there was a POST, then process it
msg = ''
if 'qesubmit' in request.POST:
action = request.POST['qesubmit']
if "Revert" in action:
msg = "Reverted to original"
elif action=='Change Problem':
key = 'quickedit_%s' % id
if not key in request.POST:
msg = "oops, missing code key=%s" % key
else:
newcode = request.POST[key]
# see if code changed
if str(newcode)==str(pxmls) or '<?xml version="1.0"?>\n'+str(newcode)==str(pxmls):
msg = "No changes"
else:
# check new code
isok = False
try:
newxml = etree.fromstring(newcode)
isok = True
except Exception,err:
msg = "Failed to change problem: XML error \"<font color=red>%s</font>\"" % err
if isok:
filename = instance.lcp.fileobject.name
fp = open(filename,'w') # TODO - replace with filestore call?
fp.write(newcode)
fp.close()
msg = "<font color=green>Problem changed!</font> (<tt>%s</tt>)" % filename
instance, pxmls = get_lcp(coursename,id)
lcp = instance.lcp
# get the rendered problem HTML
phtml = instance.get_problem_html()
context = {'id':id,
'msg' : msg,
'lcp' : lcp,
'filename' : lcp.fileobject.name,
'pxmls' : pxmls,
'phtml' : phtml,
'init_js':instance.get_init_js(),
}
result = render_to_response('quickedit.html', context)
return result
# multicourse/multicourse_settings.py
#
# central module for providing fixed settings (course name, number, title)
# for multiple courses. Loads this information from django.conf.settings
#
# Allows backward compatibility with settings configurations without
# multiple courses specified.
#
# The central piece of configuration data is the dict COURSE_SETTINGS, with
# keys being the COURSE_NAME (spaces ok), and the value being a dict of
# parameter,value pairs. The required parameters are:
#
# - number : course number (used in the simplewiki pages)
# - title : humanized descriptive course title
#
# Optional parameters:
#
# - xmlpath : path (relative to data directory) for this course (defaults to "")
#
# If COURSE_SETTINGS does not exist, then fallback to 6.002_Spring_2012 default,
# for now.
from django.conf import settings
#-----------------------------------------------------------------------------
# load course settings
if hasattr(settings,'COURSE_SETTINGS'): # in the future, this could be replaced by reading an XML file
COURSE_SETTINGS = settings.COURSE_SETTINGS
elif hasattr(settings,'COURSE_NAME'): # backward compatibility
COURSE_SETTINGS = {settings.COURSE_NAME: {'number': settings.COURSE_NUMBER,
'title': settings.COURSE_TITLE,
},
}
else: # default to 6.002_Spring_2012
COURSE_SETTINGS = {'6.002_Spring_2012': {'number': '6.002x',
'title': 'Circuits and Electronics',
},
}
#-----------------------------------------------------------------------------
# wrapper functions around course settings
def get_course_settings(coursename):
if not coursename:
if hasattr(settings,'COURSE_DEFAULT'):
coursename = settings.COURSE_DEFAULT
else:
coursename = '6.002_Spring_2012'
if coursename in COURSE_SETTINGS: return COURSE_SETTINGS[coursename]
coursename = coursename.replace(' ','_')
if coursename in COURSE_SETTINGS: return COURSE_SETTINGS[coursename]
return None
def is_valid_course(coursename):
return not (get_course_settings==None)
def get_course_property(coursename,property):
cs = get_course_settings(coursename)
if not cs: return '' # raise exception instead?
if property in cs: return cs[property]
return '' # default
def get_course_xmlpath(coursename):
return get_course_property(coursename,'xmlpath')
def get_course_title(coursename):
return get_course_property(coursename,'title')
def get_course_number(coursename):
return get_course_property(coursename,'number')
# multicourse/views.py
...@@ -9,6 +9,8 @@ from django.utils import simplejson ...@@ -9,6 +9,8 @@ from django.utils import simplejson
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from mitxmako.shortcuts import render_to_response from mitxmako.shortcuts import render_to_response
from multicourse import multicourse_settings
from models import Revision, Article, CreateArticleForm, RevisionFormWithTitle, RevisionForm from models import Revision, Article, CreateArticleForm, RevisionFormWithTitle, RevisionForm
import wiki_settings import wiki_settings
...@@ -17,6 +19,11 @@ def view(request, wiki_url): ...@@ -17,6 +19,11 @@ def view(request, wiki_url):
if err: if err:
return err return err
if 'coursename' in request.session: coursename = request.session['coursename']
else: coursename = None
course_number = multicourse_settings.get_course_number(coursename)
perm_err = check_permissions(request, article, check_read=True, check_deleted=True) perm_err = check_permissions(request, article, check_read=True, check_deleted=True)
if perm_err: if perm_err:
return perm_err return perm_err
...@@ -25,7 +32,7 @@ def view(request, wiki_url): ...@@ -25,7 +32,7 @@ def view(request, wiki_url):
'wiki_write': article.can_write_l(request.user), 'wiki_write': article.can_write_l(request.user),
'wiki_attachments_write': article.can_attach(request.user), 'wiki_attachments_write': article.can_attach(request.user),
'wiki_current_revision_deleted' : not (article.current_revision.deleted == 0), 'wiki_current_revision_deleted' : not (article.current_revision.deleted == 0),
'wiki_title' : article.title + " - MITX 6.002x Wiki" 'wiki_title' : article.title + " - MITX %s Wiki" % course_number
} }
d.update(csrf(request)) d.update(csrf(request))
return render_to_response('simplewiki_view.html', d) return render_to_response('simplewiki_view.html', d)
......
#!/usr/bin/python
from loncapa_check import *
#!/usr/bin/python
#
# File: mitx/lib/loncapa/loncapa_check.py
#
# Python functions which duplicate the standard comparison functions available to LON-CAPA problems.
# Used in translating LON-CAPA problems to i4x problem specification language.
import random
def lc_random(lower,upper,stepsize):
'''
like random.randrange but lower and upper can be non-integer
'''
nstep = int((upper-lower)/(1.0*stepsize))
choices = [lower+x*stepsize for x in range(nstep)]
return random.choice(choices)
...@@ -34,6 +34,9 @@ def render_to_string(template_name, dictionary, context=None, namespace='main'): ...@@ -34,6 +34,9 @@ def render_to_string(template_name, dictionary, context=None, namespace='main'):
context_dictionary.update(d) context_dictionary.update(d)
if context: if context:
context_dictionary.update(context) context_dictionary.update(context)
## HACK
## We should remove this, and possible set COURSE_TITLE in the middleware from the session.
if 'COURSE_TITLE' not in context_dictionary: context_dictionary['COURSE_TITLE'] = ''
# fetch and render template # fetch and render template
template = middleware.lookup[namespace].get_template(template_name) template = middleware.lookup[namespace].get_template(template_name)
return template.render(**context_dictionary) return template.render(**context_dictionary)
......
...@@ -3,7 +3,6 @@ import json ...@@ -3,7 +3,6 @@ import json
import sys import sys
from django.conf import settings from django.conf import settings
from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.context_processors import csrf from django.core.context_processors import csrf
from django.core.mail import send_mail from django.core.mail import send_mail
...@@ -61,3 +60,9 @@ def send_feedback(request): ...@@ -61,3 +60,9 @@ def send_feedback(request):
def info(request): def info(request):
''' Info page (link from main header) ''' ''' Info page (link from main header) '''
return render_to_response("info.html", {}) return render_to_response("info.html", {})
def mitxhome(request):
''' Home page (link from main header). List of courses. '''
if settings.ENABLE_MULTICOURSE:
return render_to_response("mitxhome.html", {})
return info(request)
...@@ -8,6 +8,7 @@ import djcelery ...@@ -8,6 +8,7 @@ import djcelery
### Dark code. Should be enabled in local settings for devel. ### Dark code. Should be enabled in local settings for devel.
ENABLE_MULTICOURSE = False # set to False to disable multicourse display (see lib.util.views.mitxhome) ENABLE_MULTICOURSE = False # set to False to disable multicourse display (see lib.util.views.mitxhome)
QUICKEDIT = False
### ###
...@@ -20,19 +21,11 @@ COURSE_TITLE = "Circuits and Electronics" ...@@ -20,19 +21,11 @@ COURSE_TITLE = "Circuits and Electronics"
COURSE_DEFAULT = '6.002_Spring_2012' COURSE_DEFAULT = '6.002_Spring_2012'
COURSE_LIST = {'6.002_Spring_2012': {'number' : '6.002x', COURSE_SETTINGS = {'6.002_Spring_2012': {'number' : '6.002x',
'title' : 'Circuits and Electronics', 'title' : 'Circuits and Electronics',
'datapath': '6002x/', 'xmlpath': '6002x/',
}, }
'8.02_Spring_2013': {'number' : '8.02x', }
'title' : 'Electricity &amp; Magnetism',
'datapath': '802x/',
},
'8.01_Spring_2013': {'number' : '8.01x',
'title' : 'Mechanics',
'datapath': '801x/',
},
}
ROOT_URLCONF = 'urls' ROOT_URLCONF = 'urls'
...@@ -150,6 +143,7 @@ MIDDLEWARE_CLASSES = ( ...@@ -150,6 +143,7 @@ MIDDLEWARE_CLASSES = (
'django.contrib.messages.middleware.MessageMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',
'track.middleware.TrackMiddleware', 'track.middleware.TrackMiddleware',
'mitxmako.middleware.MakoMiddleware', 'mitxmako.middleware.MakoMiddleware',
#'ssl_auth.ssl_auth.NginxProxyHeaderMiddleware', # ssl authentication behind nginx proxy
#'debug_toolbar.middleware.DebugToolbarMiddleware', #'debug_toolbar.middleware.DebugToolbarMiddleware',
# Uncommenting the following will prevent csrf token from being re-set if you # Uncommenting the following will prevent csrf token from being re-set if you
...@@ -179,6 +173,8 @@ INSTALLED_APPS = ( ...@@ -179,6 +173,8 @@ INSTALLED_APPS = (
'util', 'util',
'masquerade', 'masquerade',
'django_jasmine', 'django_jasmine',
#'ssl_auth', ## Broken. Disabled for now.
'multicourse', # multiple courses
# Uncomment the next line to enable the admin: # Uncomment the next line to enable the admin:
# 'django.contrib.admin', # 'django.contrib.admin',
# Uncomment the next line to enable admin documentation: # Uncomment the next line to enable admin documentation:
......
<html>
<head>
<link rel="stylesheet" href="${ settings.LIB_URL }jquery.treeview.css" type="text/css" media="all" />
<link rel="stylesheet" href="/static/css/codemirror.css" type="text/css" media="all" />
<script type="text/javascript" src="${ settings.LIB_URL }jquery-1.6.2.min.js"></script>
<script type="text/javascript" src="${ settings.LIB_URL }jquery-ui-1.8.16.custom.min.js"></script>
<script type="text/javascript" src="${ settings.LIB_URL }codemirror-compressed.js"></script>
<script type="text/javascript" src="/static/js/schematic.js"></script>
<%include file="mathjax_include.html" />
<script>
function postJSON(url, data, callback) {
$.ajax({type:'POST',
url: url,
dataType: 'json',
data: data,
success: callback,
headers : {'X-CSRFToken':'none'} // getCookie('csrftoken')}
});
}
</script>
</head>
<body>
<!--[if lt IE 9]>
<script src="/static/js/html5shiv.js"></script>
<![endif]-->
<style type="text/css">
.CodeMirror {border-style: solid;
border-width: 1px;}
.CodeMirror-scroll {
height: 500;
width: 100%
}
</style>
## -----------------------------------------------------------------------------
## information and i4x PSL code
<hr width="100%">
<h2>QuickEdit</h2>
<hr width="100%">
<ul>
<li>File = ${filename}</li>
<li>ID = ${id}</li>
</ul>
<form method="post">
<textarea rows="40" cols="160" name="quickedit_${id}" id="quickedit_${id}">${pxmls|h}</textarea>
<br/>
<input type="submit" value="Change Problem" name="qesubmit" />
<input type="submit" value="Revert to original" name="qesubmit" />
</form>
<span>${msg|n}</span>
## -----------------------------------------------------------------------------
## rendered problem display
<script>
// height: auto;
// overflow-y: hidden;
// overflow-x: auto;
$(function(){
var cm = CodeMirror.fromTextArea(document.getElementById("quickedit_${id}"),
{ 'mode': {name: "xml", alignCDATA: true},
lineNumbers: true
});
// $('.my-wymeditor').wymeditor();
});
</script>
<hr width="100%">
<script>
${init_js}
</script>
<style type="text/css">
.staff {display:none;}
}
</style>
<form>
${phtml}
</form>
</body>
</html>
...@@ -69,6 +69,12 @@ if settings.COURSEWARE_ENABLED: ...@@ -69,6 +69,12 @@ if settings.COURSEWARE_ENABLED:
url(r'^calculate$', 'util.views.calculate'), url(r'^calculate$', 'util.views.calculate'),
) )
if settings.ENABLE_MULTICOURSE:
urlpatterns += (url(r'^mitxhome$', 'util.views.mitxhome'),)
if settings.QUICKEDIT:
urlpatterns += (url(r'^quickedit/(?P<id>[^/]*)$', 'courseware.views.quickedit'),)
if settings.ASKBOT_ENABLED: if settings.ASKBOT_ENABLED:
urlpatterns += (url(r'^%s' % settings.ASKBOT_URL, include('askbot.urls')), \ urlpatterns += (url(r'^%s' % settings.ASKBOT_URL, include('askbot.urls')), \
url(r'^admin/', include(admin.site.urls)), \ url(r'^admin/', include(admin.site.urls)), \
......
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