Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
4fe22be5
Commit
4fe22be5
authored
May 21, 2012
by
David Ormsbee
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #59 from MITx/pmitros/modular-refactor
First part of courseware refactor
parents
fb146bf5
bb66cad6
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
148 additions
and
20 deletions
+148
-20
djangoapps/courseware/capa/capa_problem.py
+14
-5
djangoapps/courseware/capa/inputtypes.py
+121
-2
djangoapps/courseware/capa/responsetypes.py
+13
-13
No files found.
djangoapps/courseware/capa/capa_problem.py
View file @
4fe22be5
...
@@ -25,6 +25,7 @@ from mako.template import Template
...
@@ -25,6 +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
,
OptionResponse
from
responsetypes
import
NumericalResponse
,
FormulaResponse
,
CustomResponse
,
SchematicResponse
,
MultipleChoiceResponse
,
StudentInputError
,
TrueFalseResponse
,
ExternalResponse
,
ImageResponse
,
OptionResponse
import
calc
import
calc
...
@@ -166,7 +167,7 @@ class LoncapaProblem(object):
...
@@ -166,7 +167,7 @@ class LoncapaProblem(object):
problems_simple
=
self
.
extract_problems
(
self
.
tree
)
problems_simple
=
self
.
extract_problems
(
self
.
tree
)
for
response
in
problems_simple
:
for
response
in
problems_simple
:
grader
=
response_types
[
response
.
tag
](
response
,
self
.
context
,
self
.
system
)
grader
=
response_types
[
response
.
tag
](
response
,
self
.
context
,
self
.
system
)
results
=
grader
.
g
rad
e
(
answers
)
# call the responsetype instance to do the actual grading
results
=
grader
.
g
et_scor
e
(
answers
)
# call the responsetype instance to do the actual grading
self
.
correct_map
.
update
(
results
)
self
.
correct_map
.
update
(
results
)
return
self
.
correct_map
return
self
.
correct_map
...
@@ -239,7 +240,7 @@ class LoncapaProblem(object):
...
@@ -239,7 +240,7 @@ class LoncapaProblem(object):
# used to be
# used to be
# if problemtree.tag in html_special_response:
# if problemtree.tag in html_special_response:
if
hasattr
(
inputtypes
,
problemtree
.
tag
):
if
problemtree
.
tag
in
inputtypes
.
get_input_xml_tags
(
):
# status is currently the answer for the problem ID for the input element,
# status is currently the answer for the problem ID for the input element,
# but it will turn into a dict containing both the answer and any associated message
# but it will turn into a dict containing both the answer and any associated message
# for the problem ID for the input element.
# for the problem ID for the input element.
...
@@ -266,9 +267,17 @@ class LoncapaProblem(object):
...
@@ -266,9 +267,17 @@ class LoncapaProblem(object):
# print "[courseware.capa.capa_problem.extract_html] msg = ",msg
# print "[courseware.capa.capa_problem.extract_html] msg = ",msg
# do the rendering
# do the rendering
#render_function = html_special_response[problemtree.tag]
# This should be broken out into a helper function
render_function
=
getattr
(
inputtypes
,
problemtree
.
tag
)
# that handles all input objects
return
render_function
(
problemtree
,
value
,
status
,
msg
)
# render the special response (textline, schematic,...)
render_object
=
inputtypes
.
SimpleInput
(
system
=
self
.
system
,
xml
=
problemtree
,
state
=
{
'value'
:
value
,
'status'
:
status
,
'id'
:
problemtree
.
get
(
'id'
),
'feedback'
:{
'message'
:
msg
}
},
use
=
'capa_input'
)
return
render_object
.
get_html
()
#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
:
...
...
djangoapps/courseware/capa/inputtypes.py
View file @
4fe22be5
...
@@ -32,8 +32,121 @@ from lxml import etree
...
@@ -32,8 +32,121 @@ from lxml import etree
from
mitxmako.shortcuts
import
render_to_string
from
mitxmako.shortcuts
import
render_to_string
def
get_input_xml_tags
():
''' Eventually, this will be for all registered input types '''
return
SimpleInput
.
get_xml_tags
()
class
SimpleInput
():
# XModule
''' Type for simple inputs -- plain HTML with a form element
State is a dictionary with optional keys:
* Value
* ID
* Status (answered, unanswered, unsubmitted)
* Feedback (dictionary containing keys for hints, errors, or other
feedback from previous attempt)
'''
xml_tags
=
{}
## Maps tags to functions
@classmethod
def
get_xml_tags
(
c
):
return
c
.
xml_tags
.
keys
()
@classmethod
def
get_uses
(
c
):
return
[
'capa_input'
,
'capa_transform'
]
def
get_html
(
self
):
return
self
.
xml_tags
[
self
.
tag
](
self
.
xml
,
self
.
value
,
self
.
status
,
self
.
msg
)
def
__init__
(
self
,
system
,
xml
,
item_id
=
None
,
track_url
=
None
,
state
=
None
,
use
=
'capa_input'
):
self
.
xml
=
xml
self
.
tag
=
xml
.
tag
if
not
state
:
state
=
{}
## ID should only come from one place.
## If it comes from multiple, we use state first, XML second, and parameter
## third. Since we don't make this guarantee, we can swap this around in
## the future if there's a more logical order.
if
item_id
:
self
.
id
=
item_id
if
xml
.
get
(
'id'
):
self
.
id
=
xml
.
get
(
'id'
)
if
'id'
in
state
:
self
.
id
=
state
[
'id'
]
self
.
system
=
system
self
.
value
=
''
if
'value'
in
state
:
self
.
value
=
state
[
'value'
]
self
.
msg
=
''
if
'feedback'
in
state
and
'message'
in
state
[
'feedback'
]:
self
.
msg
=
state
[
'feedback'
][
'message'
]
self
.
status
=
'unanswered'
if
'status'
in
state
:
self
.
status
=
state
[
'status'
]
## TODO
# class SimpleTransform():
# ''' Type for simple XML to HTML transforms. Examples:
# * Math tags, which go from LON-CAPA-style m-tags to MathJAX
# '''
# xml_tags = {} ## Maps tags to functions
# @classmethod
# def get_xml_tags(c):
# return c.xml_tags.keys()
# @classmethod
# def get_uses(c):
# return ['capa_transform']
# def get_html(self):
# return self.xml_tags[self.tag](self.xml, self.value, self.status, self.msg)
# def __init__(self, system, xml, item_id = None, track_url=None, state=None, use = 'capa_input'):
# self.xml = xml
# self.tag = xml.tag
# if not state:
# state = {}
# if item_id:
# self.id = item_id
# if xml.get('id'):
# self.id = xml.get('id')
# if 'id' in state:
# self.id = state['id']
# self.system = system
# self.value = ''
# if 'value' in state:
# self.value = state['value']
# self.msg = ''
# if 'feedback' in state and 'message' in state['feedback']:
# self.msg = state['feedback']['message']
# self.status = 'unanswered'
# if 'status' in state:
# self.status = state['status']
def
register_render_function
(
fn
,
names
=
None
,
cls
=
SimpleInput
):
if
names
==
None
:
SimpleInput
.
xml_tags
[
fn
.
__name__
]
=
fn
else
:
raise
NotImplementedError
def
wrapped
():
return
fn
return
wrapped
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
@register_render_function
def
optioninput
(
element
,
value
,
status
,
msg
=
''
):
def
optioninput
(
element
,
value
,
status
,
msg
=
''
):
'''
'''
Select option input type.
Select option input type.
...
@@ -67,7 +180,7 @@ def optioninput(element, value, status, msg=''):
...
@@ -67,7 +180,7 @@ def optioninput(element, value, status, msg=''):
return
etree
.
XML
(
html
)
return
etree
.
XML
(
html
)
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
@register_render_function
def
choicegroup
(
element
,
value
,
status
,
msg
=
''
):
def
choicegroup
(
element
,
value
,
status
,
msg
=
''
):
'''
'''
Radio button inputs: multiple choice or true/false
Radio button inputs: multiple choice or true/false
...
@@ -90,6 +203,7 @@ def choicegroup(element, value, status, msg=''):
...
@@ -90,6 +203,7 @@ def choicegroup(element, value, status, msg=''):
html
=
render_to_string
(
"choicegroup.html"
,
context
)
html
=
render_to_string
(
"choicegroup.html"
,
context
)
return
etree
.
XML
(
html
)
return
etree
.
XML
(
html
)
@register_render_function
def
textline
(
element
,
value
,
state
,
msg
=
""
):
def
textline
(
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
...
@@ -100,6 +214,7 @@ def textline(element, value, state, msg=""):
...
@@ -100,6 +214,7 @@ def textline(element, value, state, msg=""):
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
@register_render_function
def
js_textline
(
element
,
value
,
status
,
msg
=
''
):
def
js_textline
(
element
,
value
,
status
,
msg
=
''
):
'''
'''
Plan: We will inspect element to figure out type
Plan: We will inspect element to figure out type
...
@@ -125,6 +240,7 @@ def js_textline(element, value, status, msg=''):
...
@@ -125,6 +240,7 @@ def js_textline(element, value, status, msg=''):
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
## TODO: Make a wrapper for <codeinput>
## TODO: Make a wrapper for <codeinput>
@register_render_function
def
textbox
(
element
,
value
,
status
,
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
...
@@ -140,6 +256,7 @@ def textbox(element, value, status, msg=''):
...
@@ -140,6 +256,7 @@ def textbox(element, value, status, msg=''):
return
etree
.
XML
(
html
)
return
etree
.
XML
(
html
)
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
@register_render_function
def
schematic
(
element
,
value
,
status
,
msg
=
''
):
def
schematic
(
element
,
value
,
status
,
msg
=
''
):
eid
=
element
.
get
(
'id'
)
eid
=
element
.
get
(
'id'
)
height
=
element
.
get
(
'height'
)
height
=
element
.
get
(
'height'
)
...
@@ -164,6 +281,7 @@ def schematic(element, value, status, msg=''):
...
@@ -164,6 +281,7 @@ def schematic(element, value, status, msg=''):
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
### TODO: Move out of inputtypes
### TODO: Move out of inputtypes
@register_render_function
def
math
(
element
,
value
,
status
,
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
...
@@ -198,6 +316,7 @@ def math(element, value, status, msg=''):
...
@@ -198,6 +316,7 @@ def math(element, value, status, msg=''):
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
@register_render_function
def
solution
(
element
,
value
,
status
,
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,
...
@@ -218,6 +337,7 @@ def solution(element, value, status, msg=''):
...
@@ -218,6 +337,7 @@ def solution(element, value, status, msg=''):
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
@register_render_function
def
imageinput
(
element
,
value
,
status
,
msg
=
''
):
def
imageinput
(
element
,
value
,
status
,
msg
=
''
):
'''
'''
Clickable image as an input field. Element should specify the image source, height, and width, eg
Clickable image as an input field. Element should specify the image source, height, and width, eg
...
@@ -253,4 +373,3 @@ def imageinput(element, value, status, msg=''):
...
@@ -253,4 +373,3 @@ def imageinput(element, value, status, msg=''):
print
'[courseware.capa.inputtypes.imageinput] context='
,
context
print
'[courseware.capa.inputtypes.imageinput] context='
,
context
html
=
render_to_string
(
"imageinput.html"
,
context
)
html
=
render_to_string
(
"imageinput.html"
,
context
)
return
etree
.
XML
(
html
)
return
etree
.
XML
(
html
)
djangoapps/courseware/capa/responsetypes.py
View file @
4fe22be5
...
@@ -47,10 +47,10 @@ def compare_with_tolerance(v1, v2, tol):
...
@@ -47,10 +47,10 @@ def compare_with_tolerance(v1, v2, tol):
return
abs
(
v1
-
v2
)
<=
tolerance
return
abs
(
v1
-
v2
)
<=
tolerance
class
GenericResponse
(
object
):
class
GenericResponse
(
object
):
__metaclass__
=
abc
.
ABCMeta
__metaclass__
=
abc
.
ABCMeta
# abc = Abstract Base Class
@abc.abstractmethod
@abc.abstractmethod
def
g
rad
e
(
self
,
student_answers
):
def
g
et_scor
e
(
self
,
student_answers
):
pass
pass
@abc.abstractmethod
@abc.abstractmethod
...
@@ -61,7 +61,7 @@ class GenericResponse(object):
...
@@ -61,7 +61,7 @@ class GenericResponse(object):
def
preprocess_response
(
self
):
def
preprocess_response
(
self
):
pass
pass
#Every response type needs methods "g
rad
e" and "get_answers"
#Every response type needs methods "g
et_scor
e" and "get_answers"
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
...
@@ -95,7 +95,7 @@ class MultipleChoiceResponse(GenericResponse):
...
@@ -95,7 +95,7 @@ class MultipleChoiceResponse(GenericResponse):
raise
Exception
(
"should have exactly one choice group per multiplechoicceresponse"
)
raise
Exception
(
"should have exactly one choice group per multiplechoicceresponse"
)
self
.
answer_id
=
self
.
answer_id
[
0
]
self
.
answer_id
=
self
.
answer_id
[
0
]
def
g
rad
e
(
self
,
student_answers
):
def
g
et_scor
e
(
self
,
student_answers
):
if
self
.
answer_id
in
student_answers
and
student_answers
[
self
.
answer_id
]
in
self
.
correct_choices
:
if
self
.
answer_id
in
student_answers
and
student_answers
[
self
.
answer_id
]
in
self
.
correct_choices
:
return
{
self
.
answer_id
:
'correct'
}
return
{
self
.
answer_id
:
'correct'
}
else
:
else
:
...
@@ -132,7 +132,7 @@ class TrueFalseResponse(MultipleChoiceResponse):
...
@@ -132,7 +132,7 @@ class TrueFalseResponse(MultipleChoiceResponse):
else
:
else
:
choice
.
set
(
"name"
,
"choice_"
+
choice
.
get
(
"name"
))
choice
.
set
(
"name"
,
"choice_"
+
choice
.
get
(
"name"
))
def
g
rad
e
(
self
,
student_answers
):
def
g
et_scor
e
(
self
,
student_answers
):
correct
=
set
(
self
.
correct_choices
)
correct
=
set
(
self
.
correct_choices
)
answers
=
set
(
student_answers
.
get
(
self
.
answer_id
,
[]))
answers
=
set
(
student_answers
.
get
(
self
.
answer_id
,
[]))
...
@@ -162,7 +162,7 @@ class OptionResponse(GenericResponse):
...
@@ -162,7 +162,7 @@ class OptionResponse(GenericResponse):
print
'[courseware.capa.responsetypes.OR.init] answer_fields=
%
s'
%
(
self
.
answer_fields
)
print
'[courseware.capa.responsetypes.OR.init] answer_fields=
%
s'
%
(
self
.
answer_fields
)
self
.
context
=
context
self
.
context
=
context
def
g
rad
e
(
self
,
student_answers
):
def
g
et_scor
e
(
self
,
student_answers
):
cmap
=
{}
cmap
=
{}
amap
=
self
.
get_answers
()
amap
=
self
.
get_answers
()
for
aid
in
amap
:
for
aid
in
amap
:
...
@@ -194,7 +194,7 @@ class NumericalResponse(GenericResponse):
...
@@ -194,7 +194,7 @@ class NumericalResponse(GenericResponse):
except
Exception
,
err
:
except
Exception
,
err
:
self
.
answer_id
=
None
self
.
answer_id
=
None
def
g
rad
e
(
self
,
student_answers
):
def
g
et_scor
e
(
self
,
student_answers
):
''' Display HTML for a numeric response '''
''' Display HTML for a numeric response '''
student_answer
=
student_answers
[
self
.
answer_id
]
student_answer
=
student_answers
[
self
.
answer_id
]
try
:
try
:
...
@@ -300,7 +300,7 @@ def sympy_check2():
...
@@ -300,7 +300,7 @@ def sympy_check2():
else
:
else
:
self
.
code
=
answer
.
text
self
.
code
=
answer
.
text
def
g
rad
e
(
self
,
student_answers
):
def
g
et_scor
e
(
self
,
student_answers
):
'''
'''
student_answers is a dict with everything from request.POST, but with the first part
student_answers is a dict with everything from request.POST, but with the first part
of each key removed (the string before the first "_").
of each key removed (the string before the first "_").
...
@@ -363,7 +363,7 @@ def sympy_check2():
...
@@ -363,7 +363,7 @@ def sympy_check2():
print
"oops in customresponse (cfn) error
%
s"
%
err
print
"oops in customresponse (cfn) error
%
s"
%
err
# print "context = ",self.context
# print "context = ",self.context
print
traceback
.
format_exc
()
print
traceback
.
format_exc
()
if
settings
.
DEBUG
:
print
"[courseware.capa.responsetypes.customresponse.g
rad
e] ret = "
,
ret
if
settings
.
DEBUG
:
print
"[courseware.capa.responsetypes.customresponse.g
et_scor
e] ret = "
,
ret
if
type
(
ret
)
==
dict
:
if
type
(
ret
)
==
dict
:
correct
[
0
]
=
'correct'
if
ret
[
'ok'
]
else
'incorrect'
correct
[
0
]
=
'correct'
if
ret
[
'ok'
]
else
'incorrect'
msg
=
ret
[
'msg'
]
msg
=
ret
[
'msg'
]
...
@@ -428,7 +428,7 @@ class ExternalResponse(GenericResponse):
...
@@ -428,7 +428,7 @@ class ExternalResponse(GenericResponse):
self
.
tests
=
xml
.
get
(
'answer'
)
self
.
tests
=
xml
.
get
(
'answer'
)
def
g
rad
e
(
self
,
student_answers
):
def
g
et_scor
e
(
self
,
student_answers
):
submission
=
[
student_answers
[
k
]
for
k
in
sorted
(
self
.
answer_ids
)]
submission
=
[
student_answers
[
k
]
for
k
in
sorted
(
self
.
answer_ids
)]
self
.
context
.
update
({
'submission'
:
submission
})
self
.
context
.
update
({
'submission'
:
submission
})
...
@@ -504,7 +504,7 @@ class FormulaResponse(GenericResponse):
...
@@ -504,7 +504,7 @@ class FormulaResponse(GenericResponse):
self
.
case_sensitive
=
False
self
.
case_sensitive
=
False
def
g
rad
e
(
self
,
student_answers
):
def
g
et_scor
e
(
self
,
student_answers
):
variables
=
self
.
samples
.
split
(
'@'
)[
0
]
.
split
(
','
)
variables
=
self
.
samples
.
split
(
'@'
)[
0
]
.
split
(
','
)
numsamples
=
int
(
self
.
samples
.
split
(
'@'
)[
1
]
.
split
(
'#'
)[
1
])
numsamples
=
int
(
self
.
samples
.
split
(
'@'
)[
1
]
.
split
(
'#'
)[
1
])
sranges
=
zip
(
*
map
(
lambda
x
:
map
(
float
,
x
.
split
(
","
)),
sranges
=
zip
(
*
map
(
lambda
x
:
map
(
float
,
x
.
split
(
","
)),
...
@@ -566,7 +566,7 @@ class SchematicResponse(GenericResponse):
...
@@ -566,7 +566,7 @@ class SchematicResponse(GenericResponse):
else
:
else
:
self
.
code
=
answer
.
text
self
.
code
=
answer
.
text
def
g
rad
e
(
self
,
student_answers
):
def
g
et_scor
e
(
self
,
student_answers
):
from
capa_problem
import
global_context
from
capa_problem
import
global_context
submission
=
[
json
.
loads
(
student_answers
[
k
])
for
k
in
sorted
(
self
.
answer_ids
)]
submission
=
[
json
.
loads
(
student_answers
[
k
])
for
k
in
sorted
(
self
.
answer_ids
)]
self
.
context
.
update
({
'submission'
:
submission
})
self
.
context
.
update
({
'submission'
:
submission
})
...
@@ -605,7 +605,7 @@ class ImageResponse(GenericResponse):
...
@@ -605,7 +605,7 @@ class ImageResponse(GenericResponse):
self
.
ielements
=
xml
.
findall
(
'imageinput'
)
self
.
ielements
=
xml
.
findall
(
'imageinput'
)
self
.
answer_ids
=
[
ie
.
get
(
'id'
)
for
ie
in
self
.
ielements
]
self
.
answer_ids
=
[
ie
.
get
(
'id'
)
for
ie
in
self
.
ielements
]
def
g
rad
e
(
self
,
student_answers
):
def
g
et_scor
e
(
self
,
student_answers
):
correct_map
=
{}
correct_map
=
{}
expectedset
=
self
.
get_answers
()
expectedset
=
self
.
get_answers
()
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment