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
fa4a240f
Commit
fa4a240f
authored
May 17, 2012
by
Piotr Mitros
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Passing i4x system deeper into capa_problem
parent
087a7cf9
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
84 additions
and
38 deletions
+84
-38
djangoapps/courseware/capa/capa_problem.py
+10
-8
djangoapps/courseware/capa/inputtypes.py
+5
-0
djangoapps/courseware/capa/responsetypes.py
+13
-13
djangoapps/courseware/module_render.py
+2
-1
djangoapps/courseware/modules/__init__.py
+0
-2
djangoapps/courseware/modules/capa_module.py
+5
-5
djangoapps/courseware/modules/template_module.py
+8
-4
djangoapps/courseware/modules/x_module.py
+18
-0
djangoapps/courseware/tests.py
+23
-5
No files found.
djangoapps/courseware/capa/capa_problem.py
View file @
fa4a240f
...
...
@@ -73,20 +73,22 @@ html_skip = ["numericalresponse", "customresponse", "schematicresponse", "formul
# removed in MC
## These should be transformed
#html_special_response = {"textline":textline.render,
# "schematic":schematic.render,
# "textbox":textbox.render,
# "solution":solution.render,
#html_special_response = {"textline":inputtypes.textline.render,
# "schematic":inputtypes.schematic.render,
# "textbox":inputtypes.textbox.render,
# "formulainput":inputtypes.jstextline.render,
# "solution":inputtypes.solution.render,
# }
class
LoncapaProblem
(
object
):
def
__init__
(
self
,
fileobject
,
id
,
state
=
None
,
seed
=
None
):
def
__init__
(
self
,
fileobject
,
id
,
state
=
None
,
seed
=
None
,
system
=
None
):
## Initialize class variables from state
self
.
seed
=
None
self
.
student_answers
=
dict
()
self
.
correct_map
=
dict
()
self
.
done
=
False
self
.
problem_id
=
id
self
.
system
=
system
if
seed
!=
None
:
self
.
seed
=
seed
...
...
@@ -117,7 +119,7 @@ class LoncapaProblem(object):
self
.
preprocess_problem
(
self
.
tree
,
correct_map
=
self
.
correct_map
,
answer_map
=
self
.
student_answers
)
self
.
context
=
self
.
extract_context
(
self
.
tree
,
seed
=
self
.
seed
)
for
response
in
self
.
tree
.
xpath
(
'//'
+
"|//"
.
join
(
response_types
)):
responder
=
response_types
[
response
.
tag
](
response
,
self
.
context
)
responder
=
response_types
[
response
.
tag
](
response
,
self
.
context
,
self
.
system
)
responder
.
preprocess_response
()
def
get_state
(
self
):
...
...
@@ -163,7 +165,7 @@ class LoncapaProblem(object):
self
.
correct_map
=
dict
()
problems_simple
=
self
.
extract_problems
(
self
.
tree
)
for
response
in
problems_simple
:
grader
=
response_types
[
response
.
tag
](
response
,
self
.
context
)
grader
=
response_types
[
response
.
tag
](
response
,
self
.
context
,
self
.
system
)
results
=
grader
.
grade
(
answers
)
# call the responsetype instance to do the actual grading
self
.
correct_map
.
update
(
results
)
return
self
.
correct_map
...
...
@@ -177,7 +179,7 @@ class LoncapaProblem(object):
answer_map
=
dict
()
problems_simple
=
self
.
extract_problems
(
self
.
tree
)
# purified (flat) XML tree of just response queries
for
response
in
problems_simple
:
responder
=
response_types
[
response
.
tag
](
response
,
self
.
context
)
# instance of numericalresponse, customresponse,...
responder
=
response_types
[
response
.
tag
](
response
,
self
.
context
,
self
.
system
)
# instance of numericalresponse, customresponse,...
results
=
responder
.
get_answers
()
answer_map
.
update
(
results
)
# dict of (id,correct_answer)
...
...
djangoapps/courseware/capa/inputtypes.py
View file @
fa4a240f
...
...
@@ -101,6 +101,11 @@ def textline(element, value, state, msg=""):
#-----------------------------------------------------------------------------
def
js_textline
(
element
,
value
,
status
,
msg
=
''
):
'''
Plan: We will inspect element to figure out type
'''
# TODO: Make a wrapper for <formulainput>
# TODO: Make an AJAX loop to confirm equation is okay in real-time as user types
## TODO: Code should follow PEP8 (4 spaces per indentation level)
'''
textline is used for simple one-line inputs, like formularesponse and symbolicresponse.
...
...
djangoapps/courseware/capa/responsetypes.py
View file @
fa4a240f
...
...
@@ -71,17 +71,17 @@ class MultipleChoiceResponse(GenericResponse):
<multiplechoiceresponse direction="vertical" randomize="yes">
<choicegroup type="MultipleChoice">
<choice location="random"
name="1"
correct="false"><span>`a+b`<br/></span></choice>
<choice location="random"
name="2"
correct="true"><span><math>a+b^2</math><br/></span></choice>
<choice location="random"
name="3"
correct="false"><math>a+b+c</math></choice>
<choice location="bottom"
name="4"
correct="false"><math>a+b+d</math></choice>
<choice location="random" correct="false"><span>`a+b`<br/></span></choice>
<choice location="random" correct="true"><span><math>a+b^2</math><br/></span></choice>
<choice location="random" correct="false"><math>a+b+c</math></choice>
<choice location="bottom" correct="false"><math>a+b+d</math></choice>
</choicegroup>
</multiplechoiceresponse>
TODO: handle direction and randomize
'''
def
__init__
(
self
,
xml
,
context
):
def
__init__
(
self
,
xml
,
context
,
system
=
None
):
self
.
xml
=
xml
self
.
correct_choices
=
xml
.
xpath
(
'//*[@id=$id]//choice[@correct="true"]'
,
id
=
xml
.
get
(
'id'
))
...
...
@@ -155,7 +155,7 @@ class OptionResponse(GenericResponse):
TODO: handle direction and randomize
'''
def
__init__
(
self
,
xml
,
context
):
def
__init__
(
self
,
xml
,
context
,
system
=
None
):
self
.
xml
=
xml
self
.
answer_fields
=
xml
.
findall
(
'optioninput'
)
if
settings
.
DEBUG
:
...
...
@@ -179,7 +179,7 @@ class OptionResponse(GenericResponse):
#-----------------------------------------------------------------------------
class
NumericalResponse
(
GenericResponse
):
def
__init__
(
self
,
xml
,
context
):
def
__init__
(
self
,
xml
,
context
,
system
=
None
):
self
.
xml
=
xml
self
.
correct_answer
=
contextualize_text
(
xml
.
get
(
'answer'
),
context
)
try
:
...
...
@@ -257,7 +257,7 @@ def sympy_check2():
</customresponse>
'''
def
__init__
(
self
,
xml
,
context
):
def
__init__
(
self
,
xml
,
context
,
system
=
None
):
self
.
xml
=
xml
## CRITICAL TODO: Should cover all entrytypes
## NOTE: xpath will look at root of XML tree, not just
...
...
@@ -412,7 +412,7 @@ class ExternalResponse(GenericResponse):
Typically used by coding problems.
"""
def
__init__
(
self
,
xml
,
context
):
def
__init__
(
self
,
xml
,
context
,
system
=
None
):
self
.
xml
=
xml
self
.
answer_ids
=
xml
.
xpath
(
'//*[@id=$id]//textbox/@id|//*[@id=$id]//textline/@id'
,
id
=
xml
.
get
(
'id'
))
...
...
@@ -472,7 +472,7 @@ class StudentInputError(Exception):
#-----------------------------------------------------------------------------
class
FormulaResponse
(
GenericResponse
):
def
__init__
(
self
,
xml
,
context
):
def
__init__
(
self
,
xml
,
context
,
system
=
None
):
self
.
xml
=
xml
self
.
correct_answer
=
contextualize_text
(
xml
.
get
(
'answer'
),
context
)
self
.
samples
=
contextualize_text
(
xml
.
get
(
'samples'
),
context
)
...
...
@@ -553,7 +553,7 @@ class FormulaResponse(GenericResponse):
#-----------------------------------------------------------------------------
class
SchematicResponse
(
GenericResponse
):
def
__init__
(
self
,
xml
,
context
):
def
__init__
(
self
,
xml
,
context
,
system
=
None
):
self
.
xml
=
xml
self
.
answer_ids
=
xml
.
xpath
(
'//*[@id=$id]//schematic/@id'
,
id
=
xml
.
get
(
'id'
))
...
...
@@ -562,7 +562,7 @@ class SchematicResponse(GenericResponse):
id
=
xml
.
get
(
'id'
))[
0
]
answer_src
=
answer
.
get
(
'src'
)
if
answer_src
!=
None
:
self
.
code
=
open
(
settings
.
DATA_DIR
+
'src/'
+
answer_src
)
.
read
()
self
.
code
=
self
.
system
.
filestore
.
open
(
'src/'
+
answer_src
)
.
read
()
# Untested; never used
else
:
self
.
code
=
answer
.
text
...
...
@@ -599,7 +599,7 @@ class ImageResponse(GenericResponse):
</imageresponse>
"""
def
__init__
(
self
,
xml
,
context
):
def
__init__
(
self
,
xml
,
context
,
system
=
None
):
self
.
xml
=
xml
self
.
context
=
context
self
.
ielements
=
xml
.
findall
(
'imageinput'
)
...
...
djangoapps/courseware/module_render.py
View file @
fa4a240f
...
...
@@ -15,7 +15,6 @@ from mitxmako.shortcuts import render_to_string
from
models
import
StudentModule
import
track.views
import
courseware.modules
...
...
@@ -56,6 +55,8 @@ def make_track_function(request):
tracking function to them. This generates a closure for each request
that gives a clean interface on both sides.
'''
import
track.views
def
f
(
event_type
,
event
):
return
track
.
views
.
server_track
(
request
,
event_type
,
event
,
page
=
'x_module'
)
return
f
...
...
djangoapps/courseware/modules/__init__.py
View file @
fa4a240f
import
os
import
os.path
from
django.conf
import
settings
import
capa_module
import
html_module
import
schematic_module
...
...
djangoapps/courseware/modules/capa_module.py
View file @
fa4a240f
...
...
@@ -188,7 +188,7 @@ class Module(XModule):
if
state
!=
None
and
'attempts'
in
state
:
self
.
attempts
=
state
[
'attempts'
]
self
.
filename
=
"problems/"
+
content_parser
.
item
(
dom2
.
xpath
(
'/problem/@filename'
))
+
".xml"
self
.
filename
=
content_parser
.
item
(
dom2
.
xpath
(
'/problem/@filename'
))
#
"problems/"+content_parser.item(dom2.xpath('/problem/@filename'))+".xml"
self
.
name
=
content_parser
.
item
(
dom2
.
xpath
(
'/problem/@name'
))
self
.
weight
=
content_parser
.
item
(
dom2
.
xpath
(
'/problem/@weight'
))
if
self
.
rerandomize
==
'never'
:
...
...
@@ -200,7 +200,7 @@ class Module(XModule):
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
)
self
.
lcp
=
LoncapaProblem
(
fp
,
self
.
item_id
,
state
,
seed
=
seed
,
system
=
self
.
system
)
def
handle_ajax
(
self
,
dispatch
,
get
):
'''
...
...
@@ -307,11 +307,11 @@ class Module(XModule):
lcp_id
=
self
.
lcp
.
problem_id
correct_map
=
self
.
lcp
.
grade_answers
(
answers
)
except
StudentInputError
as
inst
:
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
,
system
=
self
.
system
)
traceback
.
print_exc
()
return
json
.
dumps
({
'success'
:
inst
.
message
})
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
,
system
=
self
.
system
)
traceback
.
print_exc
()
raise
Exception
,
"error in capa_module"
return
json
.
dumps
({
'success'
:
'Unknown Error'
})
...
...
@@ -388,7 +388,7 @@ class Module(XModule):
self
.
lcp
.
questions
=
dict
()
# Detailed info about questions in problem instance. TODO: Should be by id and not lid.
self
.
lcp
.
seed
=
None
self
.
lcp
=
LoncapaProblem
(
self
.
filestore
.
open
(
self
.
filename
),
self
.
item_id
,
self
.
lcp
.
get_state
())
self
.
lcp
=
LoncapaProblem
(
self
.
filestore
.
open
(
self
.
filename
),
self
.
item_id
,
self
.
lcp
.
get_state
()
,
system
=
self
.
system
)
event_info
[
'new_state'
]
=
self
.
lcp
.
get_state
()
self
.
tracker
(
'reset_problem'
,
event_info
)
...
...
djangoapps/courseware/modules/template_module.py
View file @
fa4a240f
import
json
import
os
## TODO: Abstract out from Django
from
django.conf
import
settings
from
mitxmako.shortcuts
import
render_to_response
,
render_to_string
from
x_module
import
XModule
...
...
@@ -14,8 +12,14 @@ class Module(XModule):
@classmethod
def
get_xml_tags
(
c
):
## TODO: Abstract out from filesystem
tags
=
os
.
listdir
(
settings
.
DATA_DIR
+
'/custom_tags'
)
## TODO: Abstract out from filesystem and Django
## HACK: For now, this lets us import without abstracting out
try
:
from
django.conf
import
settings
tags
=
os
.
listdir
(
settings
.
DATA_DIR
+
'/custom_tags'
)
except
:
print
"Could not open tags directory."
tags
=
[]
return
tags
def
get_html
(
self
):
...
...
djangoapps/courseware/modules/x_module.py
View file @
fa4a240f
...
...
@@ -7,6 +7,8 @@ class XModule(object):
''' Implements a generic learning module.
Initialized on access with __init__, first time with state=None, and
then with state
See the HTML module for a simple example
'''
id_attribute
=
'id'
# An attribute guaranteed to be unique
...
...
@@ -16,18 +18,31 @@ class XModule(object):
return
[]
def
get_completion
(
self
):
''' This is mostly unimplemented.
It gives a progress indication -- e.g. 30 minutes of 1.5 hours watched. 3 of 5 problems done, etc. '''
return
courseware
.
progress
.
completion
()
def
get_state
(
self
):
''' State of the object, as stored in the database
'''
return
""
def
get_score
(
self
):
''' Score the student received on the problem.
'''
return
None
def
max_score
(
self
):
''' Maximum score. Two notes:
* This is generic; in abstract, a problem could be 3/5 points on one randomization, and 5/7 on another
* In practice, this is a Very Bad Idea, and (a) will break some code in place (although that code
should get fixed), and (b) break some analytics we plan to put in place.
'''
return
None
def
get_html
(
self
):
''' HTML, as shown in the browser. This is the only method that must be implemented
'''
return
"Unimplemented"
def
get_init_js
(
self
):
...
...
@@ -38,6 +53,9 @@ class XModule(object):
return
""
def
get_destroy_js
(
self
):
''' JavaScript called to destroy the problem (e.g. when a user switches to a different tab).
We make an attempt, but not a promise, to call this when the user closes the web page.
'''
return
""
def
handle_ajax
(
self
,
dispatch
,
get
):
...
...
djangoapps/courseware/tests.py
View file @
fa4a240f
...
...
@@ -10,6 +10,24 @@ import courseware.graders as graders
from
courseware.graders
import
Score
,
CourseGrader
,
WeightedSubsectionsGrader
,
SingleSectionGrader
,
AssignmentFormatGrader
from
courseware.grades
import
aggregate_scores
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
):
self
.
ajax_url
=
'/'
self
.
track_function
=
lambda
x
:
None
self
.
render_function
=
lambda
x
:
{}
# Probably incorrect
self
.
exception404
=
Exception
def
__repr__
(
self
):
return
repr
(
self
.
__dict__
)
def
__str__
(
self
):
return
str
(
self
.
__dict__
)
i4xs
=
I4xSystem
class
ModelsTest
(
unittest
.
TestCase
):
def
setUp
(
self
):
pass
...
...
@@ -69,7 +87,7 @@ class ModelsTest(unittest.TestCase):
class
MultiChoiceTest
(
unittest
.
TestCase
):
def
test_MC_grade
(
self
):
multichoice_file
=
os
.
path
.
dirname
(
__file__
)
+
"/test_files/multichoice.xml"
test_lcp
=
lcp
.
LoncapaProblem
(
open
(
multichoice_file
),
'1'
)
test_lcp
=
lcp
.
LoncapaProblem
(
open
(
multichoice_file
),
'1'
,
system
=
i4xs
)
correct_answers
=
{
'1_2_1'
:
'choice_foil3'
}
self
.
assertEquals
(
test_lcp
.
grade_answers
(
correct_answers
)[
'1_2_1'
],
'correct'
)
false_answers
=
{
'1_2_1'
:
'choice_foil2'
}
...
...
@@ -77,7 +95,7 @@ class MultiChoiceTest(unittest.TestCase):
def
test_MC_bare_grades
(
self
):
multichoice_file
=
os
.
path
.
dirname
(
__file__
)
+
"/test_files/multi_bare.xml"
test_lcp
=
lcp
.
LoncapaProblem
(
open
(
multichoice_file
),
'1'
)
test_lcp
=
lcp
.
LoncapaProblem
(
open
(
multichoice_file
),
'1'
,
system
=
i4xs
)
correct_answers
=
{
'1_2_1'
:
'choice_2'
}
self
.
assertEquals
(
test_lcp
.
grade_answers
(
correct_answers
)[
'1_2_1'
],
'correct'
)
false_answers
=
{
'1_2_1'
:
'choice_1'
}
...
...
@@ -85,7 +103,7 @@ class MultiChoiceTest(unittest.TestCase):
def
test_TF_grade
(
self
):
truefalse_file
=
os
.
getcwd
()
+
"/djangoapps/courseware/test_files/truefalse.xml"
test_lcp
=
lcp
.
LoncapaProblem
(
open
(
truefalse_file
),
'1'
)
test_lcp
=
lcp
.
LoncapaProblem
(
open
(
truefalse_file
),
'1'
,
system
=
i4xs
)
correct_answers
=
{
'1_2_1'
:[
'choice_foil2'
,
'choice_foil1'
]}
self
.
assertEquals
(
test_lcp
.
grade_answers
(
correct_answers
)[
'1_2_1'
],
'correct'
)
false_answers
=
{
'1_2_1'
:[
'choice_foil1'
]}
...
...
@@ -100,7 +118,7 @@ class MultiChoiceTest(unittest.TestCase):
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'
)
test_lcp
=
lcp
.
LoncapaProblem
(
open
(
imageresponse_file
),
'1'
,
system
=
i4xs
)
correct_answers
=
{
'1_2_1'
:
'(490,11)-(556,98)'
,
'1_2_2'
:
'(242,202)-(296,276)'
}
test_answers
=
{
'1_2_1'
:
'[500,20]'
,
...
...
@@ -117,7 +135,7 @@ class OptionResponseTest(unittest.TestCase):
'''
def
test_or_grade
(
self
):
optionresponse_file
=
os
.
path
.
dirname
(
__file__
)
+
"/test_files/optionresponse.xml"
test_lcp
=
lcp
.
LoncapaProblem
(
open
(
optionresponse_file
),
'1'
)
test_lcp
=
lcp
.
LoncapaProblem
(
open
(
optionresponse_file
),
'1'
,
system
=
i4xs
)
correct_answers
=
{
'1_2_1'
:
'True'
,
'1_2_2'
:
'False'
}
test_answers
=
{
'1_2_1'
:
'True'
,
...
...
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